diff --git a/packages/dashboards/examples/src/components/docs/dashboard-docs.module.ts b/packages/dashboards/examples/src/components/docs/dashboard-docs.module.ts index 1e59be15f..78acbd762 100644 --- a/packages/dashboards/examples/src/components/docs/dashboard-docs.module.ts +++ b/packages/dashboards/examples/src/components/docs/dashboard-docs.module.ts @@ -18,7 +18,7 @@ // OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN // THE SOFTWARE. -import { provideHttpClient } from "@angular/common/http"; +import { provideHttpClient, withInterceptorsFromDi } from "@angular/common/http"; import { inject, NgModule, Type } from "@angular/core"; import { RouterModule, Routes } from "@angular/router"; import { InMemoryCache } from "@apollo/client/core"; @@ -61,7 +61,7 @@ const exampleRoutes: Routes = [ RouterModule.forChild(exampleRoutes), ], providers: [ - provideHttpClient(), + provideHttpClient(withInterceptorsFromDi()), provideApollo(() => { const httpLink = inject(HttpLink); diff --git a/packages/dashboards/examples/src/components/docs/demo.files.ts b/packages/dashboards/examples/src/components/docs/demo.files.ts index fe1355b07..a4c32e0a1 100644 --- a/packages/dashboards/examples/src/components/docs/demo.files.ts +++ b/packages/dashboards/examples/src/components/docs/demo.files.ts @@ -200,5 +200,288334 @@ export const DEMO_PATHS = [ "widget-types/timeseries/timeseries-widget-status-bar-example/timeseries-widget-status-bar-example.component.html", "widget-types/timeseries/timeseries-widget-status-bar-example/timeseries-widget-status-bar-example.component.less", "widget-types/timeseries/timeseries-widget-status-bar-example/timeseries-widget-status-bar-example.component.ts", + "widget-types/view-components/kpi-tile-view-basic/kpi-tile-view-basic-example.component.html", + "widget-types/view-components/kpi-tile-view-basic/kpi-tile-view-basic-example.component.ts", + "widget-types/view-components/kpi-tile-view-interactive/kpi-tile-view-interactive-example.component.html", + "widget-types/view-components/kpi-tile-view-interactive/kpi-tile-view-interactive-example.component.ts", + "widget-types/view-components/proportional-chart-view-playground/proportional-chart-view-playground-example.component.html", + "widget-types/view-components/proportional-chart-view-playground/proportional-chart-view-playground-example.component.ts", + "widget-types/view-components/view-components-docs.component.html", + "widget-types/view-components/view-components-docs.component.ts", + "widget-types/view-components/view-components-docs.module.ts", "widget-types/widget-types.module.ts", ]; + +export const DEMO_TS_SOURCES: Record = { + "dashboard-docs.module.ts": `// © 2022 SolarWinds Worldwide, LLC. All rights reserved. +// +// Permission is hereby granted, free of charge, to any person obtaining a copy +// of this software and associated documentation files (the "Software"), to +// deal in the Software without restriction, including without limitation the +// rights to use, copy, modify, merge, publish, distribute, sublicense, and/or +// sell copies of the Software, and to permit persons to whom the Software is +// furnished to do so, subject to the following conditions: +// +// The above copyright notice and this permission notice shall be included in +// all copies or substantial portions of the Software. +// +// THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR +// IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, +// FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE +// AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER +// LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, +// OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN +// THE SOFTWARE. + +import { provideHttpClient, withInterceptorsFromDi } from "@angular/common/http"; +import { inject, NgModule, Type } from "@angular/core"; +import { RouterModule, Routes } from "@angular/router"; +import { InMemoryCache } from "@apollo/client/core"; +import { provideApollo } from "apollo-angular"; +import { HttpLink } from "apollo-angular/http"; + +import { NuiDocsModule, NuiMessageModule } from "@nova-ui/bits"; + + +const COUNTRIES_API = "https://countries.trevorblades.com/graphql"; + +const exampleRoutes: Routes = [ + { + path: "overview", + loadChildren: async () => + import("./overview/overview.module") as object as Promise< + Type + >, + }, + { + path: "tutorials", + loadChildren: async () => + import("./tutorials/tutorials.module") as object as Promise< + Type + >, + }, + { + path: "widget-types", + loadChildren: async () => + import("./widget-types/widget-types.module") as object as Promise< + Type + >, + }, +]; + +@NgModule({ + imports: [ + NuiDocsModule, + NuiMessageModule, + RouterModule.forChild(exampleRoutes), + ], + providers: [ + provideHttpClient(withInterceptorsFromDi()), + provideApollo(() => { + const httpLink = inject(HttpLink); + + return { + link: httpLink.create({ uri: COUNTRIES_API }), + cache: new InMemoryCache(), + }; + }), + ], +}) +export default class DashboardDocsModule {} +`, + "demo.files.ts": `// this file autogenerated, do not edit it manually please run the script +// yarn run compile-demo-paths +export const DEMO_PATHS = [ + "dashboard-docs.module.ts", + "demo.files.ts", + "overview/hero/dashboard/hero-dashboard.component.html", + "overview/hero/dashboard/hero-dashboard.component.less", + "overview/hero/dashboard/hero-dashboard.component.ts", + "overview/hero/dashboard/widget-configs.ts", + "overview/hero/data/kpi-datasources.ts", + "overview/hero/data/proportional-datasources.ts", + "overview/hero/data/table/beer-data-source.ts", + "overview/hero/data/table/constants.ts", + "overview/hero/data/table/random-user-data-source.ts", + "overview/hero/data/table/types.ts", + "overview/hero/data/timeseries-data-sources.ts", + "overview/hero/data/types.ts", + "overview/hero/data/widget-data.ts", + "overview/hero/widget-configs/kpi.ts", + "overview/hero/widget-configs/proportional.ts", + "overview/hero/widget-configs/risk-score.ts", + "overview/hero/widget-configs/table.ts", + "overview/hero/widget-configs/timeseries.ts", + "overview/overview-docs.component.html", + "overview/overview-docs.component.ts", + "overview/overview.module.ts", + "tutorials/customization/configurator-section/custom-configurator-section/custom-configurator-section.example.component.html", + "tutorials/customization/configurator-section/custom-configurator-section/custom-configurator-section.example.component.less", + "tutorials/customization/configurator-section/custom-configurator-section/custom-configurator-section.example.component.ts", + "tutorials/customization/configurator-section/custom-configurator-section-docs.component.html", + "tutorials/customization/configurator-section/custom-configurator-section-docs.component.ts", + "tutorials/customization/configurator-section/custom-configurator-section.module.ts", + "tutorials/customization/customization.module.ts", + "tutorials/customization/data-source-configurator/custom-data-source-configurator-docs.component.html", + "tutorials/customization/data-source-configurator/custom-data-source-configurator-docs.component.ts", + "tutorials/customization/data-source-configurator/custom-data-source-configurator.module.ts", + "tutorials/customization/data-source-configurator/example/custom-data-source-configurator-example.component.html", + "tutorials/customization/data-source-configurator/example/custom-data-source-configurator-example.component.less", + "tutorials/customization/data-source-configurator/example/custom-data-source-configurator-example.component.ts", + "tutorials/customization/formatter/custom-formatter.module.ts", + "tutorials/customization/formatter/donut-content-formatter-example/custom-donut-content-formatter-docs.component.html", + "tutorials/customization/formatter/donut-content-formatter-example/custom-donut-content-formatter-docs.component.ts", + "tutorials/customization/formatter/donut-content-formatter-example/custom-donut-content-formatter-example.component.html", + "tutorials/customization/formatter/donut-content-formatter-example/custom-donut-content-formatter-example.component.less", + "tutorials/customization/formatter/donut-content-formatter-example/custom-donut-content-formatter-example.component.ts", + "tutorials/customization/formatter/formatter-example/custom-formatter-docs.component.html", + "tutorials/customization/formatter/formatter-example/custom-formatter-docs.component.ts", + "tutorials/customization/formatter/formatter-example/custom-formatter-example.component.html", + "tutorials/customization/formatter/formatter-example/custom-formatter-example.component.less", + "tutorials/customization/formatter/formatter-example/custom-formatter-example.component.ts", + "tutorials/customization/widget/custom-widget-docs.component.html", + "tutorials/customization/widget/custom-widget-docs.component.ts", + "tutorials/customization/widget/custom-widget.component.html", + "tutorials/customization/widget/custom-widget.component.less", + "tutorials/customization/widget/custom-widget.component.ts", + "tutorials/customization/widget/custom-widget.module.ts", + "tutorials/data-source-setup/data-source-setup-docs.component.html", + "tutorials/data-source-setup/data-source-setup-docs.component.ts", + "tutorials/data-source-setup/data-source-setup.component.html", + "tutorials/data-source-setup/data-source-setup.component.less", + "tutorials/data-source-setup/data-source-setup.component.ts", + "tutorials/data-source-setup/data-source-setup.module.ts", + "tutorials/dynamic-header-links/dynamic-header-links-docs.component.html", + "tutorials/dynamic-header-links/dynamic-header-links-docs.component.ts", + "tutorials/dynamic-header-links/dynamic-header-links-docs.module.ts", + "tutorials/hello-dashboards/hello-dashboards-docs.component.html", + "tutorials/hello-dashboards/hello-dashboards-docs.component.ts", + "tutorials/hello-dashboards/hello-dashboards-example/hello-dashboards-example.component.html", + "tutorials/hello-dashboards/hello-dashboards-example/hello-dashboards-example.component.less", + "tutorials/hello-dashboards/hello-dashboards-example/hello-dashboards-example.component.ts", + "tutorials/hello-dashboards/hello-dashboards.module.ts", + "tutorials/persistence-handler-setup/persistence-handler-setup-docs.component.html", + "tutorials/persistence-handler-setup/persistence-handler-setup-docs.component.ts", + "tutorials/persistence-handler-setup/persistence-handler-setup.component.html", + "tutorials/persistence-handler-setup/persistence-handler-setup.component.less", + "tutorials/persistence-handler-setup/persistence-handler-setup.component.ts", + "tutorials/persistence-handler-setup/persistence-handler-setup.module.ts", + "tutorials/tutorials.module.ts", + "tutorials/widget-creation/widget-creation-docs.component.html", + "tutorials/widget-creation/widget-creation-docs.component.ts", + "tutorials/widget-creation/widget-creation.component.html", + "tutorials/widget-creation/widget-creation.component.less", + "tutorials/widget-creation/widget-creation.component.ts", + "tutorials/widget-creation/widget-creation.module.ts", + "tutorials/widget-editor-setup/widget-editor-setup-docs.component.html", + "tutorials/widget-editor-setup/widget-editor-setup-docs.component.ts", + "tutorials/widget-editor-setup/widget-editor-setup.component.html", + "tutorials/widget-editor-setup/widget-editor-setup.component.less", + "tutorials/widget-editor-setup/widget-editor-setup.component.ts", + "tutorials/widget-editor-setup/widget-editor-setup.module.ts", + "tutorials/widget-error-handling/widget-error-handling-docs.component.html", + "tutorials/widget-error-handling/widget-error-handling-docs.component.ts", + "tutorials/widget-error-handling/widget-error-handling.component.html", + "tutorials/widget-error-handling/widget-error-handling.component.less", + "tutorials/widget-error-handling/widget-error-handling.component.ts", + "tutorials/widget-error-handling/widget-error-handling.module.ts", + "types.ts", + "widget-types/drilldown/drilldown-multi-request-widget/drilldown-multi-request-widget-example.component.html", + "widget-types/drilldown/drilldown-multi-request-widget/drilldown-multi-request-widget-example.component.less", + "widget-types/drilldown/drilldown-multi-request-widget/drilldown-multi-request-widget-example.component.ts", + "widget-types/drilldown/drilldown-widget/data-mock.ts", + "widget-types/drilldown/drilldown-widget/drilldown-widget-example.component.html", + "widget-types/drilldown/drilldown-widget/drilldown-widget-example.component.less", + "widget-types/drilldown/drilldown-widget/drilldown-widget-example.component.ts", + "widget-types/drilldown/drilldown-widget/mock-data-source.ts", + "widget-types/drilldown/drilldown-widget-docs.component.html", + "widget-types/drilldown/drilldown-widget-docs.component.ts", + "widget-types/drilldown/drilldown-widget-docs.module.ts", + "widget-types/embedded-content/embedded-content-docs.component.html", + "widget-types/embedded-content/embedded-content-docs.component.ts", + "widget-types/embedded-content/embedded-content-docs.module.ts", + "widget-types/embedded-content/embedded-content-widget-example/embedded-content-widget-example.component.html", + "widget-types/embedded-content/embedded-content-widget-example/embedded-content-widget-example.component.less", + "widget-types/embedded-content/embedded-content-widget-example/embedded-content-widget-example.component.ts", + "widget-types/kpi/kpi-docs.component.html", + "widget-types/kpi/kpi-docs.component.ts", + "widget-types/kpi/kpi-docs.module.ts", + "widget-types/kpi/kpi-sync-broker/kpi-sync-broker-example.component.html", + "widget-types/kpi/kpi-sync-broker/kpi-sync-broker-example.component.less", + "widget-types/kpi/kpi-sync-broker/kpi-sync-broker-example.component.ts", + "widget-types/kpi/kpi-sync-broker-docs.component.html", + "widget-types/kpi/kpi-sync-broker-docs.component.ts", + "widget-types/kpi/kpi-sync-broker-for-all-tiles/kpi-sync-broker-for-all-tiles-example.component.html", + "widget-types/kpi/kpi-sync-broker-for-all-tiles/kpi-sync-broker-for-all-tiles-example.component.less", + "widget-types/kpi/kpi-sync-broker-for-all-tiles/kpi-sync-broker-for-all-tiles-example.component.ts", + "widget-types/kpi/kpi-widget/kpi-widget-example.component.html", + "widget-types/kpi/kpi-widget/kpi-widget-example.component.less", + "widget-types/kpi/kpi-widget/kpi-widget-example.component.ts", + "widget-types/kpi/kpi-widget-background-color/kpi-widget-background-color-example.component.html", + "widget-types/kpi/kpi-widget-background-color/kpi-widget-background-color-example.component.less", + "widget-types/kpi/kpi-widget-background-color/kpi-widget-background-color-example.component.ts", + "widget-types/kpi/kpi-widget-background-color-docs.component.html", + "widget-types/kpi/kpi-widget-background-color-docs.component.ts", + "widget-types/kpi/kpi-widget-interactive/kpi-widget-interactive-example.component.html", + "widget-types/kpi/kpi-widget-interactive/kpi-widget-interactive-example.component.less", + "widget-types/kpi/kpi-widget-interactive/kpi-widget-interactive-example.component.ts", + "widget-types/proportional/models.ts", + "widget-types/proportional/proportional-docs.component.html", + "widget-types/proportional/proportional-docs.component.ts", + "widget-types/proportional/proportional-docs.module.ts", + "widget-types/proportional/proportional-donut-content-docs.component.html", + "widget-types/proportional/proportional-donut-content-docs.component.ts", + "widget-types/proportional/proportional-donut-content-formatters/proportional-donut-content-formatters-example.component.html", + "widget-types/proportional/proportional-donut-content-formatters/proportional-donut-content-formatters-example.component.less", + "widget-types/proportional/proportional-donut-content-formatters/proportional-donut-content-formatters-example.component.ts", + "widget-types/proportional/proportional-widget/proportional-widget-example.component.html", + "widget-types/proportional/proportional-widget/proportional-widget-example.component.less", + "widget-types/proportional/proportional-widget/proportional-widget-example.component.ts", + "widget-types/proportional/proportional-widget-interactive/proportional-widget-interactive-example.component.html", + "widget-types/proportional/proportional-widget-interactive/proportional-widget-interactive-example.component.less", + "widget-types/proportional/proportional-widget-interactive/proportional-widget-interactive-example.component.ts", + "widget-types/risk-score/risk-score-docs.component.html", + "widget-types/risk-score/risk-score-docs.component.ts", + "widget-types/risk-score/risk-score-docs.module.ts", + "widget-types/risk-score/risk-score-widget-example/risk-score-widget-example.component.html", + "widget-types/risk-score/risk-score-widget-example/risk-score-widget-example.component.less", + "widget-types/risk-score/risk-score-widget-example/risk-score-widget-example.component.ts", + "widget-types/table/table-docs.component.html", + "widget-types/table/table-docs.component.ts", + "widget-types/table/table-docs.module.ts", + "widget-types/table/table-paginator-docs.component.html", + "widget-types/table/table-paginator-docs.component.ts", + "widget-types/table/table-selectable-docs.component.html", + "widget-types/table/table-selectable-docs.component.ts", + "widget-types/table/table-widget/table-widget-example.component.html", + "widget-types/table/table-widget/table-widget-example.component.less", + "widget-types/table/table-widget/table-widget-example.component.ts", + "widget-types/table/table-widget-interactive/table-widget-interactive-example.component.html", + "widget-types/table/table-widget-interactive/table-widget-interactive-example.component.less", + "widget-types/table/table-widget-interactive/table-widget-interactive-example.component.ts", + "widget-types/table/table-widget-paginator/table-widget-paginator-example.component.html", + "widget-types/table/table-widget-paginator/table-widget-paginator-example.component.less", + "widget-types/table/table-widget-paginator/table-widget-paginator-example.component.ts", + "widget-types/table/table-widget-search/table-widget-search-example.component.html", + "widget-types/table/table-widget-search/table-widget-search-example.component.less", + "widget-types/table/table-widget-search/table-widget-search-example.component.ts", + "widget-types/table/table-widget-search-docs.component.html", + "widget-types/table/table-widget-search-docs.component.ts", + "widget-types/table/table-widget-selectable/table-widget-selectable-multi/table-widget-selectable-multi.example.component.html", + "widget-types/table/table-widget-selectable/table-widget-selectable-multi/table-widget-selectable-multi.example.component.less", + "widget-types/table/table-widget-selectable/table-widget-selectable-multi/table-widget-selectable-multi.example.component.ts", + "widget-types/table/table-widget-selectable/table-widget-selectable-radio/table-widget-selectable-radio.example.component.html", + "widget-types/table/table-widget-selectable/table-widget-selectable-radio/table-widget-selectable-radio.example.component.less", + "widget-types/table/table-widget-selectable/table-widget-selectable-radio/table-widget-selectable-radio.example.component.ts", + "widget-types/table/table-widget-selectable/table-widget-selectable-single/table-widget-selectable-single.example.component.html", + "widget-types/table/table-widget-selectable/table-widget-selectable-single/table-widget-selectable-single.example.component.less", + "widget-types/table/table-widget-selectable/table-widget-selectable-single/table-widget-selectable-single.example.component.ts", + "widget-types/table/table-widget-selectable/table-widget-selectable.example.component.html", + "widget-types/table/table-widget-selectable/table-widget-selectable.example.component.less", + "widget-types/table/table-widget-selectable/table-widget-selectable.example.component.ts", + "widget-types/timeseries/timeseries-docs.component.html", + "widget-types/timeseries/timeseries-docs.component.ts", + "widget-types/timeseries/timeseries-docs.module.ts", + "widget-types/timeseries/timeseries-widget-example/timeseries-widget-example.component.html", + "widget-types/timeseries/timeseries-widget-example/timeseries-widget-example.component.less", + "widget-types/timeseries/timeseries-widget-example/timeseries-widget-example.component.ts", + "widget-types/timeseries/timeseries-widget-interactive-example/timeseries-widget-interactive-example.component.html", + "widget-types/timeseries/timeseries-widget-interactive-example/timeseries-widget-interactive-example.component.less", + "widget-types/timeseries/timeseries-widget-interactive-example/timeseries-widget-interactive-example.component.ts", + "widget-types/timeseries/timeseries-widget-status-bar-example/timeseries-widget-status-bar-example.component.html", + "widget-types/timeseries/timeseries-widget-status-bar-example/timeseries-widget-status-bar-example.component.less", + "widget-types/timeseries/timeseries-widget-status-bar-example/timeseries-widget-status-bar-example.component.ts", + "widget-types/view-components/kpi-tile-view-basic/kpi-tile-view-basic-example.component.html", + "widget-types/view-components/kpi-tile-view-basic/kpi-tile-view-basic-example.component.ts", + "widget-types/view-components/kpi-tile-view-interactive/kpi-tile-view-interactive-example.component.html", + "widget-types/view-components/kpi-tile-view-interactive/kpi-tile-view-interactive-example.component.ts", + "widget-types/view-components/proportional-chart-view-playground/proportional-chart-view-playground-example.component.html", + "widget-types/view-components/proportional-chart-view-playground/proportional-chart-view-playground-example.component.ts", + "widget-types/view-components/view-components-docs.component.html", + "widget-types/view-components/view-components-docs.component.ts", + "widget-types/view-components/view-components-docs.module.ts", + "widget-types/widget-types.module.ts", +]; + +export const DEMO_TS_SOURCES: Record = { + "dashboard-docs.module.ts": \`// © 2022 SolarWinds Worldwide, LLC. All rights reserved. +// +// Permission is hereby granted, free of charge, to any person obtaining a copy +// of this software and associated documentation files (the "Software"), to +// deal in the Software without restriction, including without limitation the +// rights to use, copy, modify, merge, publish, distribute, sublicense, and/or +// sell copies of the Software, and to permit persons to whom the Software is +// furnished to do so, subject to the following conditions: +// +// The above copyright notice and this permission notice shall be included in +// all copies or substantial portions of the Software. +// +// THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR +// IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, +// FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE +// AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER +// LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, +// OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN +// THE SOFTWARE. + +import { provideHttpClient, withInterceptorsFromDi } from "@angular/common/http"; +import { inject, NgModule, Type } from "@angular/core"; +import { RouterModule, Routes } from "@angular/router"; +import { InMemoryCache } from "@apollo/client/core"; +import { provideApollo } from "apollo-angular"; +import { HttpLink } from "apollo-angular/http"; + +import { NuiDocsModule, NuiMessageModule } from "@nova-ui/bits"; + + +const COUNTRIES_API = "https://countries.trevorblades.com/graphql"; + +const exampleRoutes: Routes = [ + { + path: "overview", + loadChildren: async () => + import("./overview/overview.module") as object as Promise< + Type + >, + }, + { + path: "tutorials", + loadChildren: async () => + import("./tutorials/tutorials.module") as object as Promise< + Type + >, + }, + { + path: "widget-types", + loadChildren: async () => + import("./widget-types/widget-types.module") as object as Promise< + Type + >, + }, +]; + +@NgModule({ + imports: [ + NuiDocsModule, + NuiMessageModule, + RouterModule.forChild(exampleRoutes), + ], + providers: [ + provideHttpClient(withInterceptorsFromDi()), + provideApollo(() => { + const httpLink = inject(HttpLink); + + return { + link: httpLink.create({ uri: COUNTRIES_API }), + cache: new InMemoryCache(), + }; + }), + ], +}) +export default class DashboardDocsModule {} +\`, + "demo.files.ts": \`// this file autogenerated, do not edit it manually please run the script +// yarn run compile-demo-paths +export const DEMO_PATHS = [ + "dashboard-docs.module.ts", + "demo.files.ts", + "overview/hero/dashboard/hero-dashboard.component.html", + "overview/hero/dashboard/hero-dashboard.component.less", + "overview/hero/dashboard/hero-dashboard.component.ts", + "overview/hero/dashboard/widget-configs.ts", + "overview/hero/data/kpi-datasources.ts", + "overview/hero/data/proportional-datasources.ts", + "overview/hero/data/table/beer-data-source.ts", + "overview/hero/data/table/constants.ts", + "overview/hero/data/table/random-user-data-source.ts", + "overview/hero/data/table/types.ts", + "overview/hero/data/timeseries-data-sources.ts", + "overview/hero/data/types.ts", + "overview/hero/data/widget-data.ts", + "overview/hero/widget-configs/kpi.ts", + "overview/hero/widget-configs/proportional.ts", + "overview/hero/widget-configs/risk-score.ts", + "overview/hero/widget-configs/table.ts", + "overview/hero/widget-configs/timeseries.ts", + "overview/overview-docs.component.html", + "overview/overview-docs.component.ts", + "overview/overview.module.ts", + "tutorials/customization/configurator-section/custom-configurator-section/custom-configurator-section.example.component.html", + "tutorials/customization/configurator-section/custom-configurator-section/custom-configurator-section.example.component.less", + "tutorials/customization/configurator-section/custom-configurator-section/custom-configurator-section.example.component.ts", + "tutorials/customization/configurator-section/custom-configurator-section-docs.component.html", + "tutorials/customization/configurator-section/custom-configurator-section-docs.component.ts", + "tutorials/customization/configurator-section/custom-configurator-section.module.ts", + "tutorials/customization/customization.module.ts", + "tutorials/customization/data-source-configurator/custom-data-source-configurator-docs.component.html", + "tutorials/customization/data-source-configurator/custom-data-source-configurator-docs.component.ts", + "tutorials/customization/data-source-configurator/custom-data-source-configurator.module.ts", + "tutorials/customization/data-source-configurator/example/custom-data-source-configurator-example.component.html", + "tutorials/customization/data-source-configurator/example/custom-data-source-configurator-example.component.less", + "tutorials/customization/data-source-configurator/example/custom-data-source-configurator-example.component.ts", + "tutorials/customization/formatter/custom-formatter.module.ts", + "tutorials/customization/formatter/donut-content-formatter-example/custom-donut-content-formatter-docs.component.html", + "tutorials/customization/formatter/donut-content-formatter-example/custom-donut-content-formatter-docs.component.ts", + "tutorials/customization/formatter/donut-content-formatter-example/custom-donut-content-formatter-example.component.html", + "tutorials/customization/formatter/donut-content-formatter-example/custom-donut-content-formatter-example.component.less", + "tutorials/customization/formatter/donut-content-formatter-example/custom-donut-content-formatter-example.component.ts", + "tutorials/customization/formatter/formatter-example/custom-formatter-docs.component.html", + "tutorials/customization/formatter/formatter-example/custom-formatter-docs.component.ts", + "tutorials/customization/formatter/formatter-example/custom-formatter-example.component.html", + "tutorials/customization/formatter/formatter-example/custom-formatter-example.component.less", + "tutorials/customization/formatter/formatter-example/custom-formatter-example.component.ts", + "tutorials/customization/widget/custom-widget-docs.component.html", + "tutorials/customization/widget/custom-widget-docs.component.ts", + "tutorials/customization/widget/custom-widget.component.html", + "tutorials/customization/widget/custom-widget.component.less", + "tutorials/customization/widget/custom-widget.component.ts", + "tutorials/customization/widget/custom-widget.module.ts", + "tutorials/data-source-setup/data-source-setup-docs.component.html", + "tutorials/data-source-setup/data-source-setup-docs.component.ts", + "tutorials/data-source-setup/data-source-setup.component.html", + "tutorials/data-source-setup/data-source-setup.component.less", + "tutorials/data-source-setup/data-source-setup.component.ts", + "tutorials/data-source-setup/data-source-setup.module.ts", + "tutorials/dynamic-header-links/dynamic-header-links-docs.component.html", + "tutorials/dynamic-header-links/dynamic-header-links-docs.component.ts", + "tutorials/dynamic-header-links/dynamic-header-links-docs.module.ts", + "tutorials/hello-dashboards/hello-dashboards-docs.component.html", + "tutorials/hello-dashboards/hello-dashboards-docs.component.ts", + "tutorials/hello-dashboards/hello-dashboards-example/hello-dashboards-example.component.html", + "tutorials/hello-dashboards/hello-dashboards-example/hello-dashboards-example.component.less", + "tutorials/hello-dashboards/hello-dashboards-example/hello-dashboards-example.component.ts", + "tutorials/hello-dashboards/hello-dashboards.module.ts", + "tutorials/persistence-handler-setup/persistence-handler-setup-docs.component.html", + "tutorials/persistence-handler-setup/persistence-handler-setup-docs.component.ts", + "tutorials/persistence-handler-setup/persistence-handler-setup.component.html", + "tutorials/persistence-handler-setup/persistence-handler-setup.component.less", + "tutorials/persistence-handler-setup/persistence-handler-setup.component.ts", + "tutorials/persistence-handler-setup/persistence-handler-setup.module.ts", + "tutorials/tutorials.module.ts", + "tutorials/widget-creation/widget-creation-docs.component.html", + "tutorials/widget-creation/widget-creation-docs.component.ts", + "tutorials/widget-creation/widget-creation.component.html", + "tutorials/widget-creation/widget-creation.component.less", + "tutorials/widget-creation/widget-creation.component.ts", + "tutorials/widget-creation/widget-creation.module.ts", + "tutorials/widget-editor-setup/widget-editor-setup-docs.component.html", + "tutorials/widget-editor-setup/widget-editor-setup-docs.component.ts", + "tutorials/widget-editor-setup/widget-editor-setup.component.html", + "tutorials/widget-editor-setup/widget-editor-setup.component.less", + "tutorials/widget-editor-setup/widget-editor-setup.component.ts", + "tutorials/widget-editor-setup/widget-editor-setup.module.ts", + "tutorials/widget-error-handling/widget-error-handling-docs.component.html", + "tutorials/widget-error-handling/widget-error-handling-docs.component.ts", + "tutorials/widget-error-handling/widget-error-handling.component.html", + "tutorials/widget-error-handling/widget-error-handling.component.less", + "tutorials/widget-error-handling/widget-error-handling.component.ts", + "tutorials/widget-error-handling/widget-error-handling.module.ts", + "types.ts", + "widget-types/drilldown/drilldown-multi-request-widget/drilldown-multi-request-widget-example.component.html", + "widget-types/drilldown/drilldown-multi-request-widget/drilldown-multi-request-widget-example.component.less", + "widget-types/drilldown/drilldown-multi-request-widget/drilldown-multi-request-widget-example.component.ts", + "widget-types/drilldown/drilldown-widget/data-mock.ts", + "widget-types/drilldown/drilldown-widget/drilldown-widget-example.component.html", + "widget-types/drilldown/drilldown-widget/drilldown-widget-example.component.less", + "widget-types/drilldown/drilldown-widget/drilldown-widget-example.component.ts", + "widget-types/drilldown/drilldown-widget/mock-data-source.ts", + "widget-types/drilldown/drilldown-widget-docs.component.html", + "widget-types/drilldown/drilldown-widget-docs.component.ts", + "widget-types/drilldown/drilldown-widget-docs.module.ts", + "widget-types/embedded-content/embedded-content-docs.component.html", + "widget-types/embedded-content/embedded-content-docs.component.ts", + "widget-types/embedded-content/embedded-content-docs.module.ts", + "widget-types/embedded-content/embedded-content-widget-example/embedded-content-widget-example.component.html", + "widget-types/embedded-content/embedded-content-widget-example/embedded-content-widget-example.component.less", + "widget-types/embedded-content/embedded-content-widget-example/embedded-content-widget-example.component.ts", + "widget-types/kpi/kpi-docs.component.html", + "widget-types/kpi/kpi-docs.component.ts", + "widget-types/kpi/kpi-docs.module.ts", + "widget-types/kpi/kpi-sync-broker/kpi-sync-broker-example.component.html", + "widget-types/kpi/kpi-sync-broker/kpi-sync-broker-example.component.less", + "widget-types/kpi/kpi-sync-broker/kpi-sync-broker-example.component.ts", + "widget-types/kpi/kpi-sync-broker-docs.component.html", + "widget-types/kpi/kpi-sync-broker-docs.component.ts", + "widget-types/kpi/kpi-sync-broker-for-all-tiles/kpi-sync-broker-for-all-tiles-example.component.html", + "widget-types/kpi/kpi-sync-broker-for-all-tiles/kpi-sync-broker-for-all-tiles-example.component.less", + "widget-types/kpi/kpi-sync-broker-for-all-tiles/kpi-sync-broker-for-all-tiles-example.component.ts", + "widget-types/kpi/kpi-widget/kpi-widget-example.component.html", + "widget-types/kpi/kpi-widget/kpi-widget-example.component.less", + "widget-types/kpi/kpi-widget/kpi-widget-example.component.ts", + "widget-types/kpi/kpi-widget-background-color/kpi-widget-background-color-example.component.html", + "widget-types/kpi/kpi-widget-background-color/kpi-widget-background-color-example.component.less", + "widget-types/kpi/kpi-widget-background-color/kpi-widget-background-color-example.component.ts", + "widget-types/kpi/kpi-widget-background-color-docs.component.html", + "widget-types/kpi/kpi-widget-background-color-docs.component.ts", + "widget-types/kpi/kpi-widget-interactive/kpi-widget-interactive-example.component.html", + "widget-types/kpi/kpi-widget-interactive/kpi-widget-interactive-example.component.less", + "widget-types/kpi/kpi-widget-interactive/kpi-widget-interactive-example.component.ts", + "widget-types/proportional/models.ts", + "widget-types/proportional/proportional-docs.component.html", + "widget-types/proportional/proportional-docs.component.ts", + "widget-types/proportional/proportional-docs.module.ts", + "widget-types/proportional/proportional-donut-content-docs.component.html", + "widget-types/proportional/proportional-donut-content-docs.component.ts", + "widget-types/proportional/proportional-donut-content-formatters/proportional-donut-content-formatters-example.component.html", + "widget-types/proportional/proportional-donut-content-formatters/proportional-donut-content-formatters-example.component.less", + "widget-types/proportional/proportional-donut-content-formatters/proportional-donut-content-formatters-example.component.ts", + "widget-types/proportional/proportional-widget/proportional-widget-example.component.html", + "widget-types/proportional/proportional-widget/proportional-widget-example.component.less", + "widget-types/proportional/proportional-widget/proportional-widget-example.component.ts", + "widget-types/proportional/proportional-widget-interactive/proportional-widget-interactive-example.component.html", + "widget-types/proportional/proportional-widget-interactive/proportional-widget-interactive-example.component.less", + "widget-types/proportional/proportional-widget-interactive/proportional-widget-interactive-example.component.ts", + "widget-types/risk-score/risk-score-docs.component.html", + "widget-types/risk-score/risk-score-docs.component.ts", + "widget-types/risk-score/risk-score-docs.module.ts", + "widget-types/risk-score/risk-score-widget-example/risk-score-widget-example.component.html", + "widget-types/risk-score/risk-score-widget-example/risk-score-widget-example.component.less", + "widget-types/risk-score/risk-score-widget-example/risk-score-widget-example.component.ts", + "widget-types/table/table-docs.component.html", + "widget-types/table/table-docs.component.ts", + "widget-types/table/table-docs.module.ts", + "widget-types/table/table-paginator-docs.component.html", + "widget-types/table/table-paginator-docs.component.ts", + "widget-types/table/table-selectable-docs.component.html", + "widget-types/table/table-selectable-docs.component.ts", + "widget-types/table/table-widget/table-widget-example.component.html", + "widget-types/table/table-widget/table-widget-example.component.less", + "widget-types/table/table-widget/table-widget-example.component.ts", + "widget-types/table/table-widget-interactive/table-widget-interactive-example.component.html", + "widget-types/table/table-widget-interactive/table-widget-interactive-example.component.less", + "widget-types/table/table-widget-interactive/table-widget-interactive-example.component.ts", + "widget-types/table/table-widget-paginator/table-widget-paginator-example.component.html", + "widget-types/table/table-widget-paginator/table-widget-paginator-example.component.less", + "widget-types/table/table-widget-paginator/table-widget-paginator-example.component.ts", + "widget-types/table/table-widget-search/table-widget-search-example.component.html", + "widget-types/table/table-widget-search/table-widget-search-example.component.less", + "widget-types/table/table-widget-search/table-widget-search-example.component.ts", + "widget-types/table/table-widget-search-docs.component.html", + "widget-types/table/table-widget-search-docs.component.ts", + "widget-types/table/table-widget-selectable/table-widget-selectable-multi/table-widget-selectable-multi.example.component.html", + "widget-types/table/table-widget-selectable/table-widget-selectable-multi/table-widget-selectable-multi.example.component.less", + "widget-types/table/table-widget-selectable/table-widget-selectable-multi/table-widget-selectable-multi.example.component.ts", + "widget-types/table/table-widget-selectable/table-widget-selectable-radio/table-widget-selectable-radio.example.component.html", + "widget-types/table/table-widget-selectable/table-widget-selectable-radio/table-widget-selectable-radio.example.component.less", + "widget-types/table/table-widget-selectable/table-widget-selectable-radio/table-widget-selectable-radio.example.component.ts", + "widget-types/table/table-widget-selectable/table-widget-selectable-single/table-widget-selectable-single.example.component.html", + "widget-types/table/table-widget-selectable/table-widget-selectable-single/table-widget-selectable-single.example.component.less", + "widget-types/table/table-widget-selectable/table-widget-selectable-single/table-widget-selectable-single.example.component.ts", + "widget-types/table/table-widget-selectable/table-widget-selectable.example.component.html", + "widget-types/table/table-widget-selectable/table-widget-selectable.example.component.less", + "widget-types/table/table-widget-selectable/table-widget-selectable.example.component.ts", + "widget-types/timeseries/timeseries-docs.component.html", + "widget-types/timeseries/timeseries-docs.component.ts", + "widget-types/timeseries/timeseries-docs.module.ts", + "widget-types/timeseries/timeseries-widget-example/timeseries-widget-example.component.html", + "widget-types/timeseries/timeseries-widget-example/timeseries-widget-example.component.less", + "widget-types/timeseries/timeseries-widget-example/timeseries-widget-example.component.ts", + "widget-types/timeseries/timeseries-widget-interactive-example/timeseries-widget-interactive-example.component.html", + "widget-types/timeseries/timeseries-widget-interactive-example/timeseries-widget-interactive-example.component.less", + "widget-types/timeseries/timeseries-widget-interactive-example/timeseries-widget-interactive-example.component.ts", + "widget-types/timeseries/timeseries-widget-status-bar-example/timeseries-widget-status-bar-example.component.html", + "widget-types/timeseries/timeseries-widget-status-bar-example/timeseries-widget-status-bar-example.component.less", + "widget-types/timeseries/timeseries-widget-status-bar-example/timeseries-widget-status-bar-example.component.ts", + "widget-types/view-components/kpi-tile-view-basic/kpi-tile-view-basic-example.component.html", + "widget-types/view-components/kpi-tile-view-basic/kpi-tile-view-basic-example.component.ts", + "widget-types/view-components/kpi-tile-view-interactive/kpi-tile-view-interactive-example.component.html", + "widget-types/view-components/kpi-tile-view-interactive/kpi-tile-view-interactive-example.component.ts", + "widget-types/view-components/proportional-chart-view-playground/proportional-chart-view-playground-example.component.html", + "widget-types/view-components/proportional-chart-view-playground/proportional-chart-view-playground-example.component.ts", + "widget-types/view-components/view-components-docs.component.html", + "widget-types/view-components/view-components-docs.component.ts", + "widget-types/view-components/view-components-docs.module.ts", + "widget-types/widget-types.module.ts", +]; + +export const DEMO_TS_SOURCES: Record = { + "dashboard-docs.module.ts": \\\`// © 2022 SolarWinds Worldwide, LLC. All rights reserved. +// +// Permission is hereby granted, free of charge, to any person obtaining a copy +// of this software and associated documentation files (the "Software"), to +// deal in the Software without restriction, including without limitation the +// rights to use, copy, modify, merge, publish, distribute, sublicense, and/or +// sell copies of the Software, and to permit persons to whom the Software is +// furnished to do so, subject to the following conditions: +// +// The above copyright notice and this permission notice shall be included in +// all copies or substantial portions of the Software. +// +// THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR +// IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, +// FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE +// AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER +// LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, +// OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN +// THE SOFTWARE. + +import { provideHttpClient, withInterceptorsFromDi } from "@angular/common/http"; +import { inject, NgModule, Type } from "@angular/core"; +import { RouterModule, Routes } from "@angular/router"; +import { InMemoryCache } from "@apollo/client/core"; +import { provideApollo } from "apollo-angular"; +import { HttpLink } from "apollo-angular/http"; + +import { NuiDocsModule, NuiMessageModule } from "@nova-ui/bits"; + + +const COUNTRIES_API = "https://countries.trevorblades.com/graphql"; + +const exampleRoutes: Routes = [ + { + path: "overview", + loadChildren: async () => + import("./overview/overview.module") as object as Promise< + Type + >, + }, + { + path: "tutorials", + loadChildren: async () => + import("./tutorials/tutorials.module") as object as Promise< + Type + >, + }, + { + path: "widget-types", + loadChildren: async () => + import("./widget-types/widget-types.module") as object as Promise< + Type + >, + }, +]; + +@NgModule({ + imports: [ + NuiDocsModule, + NuiMessageModule, + RouterModule.forChild(exampleRoutes), + ], + providers: [ + provideHttpClient(withInterceptorsFromDi()), + provideApollo(() => { + const httpLink = inject(HttpLink); + + return { + link: httpLink.create({ uri: COUNTRIES_API }), + cache: new InMemoryCache(), + }; + }), + ], +}) +export default class DashboardDocsModule {} +\\\`, + "demo.files.ts": \\\`// this file autogenerated, do not edit it manually please run the script +// yarn run compile-demo-paths +export const DEMO_PATHS = [ + "dashboard-docs.module.ts", + "demo.files.ts", + "overview/hero/dashboard/hero-dashboard.component.html", + "overview/hero/dashboard/hero-dashboard.component.less", + "overview/hero/dashboard/hero-dashboard.component.ts", + "overview/hero/dashboard/widget-configs.ts", + "overview/hero/data/kpi-datasources.ts", + "overview/hero/data/proportional-datasources.ts", + "overview/hero/data/table/beer-data-source.ts", + "overview/hero/data/table/constants.ts", + "overview/hero/data/table/random-user-data-source.ts", + "overview/hero/data/table/types.ts", + "overview/hero/data/timeseries-data-sources.ts", + "overview/hero/data/types.ts", + "overview/hero/data/widget-data.ts", + "overview/hero/widget-configs/kpi.ts", + "overview/hero/widget-configs/proportional.ts", + "overview/hero/widget-configs/risk-score.ts", + "overview/hero/widget-configs/table.ts", + "overview/hero/widget-configs/timeseries.ts", + "overview/overview-docs.component.html", + "overview/overview-docs.component.ts", + "overview/overview.module.ts", + "tutorials/customization/configurator-section/custom-configurator-section/custom-configurator-section.example.component.html", + "tutorials/customization/configurator-section/custom-configurator-section/custom-configurator-section.example.component.less", + "tutorials/customization/configurator-section/custom-configurator-section/custom-configurator-section.example.component.ts", + "tutorials/customization/configurator-section/custom-configurator-section-docs.component.html", + "tutorials/customization/configurator-section/custom-configurator-section-docs.component.ts", + "tutorials/customization/configurator-section/custom-configurator-section.module.ts", + "tutorials/customization/customization.module.ts", + "tutorials/customization/data-source-configurator/custom-data-source-configurator-docs.component.html", + "tutorials/customization/data-source-configurator/custom-data-source-configurator-docs.component.ts", + "tutorials/customization/data-source-configurator/custom-data-source-configurator.module.ts", + "tutorials/customization/data-source-configurator/example/custom-data-source-configurator-example.component.html", + "tutorials/customization/data-source-configurator/example/custom-data-source-configurator-example.component.less", + "tutorials/customization/data-source-configurator/example/custom-data-source-configurator-example.component.ts", + "tutorials/customization/formatter/custom-formatter.module.ts", + "tutorials/customization/formatter/donut-content-formatter-example/custom-donut-content-formatter-docs.component.html", + "tutorials/customization/formatter/donut-content-formatter-example/custom-donut-content-formatter-docs.component.ts", + "tutorials/customization/formatter/donut-content-formatter-example/custom-donut-content-formatter-example.component.html", + "tutorials/customization/formatter/donut-content-formatter-example/custom-donut-content-formatter-example.component.less", + "tutorials/customization/formatter/donut-content-formatter-example/custom-donut-content-formatter-example.component.ts", + "tutorials/customization/formatter/formatter-example/custom-formatter-docs.component.html", + "tutorials/customization/formatter/formatter-example/custom-formatter-docs.component.ts", + "tutorials/customization/formatter/formatter-example/custom-formatter-example.component.html", + "tutorials/customization/formatter/formatter-example/custom-formatter-example.component.less", + "tutorials/customization/formatter/formatter-example/custom-formatter-example.component.ts", + "tutorials/customization/widget/custom-widget-docs.component.html", + "tutorials/customization/widget/custom-widget-docs.component.ts", + "tutorials/customization/widget/custom-widget.component.html", + "tutorials/customization/widget/custom-widget.component.less", + "tutorials/customization/widget/custom-widget.component.ts", + "tutorials/customization/widget/custom-widget.module.ts", + "tutorials/data-source-setup/data-source-setup-docs.component.html", + "tutorials/data-source-setup/data-source-setup-docs.component.ts", + "tutorials/data-source-setup/data-source-setup.component.html", + "tutorials/data-source-setup/data-source-setup.component.less", + "tutorials/data-source-setup/data-source-setup.component.ts", + "tutorials/data-source-setup/data-source-setup.module.ts", + "tutorials/dynamic-header-links/dynamic-header-links-docs.component.html", + "tutorials/dynamic-header-links/dynamic-header-links-docs.component.ts", + "tutorials/dynamic-header-links/dynamic-header-links-docs.module.ts", + "tutorials/hello-dashboards/hello-dashboards-docs.component.html", + "tutorials/hello-dashboards/hello-dashboards-docs.component.ts", + "tutorials/hello-dashboards/hello-dashboards-example/hello-dashboards-example.component.html", + "tutorials/hello-dashboards/hello-dashboards-example/hello-dashboards-example.component.less", + "tutorials/hello-dashboards/hello-dashboards-example/hello-dashboards-example.component.ts", + "tutorials/hello-dashboards/hello-dashboards.module.ts", + "tutorials/persistence-handler-setup/persistence-handler-setup-docs.component.html", + "tutorials/persistence-handler-setup/persistence-handler-setup-docs.component.ts", + "tutorials/persistence-handler-setup/persistence-handler-setup.component.html", + "tutorials/persistence-handler-setup/persistence-handler-setup.component.less", + "tutorials/persistence-handler-setup/persistence-handler-setup.component.ts", + "tutorials/persistence-handler-setup/persistence-handler-setup.module.ts", + "tutorials/tutorials.module.ts", + "tutorials/widget-creation/widget-creation-docs.component.html", + "tutorials/widget-creation/widget-creation-docs.component.ts", + "tutorials/widget-creation/widget-creation.component.html", + "tutorials/widget-creation/widget-creation.component.less", + "tutorials/widget-creation/widget-creation.component.ts", + "tutorials/widget-creation/widget-creation.module.ts", + "tutorials/widget-editor-setup/widget-editor-setup-docs.component.html", + "tutorials/widget-editor-setup/widget-editor-setup-docs.component.ts", + "tutorials/widget-editor-setup/widget-editor-setup.component.html", + "tutorials/widget-editor-setup/widget-editor-setup.component.less", + "tutorials/widget-editor-setup/widget-editor-setup.component.ts", + "tutorials/widget-editor-setup/widget-editor-setup.module.ts", + "tutorials/widget-error-handling/widget-error-handling-docs.component.html", + "tutorials/widget-error-handling/widget-error-handling-docs.component.ts", + "tutorials/widget-error-handling/widget-error-handling.component.html", + "tutorials/widget-error-handling/widget-error-handling.component.less", + "tutorials/widget-error-handling/widget-error-handling.component.ts", + "tutorials/widget-error-handling/widget-error-handling.module.ts", + "types.ts", + "widget-types/drilldown/drilldown-multi-request-widget/drilldown-multi-request-widget-example.component.html", + "widget-types/drilldown/drilldown-multi-request-widget/drilldown-multi-request-widget-example.component.less", + "widget-types/drilldown/drilldown-multi-request-widget/drilldown-multi-request-widget-example.component.ts", + "widget-types/drilldown/drilldown-widget/data-mock.ts", + "widget-types/drilldown/drilldown-widget/drilldown-widget-example.component.html", + "widget-types/drilldown/drilldown-widget/drilldown-widget-example.component.less", + "widget-types/drilldown/drilldown-widget/drilldown-widget-example.component.ts", + "widget-types/drilldown/drilldown-widget/mock-data-source.ts", + "widget-types/drilldown/drilldown-widget-docs.component.html", + "widget-types/drilldown/drilldown-widget-docs.component.ts", + "widget-types/drilldown/drilldown-widget-docs.module.ts", + "widget-types/embedded-content/embedded-content-docs.component.html", + "widget-types/embedded-content/embedded-content-docs.component.ts", + "widget-types/embedded-content/embedded-content-docs.module.ts", + "widget-types/embedded-content/embedded-content-widget-example/embedded-content-widget-example.component.html", + "widget-types/embedded-content/embedded-content-widget-example/embedded-content-widget-example.component.less", + "widget-types/embedded-content/embedded-content-widget-example/embedded-content-widget-example.component.ts", + "widget-types/kpi/kpi-docs.component.html", + "widget-types/kpi/kpi-docs.component.ts", + "widget-types/kpi/kpi-docs.module.ts", + "widget-types/kpi/kpi-sync-broker/kpi-sync-broker-example.component.html", + "widget-types/kpi/kpi-sync-broker/kpi-sync-broker-example.component.less", + "widget-types/kpi/kpi-sync-broker/kpi-sync-broker-example.component.ts", + "widget-types/kpi/kpi-sync-broker-docs.component.html", + "widget-types/kpi/kpi-sync-broker-docs.component.ts", + "widget-types/kpi/kpi-sync-broker-for-all-tiles/kpi-sync-broker-for-all-tiles-example.component.html", + "widget-types/kpi/kpi-sync-broker-for-all-tiles/kpi-sync-broker-for-all-tiles-example.component.less", + "widget-types/kpi/kpi-sync-broker-for-all-tiles/kpi-sync-broker-for-all-tiles-example.component.ts", + "widget-types/kpi/kpi-widget/kpi-widget-example.component.html", + "widget-types/kpi/kpi-widget/kpi-widget-example.component.less", + "widget-types/kpi/kpi-widget/kpi-widget-example.component.ts", + "widget-types/kpi/kpi-widget-background-color/kpi-widget-background-color-example.component.html", + "widget-types/kpi/kpi-widget-background-color/kpi-widget-background-color-example.component.less", + "widget-types/kpi/kpi-widget-background-color/kpi-widget-background-color-example.component.ts", + "widget-types/kpi/kpi-widget-background-color-docs.component.html", + "widget-types/kpi/kpi-widget-background-color-docs.component.ts", + "widget-types/kpi/kpi-widget-interactive/kpi-widget-interactive-example.component.html", + "widget-types/kpi/kpi-widget-interactive/kpi-widget-interactive-example.component.less", + "widget-types/kpi/kpi-widget-interactive/kpi-widget-interactive-example.component.ts", + "widget-types/proportional/models.ts", + "widget-types/proportional/proportional-docs.component.html", + "widget-types/proportional/proportional-docs.component.ts", + "widget-types/proportional/proportional-docs.module.ts", + "widget-types/proportional/proportional-donut-content-docs.component.html", + "widget-types/proportional/proportional-donut-content-docs.component.ts", + "widget-types/proportional/proportional-donut-content-formatters/proportional-donut-content-formatters-example.component.html", + "widget-types/proportional/proportional-donut-content-formatters/proportional-donut-content-formatters-example.component.less", + "widget-types/proportional/proportional-donut-content-formatters/proportional-donut-content-formatters-example.component.ts", + "widget-types/proportional/proportional-widget/proportional-widget-example.component.html", + "widget-types/proportional/proportional-widget/proportional-widget-example.component.less", + "widget-types/proportional/proportional-widget/proportional-widget-example.component.ts", + "widget-types/proportional/proportional-widget-interactive/proportional-widget-interactive-example.component.html", + "widget-types/proportional/proportional-widget-interactive/proportional-widget-interactive-example.component.less", + "widget-types/proportional/proportional-widget-interactive/proportional-widget-interactive-example.component.ts", + "widget-types/risk-score/risk-score-docs.component.html", + "widget-types/risk-score/risk-score-docs.component.ts", + "widget-types/risk-score/risk-score-docs.module.ts", + "widget-types/risk-score/risk-score-widget-example/risk-score-widget-example.component.html", + "widget-types/risk-score/risk-score-widget-example/risk-score-widget-example.component.less", + "widget-types/risk-score/risk-score-widget-example/risk-score-widget-example.component.ts", + "widget-types/table/table-docs.component.html", + "widget-types/table/table-docs.component.ts", + "widget-types/table/table-docs.module.ts", + "widget-types/table/table-paginator-docs.component.html", + "widget-types/table/table-paginator-docs.component.ts", + "widget-types/table/table-selectable-docs.component.html", + "widget-types/table/table-selectable-docs.component.ts", + "widget-types/table/table-widget/table-widget-example.component.html", + "widget-types/table/table-widget/table-widget-example.component.less", + "widget-types/table/table-widget/table-widget-example.component.ts", + "widget-types/table/table-widget-interactive/table-widget-interactive-example.component.html", + "widget-types/table/table-widget-interactive/table-widget-interactive-example.component.less", + "widget-types/table/table-widget-interactive/table-widget-interactive-example.component.ts", + "widget-types/table/table-widget-paginator/table-widget-paginator-example.component.html", + "widget-types/table/table-widget-paginator/table-widget-paginator-example.component.less", + "widget-types/table/table-widget-paginator/table-widget-paginator-example.component.ts", + "widget-types/table/table-widget-search/table-widget-search-example.component.html", + "widget-types/table/table-widget-search/table-widget-search-example.component.less", + "widget-types/table/table-widget-search/table-widget-search-example.component.ts", + "widget-types/table/table-widget-search-docs.component.html", + "widget-types/table/table-widget-search-docs.component.ts", + "widget-types/table/table-widget-selectable/table-widget-selectable-multi/table-widget-selectable-multi.example.component.html", + "widget-types/table/table-widget-selectable/table-widget-selectable-multi/table-widget-selectable-multi.example.component.less", + "widget-types/table/table-widget-selectable/table-widget-selectable-multi/table-widget-selectable-multi.example.component.ts", + "widget-types/table/table-widget-selectable/table-widget-selectable-radio/table-widget-selectable-radio.example.component.html", + "widget-types/table/table-widget-selectable/table-widget-selectable-radio/table-widget-selectable-radio.example.component.less", + "widget-types/table/table-widget-selectable/table-widget-selectable-radio/table-widget-selectable-radio.example.component.ts", + "widget-types/table/table-widget-selectable/table-widget-selectable-single/table-widget-selectable-single.example.component.html", + "widget-types/table/table-widget-selectable/table-widget-selectable-single/table-widget-selectable-single.example.component.less", + "widget-types/table/table-widget-selectable/table-widget-selectable-single/table-widget-selectable-single.example.component.ts", + "widget-types/table/table-widget-selectable/table-widget-selectable.example.component.html", + "widget-types/table/table-widget-selectable/table-widget-selectable.example.component.less", + "widget-types/table/table-widget-selectable/table-widget-selectable.example.component.ts", + "widget-types/timeseries/timeseries-docs.component.html", + "widget-types/timeseries/timeseries-docs.component.ts", + "widget-types/timeseries/timeseries-docs.module.ts", + "widget-types/timeseries/timeseries-widget-example/timeseries-widget-example.component.html", + "widget-types/timeseries/timeseries-widget-example/timeseries-widget-example.component.less", + "widget-types/timeseries/timeseries-widget-example/timeseries-widget-example.component.ts", + "widget-types/timeseries/timeseries-widget-interactive-example/timeseries-widget-interactive-example.component.html", + "widget-types/timeseries/timeseries-widget-interactive-example/timeseries-widget-interactive-example.component.less", + "widget-types/timeseries/timeseries-widget-interactive-example/timeseries-widget-interactive-example.component.ts", + "widget-types/timeseries/timeseries-widget-status-bar-example/timeseries-widget-status-bar-example.component.html", + "widget-types/timeseries/timeseries-widget-status-bar-example/timeseries-widget-status-bar-example.component.less", + "widget-types/timeseries/timeseries-widget-status-bar-example/timeseries-widget-status-bar-example.component.ts", + "widget-types/view-components/kpi-tile-view-basic/kpi-tile-view-basic-example.component.html", + "widget-types/view-components/kpi-tile-view-basic/kpi-tile-view-basic-example.component.ts", + "widget-types/view-components/kpi-tile-view-interactive/kpi-tile-view-interactive-example.component.html", + "widget-types/view-components/kpi-tile-view-interactive/kpi-tile-view-interactive-example.component.ts", + "widget-types/view-components/proportional-chart-view-playground/proportional-chart-view-playground-example.component.html", + "widget-types/view-components/proportional-chart-view-playground/proportional-chart-view-playground-example.component.ts", + "widget-types/view-components/view-components-docs.component.html", + "widget-types/view-components/view-components-docs.component.ts", + "widget-types/view-components/view-components-docs.module.ts", + "widget-types/widget-types.module.ts", +]; + +export const DEMO_TS_SOURCES: Record = { + "dashboard-docs.module.ts": \\\\\\\`// © 2022 SolarWinds Worldwide, LLC. All rights reserved. +// +// Permission is hereby granted, free of charge, to any person obtaining a copy +// of this software and associated documentation files (the "Software"), to +// deal in the Software without restriction, including without limitation the +// rights to use, copy, modify, merge, publish, distribute, sublicense, and/or +// sell copies of the Software, and to permit persons to whom the Software is +// furnished to do so, subject to the following conditions: +// +// The above copyright notice and this permission notice shall be included in +// all copies or substantial portions of the Software. +// +// THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR +// IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, +// FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE +// AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER +// LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, +// OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN +// THE SOFTWARE. + +import { provideHttpClient, withInterceptorsFromDi } from "@angular/common/http"; +import { inject, NgModule, Type } from "@angular/core"; +import { RouterModule, Routes } from "@angular/router"; +import { InMemoryCache } from "@apollo/client/core"; +import { provideApollo } from "apollo-angular"; +import { HttpLink } from "apollo-angular/http"; + +import { NuiDocsModule, NuiMessageModule } from "@nova-ui/bits"; + + +const COUNTRIES_API = "https://countries.trevorblades.com/graphql"; + +const exampleRoutes: Routes = [ + { + path: "overview", + loadChildren: async () => + import("./overview/overview.module") as object as Promise< + Type + >, + }, + { + path: "tutorials", + loadChildren: async () => + import("./tutorials/tutorials.module") as object as Promise< + Type + >, + }, + { + path: "widget-types", + loadChildren: async () => + import("./widget-types/widget-types.module") as object as Promise< + Type + >, + }, +]; + +@NgModule({ + imports: [ + NuiDocsModule, + NuiMessageModule, + RouterModule.forChild(exampleRoutes), + ], + providers: [ + provideHttpClient(withInterceptorsFromDi()), + provideApollo(() => { + const httpLink = inject(HttpLink); + + return { + link: httpLink.create({ uri: COUNTRIES_API }), + cache: new InMemoryCache(), + }; + }), + ], +}) +export default class DashboardDocsModule {} +\\\\\\\`, + "demo.files.ts": \\\\\\\`// this file autogenerated, do not edit it manually please run the script +// yarn run compile-demo-paths +export const DEMO_PATHS = [ + "dashboard-docs.module.ts", + "demo.files.ts", + "overview/hero/dashboard/hero-dashboard.component.html", + "overview/hero/dashboard/hero-dashboard.component.less", + "overview/hero/dashboard/hero-dashboard.component.ts", + "overview/hero/dashboard/widget-configs.ts", + "overview/hero/data/kpi-datasources.ts", + "overview/hero/data/proportional-datasources.ts", + "overview/hero/data/table/beer-data-source.ts", + "overview/hero/data/table/constants.ts", + "overview/hero/data/table/random-user-data-source.ts", + "overview/hero/data/table/types.ts", + "overview/hero/data/timeseries-data-sources.ts", + "overview/hero/data/types.ts", + "overview/hero/data/widget-data.ts", + "overview/hero/widget-configs/kpi.ts", + "overview/hero/widget-configs/proportional.ts", + "overview/hero/widget-configs/risk-score.ts", + "overview/hero/widget-configs/table.ts", + "overview/hero/widget-configs/timeseries.ts", + "overview/overview-docs.component.html", + "overview/overview-docs.component.ts", + "overview/overview.module.ts", + "tutorials/customization/configurator-section/custom-configurator-section/custom-configurator-section.example.component.html", + "tutorials/customization/configurator-section/custom-configurator-section/custom-configurator-section.example.component.less", + "tutorials/customization/configurator-section/custom-configurator-section/custom-configurator-section.example.component.ts", + "tutorials/customization/configurator-section/custom-configurator-section-docs.component.html", + "tutorials/customization/configurator-section/custom-configurator-section-docs.component.ts", + "tutorials/customization/configurator-section/custom-configurator-section.module.ts", + "tutorials/customization/customization.module.ts", + "tutorials/customization/data-source-configurator/custom-data-source-configurator-docs.component.html", + "tutorials/customization/data-source-configurator/custom-data-source-configurator-docs.component.ts", + "tutorials/customization/data-source-configurator/custom-data-source-configurator.module.ts", + "tutorials/customization/data-source-configurator/example/custom-data-source-configurator-example.component.html", + "tutorials/customization/data-source-configurator/example/custom-data-source-configurator-example.component.less", + "tutorials/customization/data-source-configurator/example/custom-data-source-configurator-example.component.ts", + "tutorials/customization/formatter/custom-formatter.module.ts", + "tutorials/customization/formatter/donut-content-formatter-example/custom-donut-content-formatter-docs.component.html", + "tutorials/customization/formatter/donut-content-formatter-example/custom-donut-content-formatter-docs.component.ts", + "tutorials/customization/formatter/donut-content-formatter-example/custom-donut-content-formatter-example.component.html", + "tutorials/customization/formatter/donut-content-formatter-example/custom-donut-content-formatter-example.component.less", + "tutorials/customization/formatter/donut-content-formatter-example/custom-donut-content-formatter-example.component.ts", + "tutorials/customization/formatter/formatter-example/custom-formatter-docs.component.html", + "tutorials/customization/formatter/formatter-example/custom-formatter-docs.component.ts", + "tutorials/customization/formatter/formatter-example/custom-formatter-example.component.html", + "tutorials/customization/formatter/formatter-example/custom-formatter-example.component.less", + "tutorials/customization/formatter/formatter-example/custom-formatter-example.component.ts", + "tutorials/customization/widget/custom-widget-docs.component.html", + "tutorials/customization/widget/custom-widget-docs.component.ts", + "tutorials/customization/widget/custom-widget.component.html", + "tutorials/customization/widget/custom-widget.component.less", + "tutorials/customization/widget/custom-widget.component.ts", + "tutorials/customization/widget/custom-widget.module.ts", + "tutorials/data-source-setup/data-source-setup-docs.component.html", + "tutorials/data-source-setup/data-source-setup-docs.component.ts", + "tutorials/data-source-setup/data-source-setup.component.html", + "tutorials/data-source-setup/data-source-setup.component.less", + "tutorials/data-source-setup/data-source-setup.component.ts", + "tutorials/data-source-setup/data-source-setup.module.ts", + "tutorials/dynamic-header-links/dynamic-header-links-docs.component.html", + "tutorials/dynamic-header-links/dynamic-header-links-docs.component.ts", + "tutorials/dynamic-header-links/dynamic-header-links-docs.module.ts", + "tutorials/hello-dashboards/hello-dashboards-docs.component.html", + "tutorials/hello-dashboards/hello-dashboards-docs.component.ts", + "tutorials/hello-dashboards/hello-dashboards-example/hello-dashboards-example.component.html", + "tutorials/hello-dashboards/hello-dashboards-example/hello-dashboards-example.component.less", + "tutorials/hello-dashboards/hello-dashboards-example/hello-dashboards-example.component.ts", + "tutorials/hello-dashboards/hello-dashboards.module.ts", + "tutorials/persistence-handler-setup/persistence-handler-setup-docs.component.html", + "tutorials/persistence-handler-setup/persistence-handler-setup-docs.component.ts", + "tutorials/persistence-handler-setup/persistence-handler-setup.component.html", + "tutorials/persistence-handler-setup/persistence-handler-setup.component.less", + "tutorials/persistence-handler-setup/persistence-handler-setup.component.ts", + "tutorials/persistence-handler-setup/persistence-handler-setup.module.ts", + "tutorials/tutorials.module.ts", + "tutorials/widget-creation/widget-creation-docs.component.html", + "tutorials/widget-creation/widget-creation-docs.component.ts", + "tutorials/widget-creation/widget-creation.component.html", + "tutorials/widget-creation/widget-creation.component.less", + "tutorials/widget-creation/widget-creation.component.ts", + "tutorials/widget-creation/widget-creation.module.ts", + "tutorials/widget-editor-setup/widget-editor-setup-docs.component.html", + "tutorials/widget-editor-setup/widget-editor-setup-docs.component.ts", + "tutorials/widget-editor-setup/widget-editor-setup.component.html", + "tutorials/widget-editor-setup/widget-editor-setup.component.less", + "tutorials/widget-editor-setup/widget-editor-setup.component.ts", + "tutorials/widget-editor-setup/widget-editor-setup.module.ts", + "tutorials/widget-error-handling/widget-error-handling-docs.component.html", + "tutorials/widget-error-handling/widget-error-handling-docs.component.ts", + "tutorials/widget-error-handling/widget-error-handling.component.html", + "tutorials/widget-error-handling/widget-error-handling.component.less", + "tutorials/widget-error-handling/widget-error-handling.component.ts", + "tutorials/widget-error-handling/widget-error-handling.module.ts", + "types.ts", + "widget-types/drilldown/drilldown-multi-request-widget/drilldown-multi-request-widget-example.component.html", + "widget-types/drilldown/drilldown-multi-request-widget/drilldown-multi-request-widget-example.component.less", + "widget-types/drilldown/drilldown-multi-request-widget/drilldown-multi-request-widget-example.component.ts", + "widget-types/drilldown/drilldown-widget/data-mock.ts", + "widget-types/drilldown/drilldown-widget/drilldown-widget-example.component.html", + "widget-types/drilldown/drilldown-widget/drilldown-widget-example.component.less", + "widget-types/drilldown/drilldown-widget/drilldown-widget-example.component.ts", + "widget-types/drilldown/drilldown-widget/mock-data-source.ts", + "widget-types/drilldown/drilldown-widget-docs.component.html", + "widget-types/drilldown/drilldown-widget-docs.component.ts", + "widget-types/drilldown/drilldown-widget-docs.module.ts", + "widget-types/embedded-content/embedded-content-docs.component.html", + "widget-types/embedded-content/embedded-content-docs.component.ts", + "widget-types/embedded-content/embedded-content-docs.module.ts", + "widget-types/embedded-content/embedded-content-widget-example/embedded-content-widget-example.component.html", + "widget-types/embedded-content/embedded-content-widget-example/embedded-content-widget-example.component.less", + "widget-types/embedded-content/embedded-content-widget-example/embedded-content-widget-example.component.ts", + "widget-types/kpi/kpi-docs.component.html", + "widget-types/kpi/kpi-docs.component.ts", + "widget-types/kpi/kpi-docs.module.ts", + "widget-types/kpi/kpi-sync-broker/kpi-sync-broker-example.component.html", + "widget-types/kpi/kpi-sync-broker/kpi-sync-broker-example.component.less", + "widget-types/kpi/kpi-sync-broker/kpi-sync-broker-example.component.ts", + "widget-types/kpi/kpi-sync-broker-docs.component.html", + "widget-types/kpi/kpi-sync-broker-docs.component.ts", + "widget-types/kpi/kpi-sync-broker-for-all-tiles/kpi-sync-broker-for-all-tiles-example.component.html", + "widget-types/kpi/kpi-sync-broker-for-all-tiles/kpi-sync-broker-for-all-tiles-example.component.less", + "widget-types/kpi/kpi-sync-broker-for-all-tiles/kpi-sync-broker-for-all-tiles-example.component.ts", + "widget-types/kpi/kpi-widget/kpi-widget-example.component.html", + "widget-types/kpi/kpi-widget/kpi-widget-example.component.less", + "widget-types/kpi/kpi-widget/kpi-widget-example.component.ts", + "widget-types/kpi/kpi-widget-background-color/kpi-widget-background-color-example.component.html", + "widget-types/kpi/kpi-widget-background-color/kpi-widget-background-color-example.component.less", + "widget-types/kpi/kpi-widget-background-color/kpi-widget-background-color-example.component.ts", + "widget-types/kpi/kpi-widget-background-color-docs.component.html", + "widget-types/kpi/kpi-widget-background-color-docs.component.ts", + "widget-types/kpi/kpi-widget-interactive/kpi-widget-interactive-example.component.html", + "widget-types/kpi/kpi-widget-interactive/kpi-widget-interactive-example.component.less", + "widget-types/kpi/kpi-widget-interactive/kpi-widget-interactive-example.component.ts", + "widget-types/proportional/models.ts", + "widget-types/proportional/proportional-docs.component.html", + "widget-types/proportional/proportional-docs.component.ts", + "widget-types/proportional/proportional-docs.module.ts", + "widget-types/proportional/proportional-donut-content-docs.component.html", + "widget-types/proportional/proportional-donut-content-docs.component.ts", + "widget-types/proportional/proportional-donut-content-formatters/proportional-donut-content-formatters-example.component.html", + "widget-types/proportional/proportional-donut-content-formatters/proportional-donut-content-formatters-example.component.less", + "widget-types/proportional/proportional-donut-content-formatters/proportional-donut-content-formatters-example.component.ts", + "widget-types/proportional/proportional-widget/proportional-widget-example.component.html", + "widget-types/proportional/proportional-widget/proportional-widget-example.component.less", + "widget-types/proportional/proportional-widget/proportional-widget-example.component.ts", + "widget-types/proportional/proportional-widget-interactive/proportional-widget-interactive-example.component.html", + "widget-types/proportional/proportional-widget-interactive/proportional-widget-interactive-example.component.less", + "widget-types/proportional/proportional-widget-interactive/proportional-widget-interactive-example.component.ts", + "widget-types/risk-score/risk-score-docs.component.html", + "widget-types/risk-score/risk-score-docs.component.ts", + "widget-types/risk-score/risk-score-docs.module.ts", + "widget-types/risk-score/risk-score-widget-example/risk-score-widget-example.component.html", + "widget-types/risk-score/risk-score-widget-example/risk-score-widget-example.component.less", + "widget-types/risk-score/risk-score-widget-example/risk-score-widget-example.component.ts", + "widget-types/table/table-docs.component.html", + "widget-types/table/table-docs.component.ts", + "widget-types/table/table-docs.module.ts", + "widget-types/table/table-paginator-docs.component.html", + "widget-types/table/table-paginator-docs.component.ts", + "widget-types/table/table-selectable-docs.component.html", + "widget-types/table/table-selectable-docs.component.ts", + "widget-types/table/table-widget/table-widget-example.component.html", + "widget-types/table/table-widget/table-widget-example.component.less", + "widget-types/table/table-widget/table-widget-example.component.ts", + "widget-types/table/table-widget-interactive/table-widget-interactive-example.component.html", + "widget-types/table/table-widget-interactive/table-widget-interactive-example.component.less", + "widget-types/table/table-widget-interactive/table-widget-interactive-example.component.ts", + "widget-types/table/table-widget-paginator/table-widget-paginator-example.component.html", + "widget-types/table/table-widget-paginator/table-widget-paginator-example.component.less", + "widget-types/table/table-widget-paginator/table-widget-paginator-example.component.ts", + "widget-types/table/table-widget-search/table-widget-search-example.component.html", + "widget-types/table/table-widget-search/table-widget-search-example.component.less", + "widget-types/table/table-widget-search/table-widget-search-example.component.ts", + "widget-types/table/table-widget-search-docs.component.html", + "widget-types/table/table-widget-search-docs.component.ts", + "widget-types/table/table-widget-selectable/table-widget-selectable-multi/table-widget-selectable-multi.example.component.html", + "widget-types/table/table-widget-selectable/table-widget-selectable-multi/table-widget-selectable-multi.example.component.less", + "widget-types/table/table-widget-selectable/table-widget-selectable-multi/table-widget-selectable-multi.example.component.ts", + "widget-types/table/table-widget-selectable/table-widget-selectable-radio/table-widget-selectable-radio.example.component.html", + "widget-types/table/table-widget-selectable/table-widget-selectable-radio/table-widget-selectable-radio.example.component.less", + "widget-types/table/table-widget-selectable/table-widget-selectable-radio/table-widget-selectable-radio.example.component.ts", + "widget-types/table/table-widget-selectable/table-widget-selectable-single/table-widget-selectable-single.example.component.html", + "widget-types/table/table-widget-selectable/table-widget-selectable-single/table-widget-selectable-single.example.component.less", + "widget-types/table/table-widget-selectable/table-widget-selectable-single/table-widget-selectable-single.example.component.ts", + "widget-types/table/table-widget-selectable/table-widget-selectable.example.component.html", + "widget-types/table/table-widget-selectable/table-widget-selectable.example.component.less", + "widget-types/table/table-widget-selectable/table-widget-selectable.example.component.ts", + "widget-types/timeseries/timeseries-docs.component.html", + "widget-types/timeseries/timeseries-docs.component.ts", + "widget-types/timeseries/timeseries-docs.module.ts", + "widget-types/timeseries/timeseries-widget-example/timeseries-widget-example.component.html", + "widget-types/timeseries/timeseries-widget-example/timeseries-widget-example.component.less", + "widget-types/timeseries/timeseries-widget-example/timeseries-widget-example.component.ts", + "widget-types/timeseries/timeseries-widget-interactive-example/timeseries-widget-interactive-example.component.html", + "widget-types/timeseries/timeseries-widget-interactive-example/timeseries-widget-interactive-example.component.less", + "widget-types/timeseries/timeseries-widget-interactive-example/timeseries-widget-interactive-example.component.ts", + "widget-types/timeseries/timeseries-widget-status-bar-example/timeseries-widget-status-bar-example.component.html", + "widget-types/timeseries/timeseries-widget-status-bar-example/timeseries-widget-status-bar-example.component.less", + "widget-types/timeseries/timeseries-widget-status-bar-example/timeseries-widget-status-bar-example.component.ts", + "widget-types/view-components/kpi-tile-view-basic/kpi-tile-view-basic-example.component.html", + "widget-types/view-components/kpi-tile-view-basic/kpi-tile-view-basic-example.component.ts", + "widget-types/view-components/kpi-tile-view-interactive/kpi-tile-view-interactive-example.component.html", + "widget-types/view-components/kpi-tile-view-interactive/kpi-tile-view-interactive-example.component.ts", + "widget-types/view-components/proportional-chart-view-playground/proportional-chart-view-playground-example.component.html", + "widget-types/view-components/proportional-chart-view-playground/proportional-chart-view-playground-example.component.ts", + "widget-types/view-components/view-components-docs.component.html", + "widget-types/view-components/view-components-docs.component.ts", + "widget-types/view-components/view-components-docs.module.ts", + "widget-types/widget-types.module.ts", +]; + +export const DEMO_TS_SOURCES: Record = { + "dashboard-docs.module.ts": \\\\\\\\\\\\\\\`// © 2022 SolarWinds Worldwide, LLC. All rights reserved. +// +// Permission is hereby granted, free of charge, to any person obtaining a copy +// of this software and associated documentation files (the "Software"), to +// deal in the Software without restriction, including without limitation the +// rights to use, copy, modify, merge, publish, distribute, sublicense, and/or +// sell copies of the Software, and to permit persons to whom the Software is +// furnished to do so, subject to the following conditions: +// +// The above copyright notice and this permission notice shall be included in +// all copies or substantial portions of the Software. +// +// THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR +// IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, +// FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE +// AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER +// LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, +// OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN +// THE SOFTWARE. + +import { provideHttpClient, withInterceptorsFromDi } from "@angular/common/http"; +import { inject, NgModule, Type } from "@angular/core"; +import { RouterModule, Routes } from "@angular/router"; +import { InMemoryCache } from "@apollo/client/core"; +import { provideApollo } from "apollo-angular"; +import { HttpLink } from "apollo-angular/http"; + +import { NuiDocsModule, NuiMessageModule } from "@nova-ui/bits"; + + +const COUNTRIES_API = "https://countries.trevorblades.com/graphql"; + +const exampleRoutes: Routes = [ + { + path: "overview", + loadChildren: async () => + import("./overview/overview.module") as object as Promise< + Type + >, + }, + { + path: "tutorials", + loadChildren: async () => + import("./tutorials/tutorials.module") as object as Promise< + Type + >, + }, + { + path: "widget-types", + loadChildren: async () => + import("./widget-types/widget-types.module") as object as Promise< + Type + >, + }, +]; + +@NgModule({ + imports: [ + NuiDocsModule, + NuiMessageModule, + RouterModule.forChild(exampleRoutes), + ], + providers: [ + provideHttpClient(withInterceptorsFromDi()), + provideApollo(() => { + const httpLink = inject(HttpLink); + + return { + link: httpLink.create({ uri: COUNTRIES_API }), + cache: new InMemoryCache(), + }; + }), + ], +}) +export default class DashboardDocsModule {} +\\\\\\\\\\\\\\\`, + "demo.files.ts": \\\\\\\\\\\\\\\`// this file autogenerated, do not edit it manually please run the script +// yarn run compile-demo-paths +export const DEMO_PATHS = [ + "dashboard-docs.module.ts", + "demo.files.ts", + "overview/hero/dashboard/hero-dashboard.component.html", + "overview/hero/dashboard/hero-dashboard.component.less", + "overview/hero/dashboard/hero-dashboard.component.ts", + "overview/hero/dashboard/widget-configs.ts", + "overview/hero/data/kpi-datasources.ts", + "overview/hero/data/proportional-datasources.ts", + "overview/hero/data/table/beer-data-source.ts", + "overview/hero/data/table/constants.ts", + "overview/hero/data/table/random-user-data-source.ts", + "overview/hero/data/table/types.ts", + "overview/hero/data/timeseries-data-sources.ts", + "overview/hero/data/types.ts", + "overview/hero/data/widget-data.ts", + "overview/hero/widget-configs/kpi.ts", + "overview/hero/widget-configs/proportional.ts", + "overview/hero/widget-configs/risk-score.ts", + "overview/hero/widget-configs/table.ts", + "overview/hero/widget-configs/timeseries.ts", + "overview/overview-docs.component.html", + "overview/overview-docs.component.ts", + "overview/overview.module.ts", + "tutorials/customization/configurator-section/custom-configurator-section/custom-configurator-section.example.component.html", + "tutorials/customization/configurator-section/custom-configurator-section/custom-configurator-section.example.component.less", + "tutorials/customization/configurator-section/custom-configurator-section/custom-configurator-section.example.component.ts", + "tutorials/customization/configurator-section/custom-configurator-section-docs.component.html", + "tutorials/customization/configurator-section/custom-configurator-section-docs.component.ts", + "tutorials/customization/configurator-section/custom-configurator-section.module.ts", + "tutorials/customization/customization.module.ts", + "tutorials/customization/data-source-configurator/custom-data-source-configurator-docs.component.html", + "tutorials/customization/data-source-configurator/custom-data-source-configurator-docs.component.ts", + "tutorials/customization/data-source-configurator/custom-data-source-configurator.module.ts", + "tutorials/customization/data-source-configurator/example/custom-data-source-configurator-example.component.html", + "tutorials/customization/data-source-configurator/example/custom-data-source-configurator-example.component.less", + "tutorials/customization/data-source-configurator/example/custom-data-source-configurator-example.component.ts", + "tutorials/customization/formatter/custom-formatter.module.ts", + "tutorials/customization/formatter/donut-content-formatter-example/custom-donut-content-formatter-docs.component.html", + "tutorials/customization/formatter/donut-content-formatter-example/custom-donut-content-formatter-docs.component.ts", + "tutorials/customization/formatter/donut-content-formatter-example/custom-donut-content-formatter-example.component.html", + "tutorials/customization/formatter/donut-content-formatter-example/custom-donut-content-formatter-example.component.less", + "tutorials/customization/formatter/donut-content-formatter-example/custom-donut-content-formatter-example.component.ts", + "tutorials/customization/formatter/formatter-example/custom-formatter-docs.component.html", + "tutorials/customization/formatter/formatter-example/custom-formatter-docs.component.ts", + "tutorials/customization/formatter/formatter-example/custom-formatter-example.component.html", + "tutorials/customization/formatter/formatter-example/custom-formatter-example.component.less", + "tutorials/customization/formatter/formatter-example/custom-formatter-example.component.ts", + "tutorials/customization/widget/custom-widget-docs.component.html", + "tutorials/customization/widget/custom-widget-docs.component.ts", + "tutorials/customization/widget/custom-widget.component.html", + "tutorials/customization/widget/custom-widget.component.less", + "tutorials/customization/widget/custom-widget.component.ts", + "tutorials/customization/widget/custom-widget.module.ts", + "tutorials/data-source-setup/data-source-setup-docs.component.html", + "tutorials/data-source-setup/data-source-setup-docs.component.ts", + "tutorials/data-source-setup/data-source-setup.component.html", + "tutorials/data-source-setup/data-source-setup.component.less", + "tutorials/data-source-setup/data-source-setup.component.ts", + "tutorials/data-source-setup/data-source-setup.module.ts", + "tutorials/dynamic-header-links/dynamic-header-links-docs.component.html", + "tutorials/dynamic-header-links/dynamic-header-links-docs.component.ts", + "tutorials/dynamic-header-links/dynamic-header-links-docs.module.ts", + "tutorials/hello-dashboards/hello-dashboards-docs.component.html", + "tutorials/hello-dashboards/hello-dashboards-docs.component.ts", + "tutorials/hello-dashboards/hello-dashboards-example/hello-dashboards-example.component.html", + "tutorials/hello-dashboards/hello-dashboards-example/hello-dashboards-example.component.less", + "tutorials/hello-dashboards/hello-dashboards-example/hello-dashboards-example.component.ts", + "tutorials/hello-dashboards/hello-dashboards.module.ts", + "tutorials/persistence-handler-setup/persistence-handler-setup-docs.component.html", + "tutorials/persistence-handler-setup/persistence-handler-setup-docs.component.ts", + "tutorials/persistence-handler-setup/persistence-handler-setup.component.html", + "tutorials/persistence-handler-setup/persistence-handler-setup.component.less", + "tutorials/persistence-handler-setup/persistence-handler-setup.component.ts", + "tutorials/persistence-handler-setup/persistence-handler-setup.module.ts", + "tutorials/tutorials.module.ts", + "tutorials/widget-creation/widget-creation-docs.component.html", + "tutorials/widget-creation/widget-creation-docs.component.ts", + "tutorials/widget-creation/widget-creation.component.html", + "tutorials/widget-creation/widget-creation.component.less", + "tutorials/widget-creation/widget-creation.component.ts", + "tutorials/widget-creation/widget-creation.module.ts", + "tutorials/widget-editor-setup/widget-editor-setup-docs.component.html", + "tutorials/widget-editor-setup/widget-editor-setup-docs.component.ts", + "tutorials/widget-editor-setup/widget-editor-setup.component.html", + "tutorials/widget-editor-setup/widget-editor-setup.component.less", + "tutorials/widget-editor-setup/widget-editor-setup.component.ts", + "tutorials/widget-editor-setup/widget-editor-setup.module.ts", + "tutorials/widget-error-handling/widget-error-handling-docs.component.html", + "tutorials/widget-error-handling/widget-error-handling-docs.component.ts", + "tutorials/widget-error-handling/widget-error-handling.component.html", + "tutorials/widget-error-handling/widget-error-handling.component.less", + "tutorials/widget-error-handling/widget-error-handling.component.ts", + "tutorials/widget-error-handling/widget-error-handling.module.ts", + "types.ts", + "widget-types/drilldown/drilldown-multi-request-widget/drilldown-multi-request-widget-example.component.html", + "widget-types/drilldown/drilldown-multi-request-widget/drilldown-multi-request-widget-example.component.less", + "widget-types/drilldown/drilldown-multi-request-widget/drilldown-multi-request-widget-example.component.ts", + "widget-types/drilldown/drilldown-widget/data-mock.ts", + "widget-types/drilldown/drilldown-widget/drilldown-widget-example.component.html", + "widget-types/drilldown/drilldown-widget/drilldown-widget-example.component.less", + "widget-types/drilldown/drilldown-widget/drilldown-widget-example.component.ts", + "widget-types/drilldown/drilldown-widget/mock-data-source.ts", + "widget-types/drilldown/drilldown-widget-docs.component.html", + "widget-types/drilldown/drilldown-widget-docs.component.ts", + "widget-types/drilldown/drilldown-widget-docs.module.ts", + "widget-types/embedded-content/embedded-content-docs.component.html", + "widget-types/embedded-content/embedded-content-docs.component.ts", + "widget-types/embedded-content/embedded-content-docs.module.ts", + "widget-types/embedded-content/embedded-content-widget-example/embedded-content-widget-example.component.html", + "widget-types/embedded-content/embedded-content-widget-example/embedded-content-widget-example.component.less", + "widget-types/embedded-content/embedded-content-widget-example/embedded-content-widget-example.component.ts", + "widget-types/kpi/kpi-docs.component.html", + "widget-types/kpi/kpi-docs.component.ts", + "widget-types/kpi/kpi-docs.module.ts", + "widget-types/kpi/kpi-sync-broker/kpi-sync-broker-example.component.html", + "widget-types/kpi/kpi-sync-broker/kpi-sync-broker-example.component.less", + "widget-types/kpi/kpi-sync-broker/kpi-sync-broker-example.component.ts", + "widget-types/kpi/kpi-sync-broker-docs.component.html", + "widget-types/kpi/kpi-sync-broker-docs.component.ts", + "widget-types/kpi/kpi-sync-broker-for-all-tiles/kpi-sync-broker-for-all-tiles-example.component.html", + "widget-types/kpi/kpi-sync-broker-for-all-tiles/kpi-sync-broker-for-all-tiles-example.component.less", + "widget-types/kpi/kpi-sync-broker-for-all-tiles/kpi-sync-broker-for-all-tiles-example.component.ts", + "widget-types/kpi/kpi-widget/kpi-widget-example.component.html", + "widget-types/kpi/kpi-widget/kpi-widget-example.component.less", + "widget-types/kpi/kpi-widget/kpi-widget-example.component.ts", + "widget-types/kpi/kpi-widget-background-color/kpi-widget-background-color-example.component.html", + "widget-types/kpi/kpi-widget-background-color/kpi-widget-background-color-example.component.less", + "widget-types/kpi/kpi-widget-background-color/kpi-widget-background-color-example.component.ts", + "widget-types/kpi/kpi-widget-background-color-docs.component.html", + "widget-types/kpi/kpi-widget-background-color-docs.component.ts", + "widget-types/kpi/kpi-widget-interactive/kpi-widget-interactive-example.component.html", + "widget-types/kpi/kpi-widget-interactive/kpi-widget-interactive-example.component.less", + "widget-types/kpi/kpi-widget-interactive/kpi-widget-interactive-example.component.ts", + "widget-types/proportional/models.ts", + "widget-types/proportional/proportional-docs.component.html", + "widget-types/proportional/proportional-docs.component.ts", + "widget-types/proportional/proportional-docs.module.ts", + "widget-types/proportional/proportional-donut-content-docs.component.html", + "widget-types/proportional/proportional-donut-content-docs.component.ts", + "widget-types/proportional/proportional-donut-content-formatters/proportional-donut-content-formatters-example.component.html", + "widget-types/proportional/proportional-donut-content-formatters/proportional-donut-content-formatters-example.component.less", + "widget-types/proportional/proportional-donut-content-formatters/proportional-donut-content-formatters-example.component.ts", + "widget-types/proportional/proportional-widget/proportional-widget-example.component.html", + "widget-types/proportional/proportional-widget/proportional-widget-example.component.less", + "widget-types/proportional/proportional-widget/proportional-widget-example.component.ts", + "widget-types/proportional/proportional-widget-interactive/proportional-widget-interactive-example.component.html", + "widget-types/proportional/proportional-widget-interactive/proportional-widget-interactive-example.component.less", + "widget-types/proportional/proportional-widget-interactive/proportional-widget-interactive-example.component.ts", + "widget-types/risk-score/risk-score-docs.component.html", + "widget-types/risk-score/risk-score-docs.component.ts", + "widget-types/risk-score/risk-score-docs.module.ts", + "widget-types/risk-score/risk-score-widget-example/risk-score-widget-example.component.html", + "widget-types/risk-score/risk-score-widget-example/risk-score-widget-example.component.less", + "widget-types/risk-score/risk-score-widget-example/risk-score-widget-example.component.ts", + "widget-types/table/table-docs.component.html", + "widget-types/table/table-docs.component.ts", + "widget-types/table/table-docs.module.ts", + "widget-types/table/table-paginator-docs.component.html", + "widget-types/table/table-paginator-docs.component.ts", + "widget-types/table/table-selectable-docs.component.html", + "widget-types/table/table-selectable-docs.component.ts", + "widget-types/table/table-widget/table-widget-example.component.html", + "widget-types/table/table-widget/table-widget-example.component.less", + "widget-types/table/table-widget/table-widget-example.component.ts", + "widget-types/table/table-widget-interactive/table-widget-interactive-example.component.html", + "widget-types/table/table-widget-interactive/table-widget-interactive-example.component.less", + "widget-types/table/table-widget-interactive/table-widget-interactive-example.component.ts", + "widget-types/table/table-widget-paginator/table-widget-paginator-example.component.html", + "widget-types/table/table-widget-paginator/table-widget-paginator-example.component.less", + "widget-types/table/table-widget-paginator/table-widget-paginator-example.component.ts", + "widget-types/table/table-widget-search/table-widget-search-example.component.html", + "widget-types/table/table-widget-search/table-widget-search-example.component.less", + "widget-types/table/table-widget-search/table-widget-search-example.component.ts", + "widget-types/table/table-widget-search-docs.component.html", + "widget-types/table/table-widget-search-docs.component.ts", + "widget-types/table/table-widget-selectable/table-widget-selectable-multi/table-widget-selectable-multi.example.component.html", + "widget-types/table/table-widget-selectable/table-widget-selectable-multi/table-widget-selectable-multi.example.component.less", + "widget-types/table/table-widget-selectable/table-widget-selectable-multi/table-widget-selectable-multi.example.component.ts", + "widget-types/table/table-widget-selectable/table-widget-selectable-radio/table-widget-selectable-radio.example.component.html", + "widget-types/table/table-widget-selectable/table-widget-selectable-radio/table-widget-selectable-radio.example.component.less", + "widget-types/table/table-widget-selectable/table-widget-selectable-radio/table-widget-selectable-radio.example.component.ts", + "widget-types/table/table-widget-selectable/table-widget-selectable-single/table-widget-selectable-single.example.component.html", + "widget-types/table/table-widget-selectable/table-widget-selectable-single/table-widget-selectable-single.example.component.less", + "widget-types/table/table-widget-selectable/table-widget-selectable-single/table-widget-selectable-single.example.component.ts", + "widget-types/table/table-widget-selectable/table-widget-selectable.example.component.html", + "widget-types/table/table-widget-selectable/table-widget-selectable.example.component.less", + "widget-types/table/table-widget-selectable/table-widget-selectable.example.component.ts", + "widget-types/timeseries/timeseries-docs.component.html", + "widget-types/timeseries/timeseries-docs.component.ts", + "widget-types/timeseries/timeseries-docs.module.ts", + "widget-types/timeseries/timeseries-widget-example/timeseries-widget-example.component.html", + "widget-types/timeseries/timeseries-widget-example/timeseries-widget-example.component.less", + "widget-types/timeseries/timeseries-widget-example/timeseries-widget-example.component.ts", + "widget-types/timeseries/timeseries-widget-interactive-example/timeseries-widget-interactive-example.component.html", + "widget-types/timeseries/timeseries-widget-interactive-example/timeseries-widget-interactive-example.component.less", + "widget-types/timeseries/timeseries-widget-interactive-example/timeseries-widget-interactive-example.component.ts", + "widget-types/timeseries/timeseries-widget-status-bar-example/timeseries-widget-status-bar-example.component.html", + "widget-types/timeseries/timeseries-widget-status-bar-example/timeseries-widget-status-bar-example.component.less", + "widget-types/timeseries/timeseries-widget-status-bar-example/timeseries-widget-status-bar-example.component.ts", + "widget-types/view-components/kpi-tile-view-basic/kpi-tile-view-basic-example.component.html", + "widget-types/view-components/kpi-tile-view-basic/kpi-tile-view-basic-example.component.ts", + "widget-types/view-components/kpi-tile-view-interactive/kpi-tile-view-interactive-example.component.html", + "widget-types/view-components/kpi-tile-view-interactive/kpi-tile-view-interactive-example.component.ts", + "widget-types/view-components/proportional-chart-view-playground/proportional-chart-view-playground-example.component.html", + "widget-types/view-components/proportional-chart-view-playground/proportional-chart-view-playground-example.component.ts", + "widget-types/view-components/view-components-docs.component.html", + "widget-types/view-components/view-components-docs.component.ts", + "widget-types/view-components/view-components-docs.module.ts", + "widget-types/widget-types.module.ts", +]; + +export const DEMO_TS_SOURCES: Record = { + "dashboard-docs.module.ts": \\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\`// © 2022 SolarWinds Worldwide, LLC. All rights reserved. +// +// Permission is hereby granted, free of charge, to any person obtaining a copy +// of this software and associated documentation files (the "Software"), to +// deal in the Software without restriction, including without limitation the +// rights to use, copy, modify, merge, publish, distribute, sublicense, and/or +// sell copies of the Software, and to permit persons to whom the Software is +// furnished to do so, subject to the following conditions: +// +// The above copyright notice and this permission notice shall be included in +// all copies or substantial portions of the Software. +// +// THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR +// IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, +// FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE +// AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER +// LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, +// OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN +// THE SOFTWARE. + +import { provideHttpClient, withInterceptorsFromDi } from "@angular/common/http"; +import { inject, NgModule, Type } from "@angular/core"; +import { RouterModule, Routes } from "@angular/router"; +import { InMemoryCache } from "@apollo/client/core"; +import { provideApollo } from "apollo-angular"; +import { HttpLink } from "apollo-angular/http"; + +import { NuiDocsModule, NuiMessageModule } from "@nova-ui/bits"; + + +const COUNTRIES_API = "https://countries.trevorblades.com/graphql"; + +const exampleRoutes: Routes = [ + { + path: "overview", + loadChildren: async () => + import("./overview/overview.module") as object as Promise< + Type + >, + }, + { + path: "tutorials", + loadChildren: async () => + import("./tutorials/tutorials.module") as object as Promise< + Type + >, + }, + { + path: "widget-types", + loadChildren: async () => + import("./widget-types/widget-types.module") as object as Promise< + Type + >, + }, +]; + +@NgModule({ + imports: [ + NuiDocsModule, + NuiMessageModule, + RouterModule.forChild(exampleRoutes), + ], + providers: [ + provideHttpClient(withInterceptorsFromDi()), + provideApollo(() => { + const httpLink = inject(HttpLink); + + return { + link: httpLink.create({ uri: COUNTRIES_API }), + cache: new InMemoryCache(), + }; + }), + ], +}) +export default class DashboardDocsModule {} +\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\`, + "demo.files.ts": \\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\`// this file autogenerated, do not edit it manually please run the script +// yarn run compile-demo-paths +export const DEMO_PATHS = [ + "dashboard-docs.module.ts", + "demo.files.ts", + "overview/hero/dashboard/hero-dashboard.component.html", + "overview/hero/dashboard/hero-dashboard.component.less", + "overview/hero/dashboard/hero-dashboard.component.ts", + "overview/hero/dashboard/widget-configs.ts", + "overview/hero/data/kpi-datasources.ts", + "overview/hero/data/proportional-datasources.ts", + "overview/hero/data/table/beer-data-source.ts", + "overview/hero/data/table/constants.ts", + "overview/hero/data/table/random-user-data-source.ts", + "overview/hero/data/table/types.ts", + "overview/hero/data/timeseries-data-sources.ts", + "overview/hero/data/types.ts", + "overview/hero/data/widget-data.ts", + "overview/hero/widget-configs/kpi.ts", + "overview/hero/widget-configs/proportional.ts", + "overview/hero/widget-configs/risk-score.ts", + "overview/hero/widget-configs/table.ts", + "overview/hero/widget-configs/timeseries.ts", + "overview/overview-docs.component.html", + "overview/overview-docs.component.ts", + "overview/overview.module.ts", + "tutorials/customization/configurator-section/custom-configurator-section/custom-configurator-section.example.component.html", + "tutorials/customization/configurator-section/custom-configurator-section/custom-configurator-section.example.component.less", + "tutorials/customization/configurator-section/custom-configurator-section/custom-configurator-section.example.component.ts", + "tutorials/customization/configurator-section/custom-configurator-section-docs.component.html", + "tutorials/customization/configurator-section/custom-configurator-section-docs.component.ts", + "tutorials/customization/configurator-section/custom-configurator-section.module.ts", + "tutorials/customization/customization.module.ts", + "tutorials/customization/data-source-configurator/custom-data-source-configurator-docs.component.html", + "tutorials/customization/data-source-configurator/custom-data-source-configurator-docs.component.ts", + "tutorials/customization/data-source-configurator/custom-data-source-configurator.module.ts", + "tutorials/customization/data-source-configurator/example/custom-data-source-configurator-example.component.html", + "tutorials/customization/data-source-configurator/example/custom-data-source-configurator-example.component.less", + "tutorials/customization/data-source-configurator/example/custom-data-source-configurator-example.component.ts", + "tutorials/customization/formatter/custom-formatter.module.ts", + "tutorials/customization/formatter/donut-content-formatter-example/custom-donut-content-formatter-docs.component.html", + "tutorials/customization/formatter/donut-content-formatter-example/custom-donut-content-formatter-docs.component.ts", + "tutorials/customization/formatter/donut-content-formatter-example/custom-donut-content-formatter-example.component.html", + "tutorials/customization/formatter/donut-content-formatter-example/custom-donut-content-formatter-example.component.less", + "tutorials/customization/formatter/donut-content-formatter-example/custom-donut-content-formatter-example.component.ts", + "tutorials/customization/formatter/formatter-example/custom-formatter-docs.component.html", + "tutorials/customization/formatter/formatter-example/custom-formatter-docs.component.ts", + "tutorials/customization/formatter/formatter-example/custom-formatter-example.component.html", + "tutorials/customization/formatter/formatter-example/custom-formatter-example.component.less", + "tutorials/customization/formatter/formatter-example/custom-formatter-example.component.ts", + "tutorials/customization/widget/custom-widget-docs.component.html", + "tutorials/customization/widget/custom-widget-docs.component.ts", + "tutorials/customization/widget/custom-widget.component.html", + "tutorials/customization/widget/custom-widget.component.less", + "tutorials/customization/widget/custom-widget.component.ts", + "tutorials/customization/widget/custom-widget.module.ts", + "tutorials/data-source-setup/data-source-setup-docs.component.html", + "tutorials/data-source-setup/data-source-setup-docs.component.ts", + "tutorials/data-source-setup/data-source-setup.component.html", + "tutorials/data-source-setup/data-source-setup.component.less", + "tutorials/data-source-setup/data-source-setup.component.ts", + "tutorials/data-source-setup/data-source-setup.module.ts", + "tutorials/dynamic-header-links/dynamic-header-links-docs.component.html", + "tutorials/dynamic-header-links/dynamic-header-links-docs.component.ts", + "tutorials/dynamic-header-links/dynamic-header-links-docs.module.ts", + "tutorials/hello-dashboards/hello-dashboards-docs.component.html", + "tutorials/hello-dashboards/hello-dashboards-docs.component.ts", + "tutorials/hello-dashboards/hello-dashboards-example/hello-dashboards-example.component.html", + "tutorials/hello-dashboards/hello-dashboards-example/hello-dashboards-example.component.less", + "tutorials/hello-dashboards/hello-dashboards-example/hello-dashboards-example.component.ts", + "tutorials/hello-dashboards/hello-dashboards.module.ts", + "tutorials/persistence-handler-setup/persistence-handler-setup-docs.component.html", + "tutorials/persistence-handler-setup/persistence-handler-setup-docs.component.ts", + "tutorials/persistence-handler-setup/persistence-handler-setup.component.html", + "tutorials/persistence-handler-setup/persistence-handler-setup.component.less", + "tutorials/persistence-handler-setup/persistence-handler-setup.component.ts", + "tutorials/persistence-handler-setup/persistence-handler-setup.module.ts", + "tutorials/tutorials.module.ts", + "tutorials/widget-creation/widget-creation-docs.component.html", + "tutorials/widget-creation/widget-creation-docs.component.ts", + "tutorials/widget-creation/widget-creation.component.html", + "tutorials/widget-creation/widget-creation.component.less", + "tutorials/widget-creation/widget-creation.component.ts", + "tutorials/widget-creation/widget-creation.module.ts", + "tutorials/widget-editor-setup/widget-editor-setup-docs.component.html", + "tutorials/widget-editor-setup/widget-editor-setup-docs.component.ts", + "tutorials/widget-editor-setup/widget-editor-setup.component.html", + "tutorials/widget-editor-setup/widget-editor-setup.component.less", + "tutorials/widget-editor-setup/widget-editor-setup.component.ts", + "tutorials/widget-editor-setup/widget-editor-setup.module.ts", + "tutorials/widget-error-handling/widget-error-handling-docs.component.html", + "tutorials/widget-error-handling/widget-error-handling-docs.component.ts", + "tutorials/widget-error-handling/widget-error-handling.component.html", + "tutorials/widget-error-handling/widget-error-handling.component.less", + "tutorials/widget-error-handling/widget-error-handling.component.ts", + "tutorials/widget-error-handling/widget-error-handling.module.ts", + "types.ts", + "widget-types/drilldown/drilldown-multi-request-widget/drilldown-multi-request-widget-example.component.html", + "widget-types/drilldown/drilldown-multi-request-widget/drilldown-multi-request-widget-example.component.less", + "widget-types/drilldown/drilldown-multi-request-widget/drilldown-multi-request-widget-example.component.ts", + "widget-types/drilldown/drilldown-widget/data-mock.ts", + "widget-types/drilldown/drilldown-widget/drilldown-widget-example.component.html", + "widget-types/drilldown/drilldown-widget/drilldown-widget-example.component.less", + "widget-types/drilldown/drilldown-widget/drilldown-widget-example.component.ts", + "widget-types/drilldown/drilldown-widget/mock-data-source.ts", + "widget-types/drilldown/drilldown-widget-docs.component.html", + "widget-types/drilldown/drilldown-widget-docs.component.ts", + "widget-types/drilldown/drilldown-widget-docs.module.ts", + "widget-types/embedded-content/embedded-content-docs.component.html", + "widget-types/embedded-content/embedded-content-docs.component.ts", + "widget-types/embedded-content/embedded-content-docs.module.ts", + "widget-types/embedded-content/embedded-content-widget-example/embedded-content-widget-example.component.html", + "widget-types/embedded-content/embedded-content-widget-example/embedded-content-widget-example.component.less", + "widget-types/embedded-content/embedded-content-widget-example/embedded-content-widget-example.component.ts", + "widget-types/kpi/kpi-docs.component.html", + "widget-types/kpi/kpi-docs.component.ts", + "widget-types/kpi/kpi-docs.module.ts", + "widget-types/kpi/kpi-sync-broker/kpi-sync-broker-example.component.html", + "widget-types/kpi/kpi-sync-broker/kpi-sync-broker-example.component.less", + "widget-types/kpi/kpi-sync-broker/kpi-sync-broker-example.component.ts", + "widget-types/kpi/kpi-sync-broker-docs.component.html", + "widget-types/kpi/kpi-sync-broker-docs.component.ts", + "widget-types/kpi/kpi-sync-broker-for-all-tiles/kpi-sync-broker-for-all-tiles-example.component.html", + "widget-types/kpi/kpi-sync-broker-for-all-tiles/kpi-sync-broker-for-all-tiles-example.component.less", + "widget-types/kpi/kpi-sync-broker-for-all-tiles/kpi-sync-broker-for-all-tiles-example.component.ts", + "widget-types/kpi/kpi-widget/kpi-widget-example.component.html", + "widget-types/kpi/kpi-widget/kpi-widget-example.component.less", + "widget-types/kpi/kpi-widget/kpi-widget-example.component.ts", + "widget-types/kpi/kpi-widget-background-color/kpi-widget-background-color-example.component.html", + "widget-types/kpi/kpi-widget-background-color/kpi-widget-background-color-example.component.less", + "widget-types/kpi/kpi-widget-background-color/kpi-widget-background-color-example.component.ts", + "widget-types/kpi/kpi-widget-background-color-docs.component.html", + "widget-types/kpi/kpi-widget-background-color-docs.component.ts", + "widget-types/kpi/kpi-widget-interactive/kpi-widget-interactive-example.component.html", + "widget-types/kpi/kpi-widget-interactive/kpi-widget-interactive-example.component.less", + "widget-types/kpi/kpi-widget-interactive/kpi-widget-interactive-example.component.ts", + "widget-types/proportional/models.ts", + "widget-types/proportional/proportional-docs.component.html", + "widget-types/proportional/proportional-docs.component.ts", + "widget-types/proportional/proportional-docs.module.ts", + "widget-types/proportional/proportional-donut-content-docs.component.html", + "widget-types/proportional/proportional-donut-content-docs.component.ts", + "widget-types/proportional/proportional-donut-content-formatters/proportional-donut-content-formatters-example.component.html", + "widget-types/proportional/proportional-donut-content-formatters/proportional-donut-content-formatters-example.component.less", + "widget-types/proportional/proportional-donut-content-formatters/proportional-donut-content-formatters-example.component.ts", + "widget-types/proportional/proportional-widget/proportional-widget-example.component.html", + "widget-types/proportional/proportional-widget/proportional-widget-example.component.less", + "widget-types/proportional/proportional-widget/proportional-widget-example.component.ts", + "widget-types/proportional/proportional-widget-interactive/proportional-widget-interactive-example.component.html", + "widget-types/proportional/proportional-widget-interactive/proportional-widget-interactive-example.component.less", + "widget-types/proportional/proportional-widget-interactive/proportional-widget-interactive-example.component.ts", + "widget-types/risk-score/risk-score-docs.component.html", + "widget-types/risk-score/risk-score-docs.component.ts", + "widget-types/risk-score/risk-score-docs.module.ts", + "widget-types/risk-score/risk-score-widget-example/risk-score-widget-example.component.html", + "widget-types/risk-score/risk-score-widget-example/risk-score-widget-example.component.less", + "widget-types/risk-score/risk-score-widget-example/risk-score-widget-example.component.ts", + "widget-types/table/table-docs.component.html", + "widget-types/table/table-docs.component.ts", + "widget-types/table/table-docs.module.ts", + "widget-types/table/table-paginator-docs.component.html", + "widget-types/table/table-paginator-docs.component.ts", + "widget-types/table/table-selectable-docs.component.html", + "widget-types/table/table-selectable-docs.component.ts", + "widget-types/table/table-widget/table-widget-example.component.html", + "widget-types/table/table-widget/table-widget-example.component.less", + "widget-types/table/table-widget/table-widget-example.component.ts", + "widget-types/table/table-widget-interactive/table-widget-interactive-example.component.html", + "widget-types/table/table-widget-interactive/table-widget-interactive-example.component.less", + "widget-types/table/table-widget-interactive/table-widget-interactive-example.component.ts", + "widget-types/table/table-widget-paginator/table-widget-paginator-example.component.html", + "widget-types/table/table-widget-paginator/table-widget-paginator-example.component.less", + "widget-types/table/table-widget-paginator/table-widget-paginator-example.component.ts", + "widget-types/table/table-widget-search/table-widget-search-example.component.html", + "widget-types/table/table-widget-search/table-widget-search-example.component.less", + "widget-types/table/table-widget-search/table-widget-search-example.component.ts", + "widget-types/table/table-widget-search-docs.component.html", + "widget-types/table/table-widget-search-docs.component.ts", + "widget-types/table/table-widget-selectable/table-widget-selectable-multi/table-widget-selectable-multi.example.component.html", + "widget-types/table/table-widget-selectable/table-widget-selectable-multi/table-widget-selectable-multi.example.component.less", + "widget-types/table/table-widget-selectable/table-widget-selectable-multi/table-widget-selectable-multi.example.component.ts", + "widget-types/table/table-widget-selectable/table-widget-selectable-radio/table-widget-selectable-radio.example.component.html", + "widget-types/table/table-widget-selectable/table-widget-selectable-radio/table-widget-selectable-radio.example.component.less", + "widget-types/table/table-widget-selectable/table-widget-selectable-radio/table-widget-selectable-radio.example.component.ts", + "widget-types/table/table-widget-selectable/table-widget-selectable-single/table-widget-selectable-single.example.component.html", + "widget-types/table/table-widget-selectable/table-widget-selectable-single/table-widget-selectable-single.example.component.less", + "widget-types/table/table-widget-selectable/table-widget-selectable-single/table-widget-selectable-single.example.component.ts", + "widget-types/table/table-widget-selectable/table-widget-selectable.example.component.html", + "widget-types/table/table-widget-selectable/table-widget-selectable.example.component.less", + "widget-types/table/table-widget-selectable/table-widget-selectable.example.component.ts", + "widget-types/timeseries/timeseries-docs.component.html", + "widget-types/timeseries/timeseries-docs.component.ts", + "widget-types/timeseries/timeseries-docs.module.ts", + "widget-types/timeseries/timeseries-widget-example/timeseries-widget-example.component.html", + "widget-types/timeseries/timeseries-widget-example/timeseries-widget-example.component.less", + "widget-types/timeseries/timeseries-widget-example/timeseries-widget-example.component.ts", + "widget-types/timeseries/timeseries-widget-interactive-example/timeseries-widget-interactive-example.component.html", + "widget-types/timeseries/timeseries-widget-interactive-example/timeseries-widget-interactive-example.component.less", + "widget-types/timeseries/timeseries-widget-interactive-example/timeseries-widget-interactive-example.component.ts", + "widget-types/timeseries/timeseries-widget-status-bar-example/timeseries-widget-status-bar-example.component.html", + "widget-types/timeseries/timeseries-widget-status-bar-example/timeseries-widget-status-bar-example.component.less", + "widget-types/timeseries/timeseries-widget-status-bar-example/timeseries-widget-status-bar-example.component.ts", + "widget-types/view-components/kpi-tile-view-basic/kpi-tile-view-basic-example.component.html", + "widget-types/view-components/kpi-tile-view-basic/kpi-tile-view-basic-example.component.ts", + "widget-types/view-components/kpi-tile-view-interactive/kpi-tile-view-interactive-example.component.html", + "widget-types/view-components/kpi-tile-view-interactive/kpi-tile-view-interactive-example.component.ts", + "widget-types/view-components/proportional-chart-view-playground/proportional-chart-view-playground-example.component.html", + "widget-types/view-components/proportional-chart-view-playground/proportional-chart-view-playground-example.component.ts", + "widget-types/view-components/view-components-docs.component.html", + "widget-types/view-components/view-components-docs.component.ts", + "widget-types/view-components/view-components-docs.module.ts", + "widget-types/widget-types.module.ts", +]; + +export const DEMO_TS_SOURCES: Record = { + "dashboard-docs.module.ts": \\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\`// © 2022 SolarWinds Worldwide, LLC. All rights reserved. +// +// Permission is hereby granted, free of charge, to any person obtaining a copy +// of this software and associated documentation files (the "Software"), to +// deal in the Software without restriction, including without limitation the +// rights to use, copy, modify, merge, publish, distribute, sublicense, and/or +// sell copies of the Software, and to permit persons to whom the Software is +// furnished to do so, subject to the following conditions: +// +// The above copyright notice and this permission notice shall be included in +// all copies or substantial portions of the Software. +// +// THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR +// IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, +// FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE +// AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER +// LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, +// OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN +// THE SOFTWARE. + +import { provideHttpClient, withInterceptorsFromDi } from "@angular/common/http"; +import { inject, NgModule, Type } from "@angular/core"; +import { RouterModule, Routes } from "@angular/router"; +import { InMemoryCache } from "@apollo/client/core"; +import { provideApollo } from "apollo-angular"; +import { HttpLink } from "apollo-angular/http"; + +import { NuiDocsModule, NuiMessageModule } from "@nova-ui/bits"; + + +const COUNTRIES_API = "https://countries.trevorblades.com/graphql"; + +const exampleRoutes: Routes = [ + { + path: "overview", + loadChildren: async () => + import("./overview/overview.module") as object as Promise< + Type + >, + }, + { + path: "tutorials", + loadChildren: async () => + import("./tutorials/tutorials.module") as object as Promise< + Type + >, + }, + { + path: "widget-types", + loadChildren: async () => + import("./widget-types/widget-types.module") as object as Promise< + Type + >, + }, +]; + +@NgModule({ + imports: [ + NuiDocsModule, + NuiMessageModule, + RouterModule.forChild(exampleRoutes), + ], + providers: [ + provideHttpClient(withInterceptorsFromDi()), + provideApollo(() => { + const httpLink = inject(HttpLink); + + return { + link: httpLink.create({ uri: COUNTRIES_API }), + cache: new InMemoryCache(), + }; + }), + ], +}) +export default class DashboardDocsModule {} +\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\`, + "demo.files.ts": \\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\`// this file autogenerated, do not edit it manually please run the script +// yarn run compile-demo-paths +export const DEMO_PATHS = [ + "dashboard-docs.module.ts", + "demo.files.ts", + "overview/hero/dashboard/hero-dashboard.component.html", + "overview/hero/dashboard/hero-dashboard.component.less", + "overview/hero/dashboard/hero-dashboard.component.ts", + "overview/hero/dashboard/widget-configs.ts", + "overview/hero/data/kpi-datasources.ts", + "overview/hero/data/proportional-datasources.ts", + "overview/hero/data/table/beer-data-source.ts", + "overview/hero/data/table/constants.ts", + "overview/hero/data/table/random-user-data-source.ts", + "overview/hero/data/table/types.ts", + "overview/hero/data/timeseries-data-sources.ts", + "overview/hero/data/types.ts", + "overview/hero/data/widget-data.ts", + "overview/hero/widget-configs/kpi.ts", + "overview/hero/widget-configs/proportional.ts", + "overview/hero/widget-configs/risk-score.ts", + "overview/hero/widget-configs/table.ts", + "overview/hero/widget-configs/timeseries.ts", + "overview/overview-docs.component.html", + "overview/overview-docs.component.ts", + "overview/overview.module.ts", + "tutorials/customization/configurator-section/custom-configurator-section/custom-configurator-section.example.component.html", + "tutorials/customization/configurator-section/custom-configurator-section/custom-configurator-section.example.component.less", + "tutorials/customization/configurator-section/custom-configurator-section/custom-configurator-section.example.component.ts", + "tutorials/customization/configurator-section/custom-configurator-section-docs.component.html", + "tutorials/customization/configurator-section/custom-configurator-section-docs.component.ts", + "tutorials/customization/configurator-section/custom-configurator-section.module.ts", + "tutorials/customization/customization.module.ts", + "tutorials/customization/data-source-configurator/custom-data-source-configurator-docs.component.html", + "tutorials/customization/data-source-configurator/custom-data-source-configurator-docs.component.ts", + "tutorials/customization/data-source-configurator/custom-data-source-configurator.module.ts", + "tutorials/customization/data-source-configurator/example/custom-data-source-configurator-example.component.html", + "tutorials/customization/data-source-configurator/example/custom-data-source-configurator-example.component.less", + "tutorials/customization/data-source-configurator/example/custom-data-source-configurator-example.component.ts", + "tutorials/customization/formatter/custom-formatter.module.ts", + "tutorials/customization/formatter/donut-content-formatter-example/custom-donut-content-formatter-docs.component.html", + "tutorials/customization/formatter/donut-content-formatter-example/custom-donut-content-formatter-docs.component.ts", + "tutorials/customization/formatter/donut-content-formatter-example/custom-donut-content-formatter-example.component.html", + "tutorials/customization/formatter/donut-content-formatter-example/custom-donut-content-formatter-example.component.less", + "tutorials/customization/formatter/donut-content-formatter-example/custom-donut-content-formatter-example.component.ts", + "tutorials/customization/formatter/formatter-example/custom-formatter-docs.component.html", + "tutorials/customization/formatter/formatter-example/custom-formatter-docs.component.ts", + "tutorials/customization/formatter/formatter-example/custom-formatter-example.component.html", + "tutorials/customization/formatter/formatter-example/custom-formatter-example.component.less", + "tutorials/customization/formatter/formatter-example/custom-formatter-example.component.ts", + "tutorials/customization/widget/custom-widget-docs.component.html", + "tutorials/customization/widget/custom-widget-docs.component.ts", + "tutorials/customization/widget/custom-widget.component.html", + "tutorials/customization/widget/custom-widget.component.less", + "tutorials/customization/widget/custom-widget.component.ts", + "tutorials/customization/widget/custom-widget.module.ts", + "tutorials/data-source-setup/data-source-setup-docs.component.html", + "tutorials/data-source-setup/data-source-setup-docs.component.ts", + "tutorials/data-source-setup/data-source-setup.component.html", + "tutorials/data-source-setup/data-source-setup.component.less", + "tutorials/data-source-setup/data-source-setup.component.ts", + "tutorials/data-source-setup/data-source-setup.module.ts", + "tutorials/dynamic-header-links/dynamic-header-links-docs.component.html", + "tutorials/dynamic-header-links/dynamic-header-links-docs.component.ts", + "tutorials/dynamic-header-links/dynamic-header-links-docs.module.ts", + "tutorials/hello-dashboards/hello-dashboards-docs.component.html", + "tutorials/hello-dashboards/hello-dashboards-docs.component.ts", + "tutorials/hello-dashboards/hello-dashboards-example/hello-dashboards-example.component.html", + "tutorials/hello-dashboards/hello-dashboards-example/hello-dashboards-example.component.less", + "tutorials/hello-dashboards/hello-dashboards-example/hello-dashboards-example.component.ts", + "tutorials/hello-dashboards/hello-dashboards.module.ts", + "tutorials/persistence-handler-setup/persistence-handler-setup-docs.component.html", + "tutorials/persistence-handler-setup/persistence-handler-setup-docs.component.ts", + "tutorials/persistence-handler-setup/persistence-handler-setup.component.html", + "tutorials/persistence-handler-setup/persistence-handler-setup.component.less", + "tutorials/persistence-handler-setup/persistence-handler-setup.component.ts", + "tutorials/persistence-handler-setup/persistence-handler-setup.module.ts", + "tutorials/tutorials.module.ts", + "tutorials/widget-creation/widget-creation-docs.component.html", + "tutorials/widget-creation/widget-creation-docs.component.ts", + "tutorials/widget-creation/widget-creation.component.html", + "tutorials/widget-creation/widget-creation.component.less", + "tutorials/widget-creation/widget-creation.component.ts", + "tutorials/widget-creation/widget-creation.module.ts", + "tutorials/widget-editor-setup/widget-editor-setup-docs.component.html", + "tutorials/widget-editor-setup/widget-editor-setup-docs.component.ts", + "tutorials/widget-editor-setup/widget-editor-setup.component.html", + "tutorials/widget-editor-setup/widget-editor-setup.component.less", + "tutorials/widget-editor-setup/widget-editor-setup.component.ts", + "tutorials/widget-editor-setup/widget-editor-setup.module.ts", + "tutorials/widget-error-handling/widget-error-handling-docs.component.html", + "tutorials/widget-error-handling/widget-error-handling-docs.component.ts", + "tutorials/widget-error-handling/widget-error-handling.component.html", + "tutorials/widget-error-handling/widget-error-handling.component.less", + "tutorials/widget-error-handling/widget-error-handling.component.ts", + "tutorials/widget-error-handling/widget-error-handling.module.ts", + "types.ts", + "widget-types/drilldown/drilldown-multi-request-widget/drilldown-multi-request-widget-example.component.html", + "widget-types/drilldown/drilldown-multi-request-widget/drilldown-multi-request-widget-example.component.less", + "widget-types/drilldown/drilldown-multi-request-widget/drilldown-multi-request-widget-example.component.ts", + "widget-types/drilldown/drilldown-widget/data-mock.ts", + "widget-types/drilldown/drilldown-widget/drilldown-widget-example.component.html", + "widget-types/drilldown/drilldown-widget/drilldown-widget-example.component.less", + "widget-types/drilldown/drilldown-widget/drilldown-widget-example.component.ts", + "widget-types/drilldown/drilldown-widget/mock-data-source.ts", + "widget-types/drilldown/drilldown-widget-docs.component.html", + "widget-types/drilldown/drilldown-widget-docs.component.ts", + "widget-types/drilldown/drilldown-widget-docs.module.ts", + "widget-types/embedded-content/embedded-content-docs.component.html", + "widget-types/embedded-content/embedded-content-docs.component.ts", + "widget-types/embedded-content/embedded-content-docs.module.ts", + "widget-types/embedded-content/embedded-content-widget-example/embedded-content-widget-example.component.html", + "widget-types/embedded-content/embedded-content-widget-example/embedded-content-widget-example.component.less", + "widget-types/embedded-content/embedded-content-widget-example/embedded-content-widget-example.component.ts", + "widget-types/kpi/kpi-docs.component.html", + "widget-types/kpi/kpi-docs.component.ts", + "widget-types/kpi/kpi-docs.module.ts", + "widget-types/kpi/kpi-sync-broker/kpi-sync-broker-example.component.html", + "widget-types/kpi/kpi-sync-broker/kpi-sync-broker-example.component.less", + "widget-types/kpi/kpi-sync-broker/kpi-sync-broker-example.component.ts", + "widget-types/kpi/kpi-sync-broker-docs.component.html", + "widget-types/kpi/kpi-sync-broker-docs.component.ts", + "widget-types/kpi/kpi-sync-broker-for-all-tiles/kpi-sync-broker-for-all-tiles-example.component.html", + "widget-types/kpi/kpi-sync-broker-for-all-tiles/kpi-sync-broker-for-all-tiles-example.component.less", + "widget-types/kpi/kpi-sync-broker-for-all-tiles/kpi-sync-broker-for-all-tiles-example.component.ts", + "widget-types/kpi/kpi-widget/kpi-widget-example.component.html", + "widget-types/kpi/kpi-widget/kpi-widget-example.component.less", + "widget-types/kpi/kpi-widget/kpi-widget-example.component.ts", + "widget-types/kpi/kpi-widget-background-color/kpi-widget-background-color-example.component.html", + "widget-types/kpi/kpi-widget-background-color/kpi-widget-background-color-example.component.less", + "widget-types/kpi/kpi-widget-background-color/kpi-widget-background-color-example.component.ts", + "widget-types/kpi/kpi-widget-background-color-docs.component.html", + "widget-types/kpi/kpi-widget-background-color-docs.component.ts", + "widget-types/kpi/kpi-widget-interactive/kpi-widget-interactive-example.component.html", + "widget-types/kpi/kpi-widget-interactive/kpi-widget-interactive-example.component.less", + "widget-types/kpi/kpi-widget-interactive/kpi-widget-interactive-example.component.ts", + "widget-types/proportional/models.ts", + "widget-types/proportional/proportional-docs.component.html", + "widget-types/proportional/proportional-docs.component.ts", + "widget-types/proportional/proportional-docs.module.ts", + "widget-types/proportional/proportional-donut-content-docs.component.html", + "widget-types/proportional/proportional-donut-content-docs.component.ts", + "widget-types/proportional/proportional-donut-content-formatters/proportional-donut-content-formatters-example.component.html", + "widget-types/proportional/proportional-donut-content-formatters/proportional-donut-content-formatters-example.component.less", + "widget-types/proportional/proportional-donut-content-formatters/proportional-donut-content-formatters-example.component.ts", + "widget-types/proportional/proportional-widget/proportional-widget-example.component.html", + "widget-types/proportional/proportional-widget/proportional-widget-example.component.less", + "widget-types/proportional/proportional-widget/proportional-widget-example.component.ts", + "widget-types/proportional/proportional-widget-interactive/proportional-widget-interactive-example.component.html", + "widget-types/proportional/proportional-widget-interactive/proportional-widget-interactive-example.component.less", + "widget-types/proportional/proportional-widget-interactive/proportional-widget-interactive-example.component.ts", + "widget-types/risk-score/risk-score-docs.component.html", + "widget-types/risk-score/risk-score-docs.component.ts", + "widget-types/risk-score/risk-score-docs.module.ts", + "widget-types/risk-score/risk-score-widget-example/risk-score-widget-example.component.html", + "widget-types/risk-score/risk-score-widget-example/risk-score-widget-example.component.less", + "widget-types/risk-score/risk-score-widget-example/risk-score-widget-example.component.ts", + "widget-types/table/table-docs.component.html", + "widget-types/table/table-docs.component.ts", + "widget-types/table/table-docs.module.ts", + "widget-types/table/table-paginator-docs.component.html", + "widget-types/table/table-paginator-docs.component.ts", + "widget-types/table/table-selectable-docs.component.html", + "widget-types/table/table-selectable-docs.component.ts", + "widget-types/table/table-widget/table-widget-example.component.html", + "widget-types/table/table-widget/table-widget-example.component.less", + "widget-types/table/table-widget/table-widget-example.component.ts", + "widget-types/table/table-widget-interactive/table-widget-interactive-example.component.html", + "widget-types/table/table-widget-interactive/table-widget-interactive-example.component.less", + "widget-types/table/table-widget-interactive/table-widget-interactive-example.component.ts", + "widget-types/table/table-widget-paginator/table-widget-paginator-example.component.html", + "widget-types/table/table-widget-paginator/table-widget-paginator-example.component.less", + "widget-types/table/table-widget-paginator/table-widget-paginator-example.component.ts", + "widget-types/table/table-widget-search/table-widget-search-example.component.html", + "widget-types/table/table-widget-search/table-widget-search-example.component.less", + "widget-types/table/table-widget-search/table-widget-search-example.component.ts", + "widget-types/table/table-widget-search-docs.component.html", + "widget-types/table/table-widget-search-docs.component.ts", + "widget-types/table/table-widget-selectable/table-widget-selectable-multi/table-widget-selectable-multi.example.component.html", + "widget-types/table/table-widget-selectable/table-widget-selectable-multi/table-widget-selectable-multi.example.component.less", + "widget-types/table/table-widget-selectable/table-widget-selectable-multi/table-widget-selectable-multi.example.component.ts", + "widget-types/table/table-widget-selectable/table-widget-selectable-radio/table-widget-selectable-radio.example.component.html", + "widget-types/table/table-widget-selectable/table-widget-selectable-radio/table-widget-selectable-radio.example.component.less", + "widget-types/table/table-widget-selectable/table-widget-selectable-radio/table-widget-selectable-radio.example.component.ts", + "widget-types/table/table-widget-selectable/table-widget-selectable-single/table-widget-selectable-single.example.component.html", + "widget-types/table/table-widget-selectable/table-widget-selectable-single/table-widget-selectable-single.example.component.less", + "widget-types/table/table-widget-selectable/table-widget-selectable-single/table-widget-selectable-single.example.component.ts", + "widget-types/table/table-widget-selectable/table-widget-selectable.example.component.html", + "widget-types/table/table-widget-selectable/table-widget-selectable.example.component.less", + "widget-types/table/table-widget-selectable/table-widget-selectable.example.component.ts", + "widget-types/timeseries/timeseries-docs.component.html", + "widget-types/timeseries/timeseries-docs.component.ts", + "widget-types/timeseries/timeseries-docs.module.ts", + "widget-types/timeseries/timeseries-widget-example/timeseries-widget-example.component.html", + "widget-types/timeseries/timeseries-widget-example/timeseries-widget-example.component.less", + "widget-types/timeseries/timeseries-widget-example/timeseries-widget-example.component.ts", + "widget-types/timeseries/timeseries-widget-interactive-example/timeseries-widget-interactive-example.component.html", + "widget-types/timeseries/timeseries-widget-interactive-example/timeseries-widget-interactive-example.component.less", + "widget-types/timeseries/timeseries-widget-interactive-example/timeseries-widget-interactive-example.component.ts", + "widget-types/timeseries/timeseries-widget-status-bar-example/timeseries-widget-status-bar-example.component.html", + "widget-types/timeseries/timeseries-widget-status-bar-example/timeseries-widget-status-bar-example.component.less", + "widget-types/timeseries/timeseries-widget-status-bar-example/timeseries-widget-status-bar-example.component.ts", + "widget-types/view-components/kpi-tile-view-basic/kpi-tile-view-basic-example.component.html", + "widget-types/view-components/kpi-tile-view-basic/kpi-tile-view-basic-example.component.ts", + "widget-types/view-components/kpi-tile-view-interactive/kpi-tile-view-interactive-example.component.html", + "widget-types/view-components/kpi-tile-view-interactive/kpi-tile-view-interactive-example.component.ts", + "widget-types/view-components/proportional-chart-view-playground/proportional-chart-view-playground-example.component.html", + "widget-types/view-components/proportional-chart-view-playground/proportional-chart-view-playground-example.component.ts", + "widget-types/view-components/view-components-docs.component.html", + "widget-types/view-components/view-components-docs.component.ts", + "widget-types/view-components/view-components-docs.module.ts", + "widget-types/widget-types.module.ts", +]; + +export const DEMO_TS_SOURCES: Record = { + "dashboard-docs.module.ts": \\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\`// © 2022 SolarWinds Worldwide, LLC. All rights reserved. +// +// Permission is hereby granted, free of charge, to any person obtaining a copy +// of this software and associated documentation files (the "Software"), to +// deal in the Software without restriction, including without limitation the +// rights to use, copy, modify, merge, publish, distribute, sublicense, and/or +// sell copies of the Software, and to permit persons to whom the Software is +// furnished to do so, subject to the following conditions: +// +// The above copyright notice and this permission notice shall be included in +// all copies or substantial portions of the Software. +// +// THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR +// IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, +// FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE +// AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER +// LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, +// OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN +// THE SOFTWARE. + +import { provideHttpClient, withInterceptorsFromDi } from "@angular/common/http"; +import { inject, NgModule, Type } from "@angular/core"; +import { RouterModule, Routes } from "@angular/router"; +import { InMemoryCache } from "@apollo/client/core"; +import { provideApollo } from "apollo-angular"; +import { HttpLink } from "apollo-angular/http"; + +import { NuiDocsModule, NuiMessageModule } from "@nova-ui/bits"; + + +const COUNTRIES_API = "https://countries.trevorblades.com/graphql"; + +const exampleRoutes: Routes = [ + { + path: "overview", + loadChildren: async () => + import("./overview/overview.module") as object as Promise< + Type + >, + }, + { + path: "tutorials", + loadChildren: async () => + import("./tutorials/tutorials.module") as object as Promise< + Type + >, + }, + { + path: "widget-types", + loadChildren: async () => + import("./widget-types/widget-types.module") as object as Promise< + Type + >, + }, +]; + +@NgModule({ + imports: [ + NuiDocsModule, + NuiMessageModule, + RouterModule.forChild(exampleRoutes), + ], + providers: [ + provideHttpClient(withInterceptorsFromDi()), + provideApollo(() => { + const httpLink = inject(HttpLink); + + return { + link: httpLink.create({ uri: COUNTRIES_API }), + cache: new InMemoryCache(), + }; + }), + ], +}) +export default class DashboardDocsModule {} +\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\`, + "demo.files.ts": \\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\`// this file autogenerated, do not edit it manually please run the script +// yarn run compile-demo-paths +export const DEMO_PATHS = [ + "dashboard-docs.module.ts", + "demo.files.ts", + "overview/hero/dashboard/hero-dashboard.component.html", + "overview/hero/dashboard/hero-dashboard.component.less", + "overview/hero/dashboard/hero-dashboard.component.ts", + "overview/hero/dashboard/widget-configs.ts", + "overview/hero/data/kpi-datasources.ts", + "overview/hero/data/proportional-datasources.ts", + "overview/hero/data/table/beer-data-source.ts", + "overview/hero/data/table/constants.ts", + "overview/hero/data/table/random-user-data-source.ts", + "overview/hero/data/table/types.ts", + "overview/hero/data/timeseries-data-sources.ts", + "overview/hero/data/types.ts", + "overview/hero/data/widget-data.ts", + "overview/hero/widget-configs/kpi.ts", + "overview/hero/widget-configs/proportional.ts", + "overview/hero/widget-configs/risk-score.ts", + "overview/hero/widget-configs/table.ts", + "overview/hero/widget-configs/timeseries.ts", + "overview/overview-docs.component.html", + "overview/overview-docs.component.ts", + "overview/overview.module.ts", + "tutorials/customization/configurator-section/custom-configurator-section/custom-configurator-section.example.component.html", + "tutorials/customization/configurator-section/custom-configurator-section/custom-configurator-section.example.component.less", + "tutorials/customization/configurator-section/custom-configurator-section/custom-configurator-section.example.component.ts", + "tutorials/customization/configurator-section/custom-configurator-section-docs.component.html", + "tutorials/customization/configurator-section/custom-configurator-section-docs.component.ts", + "tutorials/customization/configurator-section/custom-configurator-section.module.ts", + "tutorials/customization/customization.module.ts", + "tutorials/customization/data-source-configurator/custom-data-source-configurator-docs.component.html", + "tutorials/customization/data-source-configurator/custom-data-source-configurator-docs.component.ts", + "tutorials/customization/data-source-configurator/custom-data-source-configurator.module.ts", + "tutorials/customization/data-source-configurator/example/custom-data-source-configurator-example.component.html", + "tutorials/customization/data-source-configurator/example/custom-data-source-configurator-example.component.less", + "tutorials/customization/data-source-configurator/example/custom-data-source-configurator-example.component.ts", + "tutorials/customization/formatter/custom-formatter.module.ts", + "tutorials/customization/formatter/donut-content-formatter-example/custom-donut-content-formatter-docs.component.html", + "tutorials/customization/formatter/donut-content-formatter-example/custom-donut-content-formatter-docs.component.ts", + "tutorials/customization/formatter/donut-content-formatter-example/custom-donut-content-formatter-example.component.html", + "tutorials/customization/formatter/donut-content-formatter-example/custom-donut-content-formatter-example.component.less", + "tutorials/customization/formatter/donut-content-formatter-example/custom-donut-content-formatter-example.component.ts", + "tutorials/customization/formatter/formatter-example/custom-formatter-docs.component.html", + "tutorials/customization/formatter/formatter-example/custom-formatter-docs.component.ts", + "tutorials/customization/formatter/formatter-example/custom-formatter-example.component.html", + "tutorials/customization/formatter/formatter-example/custom-formatter-example.component.less", + "tutorials/customization/formatter/formatter-example/custom-formatter-example.component.ts", + "tutorials/customization/widget/custom-widget-docs.component.html", + "tutorials/customization/widget/custom-widget-docs.component.ts", + "tutorials/customization/widget/custom-widget.component.html", + "tutorials/customization/widget/custom-widget.component.less", + "tutorials/customization/widget/custom-widget.component.ts", + "tutorials/customization/widget/custom-widget.module.ts", + "tutorials/data-source-setup/data-source-setup-docs.component.html", + "tutorials/data-source-setup/data-source-setup-docs.component.ts", + "tutorials/data-source-setup/data-source-setup.component.html", + "tutorials/data-source-setup/data-source-setup.component.less", + "tutorials/data-source-setup/data-source-setup.component.ts", + "tutorials/data-source-setup/data-source-setup.module.ts", + "tutorials/dynamic-header-links/dynamic-header-links-docs.component.html", + "tutorials/dynamic-header-links/dynamic-header-links-docs.component.ts", + "tutorials/dynamic-header-links/dynamic-header-links-docs.module.ts", + "tutorials/hello-dashboards/hello-dashboards-docs.component.html", + "tutorials/hello-dashboards/hello-dashboards-docs.component.ts", + "tutorials/hello-dashboards/hello-dashboards-example/hello-dashboards-example.component.html", + "tutorials/hello-dashboards/hello-dashboards-example/hello-dashboards-example.component.less", + "tutorials/hello-dashboards/hello-dashboards-example/hello-dashboards-example.component.ts", + "tutorials/hello-dashboards/hello-dashboards.module.ts", + "tutorials/persistence-handler-setup/persistence-handler-setup-docs.component.html", + "tutorials/persistence-handler-setup/persistence-handler-setup-docs.component.ts", + "tutorials/persistence-handler-setup/persistence-handler-setup.component.html", + "tutorials/persistence-handler-setup/persistence-handler-setup.component.less", + "tutorials/persistence-handler-setup/persistence-handler-setup.component.ts", + "tutorials/persistence-handler-setup/persistence-handler-setup.module.ts", + "tutorials/tutorials.module.ts", + "tutorials/widget-creation/widget-creation-docs.component.html", + "tutorials/widget-creation/widget-creation-docs.component.ts", + "tutorials/widget-creation/widget-creation.component.html", + "tutorials/widget-creation/widget-creation.component.less", + "tutorials/widget-creation/widget-creation.component.ts", + "tutorials/widget-creation/widget-creation.module.ts", + "tutorials/widget-editor-setup/widget-editor-setup-docs.component.html", + "tutorials/widget-editor-setup/widget-editor-setup-docs.component.ts", + "tutorials/widget-editor-setup/widget-editor-setup.component.html", + "tutorials/widget-editor-setup/widget-editor-setup.component.less", + "tutorials/widget-editor-setup/widget-editor-setup.component.ts", + "tutorials/widget-editor-setup/widget-editor-setup.module.ts", + "tutorials/widget-error-handling/widget-error-handling-docs.component.html", + "tutorials/widget-error-handling/widget-error-handling-docs.component.ts", + "tutorials/widget-error-handling/widget-error-handling.component.html", + "tutorials/widget-error-handling/widget-error-handling.component.less", + "tutorials/widget-error-handling/widget-error-handling.component.ts", + "tutorials/widget-error-handling/widget-error-handling.module.ts", + "types.ts", + "widget-types/drilldown/drilldown-multi-request-widget/drilldown-multi-request-widget-example.component.html", + "widget-types/drilldown/drilldown-multi-request-widget/drilldown-multi-request-widget-example.component.less", + "widget-types/drilldown/drilldown-multi-request-widget/drilldown-multi-request-widget-example.component.ts", + "widget-types/drilldown/drilldown-widget/data-mock.ts", + "widget-types/drilldown/drilldown-widget/drilldown-widget-example.component.html", + "widget-types/drilldown/drilldown-widget/drilldown-widget-example.component.less", + "widget-types/drilldown/drilldown-widget/drilldown-widget-example.component.ts", + "widget-types/drilldown/drilldown-widget/mock-data-source.ts", + "widget-types/drilldown/drilldown-widget-docs.component.html", + "widget-types/drilldown/drilldown-widget-docs.component.ts", + "widget-types/drilldown/drilldown-widget-docs.module.ts", + "widget-types/embedded-content/embedded-content-docs.component.html", + "widget-types/embedded-content/embedded-content-docs.component.ts", + "widget-types/embedded-content/embedded-content-docs.module.ts", + "widget-types/embedded-content/embedded-content-widget-example/embedded-content-widget-example.component.html", + "widget-types/embedded-content/embedded-content-widget-example/embedded-content-widget-example.component.less", + "widget-types/embedded-content/embedded-content-widget-example/embedded-content-widget-example.component.ts", + "widget-types/kpi/kpi-docs.component.html", + "widget-types/kpi/kpi-docs.component.ts", + "widget-types/kpi/kpi-docs.module.ts", + "widget-types/kpi/kpi-sync-broker/kpi-sync-broker-example.component.html", + "widget-types/kpi/kpi-sync-broker/kpi-sync-broker-example.component.less", + "widget-types/kpi/kpi-sync-broker/kpi-sync-broker-example.component.ts", + "widget-types/kpi/kpi-sync-broker-docs.component.html", + "widget-types/kpi/kpi-sync-broker-docs.component.ts", + "widget-types/kpi/kpi-sync-broker-for-all-tiles/kpi-sync-broker-for-all-tiles-example.component.html", + "widget-types/kpi/kpi-sync-broker-for-all-tiles/kpi-sync-broker-for-all-tiles-example.component.less", + "widget-types/kpi/kpi-sync-broker-for-all-tiles/kpi-sync-broker-for-all-tiles-example.component.ts", + "widget-types/kpi/kpi-widget/kpi-widget-example.component.html", + "widget-types/kpi/kpi-widget/kpi-widget-example.component.less", + "widget-types/kpi/kpi-widget/kpi-widget-example.component.ts", + "widget-types/kpi/kpi-widget-background-color/kpi-widget-background-color-example.component.html", + "widget-types/kpi/kpi-widget-background-color/kpi-widget-background-color-example.component.less", + "widget-types/kpi/kpi-widget-background-color/kpi-widget-background-color-example.component.ts", + "widget-types/kpi/kpi-widget-background-color-docs.component.html", + "widget-types/kpi/kpi-widget-background-color-docs.component.ts", + "widget-types/kpi/kpi-widget-interactive/kpi-widget-interactive-example.component.html", + "widget-types/kpi/kpi-widget-interactive/kpi-widget-interactive-example.component.less", + "widget-types/kpi/kpi-widget-interactive/kpi-widget-interactive-example.component.ts", + "widget-types/proportional/models.ts", + "widget-types/proportional/proportional-docs.component.html", + "widget-types/proportional/proportional-docs.component.ts", + "widget-types/proportional/proportional-docs.module.ts", + "widget-types/proportional/proportional-donut-content-docs.component.html", + "widget-types/proportional/proportional-donut-content-docs.component.ts", + "widget-types/proportional/proportional-donut-content-formatters/proportional-donut-content-formatters-example.component.html", + "widget-types/proportional/proportional-donut-content-formatters/proportional-donut-content-formatters-example.component.less", + "widget-types/proportional/proportional-donut-content-formatters/proportional-donut-content-formatters-example.component.ts", + "widget-types/proportional/proportional-widget/proportional-widget-example.component.html", + "widget-types/proportional/proportional-widget/proportional-widget-example.component.less", + "widget-types/proportional/proportional-widget/proportional-widget-example.component.ts", + "widget-types/proportional/proportional-widget-interactive/proportional-widget-interactive-example.component.html", + "widget-types/proportional/proportional-widget-interactive/proportional-widget-interactive-example.component.less", + "widget-types/proportional/proportional-widget-interactive/proportional-widget-interactive-example.component.ts", + "widget-types/risk-score/risk-score-docs.component.html", + "widget-types/risk-score/risk-score-docs.component.ts", + "widget-types/risk-score/risk-score-docs.module.ts", + "widget-types/risk-score/risk-score-widget-example/risk-score-widget-example.component.html", + "widget-types/risk-score/risk-score-widget-example/risk-score-widget-example.component.less", + "widget-types/risk-score/risk-score-widget-example/risk-score-widget-example.component.ts", + "widget-types/table/table-docs.component.html", + "widget-types/table/table-docs.component.ts", + "widget-types/table/table-docs.module.ts", + "widget-types/table/table-paginator-docs.component.html", + "widget-types/table/table-paginator-docs.component.ts", + "widget-types/table/table-selectable-docs.component.html", + "widget-types/table/table-selectable-docs.component.ts", + "widget-types/table/table-widget/table-widget-example.component.html", + "widget-types/table/table-widget/table-widget-example.component.less", + "widget-types/table/table-widget/table-widget-example.component.ts", + "widget-types/table/table-widget-interactive/table-widget-interactive-example.component.html", + "widget-types/table/table-widget-interactive/table-widget-interactive-example.component.less", + "widget-types/table/table-widget-interactive/table-widget-interactive-example.component.ts", + "widget-types/table/table-widget-paginator/table-widget-paginator-example.component.html", + "widget-types/table/table-widget-paginator/table-widget-paginator-example.component.less", + "widget-types/table/table-widget-paginator/table-widget-paginator-example.component.ts", + "widget-types/table/table-widget-search/table-widget-search-example.component.html", + "widget-types/table/table-widget-search/table-widget-search-example.component.less", + "widget-types/table/table-widget-search/table-widget-search-example.component.ts", + "widget-types/table/table-widget-search-docs.component.html", + "widget-types/table/table-widget-search-docs.component.ts", + "widget-types/table/table-widget-selectable/table-widget-selectable-multi/table-widget-selectable-multi.example.component.html", + "widget-types/table/table-widget-selectable/table-widget-selectable-multi/table-widget-selectable-multi.example.component.less", + "widget-types/table/table-widget-selectable/table-widget-selectable-multi/table-widget-selectable-multi.example.component.ts", + "widget-types/table/table-widget-selectable/table-widget-selectable-radio/table-widget-selectable-radio.example.component.html", + "widget-types/table/table-widget-selectable/table-widget-selectable-radio/table-widget-selectable-radio.example.component.less", + "widget-types/table/table-widget-selectable/table-widget-selectable-radio/table-widget-selectable-radio.example.component.ts", + "widget-types/table/table-widget-selectable/table-widget-selectable-single/table-widget-selectable-single.example.component.html", + "widget-types/table/table-widget-selectable/table-widget-selectable-single/table-widget-selectable-single.example.component.less", + "widget-types/table/table-widget-selectable/table-widget-selectable-single/table-widget-selectable-single.example.component.ts", + "widget-types/table/table-widget-selectable/table-widget-selectable.example.component.html", + "widget-types/table/table-widget-selectable/table-widget-selectable.example.component.less", + "widget-types/table/table-widget-selectable/table-widget-selectable.example.component.ts", + "widget-types/timeseries/timeseries-docs.component.html", + "widget-types/timeseries/timeseries-docs.component.ts", + "widget-types/timeseries/timeseries-docs.module.ts", + "widget-types/timeseries/timeseries-widget-example/timeseries-widget-example.component.html", + "widget-types/timeseries/timeseries-widget-example/timeseries-widget-example.component.less", + "widget-types/timeseries/timeseries-widget-example/timeseries-widget-example.component.ts", + "widget-types/timeseries/timeseries-widget-interactive-example/timeseries-widget-interactive-example.component.html", + "widget-types/timeseries/timeseries-widget-interactive-example/timeseries-widget-interactive-example.component.less", + "widget-types/timeseries/timeseries-widget-interactive-example/timeseries-widget-interactive-example.component.ts", + "widget-types/timeseries/timeseries-widget-status-bar-example/timeseries-widget-status-bar-example.component.html", + "widget-types/timeseries/timeseries-widget-status-bar-example/timeseries-widget-status-bar-example.component.less", + "widget-types/timeseries/timeseries-widget-status-bar-example/timeseries-widget-status-bar-example.component.ts", + "widget-types/view-components/kpi-tile-view-basic/kpi-tile-view-basic-example.component.html", + "widget-types/view-components/kpi-tile-view-basic/kpi-tile-view-basic-example.component.ts", + "widget-types/view-components/kpi-tile-view-interactive/kpi-tile-view-interactive-example.component.html", + "widget-types/view-components/kpi-tile-view-interactive/kpi-tile-view-interactive-example.component.ts", + "widget-types/view-components/proportional-chart-view-playground/proportional-chart-view-playground-example.component.html", + "widget-types/view-components/proportional-chart-view-playground/proportional-chart-view-playground-example.component.ts", + "widget-types/view-components/view-components-docs.component.html", + "widget-types/view-components/view-components-docs.component.ts", + "widget-types/view-components/view-components-docs.module.ts", + "widget-types/widget-types.module.ts", +]; + +export const DEMO_TS_SOURCES: Record = { + "dashboard-docs.module.ts": \\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\`// © 2022 SolarWinds Worldwide, LLC. All rights reserved. +// +// Permission is hereby granted, free of charge, to any person obtaining a copy +// of this software and associated documentation files (the "Software"), to +// deal in the Software without restriction, including without limitation the +// rights to use, copy, modify, merge, publish, distribute, sublicense, and/or +// sell copies of the Software, and to permit persons to whom the Software is +// furnished to do so, subject to the following conditions: +// +// The above copyright notice and this permission notice shall be included in +// all copies or substantial portions of the Software. +// +// THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR +// IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, +// FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE +// AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER +// LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, +// OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN +// THE SOFTWARE. + +import { provideHttpClient, withInterceptorsFromDi } from "@angular/common/http"; +import { inject, NgModule, Type } from "@angular/core"; +import { RouterModule, Routes } from "@angular/router"; +import { InMemoryCache } from "@apollo/client/core"; +import { provideApollo } from "apollo-angular"; +import { HttpLink } from "apollo-angular/http"; + +import { NuiDocsModule, NuiMessageModule } from "@nova-ui/bits"; + + +const COUNTRIES_API = "https://countries.trevorblades.com/graphql"; + +const exampleRoutes: Routes = [ + { + path: "overview", + loadChildren: async () => + import("./overview/overview.module") as object as Promise< + Type + >, + }, + { + path: "tutorials", + loadChildren: async () => + import("./tutorials/tutorials.module") as object as Promise< + Type + >, + }, + { + path: "widget-types", + loadChildren: async () => + import("./widget-types/widget-types.module") as object as Promise< + Type + >, + }, +]; + +@NgModule({ + imports: [ + NuiDocsModule, + NuiMessageModule, + RouterModule.forChild(exampleRoutes), + ], + providers: [ + provideHttpClient(withInterceptorsFromDi()), + provideApollo(() => { + const httpLink = inject(HttpLink); + + return { + link: httpLink.create({ uri: COUNTRIES_API }), + cache: new InMemoryCache(), + }; + }), + ], +}) +export default class DashboardDocsModule {} +\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\`, + "demo.files.ts": \\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\`// this file autogenerated, do not edit it manually please run the script +// yarn run compile-demo-paths +export const DEMO_PATHS = [ + "dashboard-docs.module.ts", + "demo.files.ts", + "overview/hero/dashboard/hero-dashboard.component.html", + "overview/hero/dashboard/hero-dashboard.component.less", + "overview/hero/dashboard/hero-dashboard.component.ts", + "overview/hero/dashboard/widget-configs.ts", + "overview/hero/data/kpi-datasources.ts", + "overview/hero/data/proportional-datasources.ts", + "overview/hero/data/table/beer-data-source.ts", + "overview/hero/data/table/constants.ts", + "overview/hero/data/table/random-user-data-source.ts", + "overview/hero/data/table/types.ts", + "overview/hero/data/timeseries-data-sources.ts", + "overview/hero/data/types.ts", + "overview/hero/data/widget-data.ts", + "overview/hero/widget-configs/kpi.ts", + "overview/hero/widget-configs/proportional.ts", + "overview/hero/widget-configs/risk-score.ts", + "overview/hero/widget-configs/table.ts", + "overview/hero/widget-configs/timeseries.ts", + "overview/overview-docs.component.html", + "overview/overview-docs.component.ts", + "overview/overview.module.ts", + "tutorials/customization/configurator-section/custom-configurator-section/custom-configurator-section.example.component.html", + "tutorials/customization/configurator-section/custom-configurator-section/custom-configurator-section.example.component.less", + "tutorials/customization/configurator-section/custom-configurator-section/custom-configurator-section.example.component.ts", + "tutorials/customization/configurator-section/custom-configurator-section-docs.component.html", + "tutorials/customization/configurator-section/custom-configurator-section-docs.component.ts", + "tutorials/customization/configurator-section/custom-configurator-section.module.ts", + "tutorials/customization/customization.module.ts", + "tutorials/customization/data-source-configurator/custom-data-source-configurator-docs.component.html", + "tutorials/customization/data-source-configurator/custom-data-source-configurator-docs.component.ts", + "tutorials/customization/data-source-configurator/custom-data-source-configurator.module.ts", + "tutorials/customization/data-source-configurator/example/custom-data-source-configurator-example.component.html", + "tutorials/customization/data-source-configurator/example/custom-data-source-configurator-example.component.less", + "tutorials/customization/data-source-configurator/example/custom-data-source-configurator-example.component.ts", + "tutorials/customization/formatter/custom-formatter.module.ts", + "tutorials/customization/formatter/donut-content-formatter-example/custom-donut-content-formatter-docs.component.html", + "tutorials/customization/formatter/donut-content-formatter-example/custom-donut-content-formatter-docs.component.ts", + "tutorials/customization/formatter/donut-content-formatter-example/custom-donut-content-formatter-example.component.html", + "tutorials/customization/formatter/donut-content-formatter-example/custom-donut-content-formatter-example.component.less", + "tutorials/customization/formatter/donut-content-formatter-example/custom-donut-content-formatter-example.component.ts", + "tutorials/customization/formatter/formatter-example/custom-formatter-docs.component.html", + "tutorials/customization/formatter/formatter-example/custom-formatter-docs.component.ts", + "tutorials/customization/formatter/formatter-example/custom-formatter-example.component.html", + "tutorials/customization/formatter/formatter-example/custom-formatter-example.component.less", + "tutorials/customization/formatter/formatter-example/custom-formatter-example.component.ts", + "tutorials/customization/widget/custom-widget-docs.component.html", + "tutorials/customization/widget/custom-widget-docs.component.ts", + "tutorials/customization/widget/custom-widget.component.html", + "tutorials/customization/widget/custom-widget.component.less", + "tutorials/customization/widget/custom-widget.component.ts", + "tutorials/customization/widget/custom-widget.module.ts", + "tutorials/data-source-setup/data-source-setup-docs.component.html", + "tutorials/data-source-setup/data-source-setup-docs.component.ts", + "tutorials/data-source-setup/data-source-setup.component.html", + "tutorials/data-source-setup/data-source-setup.component.less", + "tutorials/data-source-setup/data-source-setup.component.ts", + "tutorials/data-source-setup/data-source-setup.module.ts", + "tutorials/dynamic-header-links/dynamic-header-links-docs.component.html", + "tutorials/dynamic-header-links/dynamic-header-links-docs.component.ts", + "tutorials/dynamic-header-links/dynamic-header-links-docs.module.ts", + "tutorials/hello-dashboards/hello-dashboards-docs.component.html", + "tutorials/hello-dashboards/hello-dashboards-docs.component.ts", + "tutorials/hello-dashboards/hello-dashboards-example/hello-dashboards-example.component.html", + "tutorials/hello-dashboards/hello-dashboards-example/hello-dashboards-example.component.less", + "tutorials/hello-dashboards/hello-dashboards-example/hello-dashboards-example.component.ts", + "tutorials/hello-dashboards/hello-dashboards.module.ts", + "tutorials/persistence-handler-setup/persistence-handler-setup-docs.component.html", + "tutorials/persistence-handler-setup/persistence-handler-setup-docs.component.ts", + "tutorials/persistence-handler-setup/persistence-handler-setup.component.html", + "tutorials/persistence-handler-setup/persistence-handler-setup.component.less", + "tutorials/persistence-handler-setup/persistence-handler-setup.component.ts", + "tutorials/persistence-handler-setup/persistence-handler-setup.module.ts", + "tutorials/tutorials.module.ts", + "tutorials/widget-creation/widget-creation-docs.component.html", + "tutorials/widget-creation/widget-creation-docs.component.ts", + "tutorials/widget-creation/widget-creation.component.html", + "tutorials/widget-creation/widget-creation.component.less", + "tutorials/widget-creation/widget-creation.component.ts", + "tutorials/widget-creation/widget-creation.module.ts", + "tutorials/widget-editor-setup/widget-editor-setup-docs.component.html", + "tutorials/widget-editor-setup/widget-editor-setup-docs.component.ts", + "tutorials/widget-editor-setup/widget-editor-setup.component.html", + "tutorials/widget-editor-setup/widget-editor-setup.component.less", + "tutorials/widget-editor-setup/widget-editor-setup.component.ts", + "tutorials/widget-editor-setup/widget-editor-setup.module.ts", + "tutorials/widget-error-handling/widget-error-handling-docs.component.html", + "tutorials/widget-error-handling/widget-error-handling-docs.component.ts", + "tutorials/widget-error-handling/widget-error-handling.component.html", + "tutorials/widget-error-handling/widget-error-handling.component.less", + "tutorials/widget-error-handling/widget-error-handling.component.ts", + "tutorials/widget-error-handling/widget-error-handling.module.ts", + "types.ts", + "widget-types/drilldown/drilldown-multi-request-widget/drilldown-multi-request-widget-example.component.html", + "widget-types/drilldown/drilldown-multi-request-widget/drilldown-multi-request-widget-example.component.less", + "widget-types/drilldown/drilldown-multi-request-widget/drilldown-multi-request-widget-example.component.ts", + "widget-types/drilldown/drilldown-widget/data-mock.ts", + "widget-types/drilldown/drilldown-widget/drilldown-widget-example.component.html", + "widget-types/drilldown/drilldown-widget/drilldown-widget-example.component.less", + "widget-types/drilldown/drilldown-widget/drilldown-widget-example.component.ts", + "widget-types/drilldown/drilldown-widget/mock-data-source.ts", + "widget-types/drilldown/drilldown-widget-docs.component.html", + "widget-types/drilldown/drilldown-widget-docs.component.ts", + "widget-types/drilldown/drilldown-widget-docs.module.ts", + "widget-types/embedded-content/embedded-content-docs.component.html", + "widget-types/embedded-content/embedded-content-docs.component.ts", + "widget-types/embedded-content/embedded-content-docs.module.ts", + "widget-types/embedded-content/embedded-content-widget-example/embedded-content-widget-example.component.html", + "widget-types/embedded-content/embedded-content-widget-example/embedded-content-widget-example.component.less", + "widget-types/embedded-content/embedded-content-widget-example/embedded-content-widget-example.component.ts", + "widget-types/kpi/kpi-docs.component.html", + "widget-types/kpi/kpi-docs.component.ts", + "widget-types/kpi/kpi-docs.module.ts", + "widget-types/kpi/kpi-sync-broker/kpi-sync-broker-example.component.html", + "widget-types/kpi/kpi-sync-broker/kpi-sync-broker-example.component.less", + "widget-types/kpi/kpi-sync-broker/kpi-sync-broker-example.component.ts", + "widget-types/kpi/kpi-sync-broker-docs.component.html", + "widget-types/kpi/kpi-sync-broker-docs.component.ts", + "widget-types/kpi/kpi-sync-broker-for-all-tiles/kpi-sync-broker-for-all-tiles-example.component.html", + "widget-types/kpi/kpi-sync-broker-for-all-tiles/kpi-sync-broker-for-all-tiles-example.component.less", + "widget-types/kpi/kpi-sync-broker-for-all-tiles/kpi-sync-broker-for-all-tiles-example.component.ts", + "widget-types/kpi/kpi-widget/kpi-widget-example.component.html", + "widget-types/kpi/kpi-widget/kpi-widget-example.component.less", + "widget-types/kpi/kpi-widget/kpi-widget-example.component.ts", + "widget-types/kpi/kpi-widget-background-color/kpi-widget-background-color-example.component.html", + "widget-types/kpi/kpi-widget-background-color/kpi-widget-background-color-example.component.less", + "widget-types/kpi/kpi-widget-background-color/kpi-widget-background-color-example.component.ts", + "widget-types/kpi/kpi-widget-background-color-docs.component.html", + "widget-types/kpi/kpi-widget-background-color-docs.component.ts", + "widget-types/kpi/kpi-widget-interactive/kpi-widget-interactive-example.component.html", + "widget-types/kpi/kpi-widget-interactive/kpi-widget-interactive-example.component.less", + "widget-types/kpi/kpi-widget-interactive/kpi-widget-interactive-example.component.ts", + "widget-types/proportional/models.ts", + "widget-types/proportional/proportional-docs.component.html", + "widget-types/proportional/proportional-docs.component.ts", + "widget-types/proportional/proportional-docs.module.ts", + "widget-types/proportional/proportional-donut-content-docs.component.html", + "widget-types/proportional/proportional-donut-content-docs.component.ts", + "widget-types/proportional/proportional-donut-content-formatters/proportional-donut-content-formatters-example.component.html", + "widget-types/proportional/proportional-donut-content-formatters/proportional-donut-content-formatters-example.component.less", + "widget-types/proportional/proportional-donut-content-formatters/proportional-donut-content-formatters-example.component.ts", + "widget-types/proportional/proportional-widget/proportional-widget-example.component.html", + "widget-types/proportional/proportional-widget/proportional-widget-example.component.less", + "widget-types/proportional/proportional-widget/proportional-widget-example.component.ts", + "widget-types/proportional/proportional-widget-interactive/proportional-widget-interactive-example.component.html", + "widget-types/proportional/proportional-widget-interactive/proportional-widget-interactive-example.component.less", + "widget-types/proportional/proportional-widget-interactive/proportional-widget-interactive-example.component.ts", + "widget-types/risk-score/risk-score-docs.component.html", + "widget-types/risk-score/risk-score-docs.component.ts", + "widget-types/risk-score/risk-score-docs.module.ts", + "widget-types/risk-score/risk-score-widget-example/risk-score-widget-example.component.html", + "widget-types/risk-score/risk-score-widget-example/risk-score-widget-example.component.less", + "widget-types/risk-score/risk-score-widget-example/risk-score-widget-example.component.ts", + "widget-types/table/table-docs.component.html", + "widget-types/table/table-docs.component.ts", + "widget-types/table/table-docs.module.ts", + "widget-types/table/table-paginator-docs.component.html", + "widget-types/table/table-paginator-docs.component.ts", + "widget-types/table/table-selectable-docs.component.html", + "widget-types/table/table-selectable-docs.component.ts", + "widget-types/table/table-widget/table-widget-example.component.html", + "widget-types/table/table-widget/table-widget-example.component.less", + "widget-types/table/table-widget/table-widget-example.component.ts", + "widget-types/table/table-widget-interactive/table-widget-interactive-example.component.html", + "widget-types/table/table-widget-interactive/table-widget-interactive-example.component.less", + "widget-types/table/table-widget-interactive/table-widget-interactive-example.component.ts", + "widget-types/table/table-widget-paginator/table-widget-paginator-example.component.html", + "widget-types/table/table-widget-paginator/table-widget-paginator-example.component.less", + "widget-types/table/table-widget-paginator/table-widget-paginator-example.component.ts", + "widget-types/table/table-widget-search/table-widget-search-example.component.html", + "widget-types/table/table-widget-search/table-widget-search-example.component.less", + "widget-types/table/table-widget-search/table-widget-search-example.component.ts", + "widget-types/table/table-widget-search-docs.component.html", + "widget-types/table/table-widget-search-docs.component.ts", + "widget-types/table/table-widget-selectable/table-widget-selectable-multi/table-widget-selectable-multi.example.component.html", + "widget-types/table/table-widget-selectable/table-widget-selectable-multi/table-widget-selectable-multi.example.component.less", + "widget-types/table/table-widget-selectable/table-widget-selectable-multi/table-widget-selectable-multi.example.component.ts", + "widget-types/table/table-widget-selectable/table-widget-selectable-radio/table-widget-selectable-radio.example.component.html", + "widget-types/table/table-widget-selectable/table-widget-selectable-radio/table-widget-selectable-radio.example.component.less", + "widget-types/table/table-widget-selectable/table-widget-selectable-radio/table-widget-selectable-radio.example.component.ts", + "widget-types/table/table-widget-selectable/table-widget-selectable-single/table-widget-selectable-single.example.component.html", + "widget-types/table/table-widget-selectable/table-widget-selectable-single/table-widget-selectable-single.example.component.less", + "widget-types/table/table-widget-selectable/table-widget-selectable-single/table-widget-selectable-single.example.component.ts", + "widget-types/table/table-widget-selectable/table-widget-selectable.example.component.html", + "widget-types/table/table-widget-selectable/table-widget-selectable.example.component.less", + "widget-types/table/table-widget-selectable/table-widget-selectable.example.component.ts", + "widget-types/timeseries/timeseries-docs.component.html", + "widget-types/timeseries/timeseries-docs.component.ts", + "widget-types/timeseries/timeseries-docs.module.ts", + "widget-types/timeseries/timeseries-widget-example/timeseries-widget-example.component.html", + "widget-types/timeseries/timeseries-widget-example/timeseries-widget-example.component.less", + "widget-types/timeseries/timeseries-widget-example/timeseries-widget-example.component.ts", + "widget-types/timeseries/timeseries-widget-interactive-example/timeseries-widget-interactive-example.component.html", + "widget-types/timeseries/timeseries-widget-interactive-example/timeseries-widget-interactive-example.component.less", + "widget-types/timeseries/timeseries-widget-interactive-example/timeseries-widget-interactive-example.component.ts", + "widget-types/timeseries/timeseries-widget-status-bar-example/timeseries-widget-status-bar-example.component.html", + "widget-types/timeseries/timeseries-widget-status-bar-example/timeseries-widget-status-bar-example.component.less", + "widget-types/timeseries/timeseries-widget-status-bar-example/timeseries-widget-status-bar-example.component.ts", + "widget-types/view-components/kpi-tile-view-basic/kpi-tile-view-basic-example.component.html", + "widget-types/view-components/kpi-tile-view-basic/kpi-tile-view-basic-example.component.ts", + "widget-types/view-components/kpi-tile-view-interactive/kpi-tile-view-interactive-example.component.html", + "widget-types/view-components/kpi-tile-view-interactive/kpi-tile-view-interactive-example.component.ts", + "widget-types/view-components/proportional-chart-view-bar/proportional-chart-view-bar-example.component.html", + "widget-types/view-components/proportional-chart-view-bar/proportional-chart-view-bar-example.component.ts", + "widget-types/view-components/proportional-chart-view-donut/proportional-chart-view-donut-example.component.html", + "widget-types/view-components/proportional-chart-view-donut/proportional-chart-view-donut-example.component.ts", + "widget-types/view-components/view-components-docs.component.html", + "widget-types/view-components/view-components-docs.component.ts", + "widget-types/view-components/view-components-docs.module.ts", + "widget-types/widget-types.module.ts", +]; + +export const DEMO_TS_SOURCES: Record = { + "dashboard-docs.module.ts": \\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\`// © 2022 SolarWinds Worldwide, LLC. All rights reserved. +// +// Permission is hereby granted, free of charge, to any person obtaining a copy +// of this software and associated documentation files (the "Software"), to +// deal in the Software without restriction, including without limitation the +// rights to use, copy, modify, merge, publish, distribute, sublicense, and/or +// sell copies of the Software, and to permit persons to whom the Software is +// furnished to do so, subject to the following conditions: +// +// The above copyright notice and this permission notice shall be included in +// all copies or substantial portions of the Software. +// +// THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR +// IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, +// FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE +// AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER +// LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, +// OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN +// THE SOFTWARE. + +import { provideHttpClient, withInterceptorsFromDi } from "@angular/common/http"; +import { inject, NgModule, Type } from "@angular/core"; +import { RouterModule, Routes } from "@angular/router"; +import { InMemoryCache } from "@apollo/client/core"; +import { provideApollo } from "apollo-angular"; +import { HttpLink } from "apollo-angular/http"; + +import { NuiDocsModule, NuiMessageModule } from "@nova-ui/bits"; + + +const COUNTRIES_API = "https://countries.trevorblades.com/graphql"; + +const exampleRoutes: Routes = [ + { + path: "overview", + loadChildren: async () => + import("./overview/overview.module") as object as Promise< + Type + >, + }, + { + path: "tutorials", + loadChildren: async () => + import("./tutorials/tutorials.module") as object as Promise< + Type + >, + }, + { + path: "widget-types", + loadChildren: async () => + import("./widget-types/widget-types.module") as object as Promise< + Type + >, + }, +]; + +@NgModule({ + imports: [ + NuiDocsModule, + NuiMessageModule, + RouterModule.forChild(exampleRoutes), + ], + providers: [ + provideHttpClient(withInterceptorsFromDi()), + provideApollo(() => { + const httpLink = inject(HttpLink); + + return { + link: httpLink.create({ uri: COUNTRIES_API }), + cache: new InMemoryCache(), + }; + }), + ], +}) +export default class DashboardDocsModule {} +\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\`, + "demo.files.ts": \\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\`// this file autogenerated, do not edit it manually please run the script +// yarn run compile-demo-paths +export const DEMO_PATHS = [ + "dashboard-docs.module.ts", + "demo.files.ts", + "overview/hero/dashboard/hero-dashboard.component.html", + "overview/hero/dashboard/hero-dashboard.component.less", + "overview/hero/dashboard/hero-dashboard.component.ts", + "overview/hero/dashboard/widget-configs.ts", + "overview/hero/data/kpi-datasources.ts", + "overview/hero/data/proportional-datasources.ts", + "overview/hero/data/table/beer-data-source.ts", + "overview/hero/data/table/constants.ts", + "overview/hero/data/table/random-user-data-source.ts", + "overview/hero/data/table/types.ts", + "overview/hero/data/timeseries-data-sources.ts", + "overview/hero/data/types.ts", + "overview/hero/data/widget-data.ts", + "overview/hero/widget-configs/kpi.ts", + "overview/hero/widget-configs/proportional.ts", + "overview/hero/widget-configs/risk-score.ts", + "overview/hero/widget-configs/table.ts", + "overview/hero/widget-configs/timeseries.ts", + "overview/overview-docs.component.html", + "overview/overview-docs.component.ts", + "overview/overview.module.ts", + "tutorials/customization/configurator-section/custom-configurator-section/custom-configurator-section.example.component.html", + "tutorials/customization/configurator-section/custom-configurator-section/custom-configurator-section.example.component.less", + "tutorials/customization/configurator-section/custom-configurator-section/custom-configurator-section.example.component.ts", + "tutorials/customization/configurator-section/custom-configurator-section-docs.component.html", + "tutorials/customization/configurator-section/custom-configurator-section-docs.component.ts", + "tutorials/customization/configurator-section/custom-configurator-section.module.ts", + "tutorials/customization/customization.module.ts", + "tutorials/customization/data-source-configurator/custom-data-source-configurator-docs.component.html", + "tutorials/customization/data-source-configurator/custom-data-source-configurator-docs.component.ts", + "tutorials/customization/data-source-configurator/custom-data-source-configurator.module.ts", + "tutorials/customization/data-source-configurator/example/custom-data-source-configurator-example.component.html", + "tutorials/customization/data-source-configurator/example/custom-data-source-configurator-example.component.less", + "tutorials/customization/data-source-configurator/example/custom-data-source-configurator-example.component.ts", + "tutorials/customization/formatter/custom-formatter.module.ts", + "tutorials/customization/formatter/donut-content-formatter-example/custom-donut-content-formatter-docs.component.html", + "tutorials/customization/formatter/donut-content-formatter-example/custom-donut-content-formatter-docs.component.ts", + "tutorials/customization/formatter/donut-content-formatter-example/custom-donut-content-formatter-example.component.html", + "tutorials/customization/formatter/donut-content-formatter-example/custom-donut-content-formatter-example.component.less", + "tutorials/customization/formatter/donut-content-formatter-example/custom-donut-content-formatter-example.component.ts", + "tutorials/customization/formatter/formatter-example/custom-formatter-docs.component.html", + "tutorials/customization/formatter/formatter-example/custom-formatter-docs.component.ts", + "tutorials/customization/formatter/formatter-example/custom-formatter-example.component.html", + "tutorials/customization/formatter/formatter-example/custom-formatter-example.component.less", + "tutorials/customization/formatter/formatter-example/custom-formatter-example.component.ts", + "tutorials/customization/widget/custom-widget-docs.component.html", + "tutorials/customization/widget/custom-widget-docs.component.ts", + "tutorials/customization/widget/custom-widget.component.html", + "tutorials/customization/widget/custom-widget.component.less", + "tutorials/customization/widget/custom-widget.component.ts", + "tutorials/customization/widget/custom-widget.module.ts", + "tutorials/data-source-setup/data-source-setup-docs.component.html", + "tutorials/data-source-setup/data-source-setup-docs.component.ts", + "tutorials/data-source-setup/data-source-setup.component.html", + "tutorials/data-source-setup/data-source-setup.component.less", + "tutorials/data-source-setup/data-source-setup.component.ts", + "tutorials/data-source-setup/data-source-setup.module.ts", + "tutorials/dynamic-header-links/dynamic-header-links-docs.component.html", + "tutorials/dynamic-header-links/dynamic-header-links-docs.component.ts", + "tutorials/dynamic-header-links/dynamic-header-links-docs.module.ts", + "tutorials/hello-dashboards/hello-dashboards-docs.component.html", + "tutorials/hello-dashboards/hello-dashboards-docs.component.ts", + "tutorials/hello-dashboards/hello-dashboards-example/hello-dashboards-example.component.html", + "tutorials/hello-dashboards/hello-dashboards-example/hello-dashboards-example.component.less", + "tutorials/hello-dashboards/hello-dashboards-example/hello-dashboards-example.component.ts", + "tutorials/hello-dashboards/hello-dashboards.module.ts", + "tutorials/persistence-handler-setup/persistence-handler-setup-docs.component.html", + "tutorials/persistence-handler-setup/persistence-handler-setup-docs.component.ts", + "tutorials/persistence-handler-setup/persistence-handler-setup.component.html", + "tutorials/persistence-handler-setup/persistence-handler-setup.component.less", + "tutorials/persistence-handler-setup/persistence-handler-setup.component.ts", + "tutorials/persistence-handler-setup/persistence-handler-setup.module.ts", + "tutorials/tutorials.module.ts", + "tutorials/widget-creation/widget-creation-docs.component.html", + "tutorials/widget-creation/widget-creation-docs.component.ts", + "tutorials/widget-creation/widget-creation.component.html", + "tutorials/widget-creation/widget-creation.component.less", + "tutorials/widget-creation/widget-creation.component.ts", + "tutorials/widget-creation/widget-creation.module.ts", + "tutorials/widget-editor-setup/widget-editor-setup-docs.component.html", + "tutorials/widget-editor-setup/widget-editor-setup-docs.component.ts", + "tutorials/widget-editor-setup/widget-editor-setup.component.html", + "tutorials/widget-editor-setup/widget-editor-setup.component.less", + "tutorials/widget-editor-setup/widget-editor-setup.component.ts", + "tutorials/widget-editor-setup/widget-editor-setup.module.ts", + "tutorials/widget-error-handling/widget-error-handling-docs.component.html", + "tutorials/widget-error-handling/widget-error-handling-docs.component.ts", + "tutorials/widget-error-handling/widget-error-handling.component.html", + "tutorials/widget-error-handling/widget-error-handling.component.less", + "tutorials/widget-error-handling/widget-error-handling.component.ts", + "tutorials/widget-error-handling/widget-error-handling.module.ts", + "types.ts", + "widget-types/drilldown/drilldown-multi-request-widget/drilldown-multi-request-widget-example.component.html", + "widget-types/drilldown/drilldown-multi-request-widget/drilldown-multi-request-widget-example.component.less", + "widget-types/drilldown/drilldown-multi-request-widget/drilldown-multi-request-widget-example.component.ts", + "widget-types/drilldown/drilldown-widget/data-mock.ts", + "widget-types/drilldown/drilldown-widget/drilldown-widget-example.component.html", + "widget-types/drilldown/drilldown-widget/drilldown-widget-example.component.less", + "widget-types/drilldown/drilldown-widget/drilldown-widget-example.component.ts", + "widget-types/drilldown/drilldown-widget/mock-data-source.ts", + "widget-types/drilldown/drilldown-widget-docs.component.html", + "widget-types/drilldown/drilldown-widget-docs.component.ts", + "widget-types/drilldown/drilldown-widget-docs.module.ts", + "widget-types/embedded-content/embedded-content-docs.component.html", + "widget-types/embedded-content/embedded-content-docs.component.ts", + "widget-types/embedded-content/embedded-content-docs.module.ts", + "widget-types/embedded-content/embedded-content-widget-example/embedded-content-widget-example.component.html", + "widget-types/embedded-content/embedded-content-widget-example/embedded-content-widget-example.component.less", + "widget-types/embedded-content/embedded-content-widget-example/embedded-content-widget-example.component.ts", + "widget-types/kpi/kpi-docs.component.html", + "widget-types/kpi/kpi-docs.component.ts", + "widget-types/kpi/kpi-docs.module.ts", + "widget-types/kpi/kpi-sync-broker/kpi-sync-broker-example.component.html", + "widget-types/kpi/kpi-sync-broker/kpi-sync-broker-example.component.less", + "widget-types/kpi/kpi-sync-broker/kpi-sync-broker-example.component.ts", + "widget-types/kpi/kpi-sync-broker-docs.component.html", + "widget-types/kpi/kpi-sync-broker-docs.component.ts", + "widget-types/kpi/kpi-sync-broker-for-all-tiles/kpi-sync-broker-for-all-tiles-example.component.html", + "widget-types/kpi/kpi-sync-broker-for-all-tiles/kpi-sync-broker-for-all-tiles-example.component.less", + "widget-types/kpi/kpi-sync-broker-for-all-tiles/kpi-sync-broker-for-all-tiles-example.component.ts", + "widget-types/kpi/kpi-widget/kpi-widget-example.component.html", + "widget-types/kpi/kpi-widget/kpi-widget-example.component.less", + "widget-types/kpi/kpi-widget/kpi-widget-example.component.ts", + "widget-types/kpi/kpi-widget-background-color/kpi-widget-background-color-example.component.html", + "widget-types/kpi/kpi-widget-background-color/kpi-widget-background-color-example.component.less", + "widget-types/kpi/kpi-widget-background-color/kpi-widget-background-color-example.component.ts", + "widget-types/kpi/kpi-widget-background-color-docs.component.html", + "widget-types/kpi/kpi-widget-background-color-docs.component.ts", + "widget-types/kpi/kpi-widget-interactive/kpi-widget-interactive-example.component.html", + "widget-types/kpi/kpi-widget-interactive/kpi-widget-interactive-example.component.less", + "widget-types/kpi/kpi-widget-interactive/kpi-widget-interactive-example.component.ts", + "widget-types/proportional/models.ts", + "widget-types/proportional/proportional-docs.component.html", + "widget-types/proportional/proportional-docs.component.ts", + "widget-types/proportional/proportional-docs.module.ts", + "widget-types/proportional/proportional-donut-content-docs.component.html", + "widget-types/proportional/proportional-donut-content-docs.component.ts", + "widget-types/proportional/proportional-donut-content-formatters/proportional-donut-content-formatters-example.component.html", + "widget-types/proportional/proportional-donut-content-formatters/proportional-donut-content-formatters-example.component.less", + "widget-types/proportional/proportional-donut-content-formatters/proportional-donut-content-formatters-example.component.ts", + "widget-types/proportional/proportional-widget/proportional-widget-example.component.html", + "widget-types/proportional/proportional-widget/proportional-widget-example.component.less", + "widget-types/proportional/proportional-widget/proportional-widget-example.component.ts", + "widget-types/proportional/proportional-widget-interactive/proportional-widget-interactive-example.component.html", + "widget-types/proportional/proportional-widget-interactive/proportional-widget-interactive-example.component.less", + "widget-types/proportional/proportional-widget-interactive/proportional-widget-interactive-example.component.ts", + "widget-types/risk-score/risk-score-docs.component.html", + "widget-types/risk-score/risk-score-docs.component.ts", + "widget-types/risk-score/risk-score-docs.module.ts", + "widget-types/risk-score/risk-score-widget-example/risk-score-widget-example.component.html", + "widget-types/risk-score/risk-score-widget-example/risk-score-widget-example.component.less", + "widget-types/risk-score/risk-score-widget-example/risk-score-widget-example.component.ts", + "widget-types/table/table-docs.component.html", + "widget-types/table/table-docs.component.ts", + "widget-types/table/table-docs.module.ts", + "widget-types/table/table-paginator-docs.component.html", + "widget-types/table/table-paginator-docs.component.ts", + "widget-types/table/table-selectable-docs.component.html", + "widget-types/table/table-selectable-docs.component.ts", + "widget-types/table/table-widget/table-widget-example.component.html", + "widget-types/table/table-widget/table-widget-example.component.less", + "widget-types/table/table-widget/table-widget-example.component.ts", + "widget-types/table/table-widget-interactive/table-widget-interactive-example.component.html", + "widget-types/table/table-widget-interactive/table-widget-interactive-example.component.less", + "widget-types/table/table-widget-interactive/table-widget-interactive-example.component.ts", + "widget-types/table/table-widget-paginator/table-widget-paginator-example.component.html", + "widget-types/table/table-widget-paginator/table-widget-paginator-example.component.less", + "widget-types/table/table-widget-paginator/table-widget-paginator-example.component.ts", + "widget-types/table/table-widget-search/table-widget-search-example.component.html", + "widget-types/table/table-widget-search/table-widget-search-example.component.less", + "widget-types/table/table-widget-search/table-widget-search-example.component.ts", + "widget-types/table/table-widget-search-docs.component.html", + "widget-types/table/table-widget-search-docs.component.ts", + "widget-types/table/table-widget-selectable/table-widget-selectable-multi/table-widget-selectable-multi.example.component.html", + "widget-types/table/table-widget-selectable/table-widget-selectable-multi/table-widget-selectable-multi.example.component.less", + "widget-types/table/table-widget-selectable/table-widget-selectable-multi/table-widget-selectable-multi.example.component.ts", + "widget-types/table/table-widget-selectable/table-widget-selectable-radio/table-widget-selectable-radio.example.component.html", + "widget-types/table/table-widget-selectable/table-widget-selectable-radio/table-widget-selectable-radio.example.component.less", + "widget-types/table/table-widget-selectable/table-widget-selectable-radio/table-widget-selectable-radio.example.component.ts", + "widget-types/table/table-widget-selectable/table-widget-selectable-single/table-widget-selectable-single.example.component.html", + "widget-types/table/table-widget-selectable/table-widget-selectable-single/table-widget-selectable-single.example.component.less", + "widget-types/table/table-widget-selectable/table-widget-selectable-single/table-widget-selectable-single.example.component.ts", + "widget-types/table/table-widget-selectable/table-widget-selectable.example.component.html", + "widget-types/table/table-widget-selectable/table-widget-selectable.example.component.less", + "widget-types/table/table-widget-selectable/table-widget-selectable.example.component.ts", + "widget-types/timeseries/timeseries-docs.component.html", + "widget-types/timeseries/timeseries-docs.component.ts", + "widget-types/timeseries/timeseries-docs.module.ts", + "widget-types/timeseries/timeseries-widget-example/timeseries-widget-example.component.html", + "widget-types/timeseries/timeseries-widget-example/timeseries-widget-example.component.less", + "widget-types/timeseries/timeseries-widget-example/timeseries-widget-example.component.ts", + "widget-types/timeseries/timeseries-widget-interactive-example/timeseries-widget-interactive-example.component.html", + "widget-types/timeseries/timeseries-widget-interactive-example/timeseries-widget-interactive-example.component.less", + "widget-types/timeseries/timeseries-widget-interactive-example/timeseries-widget-interactive-example.component.ts", + "widget-types/timeseries/timeseries-widget-status-bar-example/timeseries-widget-status-bar-example.component.html", + "widget-types/timeseries/timeseries-widget-status-bar-example/timeseries-widget-status-bar-example.component.less", + "widget-types/timeseries/timeseries-widget-status-bar-example/timeseries-widget-status-bar-example.component.ts", + "widget-types/view-components/kpi-tile-view-basic/kpi-tile-view-basic-example.component.html", + "widget-types/view-components/kpi-tile-view-basic/kpi-tile-view-basic-example.component.ts", + "widget-types/view-components/kpi-tile-view-interactive/kpi-tile-view-interactive-example.component.html", + "widget-types/view-components/kpi-tile-view-interactive/kpi-tile-view-interactive-example.component.ts", + "widget-types/view-components/proportional-chart-view-bar/proportional-chart-view-bar-example.component.html", + "widget-types/view-components/proportional-chart-view-bar/proportional-chart-view-bar-example.component.ts", + "widget-types/view-components/proportional-chart-view-donut/proportional-chart-view-donut-example.component.html", + "widget-types/view-components/proportional-chart-view-donut/proportional-chart-view-donut-example.component.ts", + "widget-types/view-components/view-components-docs.component.html", + "widget-types/view-components/view-components-docs.component.ts", + "widget-types/view-components/view-components-docs.module.ts", + "widget-types/widget-types.module.ts", +]; + +export const DEMO_TS_SOURCES: Record = { + "dashboard-docs.module.ts": \\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\`// © 2022 SolarWinds Worldwide, LLC. All rights reserved. +// +// Permission is hereby granted, free of charge, to any person obtaining a copy +// of this software and associated documentation files (the "Software"), to +// deal in the Software without restriction, including without limitation the +// rights to use, copy, modify, merge, publish, distribute, sublicense, and/or +// sell copies of the Software, and to permit persons to whom the Software is +// furnished to do so, subject to the following conditions: +// +// The above copyright notice and this permission notice shall be included in +// all copies or substantial portions of the Software. +// +// THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR +// IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, +// FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE +// AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER +// LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, +// OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN +// THE SOFTWARE. + +import { provideHttpClient, withInterceptorsFromDi } from "@angular/common/http"; +import { inject, NgModule, Type } from "@angular/core"; +import { RouterModule, Routes } from "@angular/router"; +import { InMemoryCache } from "@apollo/client/core"; +import { provideApollo } from "apollo-angular"; +import { HttpLink } from "apollo-angular/http"; + +import { NuiDocsModule, NuiMessageModule } from "@nova-ui/bits"; + + +const COUNTRIES_API = "https://countries.trevorblades.com/graphql"; + +const exampleRoutes: Routes = [ + { + path: "overview", + loadChildren: async () => + import("./overview/overview.module") as object as Promise< + Type + >, + }, + { + path: "tutorials", + loadChildren: async () => + import("./tutorials/tutorials.module") as object as Promise< + Type + >, + }, + { + path: "widget-types", + loadChildren: async () => + import("./widget-types/widget-types.module") as object as Promise< + Type + >, + }, +]; + +@NgModule({ + imports: [ + NuiDocsModule, + NuiMessageModule, + RouterModule.forChild(exampleRoutes), + ], + providers: [ + provideHttpClient(withInterceptorsFromDi()), + provideApollo(() => { + const httpLink = inject(HttpLink); + + return { + link: httpLink.create({ uri: COUNTRIES_API }), + cache: new InMemoryCache(), + }; + }), + ], +}) +export default class DashboardDocsModule {} +\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\`, + "demo.files.ts": \\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\`// this file autogenerated, do not edit it manually please run the script +// yarn run compile-demo-paths +export const DEMO_PATHS = [ + "dashboard-docs.module.ts", + "demo.files.ts", + "overview/hero/dashboard/hero-dashboard.component.html", + "overview/hero/dashboard/hero-dashboard.component.less", + "overview/hero/dashboard/hero-dashboard.component.ts", + "overview/hero/dashboard/widget-configs.ts", + "overview/hero/data/kpi-datasources.ts", + "overview/hero/data/proportional-datasources.ts", + "overview/hero/data/table/beer-data-source.ts", + "overview/hero/data/table/constants.ts", + "overview/hero/data/table/random-user-data-source.ts", + "overview/hero/data/table/types.ts", + "overview/hero/data/timeseries-data-sources.ts", + "overview/hero/data/types.ts", + "overview/hero/data/widget-data.ts", + "overview/hero/widget-configs/kpi.ts", + "overview/hero/widget-configs/proportional.ts", + "overview/hero/widget-configs/risk-score.ts", + "overview/hero/widget-configs/table.ts", + "overview/hero/widget-configs/timeseries.ts", + "overview/overview-docs.component.html", + "overview/overview-docs.component.ts", + "overview/overview.module.ts", + "tutorials/customization/configurator-section/custom-configurator-section/custom-configurator-section.example.component.html", + "tutorials/customization/configurator-section/custom-configurator-section/custom-configurator-section.example.component.less", + "tutorials/customization/configurator-section/custom-configurator-section/custom-configurator-section.example.component.ts", + "tutorials/customization/configurator-section/custom-configurator-section-docs.component.html", + "tutorials/customization/configurator-section/custom-configurator-section-docs.component.ts", + "tutorials/customization/configurator-section/custom-configurator-section.module.ts", + "tutorials/customization/customization.module.ts", + "tutorials/customization/data-source-configurator/custom-data-source-configurator-docs.component.html", + "tutorials/customization/data-source-configurator/custom-data-source-configurator-docs.component.ts", + "tutorials/customization/data-source-configurator/custom-data-source-configurator.module.ts", + "tutorials/customization/data-source-configurator/example/custom-data-source-configurator-example.component.html", + "tutorials/customization/data-source-configurator/example/custom-data-source-configurator-example.component.less", + "tutorials/customization/data-source-configurator/example/custom-data-source-configurator-example.component.ts", + "tutorials/customization/formatter/custom-formatter.module.ts", + "tutorials/customization/formatter/donut-content-formatter-example/custom-donut-content-formatter-docs.component.html", + "tutorials/customization/formatter/donut-content-formatter-example/custom-donut-content-formatter-docs.component.ts", + "tutorials/customization/formatter/donut-content-formatter-example/custom-donut-content-formatter-example.component.html", + "tutorials/customization/formatter/donut-content-formatter-example/custom-donut-content-formatter-example.component.less", + "tutorials/customization/formatter/donut-content-formatter-example/custom-donut-content-formatter-example.component.ts", + "tutorials/customization/formatter/formatter-example/custom-formatter-docs.component.html", + "tutorials/customization/formatter/formatter-example/custom-formatter-docs.component.ts", + "tutorials/customization/formatter/formatter-example/custom-formatter-example.component.html", + "tutorials/customization/formatter/formatter-example/custom-formatter-example.component.less", + "tutorials/customization/formatter/formatter-example/custom-formatter-example.component.ts", + "tutorials/customization/widget/custom-widget-docs.component.html", + "tutorials/customization/widget/custom-widget-docs.component.ts", + "tutorials/customization/widget/custom-widget.component.html", + "tutorials/customization/widget/custom-widget.component.less", + "tutorials/customization/widget/custom-widget.component.ts", + "tutorials/customization/widget/custom-widget.module.ts", + "tutorials/data-source-setup/data-source-setup-docs.component.html", + "tutorials/data-source-setup/data-source-setup-docs.component.ts", + "tutorials/data-source-setup/data-source-setup.component.html", + "tutorials/data-source-setup/data-source-setup.component.less", + "tutorials/data-source-setup/data-source-setup.component.ts", + "tutorials/data-source-setup/data-source-setup.module.ts", + "tutorials/dynamic-header-links/dynamic-header-links-docs.component.html", + "tutorials/dynamic-header-links/dynamic-header-links-docs.component.ts", + "tutorials/dynamic-header-links/dynamic-header-links-docs.module.ts", + "tutorials/hello-dashboards/hello-dashboards-docs.component.html", + "tutorials/hello-dashboards/hello-dashboards-docs.component.ts", + "tutorials/hello-dashboards/hello-dashboards-example/hello-dashboards-example.component.html", + "tutorials/hello-dashboards/hello-dashboards-example/hello-dashboards-example.component.less", + "tutorials/hello-dashboards/hello-dashboards-example/hello-dashboards-example.component.ts", + "tutorials/hello-dashboards/hello-dashboards.module.ts", + "tutorials/persistence-handler-setup/persistence-handler-setup-docs.component.html", + "tutorials/persistence-handler-setup/persistence-handler-setup-docs.component.ts", + "tutorials/persistence-handler-setup/persistence-handler-setup.component.html", + "tutorials/persistence-handler-setup/persistence-handler-setup.component.less", + "tutorials/persistence-handler-setup/persistence-handler-setup.component.ts", + "tutorials/persistence-handler-setup/persistence-handler-setup.module.ts", + "tutorials/tutorials.module.ts", + "tutorials/widget-creation/widget-creation-docs.component.html", + "tutorials/widget-creation/widget-creation-docs.component.ts", + "tutorials/widget-creation/widget-creation.component.html", + "tutorials/widget-creation/widget-creation.component.less", + "tutorials/widget-creation/widget-creation.component.ts", + "tutorials/widget-creation/widget-creation.module.ts", + "tutorials/widget-editor-setup/widget-editor-setup-docs.component.html", + "tutorials/widget-editor-setup/widget-editor-setup-docs.component.ts", + "tutorials/widget-editor-setup/widget-editor-setup.component.html", + "tutorials/widget-editor-setup/widget-editor-setup.component.less", + "tutorials/widget-editor-setup/widget-editor-setup.component.ts", + "tutorials/widget-editor-setup/widget-editor-setup.module.ts", + "tutorials/widget-error-handling/widget-error-handling-docs.component.html", + "tutorials/widget-error-handling/widget-error-handling-docs.component.ts", + "tutorials/widget-error-handling/widget-error-handling.component.html", + "tutorials/widget-error-handling/widget-error-handling.component.less", + "tutorials/widget-error-handling/widget-error-handling.component.ts", + "tutorials/widget-error-handling/widget-error-handling.module.ts", + "types.ts", + "widget-types/drilldown/drilldown-multi-request-widget/drilldown-multi-request-widget-example.component.html", + "widget-types/drilldown/drilldown-multi-request-widget/drilldown-multi-request-widget-example.component.less", + "widget-types/drilldown/drilldown-multi-request-widget/drilldown-multi-request-widget-example.component.ts", + "widget-types/drilldown/drilldown-widget/data-mock.ts", + "widget-types/drilldown/drilldown-widget/drilldown-widget-example.component.html", + "widget-types/drilldown/drilldown-widget/drilldown-widget-example.component.less", + "widget-types/drilldown/drilldown-widget/drilldown-widget-example.component.ts", + "widget-types/drilldown/drilldown-widget/mock-data-source.ts", + "widget-types/drilldown/drilldown-widget-docs.component.html", + "widget-types/drilldown/drilldown-widget-docs.component.ts", + "widget-types/drilldown/drilldown-widget-docs.module.ts", + "widget-types/embedded-content/embedded-content-docs.component.html", + "widget-types/embedded-content/embedded-content-docs.component.ts", + "widget-types/embedded-content/embedded-content-docs.module.ts", + "widget-types/embedded-content/embedded-content-widget-example/embedded-content-widget-example.component.html", + "widget-types/embedded-content/embedded-content-widget-example/embedded-content-widget-example.component.less", + "widget-types/embedded-content/embedded-content-widget-example/embedded-content-widget-example.component.ts", + "widget-types/kpi/kpi-docs.component.html", + "widget-types/kpi/kpi-docs.component.ts", + "widget-types/kpi/kpi-docs.module.ts", + "widget-types/kpi/kpi-sync-broker/kpi-sync-broker-example.component.html", + "widget-types/kpi/kpi-sync-broker/kpi-sync-broker-example.component.less", + "widget-types/kpi/kpi-sync-broker/kpi-sync-broker-example.component.ts", + "widget-types/kpi/kpi-sync-broker-docs.component.html", + "widget-types/kpi/kpi-sync-broker-docs.component.ts", + "widget-types/kpi/kpi-sync-broker-for-all-tiles/kpi-sync-broker-for-all-tiles-example.component.html", + "widget-types/kpi/kpi-sync-broker-for-all-tiles/kpi-sync-broker-for-all-tiles-example.component.less", + "widget-types/kpi/kpi-sync-broker-for-all-tiles/kpi-sync-broker-for-all-tiles-example.component.ts", + "widget-types/kpi/kpi-widget/kpi-widget-example.component.html", + "widget-types/kpi/kpi-widget/kpi-widget-example.component.less", + "widget-types/kpi/kpi-widget/kpi-widget-example.component.ts", + "widget-types/kpi/kpi-widget-background-color/kpi-widget-background-color-example.component.html", + "widget-types/kpi/kpi-widget-background-color/kpi-widget-background-color-example.component.less", + "widget-types/kpi/kpi-widget-background-color/kpi-widget-background-color-example.component.ts", + "widget-types/kpi/kpi-widget-background-color-docs.component.html", + "widget-types/kpi/kpi-widget-background-color-docs.component.ts", + "widget-types/kpi/kpi-widget-interactive/kpi-widget-interactive-example.component.html", + "widget-types/kpi/kpi-widget-interactive/kpi-widget-interactive-example.component.less", + "widget-types/kpi/kpi-widget-interactive/kpi-widget-interactive-example.component.ts", + "widget-types/proportional/models.ts", + "widget-types/proportional/proportional-docs.component.html", + "widget-types/proportional/proportional-docs.component.ts", + "widget-types/proportional/proportional-docs.module.ts", + "widget-types/proportional/proportional-donut-content-docs.component.html", + "widget-types/proportional/proportional-donut-content-docs.component.ts", + "widget-types/proportional/proportional-donut-content-formatters/proportional-donut-content-formatters-example.component.html", + "widget-types/proportional/proportional-donut-content-formatters/proportional-donut-content-formatters-example.component.less", + "widget-types/proportional/proportional-donut-content-formatters/proportional-donut-content-formatters-example.component.ts", + "widget-types/proportional/proportional-widget/proportional-widget-example.component.html", + "widget-types/proportional/proportional-widget/proportional-widget-example.component.less", + "widget-types/proportional/proportional-widget/proportional-widget-example.component.ts", + "widget-types/proportional/proportional-widget-interactive/proportional-widget-interactive-example.component.html", + "widget-types/proportional/proportional-widget-interactive/proportional-widget-interactive-example.component.less", + "widget-types/proportional/proportional-widget-interactive/proportional-widget-interactive-example.component.ts", + "widget-types/risk-score/risk-score-docs.component.html", + "widget-types/risk-score/risk-score-docs.component.ts", + "widget-types/risk-score/risk-score-docs.module.ts", + "widget-types/risk-score/risk-score-widget-example/risk-score-widget-example.component.html", + "widget-types/risk-score/risk-score-widget-example/risk-score-widget-example.component.less", + "widget-types/risk-score/risk-score-widget-example/risk-score-widget-example.component.ts", + "widget-types/table/table-docs.component.html", + "widget-types/table/table-docs.component.ts", + "widget-types/table/table-docs.module.ts", + "widget-types/table/table-paginator-docs.component.html", + "widget-types/table/table-paginator-docs.component.ts", + "widget-types/table/table-selectable-docs.component.html", + "widget-types/table/table-selectable-docs.component.ts", + "widget-types/table/table-widget/table-widget-example.component.html", + "widget-types/table/table-widget/table-widget-example.component.less", + "widget-types/table/table-widget/table-widget-example.component.ts", + "widget-types/table/table-widget-interactive/table-widget-interactive-example.component.html", + "widget-types/table/table-widget-interactive/table-widget-interactive-example.component.less", + "widget-types/table/table-widget-interactive/table-widget-interactive-example.component.ts", + "widget-types/table/table-widget-paginator/table-widget-paginator-example.component.html", + "widget-types/table/table-widget-paginator/table-widget-paginator-example.component.less", + "widget-types/table/table-widget-paginator/table-widget-paginator-example.component.ts", + "widget-types/table/table-widget-search/table-widget-search-example.component.html", + "widget-types/table/table-widget-search/table-widget-search-example.component.less", + "widget-types/table/table-widget-search/table-widget-search-example.component.ts", + "widget-types/table/table-widget-search-docs.component.html", + "widget-types/table/table-widget-search-docs.component.ts", + "widget-types/table/table-widget-selectable/table-widget-selectable-multi/table-widget-selectable-multi.example.component.html", + "widget-types/table/table-widget-selectable/table-widget-selectable-multi/table-widget-selectable-multi.example.component.less", + "widget-types/table/table-widget-selectable/table-widget-selectable-multi/table-widget-selectable-multi.example.component.ts", + "widget-types/table/table-widget-selectable/table-widget-selectable-radio/table-widget-selectable-radio.example.component.html", + "widget-types/table/table-widget-selectable/table-widget-selectable-radio/table-widget-selectable-radio.example.component.less", + "widget-types/table/table-widget-selectable/table-widget-selectable-radio/table-widget-selectable-radio.example.component.ts", + "widget-types/table/table-widget-selectable/table-widget-selectable-single/table-widget-selectable-single.example.component.html", + "widget-types/table/table-widget-selectable/table-widget-selectable-single/table-widget-selectable-single.example.component.less", + "widget-types/table/table-widget-selectable/table-widget-selectable-single/table-widget-selectable-single.example.component.ts", + "widget-types/table/table-widget-selectable/table-widget-selectable.example.component.html", + "widget-types/table/table-widget-selectable/table-widget-selectable.example.component.less", + "widget-types/table/table-widget-selectable/table-widget-selectable.example.component.ts", + "widget-types/timeseries/timeseries-docs.component.html", + "widget-types/timeseries/timeseries-docs.component.ts", + "widget-types/timeseries/timeseries-docs.module.ts", + "widget-types/timeseries/timeseries-widget-example/timeseries-widget-example.component.html", + "widget-types/timeseries/timeseries-widget-example/timeseries-widget-example.component.less", + "widget-types/timeseries/timeseries-widget-example/timeseries-widget-example.component.ts", + "widget-types/timeseries/timeseries-widget-interactive-example/timeseries-widget-interactive-example.component.html", + "widget-types/timeseries/timeseries-widget-interactive-example/timeseries-widget-interactive-example.component.less", + "widget-types/timeseries/timeseries-widget-interactive-example/timeseries-widget-interactive-example.component.ts", + "widget-types/timeseries/timeseries-widget-status-bar-example/timeseries-widget-status-bar-example.component.html", + "widget-types/timeseries/timeseries-widget-status-bar-example/timeseries-widget-status-bar-example.component.less", + "widget-types/timeseries/timeseries-widget-status-bar-example/timeseries-widget-status-bar-example.component.ts", + "widget-types/view-components/kpi-tile-view-basic/kpi-tile-view-basic-example.component.html", + "widget-types/view-components/kpi-tile-view-basic/kpi-tile-view-basic-example.component.ts", + "widget-types/view-components/kpi-tile-view-interactive/kpi-tile-view-interactive-example.component.html", + "widget-types/view-components/kpi-tile-view-interactive/kpi-tile-view-interactive-example.component.ts", + "widget-types/view-components/proportional-chart-view-bar/proportional-chart-view-bar-example.component.html", + "widget-types/view-components/proportional-chart-view-bar/proportional-chart-view-bar-example.component.ts", + "widget-types/view-components/proportional-chart-view-donut/proportional-chart-view-donut-example.component.html", + "widget-types/view-components/proportional-chart-view-donut/proportional-chart-view-donut-example.component.ts", + "widget-types/view-components/view-components-docs.component.html", + "widget-types/view-components/view-components-docs.component.ts", + "widget-types/view-components/view-components-docs.module.ts", + "widget-types/widget-types.module.ts", +]; + +export const DEMO_TS_SOURCES: Record = { + "dashboard-docs.module.ts": \\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\`// © 2022 SolarWinds Worldwide, LLC. All rights reserved. +// +// Permission is hereby granted, free of charge, to any person obtaining a copy +// of this software and associated documentation files (the "Software"), to +// deal in the Software without restriction, including without limitation the +// rights to use, copy, modify, merge, publish, distribute, sublicense, and/or +// sell copies of the Software, and to permit persons to whom the Software is +// furnished to do so, subject to the following conditions: +// +// The above copyright notice and this permission notice shall be included in +// all copies or substantial portions of the Software. +// +// THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR +// IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, +// FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE +// AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER +// LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, +// OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN +// THE SOFTWARE. + +import { provideHttpClient, withInterceptorsFromDi } from "@angular/common/http"; +import { inject, NgModule, Type } from "@angular/core"; +import { RouterModule, Routes } from "@angular/router"; +import { InMemoryCache } from "@apollo/client/core"; +import { provideApollo } from "apollo-angular"; +import { HttpLink } from "apollo-angular/http"; + +import { NuiDocsModule, NuiMessageModule } from "@nova-ui/bits"; + + +const COUNTRIES_API = "https://countries.trevorblades.com/graphql"; + +const exampleRoutes: Routes = [ + { + path: "overview", + loadChildren: async () => + import("./overview/overview.module") as object as Promise< + Type + >, + }, + { + path: "tutorials", + loadChildren: async () => + import("./tutorials/tutorials.module") as object as Promise< + Type + >, + }, + { + path: "widget-types", + loadChildren: async () => + import("./widget-types/widget-types.module") as object as Promise< + Type + >, + }, +]; + +@NgModule({ + imports: [ + NuiDocsModule, + NuiMessageModule, + RouterModule.forChild(exampleRoutes), + ], + providers: [ + provideHttpClient(withInterceptorsFromDi()), + provideApollo(() => { + const httpLink = inject(HttpLink); + + return { + link: httpLink.create({ uri: COUNTRIES_API }), + cache: new InMemoryCache(), + }; + }), + ], +}) +export default class DashboardDocsModule {} +\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\`, + "demo.files.ts": \\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\`// this file autogenerated, do not edit it manually please run the script +// yarn run compile-demo-paths +export const DEMO_PATHS = [ + "dashboard-docs.module.ts", + "demo.files.ts", + "overview/hero/dashboard/hero-dashboard.component.html", + "overview/hero/dashboard/hero-dashboard.component.less", + "overview/hero/dashboard/hero-dashboard.component.ts", + "overview/hero/dashboard/widget-configs.ts", + "overview/hero/data/kpi-datasources.ts", + "overview/hero/data/proportional-datasources.ts", + "overview/hero/data/table/beer-data-source.ts", + "overview/hero/data/table/constants.ts", + "overview/hero/data/table/random-user-data-source.ts", + "overview/hero/data/table/types.ts", + "overview/hero/data/timeseries-data-sources.ts", + "overview/hero/data/types.ts", + "overview/hero/data/widget-data.ts", + "overview/hero/widget-configs/kpi.ts", + "overview/hero/widget-configs/proportional.ts", + "overview/hero/widget-configs/risk-score.ts", + "overview/hero/widget-configs/table.ts", + "overview/hero/widget-configs/timeseries.ts", + "overview/overview-docs.component.html", + "overview/overview-docs.component.ts", + "overview/overview.module.ts", + "tutorials/customization/configurator-section/custom-configurator-section/custom-configurator-section.example.component.html", + "tutorials/customization/configurator-section/custom-configurator-section/custom-configurator-section.example.component.less", + "tutorials/customization/configurator-section/custom-configurator-section/custom-configurator-section.example.component.ts", + "tutorials/customization/configurator-section/custom-configurator-section-docs.component.html", + "tutorials/customization/configurator-section/custom-configurator-section-docs.component.ts", + "tutorials/customization/configurator-section/custom-configurator-section.module.ts", + "tutorials/customization/customization.module.ts", + "tutorials/customization/data-source-configurator/custom-data-source-configurator-docs.component.html", + "tutorials/customization/data-source-configurator/custom-data-source-configurator-docs.component.ts", + "tutorials/customization/data-source-configurator/custom-data-source-configurator.module.ts", + "tutorials/customization/data-source-configurator/example/custom-data-source-configurator-example.component.html", + "tutorials/customization/data-source-configurator/example/custom-data-source-configurator-example.component.less", + "tutorials/customization/data-source-configurator/example/custom-data-source-configurator-example.component.ts", + "tutorials/customization/formatter/custom-formatter.module.ts", + "tutorials/customization/formatter/donut-content-formatter-example/custom-donut-content-formatter-docs.component.html", + "tutorials/customization/formatter/donut-content-formatter-example/custom-donut-content-formatter-docs.component.ts", + "tutorials/customization/formatter/donut-content-formatter-example/custom-donut-content-formatter-example.component.html", + "tutorials/customization/formatter/donut-content-formatter-example/custom-donut-content-formatter-example.component.less", + "tutorials/customization/formatter/donut-content-formatter-example/custom-donut-content-formatter-example.component.ts", + "tutorials/customization/formatter/formatter-example/custom-formatter-docs.component.html", + "tutorials/customization/formatter/formatter-example/custom-formatter-docs.component.ts", + "tutorials/customization/formatter/formatter-example/custom-formatter-example.component.html", + "tutorials/customization/formatter/formatter-example/custom-formatter-example.component.less", + "tutorials/customization/formatter/formatter-example/custom-formatter-example.component.ts", + "tutorials/customization/widget/custom-widget-docs.component.html", + "tutorials/customization/widget/custom-widget-docs.component.ts", + "tutorials/customization/widget/custom-widget.component.html", + "tutorials/customization/widget/custom-widget.component.less", + "tutorials/customization/widget/custom-widget.component.ts", + "tutorials/customization/widget/custom-widget.module.ts", + "tutorials/data-source-setup/data-source-setup-docs.component.html", + "tutorials/data-source-setup/data-source-setup-docs.component.ts", + "tutorials/data-source-setup/data-source-setup.component.html", + "tutorials/data-source-setup/data-source-setup.component.less", + "tutorials/data-source-setup/data-source-setup.component.ts", + "tutorials/data-source-setup/data-source-setup.module.ts", + "tutorials/dynamic-header-links/dynamic-header-links-docs.component.html", + "tutorials/dynamic-header-links/dynamic-header-links-docs.component.ts", + "tutorials/dynamic-header-links/dynamic-header-links-docs.module.ts", + "tutorials/hello-dashboards/hello-dashboards-docs.component.html", + "tutorials/hello-dashboards/hello-dashboards-docs.component.ts", + "tutorials/hello-dashboards/hello-dashboards-example/hello-dashboards-example.component.html", + "tutorials/hello-dashboards/hello-dashboards-example/hello-dashboards-example.component.less", + "tutorials/hello-dashboards/hello-dashboards-example/hello-dashboards-example.component.ts", + "tutorials/hello-dashboards/hello-dashboards.module.ts", + "tutorials/persistence-handler-setup/persistence-handler-setup-docs.component.html", + "tutorials/persistence-handler-setup/persistence-handler-setup-docs.component.ts", + "tutorials/persistence-handler-setup/persistence-handler-setup.component.html", + "tutorials/persistence-handler-setup/persistence-handler-setup.component.less", + "tutorials/persistence-handler-setup/persistence-handler-setup.component.ts", + "tutorials/persistence-handler-setup/persistence-handler-setup.module.ts", + "tutorials/tutorials.module.ts", + "tutorials/widget-creation/widget-creation-docs.component.html", + "tutorials/widget-creation/widget-creation-docs.component.ts", + "tutorials/widget-creation/widget-creation.component.html", + "tutorials/widget-creation/widget-creation.component.less", + "tutorials/widget-creation/widget-creation.component.ts", + "tutorials/widget-creation/widget-creation.module.ts", + "tutorials/widget-editor-setup/widget-editor-setup-docs.component.html", + "tutorials/widget-editor-setup/widget-editor-setup-docs.component.ts", + "tutorials/widget-editor-setup/widget-editor-setup.component.html", + "tutorials/widget-editor-setup/widget-editor-setup.component.less", + "tutorials/widget-editor-setup/widget-editor-setup.component.ts", + "tutorials/widget-editor-setup/widget-editor-setup.module.ts", + "tutorials/widget-error-handling/widget-error-handling-docs.component.html", + "tutorials/widget-error-handling/widget-error-handling-docs.component.ts", + "tutorials/widget-error-handling/widget-error-handling.component.html", + "tutorials/widget-error-handling/widget-error-handling.component.less", + "tutorials/widget-error-handling/widget-error-handling.component.ts", + "tutorials/widget-error-handling/widget-error-handling.module.ts", + "types.ts", + "widget-types/drilldown/drilldown-multi-request-widget/drilldown-multi-request-widget-example.component.html", + "widget-types/drilldown/drilldown-multi-request-widget/drilldown-multi-request-widget-example.component.less", + "widget-types/drilldown/drilldown-multi-request-widget/drilldown-multi-request-widget-example.component.ts", + "widget-types/drilldown/drilldown-widget/data-mock.ts", + "widget-types/drilldown/drilldown-widget/drilldown-widget-example.component.html", + "widget-types/drilldown/drilldown-widget/drilldown-widget-example.component.less", + "widget-types/drilldown/drilldown-widget/drilldown-widget-example.component.ts", + "widget-types/drilldown/drilldown-widget/mock-data-source.ts", + "widget-types/drilldown/drilldown-widget-docs.component.html", + "widget-types/drilldown/drilldown-widget-docs.component.ts", + "widget-types/drilldown/drilldown-widget-docs.module.ts", + "widget-types/embedded-content/embedded-content-docs.component.html", + "widget-types/embedded-content/embedded-content-docs.component.ts", + "widget-types/embedded-content/embedded-content-docs.module.ts", + "widget-types/embedded-content/embedded-content-widget-example/embedded-content-widget-example.component.html", + "widget-types/embedded-content/embedded-content-widget-example/embedded-content-widget-example.component.less", + "widget-types/embedded-content/embedded-content-widget-example/embedded-content-widget-example.component.ts", + "widget-types/kpi/kpi-docs.component.html", + "widget-types/kpi/kpi-docs.component.ts", + "widget-types/kpi/kpi-docs.module.ts", + "widget-types/kpi/kpi-sync-broker/kpi-sync-broker-example.component.html", + "widget-types/kpi/kpi-sync-broker/kpi-sync-broker-example.component.less", + "widget-types/kpi/kpi-sync-broker/kpi-sync-broker-example.component.ts", + "widget-types/kpi/kpi-sync-broker-docs.component.html", + "widget-types/kpi/kpi-sync-broker-docs.component.ts", + "widget-types/kpi/kpi-sync-broker-for-all-tiles/kpi-sync-broker-for-all-tiles-example.component.html", + "widget-types/kpi/kpi-sync-broker-for-all-tiles/kpi-sync-broker-for-all-tiles-example.component.less", + "widget-types/kpi/kpi-sync-broker-for-all-tiles/kpi-sync-broker-for-all-tiles-example.component.ts", + "widget-types/kpi/kpi-widget/kpi-widget-example.component.html", + "widget-types/kpi/kpi-widget/kpi-widget-example.component.less", + "widget-types/kpi/kpi-widget/kpi-widget-example.component.ts", + "widget-types/kpi/kpi-widget-background-color/kpi-widget-background-color-example.component.html", + "widget-types/kpi/kpi-widget-background-color/kpi-widget-background-color-example.component.less", + "widget-types/kpi/kpi-widget-background-color/kpi-widget-background-color-example.component.ts", + "widget-types/kpi/kpi-widget-background-color-docs.component.html", + "widget-types/kpi/kpi-widget-background-color-docs.component.ts", + "widget-types/kpi/kpi-widget-interactive/kpi-widget-interactive-example.component.html", + "widget-types/kpi/kpi-widget-interactive/kpi-widget-interactive-example.component.less", + "widget-types/kpi/kpi-widget-interactive/kpi-widget-interactive-example.component.ts", + "widget-types/proportional/models.ts", + "widget-types/proportional/proportional-docs.component.html", + "widget-types/proportional/proportional-docs.component.ts", + "widget-types/proportional/proportional-docs.module.ts", + "widget-types/proportional/proportional-donut-content-docs.component.html", + "widget-types/proportional/proportional-donut-content-docs.component.ts", + "widget-types/proportional/proportional-donut-content-formatters/proportional-donut-content-formatters-example.component.html", + "widget-types/proportional/proportional-donut-content-formatters/proportional-donut-content-formatters-example.component.less", + "widget-types/proportional/proportional-donut-content-formatters/proportional-donut-content-formatters-example.component.ts", + "widget-types/proportional/proportional-widget/proportional-widget-example.component.html", + "widget-types/proportional/proportional-widget/proportional-widget-example.component.less", + "widget-types/proportional/proportional-widget/proportional-widget-example.component.ts", + "widget-types/proportional/proportional-widget-interactive/proportional-widget-interactive-example.component.html", + "widget-types/proportional/proportional-widget-interactive/proportional-widget-interactive-example.component.less", + "widget-types/proportional/proportional-widget-interactive/proportional-widget-interactive-example.component.ts", + "widget-types/risk-score/risk-score-docs.component.html", + "widget-types/risk-score/risk-score-docs.component.ts", + "widget-types/risk-score/risk-score-docs.module.ts", + "widget-types/risk-score/risk-score-widget-example/risk-score-widget-example.component.html", + "widget-types/risk-score/risk-score-widget-example/risk-score-widget-example.component.less", + "widget-types/risk-score/risk-score-widget-example/risk-score-widget-example.component.ts", + "widget-types/table/table-docs.component.html", + "widget-types/table/table-docs.component.ts", + "widget-types/table/table-docs.module.ts", + "widget-types/table/table-paginator-docs.component.html", + "widget-types/table/table-paginator-docs.component.ts", + "widget-types/table/table-selectable-docs.component.html", + "widget-types/table/table-selectable-docs.component.ts", + "widget-types/table/table-widget/table-widget-example.component.html", + "widget-types/table/table-widget/table-widget-example.component.less", + "widget-types/table/table-widget/table-widget-example.component.ts", + "widget-types/table/table-widget-interactive/table-widget-interactive-example.component.html", + "widget-types/table/table-widget-interactive/table-widget-interactive-example.component.less", + "widget-types/table/table-widget-interactive/table-widget-interactive-example.component.ts", + "widget-types/table/table-widget-paginator/table-widget-paginator-example.component.html", + "widget-types/table/table-widget-paginator/table-widget-paginator-example.component.less", + "widget-types/table/table-widget-paginator/table-widget-paginator-example.component.ts", + "widget-types/table/table-widget-search/table-widget-search-example.component.html", + "widget-types/table/table-widget-search/table-widget-search-example.component.less", + "widget-types/table/table-widget-search/table-widget-search-example.component.ts", + "widget-types/table/table-widget-search-docs.component.html", + "widget-types/table/table-widget-search-docs.component.ts", + "widget-types/table/table-widget-selectable/table-widget-selectable-multi/table-widget-selectable-multi.example.component.html", + "widget-types/table/table-widget-selectable/table-widget-selectable-multi/table-widget-selectable-multi.example.component.less", + "widget-types/table/table-widget-selectable/table-widget-selectable-multi/table-widget-selectable-multi.example.component.ts", + "widget-types/table/table-widget-selectable/table-widget-selectable-radio/table-widget-selectable-radio.example.component.html", + "widget-types/table/table-widget-selectable/table-widget-selectable-radio/table-widget-selectable-radio.example.component.less", + "widget-types/table/table-widget-selectable/table-widget-selectable-radio/table-widget-selectable-radio.example.component.ts", + "widget-types/table/table-widget-selectable/table-widget-selectable-single/table-widget-selectable-single.example.component.html", + "widget-types/table/table-widget-selectable/table-widget-selectable-single/table-widget-selectable-single.example.component.less", + "widget-types/table/table-widget-selectable/table-widget-selectable-single/table-widget-selectable-single.example.component.ts", + "widget-types/table/table-widget-selectable/table-widget-selectable.example.component.html", + "widget-types/table/table-widget-selectable/table-widget-selectable.example.component.less", + "widget-types/table/table-widget-selectable/table-widget-selectable.example.component.ts", + "widget-types/timeseries/timeseries-docs.component.html", + "widget-types/timeseries/timeseries-docs.component.ts", + "widget-types/timeseries/timeseries-docs.module.ts", + "widget-types/timeseries/timeseries-widget-example/timeseries-widget-example.component.html", + "widget-types/timeseries/timeseries-widget-example/timeseries-widget-example.component.less", + "widget-types/timeseries/timeseries-widget-example/timeseries-widget-example.component.ts", + "widget-types/timeseries/timeseries-widget-interactive-example/timeseries-widget-interactive-example.component.html", + "widget-types/timeseries/timeseries-widget-interactive-example/timeseries-widget-interactive-example.component.less", + "widget-types/timeseries/timeseries-widget-interactive-example/timeseries-widget-interactive-example.component.ts", + "widget-types/timeseries/timeseries-widget-status-bar-example/timeseries-widget-status-bar-example.component.html", + "widget-types/timeseries/timeseries-widget-status-bar-example/timeseries-widget-status-bar-example.component.less", + "widget-types/timeseries/timeseries-widget-status-bar-example/timeseries-widget-status-bar-example.component.ts", + "widget-types/view-components/kpi-tile-view-basic/kpi-tile-view-basic-example.component.html", + "widget-types/view-components/kpi-tile-view-basic/kpi-tile-view-basic-example.component.ts", + "widget-types/view-components/kpi-tile-view-interactive/kpi-tile-view-interactive-example.component.html", + "widget-types/view-components/kpi-tile-view-interactive/kpi-tile-view-interactive-example.component.ts", + "widget-types/view-components/proportional-chart-view-bar/proportional-chart-view-bar-example.component.html", + "widget-types/view-components/proportional-chart-view-bar/proportional-chart-view-bar-example.component.ts", + "widget-types/view-components/proportional-chart-view-donut/proportional-chart-view-donut-example.component.html", + "widget-types/view-components/proportional-chart-view-donut/proportional-chart-view-donut-example.component.ts", + "widget-types/view-components/view-components-docs.component.html", + "widget-types/view-components/view-components-docs.component.ts", + "widget-types/view-components/view-components-docs.module.ts", + "widget-types/widget-types.module.ts", +]; +\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\`, + "overview/hero/dashboard/hero-dashboard.component.ts": \\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\`// © 2022 SolarWinds Worldwide, LLC. All rights reserved. +// +// Permission is hereby granted, free of charge, to any person obtaining a copy +// of this software and associated documentation files (the "Software"), to +// deal in the Software without restriction, including without limitation the +// rights to use, copy, modify, merge, publish, distribute, sublicense, and/or +// sell copies of the Software, and to permit persons to whom the Software is +// furnished to do so, subject to the following conditions: +// +// The above copyright notice and this permission notice shall be included in +// all copies or substantial portions of the Software. +// +// THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR +// IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, +// FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE +// AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER +// LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, +// OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN +// THE SOFTWARE. + +import { HttpClient } from "@angular/common/http"; +import { + ChangeDetectionStrategy, + Component, + OnInit, + ViewEncapsulation, +} from "@angular/core"; +import keyBy from "lodash/keyBy"; + +import { LoggerService, ThemeSwitchService } from "@nova-ui/bits"; +import { + DATA_SOURCE, + IDashboard, + IWidget, + ProviderRegistryService, + WidgetTypesService, +} from "@nova-ui/dashboards"; + +import { positions, widgets } from "./widget-configs"; +import { + HarryPotterAverageRatingDataSource, + HarryPotterRatingsCountDataSource, +} from "../data/kpi-datasources"; +import { + BeerReviewCountsByCityMockDataSource, + BeerReviewCountsByCityMockDataSource2, +} from "../data/proportional-datasources"; +import { BeerDataSource } from "../data/table/beer-data-source"; +import { RandomUserDataSource } from "../data/table/random-user-data-source"; +import { + BeerVsReadingMockDataSource, + LoungingVsFrisbeeGolfMockDataSource, +} from "../data/timeseries-data-sources"; + +/** + * A component that instantiates the dashboard + */ +@Component({ + selector: "hero-dashboard", + templateUrl: "./hero-dashboard.component.html", + styleUrls: ["./hero-dashboard.component.less"], + encapsulation: ViewEncapsulation.Emulated, + changeDetection: ChangeDetectionStrategy.Default, + standalone: false, +}) +export class HeroDashboardComponent implements OnInit { + public dashboard: IDashboard = { + positions: {}, + widgets: {}, + }; + + public gridsterConfig = {}; + public editMode = false; + + constructor( + private providerRegistry: ProviderRegistryService, + public themeSwitcherService: ThemeSwitchService, + private widgetTypesService: WidgetTypesService + ) { + this.providerRegistry.setProviders({ + [HarryPotterAverageRatingDataSource.providerId]: { + provide: DATA_SOURCE, + useClass: HarryPotterAverageRatingDataSource, + deps: [HttpClient], + }, + [HarryPotterRatingsCountDataSource.providerId]: { + provide: DATA_SOURCE, + useClass: HarryPotterRatingsCountDataSource, + deps: [HttpClient], + }, + [BeerReviewCountsByCityMockDataSource.providerId]: { + provide: DATA_SOURCE, + useClass: BeerReviewCountsByCityMockDataSource, + deps: [], + }, + [BeerReviewCountsByCityMockDataSource2.providerId]: { + provide: DATA_SOURCE, + useClass: BeerReviewCountsByCityMockDataSource2, + deps: [], + }, + [RandomUserDataSource.providerId]: { + provide: DATA_SOURCE, + useClass: RandomUserDataSource, + deps: [LoggerService], + }, + [BeerDataSource.providerId]: { + provide: DATA_SOURCE, + useClass: BeerDataSource, + deps: [LoggerService], + }, + [BeerVsReadingMockDataSource.providerId]: { + provide: DATA_SOURCE, + useClass: BeerVsReadingMockDataSource, + deps: [], + }, + [LoungingVsFrisbeeGolfMockDataSource.providerId]: { + provide: DATA_SOURCE, + useClass: LoungingVsFrisbeeGolfMockDataSource, + deps: [], + }, + }); + } + + public ngOnInit(): void { + const widgetsWithStructure = widgets.map((w) => ({ + ...w, + pizzagna: { + ...this.widgetTypesService.getWidgetType(w.type, w.version) + .widget, + ...w.pizzagna, + }, + })); + const widgetsIndex = keyBy(widgetsWithStructure, (w: IWidget) => w.id); + + this.dashboard = { + positions: positions, + widgets: widgetsIndex, + }; + } +} +\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\`, + "overview/hero/dashboard/widget-configs.ts": \\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\`// © 2022 SolarWinds Worldwide, LLC. All rights reserved. +// +// Permission is hereby granted, free of charge, to any person obtaining a copy +// of this software and associated documentation files (the "Software"), to +// deal in the Software without restriction, including without limitation the +// rights to use, copy, modify, merge, publish, distribute, sublicense, and/or +// sell copies of the Software, and to permit persons to whom the Software is +// furnished to do so, subject to the following conditions: +// +// The above copyright notice and this permission notice shall be included in +// all copies or substantial portions of the Software. +// +// THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR +// IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, +// FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE +// AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER +// LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, +// OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN +// THE SOFTWARE. + +import { GridsterItem } from "angular-gridster2"; + +import { IWidget } from "@nova-ui/dashboards"; + +import { kpiConfig } from "../widget-configs/kpi"; +import { proportionalConfig } from "../widget-configs/proportional"; +import { tableConfig } from "../widget-configs/table"; +import { timeseriesConfig } from "../widget-configs/timeseries"; + +export const positions: Record = { + [tableConfig.id]: { + cols: 7, + rows: 7, + y: 0, + x: 0, + }, + [proportionalConfig.id]: { + cols: 5, + rows: 7, + y: 0, + x: 7, + }, + [kpiConfig.id]: { + cols: 6, + rows: 7, + y: 7, + x: 0, + }, + [timeseriesConfig.id]: { + cols: 6, + rows: 7, + y: 7, + x: 6, + }, +}; + +export const widgets: IWidget[] = [ + { + ...tableConfig, + }, + { + ...proportionalConfig, + }, + { + ...kpiConfig, + }, + { + ...timeseriesConfig, + }, +]; +\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\`, + "overview/hero/data/kpi-datasources.ts": \\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\`// © 2022 SolarWinds Worldwide, LLC. All rights reserved. +// +// Permission is hereby granted, free of charge, to any person obtaining a copy +// of this software and associated documentation files (the "Software"), to +// deal in the Software without restriction, including without limitation the +// rights to use, copy, modify, merge, publish, distribute, sublicense, and/or +// sell copies of the Software, and to permit persons to whom the Software is +// furnished to do so, subject to the following conditions: +// +// The above copyright notice and this permission notice shall be included in +// all copies or substantial portions of the Software. +// +// THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR +// IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, +// FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE +// AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER +// LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, +// OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN +// THE SOFTWARE. + +import { HttpClient, HttpErrorResponse } from "@angular/common/http"; +import { Injectable, OnDestroy } from "@angular/core"; +import { BehaviorSubject } from "rxjs"; +import { finalize } from "rxjs/operators"; + +import { DataSourceService, IFilteringOutputs } from "@nova-ui/bits"; +import { IKpiData } from "@nova-ui/dashboards"; + +import { GOOGLE_BOOKS_URL } from "./table/constants"; + +@Injectable() +export class HarryPotterAverageRatingDataSource + extends DataSourceService + implements OnDestroy +{ + public static providerId = "HarryPotterAverageRatingDataSource"; + + public busy = new BehaviorSubject(false); + + constructor(private http: HttpClient) { + super(); + } + + public async getFilteredData(): Promise { + this.busy.next(true); + return new Promise((resolve) => { + // *** Make a resource request to an external API (if needed) + this.http + .get(\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\`\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\${GOOGLE_BOOKS_URL}/5MQFrgEACAAJ\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\`) + .pipe(finalize(() => this.busy.next(false))) + .subscribe({ + next: (data: any) => { + resolve({ + result: { + value: data.volumeInfo.averageRating, + }, + }); + }, + error: (error: HttpErrorResponse) => { + resolve({ + result: null, + error: { + type: error.status, + }, + }); + }, + }); + }); + } + + public ngOnDestroy(): void { + this.outputsSubject.complete(); + } +} + +@Injectable() +export class HarryPotterRatingsCountDataSource + extends DataSourceService + implements OnDestroy +{ + public static providerId = "HarryPotterRatingsCountDataSource"; + + public busy = new BehaviorSubject(false); + + constructor(private http: HttpClient) { + super(); + } + + public async getFilteredData(): Promise { + this.busy.next(true); + return new Promise((resolve) => { + this.http + .get(\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\`\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\${GOOGLE_BOOKS_URL}/5MQFrgEACAAJ\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\`) + .pipe(finalize(() => this.busy.next(false))) + .subscribe({ + next: (data: any) => { + resolve({ + result: { + value: data.volumeInfo.ratingsCount, + }, + }); + }, + error: (error: HttpErrorResponse) => { + resolve({ + result: null, + error: { + type: error.status, + }, + }); + }, + }); + }); + } + + public ngOnDestroy(): void { + this.outputsSubject.complete(); + } +} +\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\`, + "overview/hero/data/proportional-datasources.ts": \\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\`// © 2022 SolarWinds Worldwide, LLC. All rights reserved. +// +// Permission is hereby granted, free of charge, to any person obtaining a copy +// of this software and associated documentation files (the "Software"), to +// deal in the Software without restriction, including without limitation the +// rights to use, copy, modify, merge, publish, distribute, sublicense, and/or +// sell copies of the Software, and to permit persons to whom the Software is +// furnished to do so, subject to the following conditions: +// +// The above copyright notice and this permission notice shall be included in +// all copies or substantial portions of the Software. +// +// THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR +// IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, +// FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE +// AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER +// LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, +// OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN +// THE SOFTWARE. + +import { Injectable, OnDestroy } from "@angular/core"; +import { BehaviorSubject } from "rxjs"; + +import { + DataSourceService, + IDataSource, + IFilteringOutputs, +} from "@nova-ui/bits"; + +import { + getMockBeerReviewCountsByCity, + getMockBeerReviewCountsByCity2, + IProportionalWidgetData, +} from "./widget-data"; + +@Injectable() +export class BeerReviewCountsByCityMockDataSource + extends DataSourceService + implements IDataSource, OnDestroy +{ + // This is the ID we'll use to identify the provider + public static providerId = "BeerReviewCountsByCityMockDataSource"; + public busy = new BehaviorSubject(false); + + public async getFilteredData(): Promise { + this.busy.next(true); + return new Promise((resolve) => { + setTimeout(() => { + this.outputsSubject.next({ + result: getMockBeerReviewCountsByCity(), + }); + this.busy.next(false); + }, 300); + }); + } + + public ngOnDestroy(): void { + this.outputsSubject.complete(); + } +} + +@Injectable() +export class BeerReviewCountsByCityMockDataSource2 + extends DataSourceService + implements IDataSource, OnDestroy +{ + // This is the ID we'll use to identify the provider + public static providerId = "BeerReviewCountsByCityMockDataSource2"; + public busy = new BehaviorSubject(false); + + public async getFilteredData(): Promise { + this.busy.next(true); + return new Promise((resolve) => { + setTimeout(() => { + this.outputsSubject.next({ + result: getMockBeerReviewCountsByCity2(), + }); + this.busy.next(false); + }, 1500); + }); + } + + public ngOnDestroy(): void { + this.outputsSubject.complete(); + } +} +\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\`, + "overview/hero/data/table/beer-data-source.ts": \\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\`// © 2022 SolarWinds Worldwide, LLC. All rights reserved. +// +// Permission is hereby granted, free of charge, to any person obtaining a copy +// of this software and associated documentation files (the "Software"), to +// deal in the Software without restriction, including without limitation the +// rights to use, copy, modify, merge, publish, distribute, sublicense, and/or +// sell copies of the Software, and to permit persons to whom the Software is +// furnished to do so, subject to the following conditions: +// +// The above copyright notice and this permission notice shall be included in +// all copies or substantial portions of the Software. +// +// THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR +// IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, +// FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE +// AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER +// LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, +// OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN +// THE SOFTWARE. + +import { ListRange } from "@angular/cdk/collections"; +import { Injectable } from "@angular/core"; +import isEqual from "lodash/isEqual"; +import orderBy from "lodash/orderBy"; +import { BehaviorSubject } from "rxjs"; + +import { + DataSourceService, + IDataField, + INovaFilteringOutputs, + INovaFilters, + ISorterFilter, + LoggerService, +} from "@nova-ui/bits"; + +import { IBrewDatasourceResponse, IBrewInfo } from "../types"; +import { BREW_API_URL } from "./constants"; + +@Injectable() +export class BeerDataSource extends DataSourceService { + public static providerId = "BeerDataSource"; + + private cache = Array.from({ length: 0 }); + private lastSortValue?: ISorterFilter; + private lastVirtualScroll?: ListRange; + private totalItems: number = 325; + + public page: number = 1; + public busy = new BehaviorSubject(false); + + public dataFields: Array = [ + { id: "id", label: "No", dataType: "number" }, + { id: "name", label: "Name", dataType: "string" }, + { id: "tagline", label: "Tagline", dataType: "string" }, + { id: "first_brewed", label: "First Brewed", dataType: "string" }, + { id: "description", label: "Description", dataType: "string" }, + { id: "brewers_tips", label: "Brewer's Tips", dataType: "string" }, + ]; + + constructor(private logger: LoggerService) { + super(); + } + + public async getFilteredData( + filters: INovaFilters + ): Promise { + const start = filters.virtualScroll?.value?.start ?? 0; + const end = filters.virtualScroll?.value?.end ?? 0; + const delta = end - start; + + // This condition handles sorting. We want to sort columns without fetching another chunk of data. + // Since the data is being fetched when scrolled, we compare virtual scroll indexes here in the condition as well. + if (filters.sorter?.value) { + if ( + !isEqual(this.lastSortValue, filters.sorter.value) && + isEqual(this.lastVirtualScroll, filters.virtualScroll?.value) + ) { + const totalPages = Math.ceil( + delta ? this.totalItems / delta : 1 + ); + const itemsPerPage: number = Math.max( + delta < 80 ? delta : 80, + 1 + ); + let response: Array | null = null; + let map: IBrewDatasourceResponse; + + if (filters.sorter?.value?.direction === "desc") { + this.cache = []; + for (let i = 0; i < this.page; ++i) { + response = await ( + await fetch( + \\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\`\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\${BREW_API_URL}/?page=\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\${ + totalPages - i || 1 + }&per_page=\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\${itemsPerPage}\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\` + ) + ).json(); + + // since the last page contains only 5 items we need to fetch another page to give virtual scroll enough space to work + if (response && response.length < itemsPerPage) { + this.page++; + } + map = { + brewInfo: response?.map((result: IBrewInfo) => ({ + id: result.id, + name: result.name, + tagline: result.tagline, + first_brewed: result.first_brewed, + description: result.description, + brewers_tips: result.brewers_tips, + })), + total: response?.length, + } as IBrewDatasourceResponse; + this.cache = + totalPages - i !== 0 + ? this.cache.concat(map.brewInfo) + : this.cache; + } + } + + if (filters.sorter?.value?.direction === "asc") { + this.cache = []; + for (let i = 0; i < this.page; i++) { + response = await ( + await fetch( + \\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\`\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\${BREW_API_URL}/?page=\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\${ + i + 1 + }&per_page=\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\${itemsPerPage}\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\` + ) + ).json(); + map = { + brewInfo: response?.map((result: IBrewInfo) => ({ + id: result.id, + name: result.name, + tagline: result.tagline, + first_brewed: result.first_brewed, + description: result.description, + brewers_tips: result.brewers_tips, + })), + total: response?.length, + } as IBrewDatasourceResponse; + this.cache = this.cache.concat(map.brewInfo); + } + } + + this.lastSortValue = filters.sorter?.value; + this.lastVirtualScroll = filters.virtualScroll?.value; + + return { + repeat: { itemsSource: this.sortData(this.cache, filters) }, + paginator: { total: this.totalItems }, + dataFields: this.dataFields, + }; + } + } + + this.busy.next(true); + return new Promise((resolve) => { + setTimeout(() => { + this.getData(start, end, filters).then( + (response: INovaFilteringOutputs) => { + if (!response) { + return; + } + + this.cache = this.cache.concat(response.brewInfo); + + this.dataSubject.next(this.cache); + resolve({ + repeat: { + itemsSource: this.sortData(this.cache, filters), + }, + paginator: { total: this.totalItems }, + dataFields: this.dataFields, + }); + + this.lastSortValue = filters.sorter?.value; + this.lastVirtualScroll = filters.virtualScroll?.value; + this.busy.next(false); + } + ); + }, 500); + }); + } + + public async getData( + start: number = 0, + end: number = 20, + filters: INovaFilters + ): Promise { + const delta = end - start; + const totalPages = Math.ceil(delta ? this.totalItems / delta : 1); + let response: Array | null = null; + // The api.punk.com is able to return only 80 items per page + const itemsPerPage: number = Math.max(delta < 80 ? delta : 80, 1); + + if (filters.sorter?.value?.direction === "asc") { + response = await ( + await fetch( + \\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\`\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\${BREW_API_URL}/?page=\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\${this.page}&per_page=\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\${itemsPerPage}\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\` + ) + ).json(); + } + + if (filters.sorter?.value?.direction === "desc") { + response = await ( + await fetch( + \\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\`\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\${BREW_API_URL}/?page=\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\${ + totalPages - this.page + }&per_page=\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\${itemsPerPage}\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\` + ) + ).json(); + } + + if (!filters.sorter) { + response = await ( + await fetch( + \\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\`\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\${BREW_API_URL}/?page=\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\${this.page}&per_page=\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\${itemsPerPage}\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\` + ) + ).json(); + } + return { + brewInfo: response?.map((result: IBrewInfo, i: number) => ({ + id: result.id, + name: result.name, + tagline: result.tagline, + first_brewed: result.first_brewed, + description: result.description, + brewers_tips: result.brewers_tips, + })), + total: response?.length, + } as IBrewDatasourceResponse; + } + + private sortData(data: IBrewInfo[], filters: INovaFilters) { + return orderBy( + data, + filters.sorter?.value?.sortBy, + filters.sorter?.value?.direction as "desc" | "asc" + ); + } +} +\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\`, + "overview/hero/data/table/constants.ts": \\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\`// © 2022 SolarWinds Worldwide, LLC. All rights reserved. +// +// Permission is hereby granted, free of charge, to any person obtaining a copy +// of this software and associated documentation files (the "Software"), to +// deal in the Software without restriction, including without limitation the +// rights to use, copy, modify, merge, publish, distribute, sublicense, and/or +// sell copies of the Software, and to permit persons to whom the Software is +// furnished to do so, subject to the following conditions: +// +// The above copyright notice and this permission notice shall be included in +// all copies or substantial portions of the Software. +// +// THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR +// IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, +// FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE +// AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER +// LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, +// OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN +// THE SOFTWARE. + +export const corsProxy = "https://cors-anywhere.herokuapp.com"; +export const RANDOMUSER_API_URL = "https://randomuser.me"; +export const BREW_API_URL = "https://api.punkapi.com/v2/beers"; +export const GOOGLE_BOOKS_URL = "https://www.googleapis.com/books/v1/volumes"; +export const apiRoute = "api/1.3"; +export const responseError = \\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\`Error responding from server. Please visit \\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\${RANDOMUSER_API_URL} and \\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\${corsProxy} to see if they're available\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\`; +\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\`, + "overview/hero/data/table/random-user-data-source.ts": \\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\`// © 2022 SolarWinds Worldwide, LLC. All rights reserved. +// +// Permission is hereby granted, free of charge, to any person obtaining a copy +// of this software and associated documentation files (the "Software"), to +// deal in the Software without restriction, including without limitation the +// rights to use, copy, modify, merge, publish, distribute, sublicense, and/or +// sell copies of the Software, and to permit persons to whom the Software is +// furnished to do so, subject to the following conditions: +// +// The above copyright notice and this permission notice shall be included in +// all copies or substantial portions of the Software. +// +// THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR +// IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, +// FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE +// AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER +// LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, +// OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN +// THE SOFTWARE. + +import { ListRange } from "@angular/cdk/collections"; +import { Injectable } from "@angular/core"; +import isEqual from "lodash/isEqual"; +import orderBy from "lodash/orderBy"; +import { BehaviorSubject } from "rxjs"; + +import { + DataSourceService, + IDataField, + INovaFilteringOutputs, + INovaFilters, + ISorterFilter, + LoggerService, +} from "@nova-ui/bits"; + +import { + IRandomUserResponse, + IRandomUserResults, + IRandomUserTableModel, + UsersQueryResponse, +} from "../types"; +import { + apiRoute, + corsProxy, + RANDOMUSER_API_URL, + responseError, +} from "./constants"; + +@Injectable() +export class RandomUserDataSource extends DataSourceService { + public static providerId = "RandomUserDataSource"; + + private readonly seed = "sw"; + + private cache = Array.from({ length: 0 }); + private lastSortValue?: ISorterFilter; + private lastVirtualScroll?: ListRange; + + public page: number = 1; + public busy = new BehaviorSubject(false); + + public dataFields: Array = [ + { id: "no", label: $localize\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\`No\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\`, dataType: "number" }, + { id: "nameTitle", label: $localize\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\`Title\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\`, dataType: "string" }, + { id: "nameFirst", label: $localize\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\`First\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\`, dataType: "string" }, + { id: "nameLast", label: $localize\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\`Last\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\`, dataType: "string" }, + { id: "gender", label: $localize\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\`Gender\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\`, dataType: "string" }, + { id: "country", label: $localize\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\`Country\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\`, dataType: "string" }, + { id: "city", label: $localize\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\`City\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\`, dataType: "string" }, + { id: "postcode", label: $localize\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\`Postcode\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\`, dataType: "number" }, + { id: "email", label: $localize\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\`E-Mail\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\`, dataType: "string" }, + { id: "cell", label: $localize\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\`Cell\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\`, dataType: "string" }, + ]; + + constructor(private logger: LoggerService) { + super(); + } + + public async getFilteredData( + filters: INovaFilters + ): Promise { + // This condition handles sorting. We want to sort columns without fetching another chunk of data. + // Since the data is being fetched when scrolled, we compare virtual scroll indexes here in the condition as well. + if (filters.sorter?.value) { + if ( + !isEqual(this.lastSortValue, filters.sorter.value) && + isEqual(this.lastVirtualScroll, filters.virtualScroll?.value) + ) { + this.lastSortValue = filters.sorter?.value; + this.lastVirtualScroll = filters.virtualScroll?.value; + + return { + repeat: { itemsSource: this.sortData(this.cache, filters) }, + paginator: { total: 200 }, + dataFields: this.dataFields, + }; + } + } + this.busy.next(true); + + const virtualScrollFilter = + filters.virtualScroll && filters.virtualScroll.value; + const start = virtualScrollFilter + ? filters.virtualScroll?.value.start + : 0; + const end = virtualScrollFilter ? filters.virtualScroll?.value.end : 0; + + // We're returning Promise with setTimeout here to make the response from the server longer, as the API being used sends responses + // almost immediately. We need it longer to be able the show the spinner component on data load + return new Promise((resolve) => { + setTimeout(() => { + this.getData(start, end).then( + (response: INovaFilteringOutputs | undefined) => { + if (!response) { + return; + } + + this.cache = this.cache.concat(response.users); + + this.dataSubject.next(this.cache); + resolve({ + repeat: { + itemsSource: this.sortData(this.cache, filters), + }, + // This API can return thousands of results, however doesn't return the max number of results, + // so we set the max number of result manually here. + paginator: { total: 200 }, + dataFields: this.dataFields, + }); + + this.lastSortValue = filters.sorter?.value; + this.lastVirtualScroll = filters.virtualScroll?.value; + this.busy.next(false); + } + ); + }, 300); + }); + } + + public async getData( + start: number = 0, + end: number = 20 + ): Promise { + let response: IRandomUserResponse | null = null; + try { + response = await ( + await fetch( + \\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\`\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\${corsProxy}/\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\${RANDOMUSER_API_URL}/\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\${apiRoute}/?page=\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\${ + this.page + }&results=\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\${end - start}&seed=\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\${this.seed}\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\` + ) + ).json(); + return { + users: response?.results.map( + (result: IRandomUserResults, i: number) => ({ + no: this.cache.length + i + 1, + nameTitle: result.name.title, + nameFirst: result.name.first, + nameLast: result.name.last, + gender: result.gender, + country: result.location.country, + city: result.location.city, + postcode: result.location.postcode, + email: result.email, + cell: result.cell, + }) + ), + total: response?.results.length, + start: start, + } as UsersQueryResponse; + } catch (e) { + this.logger.error(responseError); + } + } + + private sortData(data: IRandomUserTableModel[], filters: INovaFilters) { + return orderBy( + data, + filters.sorter?.value?.sortBy, + filters.sorter?.value?.direction as "desc" | "asc" + ); + } +} +\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\`, + "overview/hero/data/table/types.ts": \\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\`// © 2022 SolarWinds Worldwide, LLC. All rights reserved. +// +// Permission is hereby granted, free of charge, to any person obtaining a copy +// of this software and associated documentation files (the "Software"), to +// deal in the Software without restriction, including without limitation the +// rights to use, copy, modify, merge, publish, distribute, sublicense, and/or +// sell copies of the Software, and to permit persons to whom the Software is +// furnished to do so, subject to the following conditions: +// +// The above copyright notice and this permission notice shall be included in +// all copies or substantial portions of the Software. +// +// THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR +// IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, +// FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE +// AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER +// LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, +// OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN +// THE SOFTWARE. + +import { IDataField, INovaFilteringOutputs } from "@nova-ui/bits"; +export interface BasicTableModel { + position: number; + name: string; + features: any; + status: string; + checks: any; + "cpu-load": number; + firstUrl: string; + firstUrlLabel: string; + secondUrl: string; + secondUrlLabel: string; +} + +export interface ITableDataSourceOutput extends INovaFilteringOutputs { + dataFields: IDataField[]; +} +\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\`, + "overview/hero/data/timeseries-data-sources.ts": \\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\`// © 2022 SolarWinds Worldwide, LLC. All rights reserved. +// +// Permission is hereby granted, free of charge, to any person obtaining a copy +// of this software and associated documentation files (the "Software"), to +// deal in the Software without restriction, including without limitation the +// rights to use, copy, modify, merge, publish, distribute, sublicense, and/or +// sell copies of the Software, and to permit persons to whom the Software is +// furnished to do so, subject to the following conditions: +// +// The above copyright notice and this permission notice shall be included in +// all copies or substantial portions of the Software. +// +// THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR +// IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, +// FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE +// AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER +// LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, +// OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN +// THE SOFTWARE. + +import { Injectable } from "@angular/core"; +import { Moment } from "moment/moment"; +import { BehaviorSubject } from "rxjs"; + +import { DataSourceService, IDataSource, INovaFilters } from "@nova-ui/bits"; +import { + ITimeseriesOutput, + ITimeseriesWidgetData, + ITimeseriesWidgetSeriesData, +} from "@nova-ui/dashboards"; + +import { + BEER_VS_READING_DATA, + LOUNGING_VS_ULTIMATE_FRISBEE_DATA, +} from "./widget-data"; + +@Injectable() +export class BeerVsReadingMockDataSource + extends DataSourceService + implements IDataSource +{ + public static providerId = "BeerVsReadingMockDataSource"; + + public busy = new BehaviorSubject(false); + + constructor() { + super(); + } + + public async getFilteredData( + filters: INovaFilters + ): Promise { + this.busy.next(true); + const result = await delay( + { series: getData(filters, BEER_VS_READING_DATA) }, + 1000 + ); + this.busy.next(false); + return result; + } +} + +@Injectable() +export class LoungingVsFrisbeeGolfMockDataSource + extends DataSourceService + implements IDataSource +{ + public static providerId = "LoungingVsFrisbeeGolfMockDataSource"; + + public busy = new BehaviorSubject(false); + + constructor() { + super(); + } + + public async getFilteredData( + filters: INovaFilters + ): Promise { + this.busy.next(true); + const result = await delay( + { series: getData(filters, LOUNGING_VS_ULTIMATE_FRISBEE_DATA) }, + 1000 + ); + this.busy.next(false); + return result; + } +} + +function getData( + filters: INovaFilters, + data: ITimeseriesWidgetData[] +): ITimeseriesWidgetData[] { + const timeframeFilter = filters.timeframe; + let filteredData = data; + // TIME FRAME PICKER FILTERING + if (timeframeFilter) { + filteredData = filteredData.map((item: ITimeseriesWidgetData) => ({ + id: item.id, + name: item.name, + description: item.description, + data: item.data.filter((seriesData: ITimeseriesWidgetSeriesData) => + filterDates( + seriesData.x, + timeframeFilter.value.startDatetime, + timeframeFilter.value.endDatetime + ) + ), + })); + } + + return filteredData; +} + +function filterDates(dateToCheck: Moment, startDate: Moment, endDate: Moment) { + return ( + dateToCheck.isBetween(startDate, endDate) || + dateToCheck.isSame(startDate) || + dateToCheck.isSame(endDate) + ); +} + +async function delay( + value: ITimeseriesOutput, + ms: number +): Promise { + return new Promise((resolve) => setTimeout(() => resolve(value), ms)); +} +\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\`, + "overview/hero/data/types.ts": \\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\`// © 2022 SolarWinds Worldwide, LLC. All rights reserved. +// +// Permission is hereby granted, free of charge, to any person obtaining a copy +// of this software and associated documentation files (the "Software"), to +// deal in the Software without restriction, including without limitation the +// rights to use, copy, modify, merge, publish, distribute, sublicense, and/or +// sell copies of the Software, and to permit persons to whom the Software is +// furnished to do so, subject to the following conditions: +// +// The above copyright notice and this permission notice shall be included in +// all copies or substantial portions of the Software. +// +// THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR +// IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, +// FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE +// AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER +// LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, +// OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN +// THE SOFTWARE. + +export interface UsersQueryResponse { + users: IRandomUserTableModel[]; + total: number; + start: number; +} + +export interface IRandomUserResponse { + info: Array; + results: Array; +} + +export interface IRandomUserInfo { + page: number; + results: number; + seed: string; + version: string; +} + +export interface IRandomUserResults { + cell: string; + dob: { + age: number; + date: string; + }; + email: string; + gender: string; + id: any; + location: IRandomUserLocation; + login: { + md5: string; + password: string; + salt: string; + sha1: string; + sha256: string; + username: string; + uuid: string; + }; + name: { + title: string; + first: string; + last: string; + }; + nat: string; + phone: string; + picture: { + large: string; + medium: string; + thumbnail: string; + }; + registered: { + date: string; + age: number; + }; +} + +export interface IRandomUserTableModel { + no: number; + nameTitle: string; + nameFirst: string; + nameLast: string; + gender: string; + country: string; + city: string; + postcode: number; + email: string; + cell: string; +} + +export interface IRandomUserLocation { + city: string; + coordinates: { latitude: string; longitude: string }; + country: string; + postcode: number; + state: string; + street: { number: number; name: string }; + timezone: any; +} + +export interface IBrewInfo { + id: number; + name: string; + tagline: string; + first_brewed: string; + description: string; + brewers_tips: string; +} + +export interface IBrewDatasourceResponse { + brewInfo: IBrewInfo[]; + total: number; +} +\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\`, + "overview/hero/data/widget-data.ts": \\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\`// © 2022 SolarWinds Worldwide, LLC. All rights reserved. +// +// Permission is hereby granted, free of charge, to any person obtaining a copy +// of this software and associated documentation files (the "Software"), to +// deal in the Software without restriction, including without limitation the +// rights to use, copy, modify, merge, publish, distribute, sublicense, and/or +// sell copies of the Software, and to permit persons to whom the Software is +// furnished to do so, subject to the following conditions: +// +// The above copyright notice and this permission notice shall be included in +// all copies or substantial portions of the Software. +// +// THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR +// IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, +// FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE +// AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER +// LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, +// OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN +// THE SOFTWARE. + +import moment from "moment/moment"; + +import { ITimeseriesWidgetData } from "@nova-ui/dashboards"; + +import { BasicTableModel } from "./table/types"; + +export interface IProportionalWidgetData { + id: string; + name: string; + data: number[]; + link: string; + value: string; +} + +export function getMockBeerReviewCountsByCity(): IProportionalWidgetData[] { + return [ + { + id: "Brno", + name: "Brno", + data: [Math.round(Math.random() * 100000)], + link: "https://en.wikipedia.org/wiki/Brno", + value: "Brno", + }, + { + id: "kyiv", + name: "Kyiv", + data: [Math.round(Math.random() * 100000)], + link: "https://en.wikipedia.org/wiki/Kyiv", + value: "Kyiv", + }, + { + id: "austin", + name: "Austin", + data: [Math.round(Math.random() * 100000)], + link: "https://en.wikipedia.org/wiki/Austin", + value: "Austin", + }, + { + id: "lisbon", + name: "Lisbon", + data: [Math.round(Math.random() * 100000)], + link: "https://en.wikipedia.org/wiki/Lisbon", + value: "Lisbon", + }, + { + id: "sydney", + name: "Sydney", + data: [Math.round(Math.random() * 100000)], + link: "https://en.wikipedia.org/wiki/Sydney", + value: "Sydney", + }, + { + id: "nur-sultan", + name: "Nur-Sultan", + data: [Math.round(Math.random() * 100000)], + link: "https://en.wikipedia.org/wiki/Nur-Sultan", + value: "Nur-Sultan", + }, + ].sort((a, b) => a.data[0] - b.data[0]); +} + +export function getMockBeerReviewCountsByCity2(): IProportionalWidgetData[] { + return [ + { + id: "london", + name: "London", + data: [Math.round(Math.random() * 100000)], + link: "https://en.wikipedia.org/wiki/London", + value: "London", + }, + { + id: "paris", + name: "Paris", + data: [Math.round(Math.random() * 100000)], + link: "https://en.wikipedia.org/wiki/Paris", + value: "Paris", + }, + { + id: "rio", + name: "Rio", + data: [Math.round(Math.random() * 100000)], + link: "https://en.wikipedia.org/wiki/Rio_de_Janeiro", + value: "Rio", + }, + ].sort((a, b) => a.data[0] - b.data[0]); +} + +export const BEER_VS_READING_DATA: ITimeseriesWidgetData[] = [ + { + id: "series-1", + name: "Beer Tasting", + description: "Havin' some suds", + data: [ + { x: moment().subtract(10, "day"), y: 30 }, + { x: moment().subtract(9, "day"), y: 35 }, + { x: moment().subtract(8, "day"), y: 33 }, + { x: moment().subtract(7, "day"), y: 40 }, + { x: moment().subtract(6, "day"), y: 35 }, + { x: moment().subtract(5, "day"), y: 30 }, + { x: moment().subtract(4, "day"), y: 35 }, + { x: moment().subtract(3, "day"), y: 15 }, + { x: moment().subtract(2, "day"), y: 30 }, + { x: moment().subtract(1, "day"), y: 35 }, + { x: moment().subtract(24, "hour"), y: 34 }, + { x: moment().subtract(15, "hour"), y: 33 }, + { x: moment().subtract(10, "hour"), y: 35 }, + { x: moment().subtract(5, "hour"), y: 36 }, + { x: moment().subtract(1, "hour"), y: 34 }, + { x: moment().subtract(50, "minute"), y: 33 }, + { x: moment().subtract(40, "minute"), y: 30 }, + { x: moment().subtract(30, "minute"), y: 32 }, + { x: moment().subtract(20, "minute"), y: 31 }, + { x: moment().subtract(10, "minute"), y: 34 }, + ], + }, + { + id: "series-2", + name: "Reading", + description: "Hittin' the books", + data: [ + { x: moment().subtract(10, "day"), y: 60 }, + { x: moment().subtract(9, "day"), y: 64 }, + { x: moment().subtract(8, "day"), y: 70 }, + { x: moment().subtract(7, "day"), y: 55 }, + { x: moment().subtract(6, "day"), y: 55 }, + { x: moment().subtract(5, "day"), y: 45 }, + { x: moment().subtract(4, "day"), y: 10 }, + { x: moment().subtract(3, "day"), y: 65 }, + { x: moment().subtract(2, "day"), y: 35 }, + { x: moment().subtract(1, "day"), y: 60 }, + { x: moment().subtract(24, "hour"), y: 61 }, + { x: moment().subtract(15, "hour"), y: 65 }, + { x: moment().subtract(10, "hour"), y: 63 }, + { x: moment().subtract(5, "hour"), y: 58 }, + { x: moment().subtract(1, "hour"), y: 64 }, + { x: moment().subtract(50, "minute"), y: 63 }, + { x: moment().subtract(40, "minute"), y: 60 }, + { x: moment().subtract(30, "minute"), y: 62 }, + { x: moment().subtract(20, "minute"), y: 61 }, + { x: moment().subtract(10, "minute"), y: 62 }, + ], + }, +]; + +export const LOUNGING_VS_ULTIMATE_FRISBEE_DATA: ITimeseriesWidgetData[] = [ + { + id: "series-a", + name: "Lounging", + description: "Shootin' the Breeze", + data: [ + { x: moment().subtract(10, "day"), y: 10 }, + { x: moment().subtract(9, "day"), y: 15 }, + { x: moment().subtract(8, "day"), y: 13 }, + { x: moment().subtract(7, "day"), y: 20 }, + { x: moment().subtract(6, "day"), y: 15 }, + { x: moment().subtract(5, "day"), y: 10 }, + { x: moment().subtract(4, "day"), y: 15 }, + { x: moment().subtract(3, "day"), y: 5 }, + { x: moment().subtract(2, "day"), y: 10 }, + { x: moment().subtract(1, "day"), y: 15 }, + { x: moment().subtract(24, "hour"), y: 14 }, + { x: moment().subtract(15, "hour"), y: 13 }, + { x: moment().subtract(10, "hour"), y: 15 }, + { x: moment().subtract(5, "hour"), y: 16 }, + { x: moment().subtract(1, "hour"), y: 14 }, + { x: moment().subtract(50, "minute"), y: 13 }, + { x: moment().subtract(40, "minute"), y: 10 }, + { x: moment().subtract(30, "minute"), y: 12 }, + { x: moment().subtract(20, "minute"), y: 11 }, + { x: moment().subtract(10, "minute"), y: 14 }, + ], + }, + { + id: "series-b", + name: "Frisbee Golfing", + description: "Golfin' with a disc", + data: [ + { x: moment().subtract(10, "day"), y: 80 }, + { x: moment().subtract(9, "day"), y: 84 }, + { x: moment().subtract(8, "day"), y: 80 }, + { x: moment().subtract(7, "day"), y: 75 }, + { x: moment().subtract(6, "day"), y: 95 }, + { x: moment().subtract(5, "day"), y: 85 }, + { x: moment().subtract(4, "day"), y: 80 }, + { x: moment().subtract(3, "day"), y: 85 }, + { x: moment().subtract(2, "day"), y: 85 }, + { x: moment().subtract(1, "day"), y: 80 }, + { x: moment().subtract(24, "hour"), y: 81 }, + { x: moment().subtract(15, "hour"), y: 85 }, + { x: moment().subtract(10, "hour"), y: 83 }, + { x: moment().subtract(5, "hour"), y: 88 }, + { x: moment().subtract(1, "hour"), y: 84 }, + { x: moment().subtract(50, "minute"), y: 83 }, + { x: moment().subtract(40, "minute"), y: 80 }, + { x: moment().subtract(30, "minute"), y: 82 }, + { x: moment().subtract(20, "minute"), y: 81 }, + { x: moment().subtract(10, "minute"), y: 82 }, + ], + }, +]; + +export const TABLE_DATA: BasicTableModel[] = [ + { + position: 1, + name: "FOCUS-SVR-02258", + features: ["remote-access-vpn-tunnel", "patch-manager01"], + status: "Active", + checks: { + icon: "status_up", + num: 25, + }, + "cpu-load": 86, + firstUrl: "https://en.wikipedia.org/wiki/Brno", + firstUrlLabel: "Brno", + secondUrl: "https://en.wikipedia.org/wiki/VMware_Workstation", + secondUrlLabel: "Workstation", + }, + { + position: 2, + name: "FOCUS-SVR-03312", + features: ["tools", "database", "orion-ape-backup"], + status: "Active", + checks: { + icon: "status_critical", + num: 25, + }, + "cpu-load": 47, + firstUrl: "https://en.wikipedia.org/wiki/Brno", + firstUrlLabel: "Brno", + secondUrl: "https://en.wikipedia.org/wiki/VMware_Workstation", + secondUrlLabel: "Workstation", + }, + { + position: 3, + name: "FOCUS-SVR-02258", + features: [ + "remote-access-vpn-tunnel", + "database", + "orion-ape-backup", + "patch-manager01", + ], + status: "Active", + checks: { + icon: "status_down", + num: 25, + }, + "cpu-load": 53, + firstUrl: "https://en.wikipedia.org/wiki/Kyiv", + firstUrlLabel: "Kyiv", + secondUrl: "https://en.wikipedia.org/wiki/VMware_Workstation", + secondUrlLabel: "Workstation", + }, + { + position: 4, + name: "Man-LT-JYJ4AD5", + features: [ + "remote-access-vpn-tunnel", + "tools", + "database", + "orion-ape-backup", + ], + status: "Active", + checks: { + icon: "status_up", + num: 25, + }, + "cpu-load": 32, + firstUrl: "https://en.wikipedia.org/wiki/Kyiv", + firstUrlLabel: "Kyiv", + secondUrl: "https://en.wikipedia.org/wiki/VirtualBox", + secondUrlLabel: "VirtualBox", + }, + { + position: 5, + name: "Man-LT-JYJ425", + features: [ + "remote-access-vpn-tunnel", + "tools", + "database", + "orion-ape-backup", + ], + status: "Active", + checks: { + icon: "status_up", + num: 25, + }, + "cpu-load": 22, + firstUrl: "https://en.wikipedia.org/wiki/Kyiv", + firstUrlLabel: "Kyiv", + secondUrl: "https://en.wikipedia.org/wiki/VirtualBox", + secondUrlLabel: "VirtualBox", + }, + { + position: 6, + name: "Man-LT-JYJ4333", + features: [ + "remote-access-vpn-tunnel", + "tools", + "database", + "orion-ape-backup", + ], + status: "Active", + checks: { + icon: "status_up", + num: 25, + }, + "cpu-load": 12, + firstUrl: "https://en.wikipedia.org/wiki/Kyiv", + firstUrlLabel: "Kyiv", + secondUrl: "https://en.wikipedia.org/wiki/VirtualBox", + secondUrlLabel: "VirtualBox", + }, + { + position: 7, + name: "FOCUS-SVR-02258", + features: ["remote-access-vpn-tunnel", "patch-manager01"], + status: "Active", + checks: { + icon: "status_up", + num: 25, + }, + "cpu-load": 86, + firstUrl: "https://en.wikipedia.org/wiki/Austin", + firstUrlLabel: "Austin", + secondUrl: "https://en.wikipedia.org/wiki/VMware_Workstation", + secondUrlLabel: "Workstation", + }, + { + position: 8, + name: "Man-LT-JYJ4AD5", + features: [ + "remote-access-vpn-tunnel", + "tools", + "database", + "orion-ape-backup", + "patch-manager01", + ], + status: "Active", + checks: { + icon: "status_inactive", + num: 25, + }, + "cpu-load": 35, + firstUrl: "https://en.wikipedia.org/wiki/Austin", + firstUrlLabel: "Austin", + secondUrl: "https://en.wikipedia.org/wiki/VMware_Workstation", + secondUrlLabel: "Workstation", + }, + { + position: 9, + name: "Man-LT-JYJ4AD5", + features: [ + "remote-access-vpn-tunnel", + "tools", + "database", + "patch-manager01", + ], + status: "Active", + checks: { + icon: "status_up", + num: 25, + }, + "cpu-load": 32, + firstUrl: "https://en.wikipedia.org/wiki/Brno", + firstUrlLabel: "Brno", + secondUrl: "https://en.wikipedia.org/wiki/VMware_Workstation", + secondUrlLabel: "Workstation", + }, + { + position: 10, + name: "Man-LT-JYJ4AD5", + features: [ + "remote-access-vpn-tunnel", + "database", + "orion-ape-backup", + "patch-manager01", + ], + status: "Active", + checks: { + icon: "status_up", + num: 25, + }, + "cpu-load": 64, + firstUrl: "https://en.wikipedia.org/wiki/Kyiv", + firstUrlLabel: "Kyiv", + secondUrl: "https://en.wikipedia.org/wiki/VMware_Workstation", + secondUrlLabel: "Workstation", + }, + { + position: 11, + name: "Man-LT-111", + features: [], + status: "Active", + checks: { + icon: "status_external", + num: 25, + }, + "cpu-load": 55, + firstUrl: "https://en.wikipedia.org/wiki/Brno", + firstUrlLabel: "Brno", + secondUrl: "https://en.wikipedia.org/wiki/VMware_Workstation", + secondUrlLabel: "Workstation", + }, + { + position: 12, + name: "Man-LT-2222", + features: [ + "remote-access-vpn-tunnel", + "tools", + "database", + "orion-ape-backup", + "patch-manager01", + ], + status: "Active", + checks: { + icon: "status_inactive", + num: 25, + }, + "cpu-load": 34, + firstUrl: "https://en.wikipedia.org/wiki/Brno", + firstUrlLabel: "Brno", + secondUrl: "https://en.wikipedia.org/wiki/VMware_Workstation", + secondUrlLabel: "Workstation", + }, + { + position: 13, + name: "Man-LT-333333", + features: [ + "remote-access-vpn-tunnel", + "tools", + "database", + "patch-manager01", + ], + status: "Active", + checks: { + icon: "status_up", + num: 25, + }, + "cpu-load": 56, + firstUrl: "https://en.wikipedia.org/wiki/Kyiv", + firstUrlLabel: "Kyiv", + secondUrl: "https://en.wikipedia.org/wiki/VMware_Workstation", + secondUrlLabel: "Workstation", + }, + { + position: 14, + name: "Man-LT-444444", + features: [ + "remote-access-vpn-tunnel", + "database", + "orion-ape-backup", + "patch-manager01", + ], + status: "Active", + checks: { + icon: "status_up", + num: 25, + }, + "cpu-load": 26, + firstUrl: "https://en.wikipedia.org/wiki/Kyiv", + firstUrlLabel: "Kyiv", + secondUrl: "https://en.wikipedia.org/wiki/VMware_Workstation", + secondUrlLabel: "Workstation", + }, + { + position: 15, + name: "Man-LT-555555", + features: [ + "remote-access-vpn-tunnel", + "database", + "orion-ape-backup", + "patch-manager01", + ], + status: "Active", + checks: { + icon: "status_up", + num: 25, + }, + "cpu-load": 76, + firstUrl: "https://en.wikipedia.org/wiki/Austin", + firstUrlLabel: "Austin", + secondUrl: "https://en.wikipedia.org/wiki/VMware_Workstation", + secondUrlLabel: "Workstation", + }, + { + position: 16, + name: "FOCUS-SVR-02258", + features: ["remote-access-vpn-tunnel", "patch-manager01"], + status: "Active", + checks: { + icon: "status_up", + num: 25, + }, + "cpu-load": 86, + firstUrl: "https://en.wikipedia.org/wiki/Brno", + firstUrlLabel: "Brno", + secondUrl: "https://en.wikipedia.org/wiki/VMware_Workstation", + secondUrlLabel: "Workstation", + }, + { + position: 17, + name: "FOCUS-SVR-03312", + features: ["tools", "database", "orion-ape-backup"], + status: "Active", + checks: { + icon: "status_critical", + num: 25, + }, + "cpu-load": 47, + firstUrl: "https://en.wikipedia.org/wiki/Brno", + firstUrlLabel: "Brno", + secondUrl: "https://en.wikipedia.org/wiki/VMware_Workstation", + secondUrlLabel: "Workstation", + }, + { + position: 18, + name: "FOCUS-SVR-02258", + features: [ + "remote-access-vpn-tunnel", + "database", + "orion-ape-backup", + "patch-manager01", + ], + status: "Active", + checks: { + icon: "status_down", + num: 25, + }, + "cpu-load": 53, + firstUrl: "https://en.wikipedia.org/wiki/Kyiv", + firstUrlLabel: "Kyiv", + secondUrl: "https://en.wikipedia.org/wiki/VMware_Workstation", + secondUrlLabel: "Workstation", + }, + { + position: 19, + name: "Man-LT-JYJ4AD5", + features: [ + "remote-access-vpn-tunnel", + "tools", + "database", + "orion-ape-backup", + ], + status: "Active", + checks: { + icon: "status_up", + num: 25, + }, + "cpu-load": 32, + firstUrl: "https://en.wikipedia.org/wiki/Kyiv", + firstUrlLabel: "Kyiv", + secondUrl: "https://en.wikipedia.org/wiki/VirtualBox", + secondUrlLabel: "VirtualBox", + }, + { + position: 20, + name: "Man-LT-JYJ425", + features: [ + "remote-access-vpn-tunnel", + "tools", + "database", + "orion-ape-backup", + ], + status: "Active", + checks: { + icon: "status_up", + num: 25, + }, + "cpu-load": 22, + firstUrl: "https://en.wikipedia.org/wiki/Kyiv", + firstUrlLabel: "Kyiv", + secondUrl: "https://en.wikipedia.org/wiki/VirtualBox", + secondUrlLabel: "VirtualBox", + }, + { + position: 21, + name: "Man-LT-JYJ4333", + features: [ + "remote-access-vpn-tunnel", + "tools", + "database", + "orion-ape-backup", + ], + status: "Active", + checks: { + icon: "status_up", + num: 25, + }, + "cpu-load": 12, + firstUrl: "https://en.wikipedia.org/wiki/Kyiv", + firstUrlLabel: "Kyiv", + secondUrl: "https://en.wikipedia.org/wiki/VirtualBox", + secondUrlLabel: "VirtualBox", + }, + { + position: 22, + name: "FOCUS-SVR-02258", + features: ["remote-access-vpn-tunnel", "patch-manager01"], + status: "Active", + checks: { + icon: "status_up", + num: 25, + }, + "cpu-load": 86, + firstUrl: "https://en.wikipedia.org/wiki/Austin", + firstUrlLabel: "Austin", + secondUrl: "https://en.wikipedia.org/wiki/VMware_Workstation", + secondUrlLabel: "Workstation", + }, + { + position: 23, + name: "Man-LT-JYJ4AD5", + features: [ + "remote-access-vpn-tunnel", + "tools", + "database", + "orion-ape-backup", + "patch-manager01", + ], + status: "Active", + checks: { + icon: "status_inactive", + num: 25, + }, + "cpu-load": 35, + firstUrl: "https://en.wikipedia.org/wiki/Austin", + firstUrlLabel: "Austin", + secondUrl: "https://en.wikipedia.org/wiki/VMware_Workstation", + secondUrlLabel: "Workstation", + }, + { + position: 24, + name: "Man-LT-JYJ4AD5", + features: [ + "remote-access-vpn-tunnel", + "tools", + "database", + "patch-manager01", + ], + status: "Active", + checks: { + icon: "status_up", + num: 25, + }, + "cpu-load": 32, + firstUrl: "https://en.wikipedia.org/wiki/Brno", + firstUrlLabel: "Brno", + secondUrl: "https://en.wikipedia.org/wiki/VMware_Workstation", + secondUrlLabel: "Workstation", + }, + { + position: 25, + name: "Man-LT-JYJ4AD5", + features: [ + "remote-access-vpn-tunnel", + "database", + "orion-ape-backup", + "patch-manager01", + ], + status: "Active", + checks: { + icon: "status_up", + num: 25, + }, + "cpu-load": 64, + firstUrl: "https://en.wikipedia.org/wiki/Kyiv", + firstUrlLabel: "Kyiv", + secondUrl: "https://en.wikipedia.org/wiki/VMware_Workstation", + secondUrlLabel: "Workstation", + }, + { + position: 26, + name: "Man-LT-111", + features: [], + status: "Active", + checks: { + icon: "status_external", + num: 25, + }, + "cpu-load": 55, + firstUrl: "https://en.wikipedia.org/wiki/Brno", + firstUrlLabel: "Brno", + secondUrl: "https://en.wikipedia.org/wiki/VMware_Workstation", + secondUrlLabel: "Workstation", + }, + { + position: 27, + name: "Man-LT-2222", + features: [ + "remote-access-vpn-tunnel", + "tools", + "database", + "orion-ape-backup", + "patch-manager01", + ], + status: "Active", + checks: { + icon: "status_inactive", + num: 25, + }, + "cpu-load": 34, + firstUrl: "https://en.wikipedia.org/wiki/Brno", + firstUrlLabel: "Brno", + secondUrl: "https://en.wikipedia.org/wiki/VMware_Workstation", + secondUrlLabel: "Workstation", + }, + { + position: 28, + name: "Man-LT-333333", + features: [ + "remote-access-vpn-tunnel", + "tools", + "database", + "patch-manager01", + ], + status: "Active", + checks: { + icon: "status_up", + num: 25, + }, + "cpu-load": 56, + firstUrl: "https://en.wikipedia.org/wiki/Kyiv", + firstUrlLabel: "Kyiv", + secondUrl: "https://en.wikipedia.org/wiki/VMware_Workstation", + secondUrlLabel: "Workstation", + }, + { + position: 29, + name: "Man-LT-444444", + features: [ + "remote-access-vpn-tunnel", + "database", + "orion-ape-backup", + "patch-manager01", + ], + status: "Active", + checks: { + icon: "status_up", + num: 25, + }, + "cpu-load": 26, + firstUrl: "https://en.wikipedia.org/wiki/Kyiv", + firstUrlLabel: "Kyiv", + secondUrl: "https://en.wikipedia.org/wiki/VMware_Workstation", + secondUrlLabel: "Workstation", + }, + { + position: 30, + name: "Man-LT-555555", + features: [ + "remote-access-vpn-tunnel", + "database", + "orion-ape-backup", + "patch-manager01", + ], + status: "Active", + checks: { + icon: "status_up", + num: 25, + }, + "cpu-load": 76, + firstUrl: "https://en.wikipedia.org/wiki/Austin", + firstUrlLabel: "Austin", + secondUrl: "https://en.wikipedia.org/wiki/VMware_Workstation", + secondUrlLabel: "Workstation", + }, + { + position: 31, + name: "FOCUS-SVR-02258", + features: ["remote-access-vpn-tunnel", "patch-manager01"], + status: "Active", + checks: { + icon: "status_up", + num: 25, + }, + "cpu-load": 86, + firstUrl: "https://en.wikipedia.org/wiki/Brno", + firstUrlLabel: "Brno", + secondUrl: "https://en.wikipedia.org/wiki/VMware_Workstation", + secondUrlLabel: "Workstation", + }, + { + position: 32, + name: "FOCUS-SVR-03312", + features: ["tools", "database", "orion-ape-backup"], + status: "Active", + checks: { + icon: "status_critical", + num: 25, + }, + "cpu-load": 47, + firstUrl: "https://en.wikipedia.org/wiki/Brno", + firstUrlLabel: "Brno", + secondUrl: "https://en.wikipedia.org/wiki/VMware_Workstation", + secondUrlLabel: "Workstation", + }, + { + position: 33, + name: "FOCUS-SVR-02258", + features: [ + "remote-access-vpn-tunnel", + "database", + "orion-ape-backup", + "patch-manager01", + ], + status: "Active", + checks: { + icon: "status_down", + num: 25, + }, + "cpu-load": 53, + firstUrl: "https://en.wikipedia.org/wiki/Kyiv", + firstUrlLabel: "Kyiv", + secondUrl: "https://en.wikipedia.org/wiki/VMware_Workstation", + secondUrlLabel: "Workstation", + }, + { + position: 34, + name: "Man-LT-JYJ4AD5", + features: [ + "remote-access-vpn-tunnel", + "tools", + "database", + "orion-ape-backup", + ], + status: "Active", + checks: { + icon: "status_up", + num: 25, + }, + "cpu-load": 32, + firstUrl: "https://en.wikipedia.org/wiki/Kyiv", + firstUrlLabel: "Kyiv", + secondUrl: "https://en.wikipedia.org/wiki/VirtualBox", + secondUrlLabel: "VirtualBox", + }, + { + position: 35, + name: "Man-LT-JYJ425", + features: [ + "remote-access-vpn-tunnel", + "tools", + "database", + "orion-ape-backup", + ], + status: "Active", + checks: { + icon: "status_up", + num: 25, + }, + "cpu-load": 22, + firstUrl: "https://en.wikipedia.org/wiki/Kyiv", + firstUrlLabel: "Kyiv", + secondUrl: "https://en.wikipedia.org/wiki/VirtualBox", + secondUrlLabel: "VirtualBox", + }, + { + position: 36, + name: "Man-LT-JYJ4333", + features: [ + "remote-access-vpn-tunnel", + "tools", + "database", + "orion-ape-backup", + ], + status: "Active", + checks: { + icon: "status_up", + num: 25, + }, + "cpu-load": 12, + firstUrl: "https://en.wikipedia.org/wiki/Kyiv", + firstUrlLabel: "Kyiv", + secondUrl: "https://en.wikipedia.org/wiki/VirtualBox", + secondUrlLabel: "VirtualBox", + }, + { + position: 37, + name: "FOCUS-SVR-02258", + features: ["remote-access-vpn-tunnel", "patch-manager01"], + status: "Active", + checks: { + icon: "status_up", + num: 25, + }, + "cpu-load": 86, + firstUrl: "https://en.wikipedia.org/wiki/Austin", + firstUrlLabel: "Austin", + secondUrl: "https://en.wikipedia.org/wiki/VMware_Workstation", + secondUrlLabel: "Workstation", + }, + { + position: 38, + name: "Man-LT-JYJ4AD5", + features: [ + "remote-access-vpn-tunnel", + "tools", + "database", + "orion-ape-backup", + "patch-manager01", + ], + status: "Active", + checks: { + icon: "status_inactive", + num: 25, + }, + "cpu-load": 35, + firstUrl: "https://en.wikipedia.org/wiki/Austin", + firstUrlLabel: "Austin", + secondUrl: "https://en.wikipedia.org/wiki/VMware_Workstation", + secondUrlLabel: "Workstation", + }, + { + position: 39, + name: "Man-LT-JYJ4AD5", + features: [ + "remote-access-vpn-tunnel", + "tools", + "database", + "patch-manager01", + ], + status: "Active", + checks: { + icon: "status_up", + num: 25, + }, + "cpu-load": 32, + firstUrl: "https://en.wikipedia.org/wiki/Brno", + firstUrlLabel: "Brno", + secondUrl: "https://en.wikipedia.org/wiki/VMware_Workstation", + secondUrlLabel: "Workstation", + }, + { + position: 40, + name: "Man-LT-JYJ4AD5", + features: [ + "remote-access-vpn-tunnel", + "database", + "orion-ape-backup", + "patch-manager01", + ], + status: "Active", + checks: { + icon: "status_up", + num: 25, + }, + "cpu-load": 64, + firstUrl: "https://en.wikipedia.org/wiki/Kyiv", + firstUrlLabel: "Kyiv", + secondUrl: "https://en.wikipedia.org/wiki/VMware_Workstation", + secondUrlLabel: "Workstation", + }, + { + position: 41, + name: "Man-LT-111", + features: [], + status: "Active", + checks: { + icon: "status_external", + num: 25, + }, + "cpu-load": 55, + firstUrl: "https://en.wikipedia.org/wiki/Brno", + firstUrlLabel: "Brno", + secondUrl: "https://en.wikipedia.org/wiki/VMware_Workstation", + secondUrlLabel: "Workstation", + }, + { + position: 42, + name: "Man-LT-2222", + features: [ + "remote-access-vpn-tunnel", + "tools", + "database", + "orion-ape-backup", + "patch-manager01", + ], + status: "Active", + checks: { + icon: "status_inactive", + num: 25, + }, + "cpu-load": 34, + firstUrl: "https://en.wikipedia.org/wiki/Brno", + firstUrlLabel: "Brno", + secondUrl: "https://en.wikipedia.org/wiki/VMware_Workstation", + secondUrlLabel: "Workstation", + }, + { + position: 43, + name: "Man-LT-333333", + features: [ + "remote-access-vpn-tunnel", + "tools", + "database", + "patch-manager01", + ], + status: "Active", + checks: { + icon: "status_up", + num: 25, + }, + "cpu-load": 56, + firstUrl: "https://en.wikipedia.org/wiki/Kyiv", + firstUrlLabel: "Kyiv", + secondUrl: "https://en.wikipedia.org/wiki/VMware_Workstation", + secondUrlLabel: "Workstation", + }, + { + position: 44, + name: "Man-LT-444444", + features: [ + "remote-access-vpn-tunnel", + "database", + "orion-ape-backup", + "patch-manager01", + ], + status: "Active", + checks: { + icon: "status_up", + num: 25, + }, + "cpu-load": 26, + firstUrl: "https://en.wikipedia.org/wiki/Kyiv", + firstUrlLabel: "Kyiv", + secondUrl: "https://en.wikipedia.org/wiki/VMware_Workstation", + secondUrlLabel: "Workstation", + }, + { + position: 45, + name: "Man-LT-555555", + features: [ + "remote-access-vpn-tunnel", + "database", + "orion-ape-backup", + "patch-manager01", + ], + status: "Active", + checks: { + icon: "status_up", + num: 25, + }, + "cpu-load": 76, + firstUrl: "https://en.wikipedia.org/wiki/Austin", + firstUrlLabel: "Austin", + secondUrl: "https://en.wikipedia.org/wiki/VMware_Workstation", + secondUrlLabel: "Workstation", + }, +]; +\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\`, + "overview/hero/widget-configs/kpi.ts": \\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\`// © 2022 SolarWinds Worldwide, LLC. All rights reserved. +// +// Permission is hereby granted, free of charge, to any person obtaining a copy +// of this software and associated documentation files (the "Software"), to +// deal in the Software without restriction, including without limitation the +// rights to use, copy, modify, merge, publish, distribute, sublicense, and/or +// sell copies of the Software, and to permit persons to whom the Software is +// furnished to do so, subject to the following conditions: +// +// The above copyright notice and this permission notice shall be included in +// all copies or substantial portions of the Software. +// +// THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR +// IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, +// FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE +// AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER +// LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, +// OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN +// THE SOFTWARE. + +import { + DEFAULT_PIZZAGNA_ROOT, + IProviderConfiguration, + IRefresherProperties, + IWidget, + KpiComponent, + NOVA_KPI_DATASOURCE_ADAPTER, + PizzagnaLayer, + WellKnownProviders, +} from "@nova-ui/dashboards"; + +import { + HarryPotterAverageRatingDataSource, + HarryPotterRatingsCountDataSource, +} from "../data/kpi-datasources"; + +export const kpiConfig: IWidget = { + id: "kpiWidgetId", + type: "kpi", + pizzagna: { + [PizzagnaLayer.Configuration]: { + [DEFAULT_PIZZAGNA_ROOT]: { + providers: { + [WellKnownProviders.Refresher]: { + properties: { + // Configuring the refresher interval so that our data source is invoked every ten minutes + interval: 60 * 10, + enabled: true, + } as IRefresherProperties, + } as Partial, + }, + }, + header: { + properties: { + title: "Harry Potter and the Sorcerer's Stone", + subtitle: "By J. K. Rowling", + }, + }, + tiles: { + properties: { + nodes: ["kpi1", "kpi2"], + }, + }, + kpi1: { + id: "kpi1", + componentType: KpiComponent.lateLoadKey, + properties: { + widgetData: { + label: "Average Rating", + backgroundColor: "var(--nui-color-chart-three)", + units: "out of 5 Stars", + }, + }, + providers: { + [WellKnownProviders.DataSource]: { + // Setting the data source providerId for the tile with id "kpi1" + providerId: + HarryPotterAverageRatingDataSource.providerId, + } as IProviderConfiguration, + [WellKnownProviders.Adapter]: { + providerId: NOVA_KPI_DATASOURCE_ADAPTER, + properties: { + componentId: "kpi1", + propertyPath: "widgetData", + }, + } as IProviderConfiguration, + }, + }, + kpi2: { + id: "kpi2", + componentType: KpiComponent.lateLoadKey, + properties: { + widgetData: { + label: "Reader Feedback", + units: "Ratings", + }, + }, + providers: { + [WellKnownProviders.DataSource]: { + // Setting the data source providerId for the tile with id "kpi" + providerId: + HarryPotterRatingsCountDataSource.providerId, + } as IProviderConfiguration, + [WellKnownProviders.Adapter]: { + providerId: NOVA_KPI_DATASOURCE_ADAPTER, + properties: { + componentId: "kpi2", + propertyPath: "widgetData", + }, + } as IProviderConfiguration, + }, + }, + }, + }, +}; +\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\`, + "overview/hero/widget-configs/proportional.ts": \\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\`// © 2022 SolarWinds Worldwide, LLC. All rights reserved. +// +// Permission is hereby granted, free of charge, to any person obtaining a copy +// of this software and associated documentation files (the "Software"), to +// deal in the Software without restriction, including without limitation the +// rights to use, copy, modify, merge, publish, distribute, sublicense, and/or +// sell copies of the Software, and to permit persons to whom the Software is +// furnished to do so, subject to the following conditions: +// +// The above copyright notice and this permission notice shall be included in +// all copies or substantial portions of the Software. +// +// THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR +// IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, +// FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE +// AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER +// LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, +// OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN +// THE SOFTWARE. + +import { + DEFAULT_PIZZAGNA_ROOT, + IProportionalWidgetChartOptions, + IProviderConfiguration, + IWidget, + LegendPlacement, + PizzagnaLayer, + ProportionalWidgetChartTypes, + WellKnownProviders, +} from "@nova-ui/dashboards"; + +import { BeerReviewCountsByCityMockDataSource } from "../data/proportional-datasources"; + +export const proportionalConfig: IWidget = { + id: "proportionalWidgetId", + type: "proportional", + pizzagna: { + [PizzagnaLayer.Configuration]: { + [DEFAULT_PIZZAGNA_ROOT]: { + providers: { + [WellKnownProviders.Refresher]: { + properties: { + interval: 0, + }, + }, + }, + }, + header: { + properties: { + title: "Beer Review Tally by City", + subtitle: "These People Love Beer", + }, + }, + chart: { + providers: { + [WellKnownProviders.DataSource]: { + providerId: + BeerReviewCountsByCityMockDataSource.providerId, + } as IProviderConfiguration, + }, + properties: { + configuration: { + chartOptions: { + type: ProportionalWidgetChartTypes.DonutChart, + legendPlacement: LegendPlacement.Right, + } as IProportionalWidgetChartOptions, + }, + }, + }, + }, + }, +}; +\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\`, + "overview/hero/widget-configs/risk-score.ts": \\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\`// © 2023 SolarWinds Worldwide, LLC. All rights reserved. +// +// Permission is hereby granted, free of charge, to any person obtaining a copy +// of this software and associated documentation files (the "Software"), to +// deal in the Software without restriction, including without limitation the +// rights to use, copy, modify, merge, publish, distribute, sublicense, and/or +// sell copies of the Software, and to permit persons to whom the Software is +// furnished to do so, subject to the following conditions: +// +// The above copyright notice and this permission notice shall be included in +// all copies or substantial portions of the Software. +// +// THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR +// IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, +// FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE +// AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER +// LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, +// OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN +// THE SOFTWARE. + +import { + DEFAULT_PIZZAGNA_ROOT, + IProviderConfiguration, + IRefresherProperties, + IWidget, + RiskScoreTileComponent, + NOVA_KPI_DATASOURCE_ADAPTER, + PizzagnaLayer, + WellKnownProviders, +} from "@nova-ui/dashboards"; + +import { HarryPotterAverageRatingDataSource } from "../data/kpi-datasources"; + +export const riskScoreConfig: IWidget = { + id: "riskScoreWidgetId", + type: "risk-score", + pizzagna: { + [PizzagnaLayer.Configuration]: { + [DEFAULT_PIZZAGNA_ROOT]: { + providers: { + [WellKnownProviders.Refresher]: { + properties: { + // Configuring the refresher interval so that our data source is invoked every ten minutes + interval: 60 * 10, + enabled: true, + } as IRefresherProperties, + } as Partial, + }, + }, + header: { + properties: { + title: "Harry Potter and the Sorcerer's Stone", + subtitle: "By J. K. Rowling", + }, + }, + tiles: { + properties: { + nodes: ["riskScore1"], + }, + }, + riskScore1: { + id: "riskScore1", + componentType: RiskScoreTileComponent.lateLoadKey, + properties: { + widgetData: { + minValue: 0, + maxValue: 5, + useStaticLabel: false, + staticLabel: undefined, + label: \\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\`Average Rating\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\`, + description: \\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\`Harry Potter and the Sorcerer's Stone By J. K. Rowling Average Rating Risk Score\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\`, + }, + }, + providers: { + [WellKnownProviders.DataSource]: { + // Setting the data source providerId for the tile with id "riskScore1" + providerId: + HarryPotterAverageRatingDataSource.providerId, + } as IProviderConfiguration, + [WellKnownProviders.Adapter]: { + providerId: NOVA_KPI_DATASOURCE_ADAPTER, + properties: { + componentId: "riskScore1", + propertyPath: "widgetData", + }, + } as IProviderConfiguration, + }, + }, + }, + }, +}; +\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\`, + "overview/hero/widget-configs/table.ts": \\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\`// © 2022 SolarWinds Worldwide, LLC. All rights reserved. +// +// Permission is hereby granted, free of charge, to any person obtaining a copy +// of this software and associated documentation files (the "Software"), to +// deal in the Software without restriction, including without limitation the +// rights to use, copy, modify, merge, publish, distribute, sublicense, and/or +// sell copies of the Software, and to permit persons to whom the Software is +// furnished to do so, subject to the following conditions: +// +// The above copyright notice and this permission notice shall be included in +// all copies or substantial portions of the Software. +// +// THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR +// IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, +// FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE +// AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER +// LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, +// OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN +// THE SOFTWARE. + +import { + ITableWidgetColumnConfig, + ITableWidgetSorterConfig, + IWidget, + PizzagnaLayer, + RawFormatterComponent, + WellKnownProviders, +} from "@nova-ui/dashboards"; + +import { BeerDataSource } from "../data/table/beer-data-source"; + +export const tableConfig: IWidget = { + id: "tableWidgetId", + type: "table", + pizzagna: { + [PizzagnaLayer.Configuration]: { + header: { + properties: { + title: "Stupendous Suds", + subtitle: "Try These Brilliant Brews", + }, + }, + table: { + providers: { + [WellKnownProviders.DataSource]: { + providerId: BeerDataSource.providerId, + }, + }, + properties: { + configuration: { + columns: [ + { + id: "column1", + label: "Beer Name", + isActive: true, + width: 185, + formatter: { + componentType: + RawFormatterComponent.lateLoadKey, + properties: { + dataFieldIds: { + value: "name", + }, + }, + }, + }, + { + id: "column2", + label: "Tagline", + isActive: true, + width: 250, + formatter: { + componentType: + RawFormatterComponent.lateLoadKey, + properties: { + dataFieldIds: { + value: "tagline", + }, + }, + }, + }, + { + id: "column3", + label: "First Brewed", + isActive: true, + formatter: { + componentType: + RawFormatterComponent.lateLoadKey, + properties: { + dataFieldIds: { + value: "first_brewed", + }, + }, + }, + }, + { + id: "column4", + label: "Description", + isActive: true, + width: 275, + formatter: { + componentType: + RawFormatterComponent.lateLoadKey, + properties: { + dataFieldIds: { + value: "description", + }, + }, + }, + }, + ] as ITableWidgetColumnConfig[], + sorterConfiguration: { + descendantSorting: false, + sortBy: "", + } as ITableWidgetSorterConfig, + hasVirtualScroll: true, + }, + }, + }, + }, + }, +}; +\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\`, + "overview/hero/widget-configs/timeseries.ts": \\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\`// © 2022 SolarWinds Worldwide, LLC. All rights reserved. +// +// Permission is hereby granted, free of charge, to any person obtaining a copy +// of this software and associated documentation files (the "Software"), to +// deal in the Software without restriction, including without limitation the +// rights to use, copy, modify, merge, publish, distribute, sublicense, and/or +// sell copies of the Software, and to permit persons to whom the Software is +// furnished to do so, subject to the following conditions: +// +// The above copyright notice and this permission notice shall be included in +// all copies or substantial portions of the Software. +// +// THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR +// IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, +// FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE +// AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER +// LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, +// OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN +// THE SOFTWARE. + +import moment from "moment/moment"; + +import { + DEFAULT_PIZZAGNA_ROOT, + IProviderConfiguration, + ISerializableTimeframe, + ITimeseriesItemConfiguration, + IWidget, + LegendPlacement, + WellKnownProviders, +} from "@nova-ui/dashboards"; + +import { BeerVsReadingMockDataSource } from "../data/timeseries-data-sources"; + +export const timeseriesConfig: IWidget = { + id: "timeseriesWidgetId", + type: "timeseries", + pizzagna: { + configuration: { + [DEFAULT_PIZZAGNA_ROOT]: { + providers: { + [WellKnownProviders.DataSource]: { + providerId: BeerVsReadingMockDataSource.providerId, + } as IProviderConfiguration, + }, + }, + header: { + properties: { + title: "Primary Leisure Activity Over Time", + subtitle: "Survey of 1000 Solarians", + }, + }, + chart: { + providers: { + [WellKnownProviders.Adapter]: { + properties: { + series: [ + { + id: "series-1", + label: "Beer Tasting", + selectedSeriesId: "series-1", + }, + { + id: "series-2", + label: "Reading", + selectedSeriesId: "series-2", + }, + ] as ITimeseriesItemConfiguration[], + }, + } as Partial, + }, + properties: { + configuration: { + legendPlacement: LegendPlacement.Right, + enableZoom: true, + leftAxisLabel: "Solarians (%)", + }, + }, + }, + timeframeSelection: { + properties: { + timeframe: { + selectedPresetId: "last7Days", + } as ISerializableTimeframe, + minDate: moment().subtract(10, "days").format(), + maxDate: moment().format(), + }, + }, + }, + }, +}; +\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\`, + "overview/overview-docs.component.ts": \\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\`// © 2022 SolarWinds Worldwide, LLC. All rights reserved. +// +// Permission is hereby granted, free of charge, to any person obtaining a copy +// of this software and associated documentation files (the "Software"), to +// deal in the Software without restriction, including without limitation the +// rights to use, copy, modify, merge, publish, distribute, sublicense, and/or +// sell copies of the Software, and to permit persons to whom the Software is +// furnished to do so, subject to the following conditions: +// +// The above copyright notice and this permission notice shall be included in +// all copies or substantial portions of the Software. +// +// THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR +// IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, +// FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE +// AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER +// LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, +// OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN +// THE SOFTWARE. + +import { Component } from "@angular/core"; + +@Component({ + selector: "nui-dashboard-overview-docs", + templateUrl: "./overview-docs.component.html", + standalone: false, +}) +export class OverviewDocsComponent {} +\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\`, + "overview/overview.module.ts": \\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\`// © 2022 SolarWinds Worldwide, LLC. All rights reserved. +// +// Permission is hereby granted, free of charge, to any person obtaining a copy +// of this software and associated documentation files (the "Software"), to +// deal in the Software without restriction, including without limitation the +// rights to use, copy, modify, merge, publish, distribute, sublicense, and/or +// sell copies of the Software, and to permit persons to whom the Software is +// furnished to do so, subject to the following conditions: +// +// The above copyright notice and this permission notice shall be included in +// all copies or substantial portions of the Software. +// +// THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR +// IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, +// FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE +// AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER +// LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, +// OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN +// THE SOFTWARE. + +import { CommonModule } from "@angular/common"; +import { NgModule } from "@angular/core"; +import { RouterModule } from "@angular/router"; + +import { + NuiBusyModule, + NuiButtonModule, + NuiDocsModule, + NuiIconModule, + NuiMessageModule, + NuiSwitchModule, +} from "@nova-ui/bits"; +import { + ConfiguratorHeadingService, + IFormatterDefinition, + LinkFormatterComponent, + NuiDashboardsModule, + WellKnownPathKey, + WidgetTypesService, +} from "@nova-ui/dashboards"; + +import { HeroDashboardComponent } from "./hero/dashboard/hero-dashboard.component"; +import { + HarryPotterAverageRatingDataSource, + HarryPotterRatingsCountDataSource, +} from "./hero/data/kpi-datasources"; +import { + BeerReviewCountsByCityMockDataSource, + BeerReviewCountsByCityMockDataSource2, +} from "./hero/data/proportional-datasources"; +import { BeerDataSource } from "./hero/data/table/beer-data-source"; +import { RandomUserDataSource } from "./hero/data/table/random-user-data-source"; +import { + BeerVsReadingMockDataSource, + LoungingVsFrisbeeGolfMockDataSource, +} from "./hero/data/timeseries-data-sources"; +import { OverviewDocsComponent } from "./overview-docs.component"; + +const routes = [ + { + path: "", + component: OverviewDocsComponent, + data: { + srlc: { + hideIndicator: true, + }, + showThemeSwitcher: true, + }, + }, + { + path: "hero", + component: HeroDashboardComponent, + data: { + srlc: { + hideIndicator: true, + }, + }, + }, +]; + +@NgModule({ + imports: [ + CommonModule, + NuiDashboardsModule, + NuiBusyModule, + NuiButtonModule, + NuiDocsModule, + NuiMessageModule, + NuiSwitchModule, + NuiIconModule, + RouterModule.forChild(routes), + ], + declarations: [OverviewDocsComponent, HeroDashboardComponent], + providers: [ConfiguratorHeadingService], +}) +export default class OverviewModule { + constructor(private widgetTypesService: WidgetTypesService) { + this.setupDataSourceProviders(); + this.setupProportionalLegendFormatters(); + } + + private setupDataSourceProviders() { + this.setDataSourceProviders("table", [ + RandomUserDataSource.providerId, + BeerDataSource.providerId, + ]); + this.setDataSourceProviders("kpi", [ + HarryPotterAverageRatingDataSource.providerId, + HarryPotterRatingsCountDataSource.providerId, + ]); + this.setDataSourceProviders("risk-score", [ + HarryPotterAverageRatingDataSource.providerId, + HarryPotterRatingsCountDataSource.providerId, + ]); + this.setDataSourceProviders("proportional", [ + BeerReviewCountsByCityMockDataSource.providerId, + BeerReviewCountsByCityMockDataSource2.providerId, + ]); + this.setDataSourceProviders("timeseries", [ + BeerVsReadingMockDataSource.providerId, + LoungingVsFrisbeeGolfMockDataSource.providerId, + ]); + } + + private setDataSourceProviders(type: string, providers: string[]) { + const widgetTemplate = this.widgetTypesService.getWidgetType(type, 1); + this.widgetTypesService.setNode( + widgetTemplate, + "configurator", + WellKnownPathKey.DataSourceProviders, + providers + ); + } + + private setupProportionalLegendFormatters() { + const formatters: IFormatterDefinition[] = [ + { + componentType: LinkFormatterComponent.lateLoadKey, + label: $localize\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\`Link\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\`, + dataTypes: { + value: "label", + link: "link", + }, + }, + ]; + + const widgetTemplate = this.widgetTypesService.getWidgetType( + "proportional", + 1 + ); + this.widgetTypesService.setNode( + widgetTemplate, + "configurator", + WellKnownPathKey.Formatters, + formatters + ); + } +} +\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\`, + "tutorials/customization/configurator-section/custom-configurator-section/custom-configurator-section.example.component.ts": \\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\`// © 2022 SolarWinds Worldwide, LLC. All rights reserved. +// +// Permission is hereby granted, free of charge, to any person obtaining a copy +// of this software and associated documentation files (the "Software"), to +// deal in the Software without restriction, including without limitation the +// rights to use, copy, modify, merge, publish, distribute, sublicense, and/or +// sell copies of the Software, and to permit persons to whom the Software is +// furnished to do so, subject to the following conditions: +// +// The above copyright notice and this permission notice shall be included in +// all copies or substantial portions of the Software. +// +// THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR +// IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, +// FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE +// AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER +// LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, +// OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN +// THE SOFTWARE. + +import { HttpClient, HttpErrorResponse } from "@angular/common/http"; +import { + ChangeDetectionStrategy, + ChangeDetectorRef, + Component, + EventEmitter, + Injectable, + Input, + OnChanges, + OnDestroy, + OnInit, + Output, + SimpleChanges, +} from "@angular/core"; +import { FormBuilder, FormGroup, Validators } from "@angular/forms"; +import { GridsterConfig, GridsterItem } from "angular-gridster2"; +// eslint-disable-next-line import/no-deprecated +import { BehaviorSubject, combineLatest, Observable } from "rxjs"; +// eslint-disable-next-line import/no-deprecated +import { finalize, map, startWith } from "rxjs/operators"; + +import { DataSourceService, IFilteringOutputs } from "@nova-ui/bits"; +import { + ComponentRegistryService, + DATA_SOURCE, + DEFAULT_PIZZAGNA_ROOT, + IDashboard, + IHasChangeDetector, + IHasForm, + IKpiData, + IProviderConfiguration, + IRefresherProperties, + IWidget, + IWidgets, + KpiComponent, + NOVA_KPI_DATASOURCE_ADAPTER, + PizzagnaLayer, + ProviderRegistryService, + WellKnownPathKey, + WellKnownProviders, + WidgetTypesService, +} from "@nova-ui/dashboards"; + +/** + * A custom version of the KpiDescriptionConfigurationComponent provided by the dashboards framework. + * --- + * For this example, the existing background color selection functionality has been replaced by custom + * template content. + */ +@Component({ + selector: "custom-kpi-description-configuration", + template: \\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\` + + +
+
+ + + +
+ + +
+
+ Custom Content +
+
+ The default version of this configurator section + displays a background color selector here. +
+
+ + +
+ + + +
+
+
+ \\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\`, + styleUrls: ["./custom-configurator-section.example.component.less"], + changeDetection: ChangeDetectionStrategy.OnPush, + standalone: false, +}) +// Remember to declare this class in the parent module +export class CustomKpiDescriptionConfigurationComponent + implements OnInit, OnChanges, IHasChangeDetector, IHasForm +{ + // Ensure that the lateLoadKey value matches class name + public static lateLoadKey = "CustomKpiDescriptionConfigurationComponent"; + + @Input() componentId: string; + @Input() configurableUnits: boolean; + + @Input() label: string = ""; + @Input() units: string = ""; + + @Output() formReady = new EventEmitter(); + + public form: FormGroup; + public subtitle$: Observable; + + constructor( + public changeDetector: ChangeDetectorRef, + private formBuilder: FormBuilder + ) {} + + public ngOnInit(): void { + this.form = this.formBuilder.group({ + label: [this.label, [Validators.required]], + }); + + if (this.configurableUnits) { + this.form.addControl("units", this.formBuilder.control(this.units)); + } + + const label = this.form.get("label"); + // eslint-disable-next-line import/no-deprecated + const labelValue = label?.valueChanges.pipe(startWith(label?.value)); + + // eslint-disable-next-line import/no-deprecated + this.subtitle$ = combineLatest([ + labelValue?.pipe(map((t) => t || $localize\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\`no label\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\`)), + ]).pipe(map((labels) => labels.join(", "))); + + this.formReady.emit(this.form); + } + + public ngOnChanges(changes: SimpleChanges): void { + if (changes.label) { + this.form.patchValue({ label: changes.label.currentValue }); + } + if (changes.units) { + this.form.patchValue({ units: changes.units.currentValue }); + } + } +} + +/** + * A simple KPI data source to retrieve the average rating of Harry Potter and the Sorcerer's Stone (book) via googleapis + */ +@Injectable() +export class AverageRatingKpiDataSource + extends DataSourceService + implements OnDestroy +{ + // This is the ID we'll use to identify the provider + public static providerId = "AverageRatingKpiDataSource"; + + // Use this subject to communicate the data source's busy state + public busy = new BehaviorSubject(false); + + constructor(private http: HttpClient) { + super(); + } + + // In this example, getFilteredData is invoked every 10 minutes (Take a look at the refresher + // provider definition in the widget configuration below to see how the interval is set) + public async getFilteredData(): Promise { + this.busy.next(true); + return new Promise((resolve) => { + // *** Make a resource request to an external API (if needed) + this.http + .get("https://www.googleapis.com/books/v1/volumes/5MQFrgEACAAJ") + .pipe(finalize(() => this.busy.next(false))) + .subscribe({ + next: (data: any) => { + resolve({ + result: { + value: data.volumeInfo.averageRating, + }, + }); + }, + error: (error: HttpErrorResponse) => { + resolve({ + result: null, + error: { + type: error.status, + }, + }); + }, + }); + }); + } + + public ngOnDestroy(): void { + this.outputsSubject.complete(); + } +} + +/** + * A simple KPI data source to retrieve the ratings count of Harry Potter and the Sorcerer's Stone (book) via googleapis + */ +@Injectable() +export class RatingsCountKpiDataSource + extends DataSourceService + implements OnDestroy +{ + public static providerId = "RatingsCountKpiDataSource"; + + // Use this subject to communicate the data source's busy state + public busy = new BehaviorSubject(false); + + constructor(private http: HttpClient) { + super(); + } + + public async getFilteredData(): Promise { + this.busy.next(true); + return new Promise((resolve) => { + this.http + .get("https://www.googleapis.com/books/v1/volumes/5MQFrgEACAAJ") + .pipe(finalize(() => this.busy.next(false))) + .subscribe({ + next: (data: any) => { + resolve({ + result: { + value: data.volumeInfo.ratingsCount, + }, + }); + }, + error: (error: HttpErrorResponse) => { + resolve({ + result: null, + error: { + type: error.status, + }, + }); + }, + }); + }); + } + + public ngOnDestroy(): void { + this.outputsSubject.complete(); + } +} + +/** + * A component that instantiates the dashboard + */ +@Component({ + selector: "custom-configurator-section-example", + templateUrl: "./custom-configurator-section.example.component.html", + styleUrls: ["./custom-configurator-section.example.component.less"], + standalone: false, +}) +export class CustomConfiguratorSectionExampleComponent implements OnInit { + // This variable will hold all the data needed to define the layout and behavior of the widgets. + // Pass this to the dashboard component's dashboard input in the template. + public dashboard: IDashboard | undefined; + + // Angular gridster requires a configuration object even if it's empty. + // Pass this to the dashboard component's gridsterConfig input in the template. + public gridsterConfig: GridsterConfig = {}; + + // Boolean which dashboard takes in as an input if its true it allows you to move widgets around. + public editMode: boolean = false; + + constructor( + // WidgetTypesService provides the widget's necessary structure information + private widgetTypesService: WidgetTypesService, + + // In general, the ProviderRegistryService is used for making entities available for injection into dynamically loaded components. + private providerRegistry: ProviderRegistryService, + + // Inject the ComponentRegistryService to make our custom component available for late loading by the dashboards framework + private componentRegistry: ComponentRegistryService, + + private changeDetectorRef: ChangeDetectorRef + ) {} + + public ngOnInit(): void { + // Grab the widget's default template which will be needed as a parameter for setNode. + const widgetTemplate = this.widgetTypesService.getWidgetType("kpi", 1); + + // Replace the default KPI description configuration component with our custom one. + // Note: This could also be done in the parent module's constructor to give + // multiple dashboards access to the same custom configurator section. + this.widgetTypesService.setNode( + widgetTemplate, + "configurator", + WellKnownPathKey.TileDescriptionConfigComponentType, + CustomKpiDescriptionConfigurationComponent.lateLoadKey + ); + + // Register the custom configurator section with the component registry to make it available + // for late loading by the dashboards framework. + this.componentRegistry.registerByLateLoadKey( + CustomKpiDescriptionConfigurationComponent + ); + + // Register our data sources as dropdown options in the widget editor/configurator + // Note: This could also be done in the parent module's constructor so that + // multiple dashboards could have access to the same widget template modification. + this.widgetTypesService.setNode( + // This is the template we grabbed above with getWidgetType + widgetTemplate, + // We are setting the editor/configurator part of the widget template + "configurator", + // This indicates which node you are changing and we want to change + // the data source providers available for selection in the editor. + WellKnownPathKey.DataSourceProviders, + // We are setting the data sources available for selection in the editor + [ + AverageRatingKpiDataSource.providerId, + RatingsCountKpiDataSource.providerId, + ] + ); + + // Register the data sources available for injection into the KPI tiles. + // Note: Each tile of a KPI widget is assigned its own instance of a data source + this.providerRegistry.setProviders({ + [AverageRatingKpiDataSource.providerId]: { + provide: DATA_SOURCE, + useClass: AverageRatingKpiDataSource, + // Any dependencies that need to be injected into the provider must be listed here + deps: [HttpClient], + }, + [RatingsCountKpiDataSource.providerId]: { + provide: DATA_SOURCE, + useClass: RatingsCountKpiDataSource, + deps: [HttpClient], + }, + }); + + this.initializeDashboard(); + } + + public initializeDashboard(): void { + // We're using a static configuration object for this example (see widgetConfig at the bottom of the file), + // but this is where the widget's configuration could potentially be populated from a database + const kpiWidget = widgetConfig; + const widgetIndex: IWidgets = { + // Complete the KPI widget with information coming from its type definition + [kpiWidget.id]: + this.widgetTypesService.mergeWithWidgetType(kpiWidget), + }; + + // Setting the widget dimensions and position (this is for gridster) + const positions: Record = { + [kpiWidget.id]: { + cols: 4, + rows: 6, + y: 0, + x: 0, + }, + }; + + // Finally, assigning the variables we created above to the dashboard + this.dashboard = { + positions, + widgets: widgetIndex, + }; + } + + /** Used for restoring widgets state */ + public reInitializeDashboard(): void { + // destroys the components and their providers so the dashboard can re init data + this.dashboard = undefined; + this.changeDetectorRef.detectChanges(); + + this.initializeDashboard(); + } +} + +const widgetConfig: IWidget = { + id: "widget1", + type: "kpi", + pizzagna: { + [PizzagnaLayer.Configuration]: { + [DEFAULT_PIZZAGNA_ROOT]: { + providers: { + [WellKnownProviders.Refresher]: { + properties: { + // Configuring the refresher interval so that our data source is invoked every ten minutes + interval: 60 * 10, + enabled: true, + } as IRefresherProperties, + } as Partial, + }, + }, + header: { + properties: { + title: "Harry Potter and the Sorcerer's Stone", + subtitle: "By J. K. Rowling", + }, + }, + tiles: { + properties: { + nodes: ["kpi1"], + }, + }, + kpi1: { + id: "kpi1", + componentType: KpiComponent.lateLoadKey, + properties: { + widgetData: { + units: "out of 5 Stars", + label: "Average Rating", + }, + }, + providers: { + [WellKnownProviders.DataSource]: { + // Setting the data source providerId for the tile with id "kpi1" + providerId: AverageRatingKpiDataSource.providerId, + } as IProviderConfiguration, + [WellKnownProviders.Adapter]: { + providerId: NOVA_KPI_DATASOURCE_ADAPTER, + properties: { + componentId: "kpi1", + propertyPath: "widgetData", + }, + } as IProviderConfiguration, + }, + }, + }, + }, +}; +\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\`, + "tutorials/customization/configurator-section/custom-configurator-section-docs.component.ts": \\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\`// © 2022 SolarWinds Worldwide, LLC. All rights reserved. +// +// Permission is hereby granted, free of charge, to any person obtaining a copy +// of this software and associated documentation files (the "Software"), to +// deal in the Software without restriction, including without limitation the +// rights to use, copy, modify, merge, publish, distribute, sublicense, and/or +// sell copies of the Software, and to permit persons to whom the Software is +// furnished to do so, subject to the following conditions: +// +// The above copyright notice and this permission notice shall be included in +// all copies or substantial portions of the Software. +// +// THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR +// IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, +// FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE +// AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER +// LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, +// OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN +// THE SOFTWARE. + +import { Component } from "@angular/core"; + +@Component({ + selector: "custom-configurator-section-docs", + templateUrl: "./custom-configurator-section-docs.component.html", + standalone: false, +}) +export class CustomConfiguratorSectionDocsComponent {} +\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\`, + "tutorials/customization/configurator-section/custom-configurator-section.module.ts": \\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\`// © 2022 SolarWinds Worldwide, LLC. All rights reserved. +// +// Permission is hereby granted, free of charge, to any person obtaining a copy +// of this software and associated documentation files (the "Software"), to +// deal in the Software without restriction, including without limitation the +// rights to use, copy, modify, merge, publish, distribute, sublicense, and/or +// sell copies of the Software, and to permit persons to whom the Software is +// furnished to do so, subject to the following conditions: +// +// The above copyright notice and this permission notice shall be included in +// all copies or substantial portions of the Software. +// +// THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR +// IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, +// FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE +// AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER +// LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, +// OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN +// THE SOFTWARE. + +import { CommonModule } from "@angular/common"; +import { HttpClientModule } from "@angular/common/http"; +import { NgModule } from "@angular/core"; +import { ReactiveFormsModule } from "@angular/forms"; +import { RouterModule } from "@angular/router"; + +import { + NuiButtonModule, + NuiDocsModule, + NuiFormFieldModule, + NuiIconModule, + NuiMessageModule, + NuiSwitchModule, + NuiTextboxModule, + DEMO_PATH_TOKEN, +} from "@nova-ui/bits"; +import { + NuiDashboardConfiguratorModule, + NuiDashboardsModule, +} from "@nova-ui/dashboards"; + +import { + CustomConfiguratorSectionExampleComponent, + CustomKpiDescriptionConfigurationComponent, +} from "./custom-configurator-section/custom-configurator-section.example.component"; +import { CustomConfiguratorSectionDocsComponent } from "./custom-configurator-section-docs.component"; +import { getDemoFiles } from "../../../../../demo-files-factory"; + +const routes = [ + { + path: "", + component: CustomConfiguratorSectionDocsComponent, + data: { + srlc: { + hideIndicator: true, + }, + showThemeSwitcher: true, + }, + }, + { + path: "example", + component: CustomConfiguratorSectionExampleComponent, + data: { + srlc: { + hideIndicator: true, + }, + }, + }, +]; + +@NgModule({ + imports: [ + CommonModule, + ReactiveFormsModule, + HttpClientModule, + NuiDashboardsModule, + NuiDashboardConfiguratorModule, + NuiDocsModule, + NuiFormFieldModule, + NuiIconModule, + NuiMessageModule, + NuiSwitchModule, + NuiTextboxModule, + NuiButtonModule, + RouterModule.forChild(routes), + ], + declarations: [ + CustomConfiguratorSectionDocsComponent, + CustomKpiDescriptionConfigurationComponent, + CustomConfiguratorSectionExampleComponent, + ], + providers: [ + { + provide: DEMO_PATH_TOKEN, + useValue: getDemoFiles("configurator-section"), + }, + ], +}) +export default class CustomConfiguratorSectionModule {} +\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\`, + "tutorials/customization/customization.module.ts": \\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\`// © 2022 SolarWinds Worldwide, LLC. All rights reserved. +// +// Permission is hereby granted, free of charge, to any person obtaining a copy +// of this software and associated documentation files (the "Software"), to +// deal in the Software without restriction, including without limitation the +// rights to use, copy, modify, merge, publish, distribute, sublicense, and/or +// sell copies of the Software, and to permit persons to whom the Software is +// furnished to do so, subject to the following conditions: +// +// The above copyright notice and this permission notice shall be included in +// all copies or substantial portions of the Software. +// +// THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR +// IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, +// FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE +// AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER +// LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, +// OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN +// THE SOFTWARE. + +import { NgModule, Type } from "@angular/core"; +import { RouterModule, Routes } from "@angular/router"; + +import { ConfiguratorHeadingService } from "@nova-ui/dashboards"; + +enum CustomizationModuleRoute { + ConfiguratorSection = "configurator-section", + Widget = "widget", + Formatter = "formatter", + DataSourceConfigurator = "data-source-configurator", +} + +const routes: Routes = [ + { + path: CustomizationModuleRoute.ConfiguratorSection, + loadChildren: async () => + import( + "./configurator-section/custom-configurator-section.module" + ) as object as Promise>, + }, + { + path: CustomizationModuleRoute.Widget, + loadChildren: async () => + import("./widget/custom-widget.module") as object as Promise< + Type + >, + }, + { + path: CustomizationModuleRoute.Formatter, + loadChildren: async () => + import("./formatter/custom-formatter.module") as object as Promise< + Type + >, + }, + { + path: CustomizationModuleRoute.DataSourceConfigurator, + loadChildren: async () => + import( + "./data-source-configurator/custom-data-source-configurator.module" + ) as object as Promise>, + }, +]; + +@NgModule({ + imports: [RouterModule.forChild(routes)], + providers: [ConfiguratorHeadingService], +}) +export default class CustomizationModule {} +\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\`, + "tutorials/customization/data-source-configurator/custom-data-source-configurator-docs.component.ts": \\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\`// © 2022 SolarWinds Worldwide, LLC. All rights reserved. +// +// Permission is hereby granted, free of charge, to any person obtaining a copy +// of this software and associated documentation files (the "Software"), to +// deal in the Software without restriction, including without limitation the +// rights to use, copy, modify, merge, publish, distribute, sublicense, and/or +// sell copies of the Software, and to permit persons to whom the Software is +// furnished to do so, subject to the following conditions: +// +// The above copyright notice and this permission notice shall be included in +// all copies or substantial portions of the Software. +// +// THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR +// IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, +// FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE +// AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER +// LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, +// OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN +// THE SOFTWARE. + +import { Component } from "@angular/core"; + +@Component({ + selector: "nui-custom-data-source-configurator-docs", + templateUrl: "./custom-data-source-configurator-docs.component.html", + standalone: false, +}) +export class CustomDataSourceConfiguratorDocComponent {} +\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\`, + "tutorials/customization/data-source-configurator/custom-data-source-configurator.module.ts": \\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\`// © 2022 SolarWinds Worldwide, LLC. All rights reserved. +// +// Permission is hereby granted, free of charge, to any person obtaining a copy +// of this software and associated documentation files (the "Software"), to +// deal in the Software without restriction, including without limitation the +// rights to use, copy, modify, merge, publish, distribute, sublicense, and/or +// sell copies of the Software, and to permit persons to whom the Software is +// furnished to do so, subject to the following conditions: +// +// The above copyright notice and this permission notice shall be included in +// all copies or substantial portions of the Software. +// +// THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR +// IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, +// FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE +// AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER +// LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, +// OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN +// THE SOFTWARE. + +import { NgModule } from "@angular/core"; +import { ReactiveFormsModule } from "@angular/forms"; +import { RouterModule, Routes } from "@angular/router"; + +// eslint-disable-next-line max-len +import { + NuiButtonModule, + NuiDocsModule, + NuiFormFieldModule, + NuiIconModule, + NuiMessageModule, + NuiSelectV2Module, + NuiSwitchModule, + NuiTextboxModule, + NuiValidationMessageModule, + DEMO_PATH_TOKEN, +} from "@nova-ui/bits"; +import { + NuiDashboardConfiguratorModule, + NuiDashboardsModule, +} from "@nova-ui/dashboards"; + +import { CustomDataSourceConfiguratorDocComponent } from "./custom-data-source-configurator-docs.component"; +import { + CustomDataSourceConfiguratorExampleComponent, + HarryPotterDataSourceConfiguratorComponent, +} from "./example/custom-data-source-configurator-example.component"; +import { getDemoFiles } from "../../../../../demo-files-factory"; + +const routes: Routes = [ + { + path: "", + component: CustomDataSourceConfiguratorDocComponent, + data: { + srlc: { + hideIndicator: true, + }, + showThemeSwitcher: true, + }, + }, +]; + +@NgModule({ + imports: [ + RouterModule.forChild(routes), + NuiDocsModule, + NuiButtonModule, + NuiMessageModule, + NuiDashboardConfiguratorModule, + NuiDashboardsModule, + NuiFormFieldModule, + NuiTextboxModule, + NuiSwitchModule, + NuiSelectV2Module, + NuiValidationMessageModule, + NuiIconModule, + ReactiveFormsModule, + ], + declarations: [ + CustomDataSourceConfiguratorDocComponent, + CustomDataSourceConfiguratorExampleComponent, + HarryPotterDataSourceConfiguratorComponent, + ], + providers: [ + { + provide: DEMO_PATH_TOKEN, + useValue: getDemoFiles("data-source-configurator"), + }, + ], +}) +export default class CustomDataSourceConfiguratorModuleRoute {} +\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\`, + "tutorials/customization/data-source-configurator/example/custom-data-source-configurator-example.component.ts": \\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\`// © 2022 SolarWinds Worldwide, LLC. All rights reserved. +// +// Permission is hereby granted, free of charge, to any person obtaining a copy +// of this software and associated documentation files (the "Software"), to +// deal in the Software without restriction, including without limitation the +// rights to use, copy, modify, merge, publish, distribute, sublicense, and/or +// sell copies of the Software, and to permit persons to whom the Software is +// furnished to do so, subject to the following conditions: +// +// The above copyright notice and this permission notice shall be included in +// all copies or substantial portions of the Software. +// +// THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR +// IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, +// FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE +// AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER +// LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, +// OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN +// THE SOFTWARE. + +import { HttpClient, HttpErrorResponse } from "@angular/common/http"; +import { + ChangeDetectorRef, + Component, + Inject, + Injectable, + Injector, + OnDestroy, + OnInit, +} from "@angular/core"; +import { FormBuilder, Validators } from "@angular/forms"; +import { GridsterConfig, GridsterItem } from "angular-gridster2"; +import { BehaviorSubject } from "rxjs"; +import { finalize } from "rxjs/operators"; + +import { + DataSourceService, + EventBus, + IEvent, + IFilteringOutputs, + LoggerService, +} from "@nova-ui/bits"; +import { + ComponentRegistryService, + ConfiguratorHeadingService, + DataSourceConfigurationV2Component, + DATA_SOURCE, + DEFAULT_PIZZAGNA_ROOT, + IConfigurable, + IDashboard, + IKpiData, + IProperties, + IProviderConfiguration, + IRefresherProperties, + IWidget, + IWidgets, + KpiComponent, + NOVA_KPI_DATASOURCE_ADAPTER, + PizzagnaLayer, + PIZZAGNA_EVENT_BUS, + ProviderRegistryService, + WellKnownPathKey, + WellKnownProviders, + WidgetTypesService, +} from "@nova-ui/dashboards"; + +/** + * This component will serve as the data source accordion in the configurator. + */ +@Component({ + selector: "harry-potter-data-source-configurator", + styleUrls: ["./custom-data-source-configurator-example.component.less"], + template: \\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\` + +
+ +
+ Data Source +
+ Harry Potter Books +
+
+
+
+ + + + {{ book.title }} + + + +
+
+ + + + {{ metric.label }} + + + +
+
+ \\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\`, + standalone: false, +}) +@Injectable() +export class HarryPotterDataSourceConfiguratorComponent + extends DataSourceConfigurationV2Component + implements OnInit +{ + // This lateLoadKey allows the component to be able to be registered by the componentRegistry + public static lateLoadKey = "HarryPotterDataSourceConfiguratorComponent"; + + // Array of books that will populate the book select + public books = [ + { + id: "5MQFrgEACAAJ", + title: $localize\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\`Harry Potter and the Sorcerer's Stone\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\`, + }, + { + id: "5iTebBW-w7QC", + title: $localize\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\`Harry Potter and the Chamber of Secrets\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\`, + }, + ]; + + // Array of metrics that will populate the metric select + public metrics = [ + { + id: "averageRating", + label: $localize\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\`Average Rating\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\`, + }, + { + id: "ratingsCount", + label: $localize\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\`Ratings Count\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\`, + }, + ]; + + // These need to be injected because DataSourceConfigurationV2Component uses them + constructor( + changeDetector: ChangeDetectorRef, + configuratorHeading: ConfiguratorHeadingService, + formBuilder: FormBuilder, + providerRegistryService: ProviderRegistryService, + @Inject(PIZZAGNA_EVENT_BUS) eventBus: EventBus, + injector: Injector, + logger: LoggerService + ) { + super( + changeDetector, + configuratorHeading, + formBuilder, + providerRegistryService, + eventBus, + injector, + logger + ); + } + + // Overriding 'ngOnInit' to add custom controls to the 'properties' form group + public ngOnInit(): void { + super.ngOnInit(); + + // Overriding the 'properties' control on the form to create a form group that accommodates our custom properties + this.form.setControl( + "properties", + this.formBuilder.group({ + bookId: [this.properties?.bookId ?? "", Validators.required], + metric: [this.properties?.metric ?? "", Validators.required], + }) + ); + // The default data source control has a required validator we're removing that validator here since we aren't using it. + this.form.setControl("dataSource", this.formBuilder.control(null)); + // Here we set the providerId to our only data source so when a new tile gets created it will default to it. + this.form.get("providerId")?.setValue(AcmeKpiDataSource.providerId); + // Here we subscribe to the form and if there are any changes we invoke the data source + this.form.valueChanges.subscribe((value) => { + if (!value.providerId) { + return; + } + this.invokeDataSource(value); + }); + } +} + +/** + * A simple KPI data source to retrieve the average rating of Harry Potter and the Sorcerer's Stone (book) via googleapis + */ +@Injectable() +export class AcmeKpiDataSource + extends DataSourceService + implements OnDestroy, IConfigurable +{ + // This is the ID we'll use to identify the provider + public static providerId = "AcmeKpiDataSource"; + + // Use this subject to communicate the data source's busy state + public busy = new BehaviorSubject(false); + + public properties: IProperties; + + constructor(private http: HttpClient) { + super(); + } + + // This function MUST be implemented in order to receive property updates from our configurator + public updateConfiguration(properties: IProperties): void { + // Saving the properties because we will need it for this data source. + this.properties = properties; + } + + // In this example, getFilteredData is invoked every 10 minutes (Take a look at the refresher + // provider definition in the widget configuration below to see how the interval is set) + public async getFilteredData(): Promise { + // For loading indicator to show + this.busy.next(true); + return new Promise((resolve) => { + // *** Make a resource request to an external API (if needed) + this.http + .get( + \\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\`https://www.googleapis.com/books/v1/volumes/\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\${this.properties?.bookId}\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\` + ) + // For loading indicator to be hidden + .pipe(finalize(() => this.busy.next(false))) + .subscribe({ + next: (data: any) => { + resolve({ + result: { + value: data.volumeInfo[this.properties?.metric], + }, + }); + }, + error: (error: HttpErrorResponse) => { + resolve({ + result: null, + error: { + type: error.status, + }, + }); + }, + }); + }); + } + + public ngOnDestroy(): void { + this.outputsSubject.complete(); + } +} + +/** + * A component that instantiates the dashboard + */ +@Component({ + selector: "custom-data-source-configurator-example", + templateUrl: "./custom-data-source-configurator-example.component.html", + styleUrls: ["./custom-data-source-configurator-example.component.less"], + standalone: false, +}) +export class CustomDataSourceConfiguratorExampleComponent implements OnInit { + // This variable will hold all the data needed to define the layout and behavior of the widgets. + // Pass this to the dashboard component's dashboard input in the template. + public dashboard: IDashboard; + + // Angular gridster requires a configuration object even if it's empty. + // Pass this to the dashboard component's gridsterConfig input in the template. + public gridsterConfig: GridsterConfig = {}; + + // Boolean which dashboard takes in as an input if its true it allows you to move widgets around. + public editMode: boolean = false; + + constructor( + // WidgetTypesService provides the widget's necessary structure information + private widgetTypesService: WidgetTypesService, + + // In general, the ProviderRegistryService is used for making entities available for injection into dynamically loaded components. + private providerRegistry: ProviderRegistryService, + + // Inject the ComponentRegistryService to make our custom component available for late loading by the dashboards framework + private componentRegistry: ComponentRegistryService + ) {} + + public ngOnInit(): void { + // Registering the new data source configurator so it can be used. + this.componentRegistry.registerByLateLoadKey( + HarryPotterDataSourceConfiguratorComponent + ); + // Registering the data source for injection into the KPI tile. + // Note: Each tile of a KPI widget is assigned its own instance of the data source + this.providerRegistry.setProviders({ + [AcmeKpiDataSource.providerId]: { + provide: DATA_SOURCE, + useClass: AcmeKpiDataSource, + // Any dependencies that need to be injected into the provider must be listed here + deps: [HttpClient], + }, + }); + + const kpiWidgetTemplate = this.widgetTypesService.getWidgetType( + "kpi", + 1 + ); + + this.widgetTypesService.setNode( + // This is the template we grabbed above with getWidgetType + kpiWidgetTemplate, + // We are setting the editor/configurator part of the widget template + "configurator", + // This is the path to go to the data source config component type. + WellKnownPathKey.DataSourceConfigComponentType, + // We are changing it to use the component we just created above instead of the default. + HarryPotterDataSourceConfiguratorComponent.lateLoadKey + ); + + // We're using a static configuration object for this example, but this is where + // the widget's configuration could potentially be populated from a database + const kpiWidget = widgetConfig; + const widgetIndex: IWidgets = { + // Complete the KPI widget with information coming from its type definition + [kpiWidget.id]: + this.widgetTypesService.mergeWithWidgetType(kpiWidget), + }; + + // Setting the widget dimensions and position (this is for gridster) + const positions: Record = { + [kpiWidget.id]: { + cols: 4, + rows: 6, + y: 0, + x: 0, + }, + }; + + // Finally, assigning the variables we created above to the dashboard + this.dashboard = { + positions, + widgets: widgetIndex, + }; + } +} + +const widgetConfig: IWidget = { + id: "widget1", + type: "kpi", + pizzagna: { + [PizzagnaLayer.Configuration]: { + [DEFAULT_PIZZAGNA_ROOT]: { + providers: { + [WellKnownProviders.Refresher]: { + properties: { + // Configuring the refresher interval so that our data source is invoked every ten minutes + interval: 60 * 10, + enabled: true, + } as IRefresherProperties, + } as Partial, + }, + }, + header: { + properties: { + title: "Harry Potter and the Sorcerer's Stone", + subtitle: "By J. K. Rowling", + }, + }, + tiles: { + properties: { + nodes: ["kpi1"], + }, + }, + kpi1: { + id: "kpi1", + componentType: KpiComponent.lateLoadKey, + properties: { + widgetData: { + units: "out of 5 Stars", + label: "Average Rating", + }, + }, + providers: { + [WellKnownProviders.DataSource]: { + // Setting the data source providerId for the tile with id "kpi1" + providerId: AcmeKpiDataSource.providerId, + properties: { + bookId: "5MQFrgEACAAJ", + metric: "averageRating", + }, + } as IProviderConfiguration, + [WellKnownProviders.Adapter]: { + providerId: NOVA_KPI_DATASOURCE_ADAPTER, + properties: { + componentId: "kpi1", + propertyPath: "widgetData", + }, + } as IProviderConfiguration, + }, + }, + }, + }, +}; +\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\`, + "tutorials/customization/formatter/custom-formatter.module.ts": \\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\`// © 2022 SolarWinds Worldwide, LLC. All rights reserved. +// +// Permission is hereby granted, free of charge, to any person obtaining a copy +// of this software and associated documentation files (the "Software"), to +// deal in the Software without restriction, including without limitation the +// rights to use, copy, modify, merge, publish, distribute, sublicense, and/or +// sell copies of the Software, and to permit persons to whom the Software is +// furnished to do so, subject to the following conditions: +// +// The above copyright notice and this permission notice shall be included in +// all copies or substantial portions of the Software. +// +// THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR +// IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, +// FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE +// AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER +// LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, +// OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN +// THE SOFTWARE. + +import { NgModule } from "@angular/core"; +import { ReactiveFormsModule } from "@angular/forms"; +import { RouterModule, Routes } from "@angular/router"; + +// eslint-disable-next-line max-len +import { + NuiButtonModule, + NuiDocsModule, + NuiFormFieldModule, + NuiIconModule, + NuiMessageModule, + NuiSelectV2Module, + NuiSwitchModule, + NuiTextboxModule, + NuiValidationMessageModule, + DEMO_PATH_TOKEN, +} from "@nova-ui/bits"; +import { NuiDashboardsModule } from "@nova-ui/dashboards"; + +import { CustomDonutContentFormatterDocComponent } from "./donut-content-formatter-example/custom-donut-content-formatter-docs.component"; +import { + CustomDonutContentFormatterComponent, + CustomDonutContentFormatterConfiguratorComponent, + CustomDonutContentFormatterExampleComponent, +} from "./donut-content-formatter-example/custom-donut-content-formatter-example.component"; +import { CustomFormatterDocComponent } from "./formatter-example/custom-formatter-docs.component"; +import { + CustomFormatterComponent, + CustomFormatterConfiguratorComponent, + CustomFormatterExampleComponent, +} from "./formatter-example/custom-formatter-example.component"; +import { getDemoFiles } from "../../../../../demo-files-factory"; + +const routes: Routes = [ + { + path: "table-formatter", + component: CustomFormatterDocComponent, + data: { + srlc: { + hideIndicator: true, + }, + showThemeSwitcher: true, + }, + }, + { + path: "donut-content-formatter", + component: CustomDonutContentFormatterDocComponent, + data: { + srlc: { + hideIndicator: true, + }, + showThemeSwitcher: true, + }, + }, +]; + +@NgModule({ + imports: [ + RouterModule.forChild(routes), + NuiDocsModule, + NuiButtonModule, + NuiMessageModule, + NuiDashboardsModule, + NuiFormFieldModule, + NuiTextboxModule, + NuiSwitchModule, + NuiSelectV2Module, + NuiValidationMessageModule, + NuiIconModule, + ReactiveFormsModule, + ], + declarations: [ + CustomDonutContentFormatterComponent, + CustomDonutContentFormatterExampleComponent, + CustomDonutContentFormatterConfiguratorComponent, + CustomDonutContentFormatterDocComponent, + CustomFormatterDocComponent, + CustomFormatterExampleComponent, + CustomFormatterConfiguratorComponent, + CustomFormatterComponent, + ], + providers: [ + { + provide: DEMO_PATH_TOKEN, + useValue: getDemoFiles("formatter"), + }, + ], +}) +export default class CustomFormatterModuleRoute {} +\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\`, + "tutorials/customization/formatter/donut-content-formatter-example/custom-donut-content-formatter-docs.component.ts": \\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\`// © 2022 SolarWinds Worldwide, LLC. All rights reserved. +// +// Permission is hereby granted, free of charge, to any person obtaining a copy +// of this software and associated documentation files (the "Software"), to +// deal in the Software without restriction, including without limitation the +// rights to use, copy, modify, merge, publish, distribute, sublicense, and/or +// sell copies of the Software, and to permit persons to whom the Software is +// furnished to do so, subject to the following conditions: +// +// The above copyright notice and this permission notice shall be included in +// all copies or substantial portions of the Software. +// +// THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR +// IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, +// FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE +// AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER +// LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, +// OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN +// THE SOFTWARE. + +import { Component } from "@angular/core"; + +@Component({ + selector: "nui-custom-donut-content-formatter-docs", + templateUrl: "./custom-donut-content-formatter-docs.component.html", + standalone: false, +}) +export class CustomDonutContentFormatterDocComponent {} +\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\`, + "tutorials/customization/formatter/donut-content-formatter-example/custom-donut-content-formatter-example.component.ts": \\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\`// © 2022 SolarWinds Worldwide, LLC. All rights reserved. +// +// Permission is hereby granted, free of charge, to any person obtaining a copy +// of this software and associated documentation files (the "Software"), to +// deal in the Software without restriction, including without limitation the +// rights to use, copy, modify, merge, publish, distribute, sublicense, and/or +// sell copies of the Software, and to permit persons to whom the Software is +// furnished to do so, subject to the following conditions: +// +// The above copyright notice and this permission notice shall be included in +// all copies or substantial portions of the Software. +// +// THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR +// IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, +// FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE +// AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER +// LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, +// OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN +// THE SOFTWARE. + +import { HttpClient } from "@angular/common/http"; +import { + ChangeDetectorRef, + Component, + Injectable, + Input, + OnChanges, + OnDestroy, + OnInit, + SimpleChanges, +} from "@angular/core"; +import { FormBuilder, FormGroup, Validators } from "@angular/forms"; +import { GridsterConfig, GridsterItem } from "angular-gridster2"; +import { Subject } from "rxjs"; +import { takeUntil, tap } from "rxjs/operators"; + +import { + DataSourceService, + IconService, + IDataSource, + IFilteringOutputs, + LoggerService, +} from "@nova-ui/bits"; +import { + ChartAssist, + IAccessors, + IChartAssistEvent, + IChartAssistSeries, +} from "@nova-ui/charts"; +import { + ComponentRegistryService, + ConfiguratorHeadingService, + DATA_SOURCE, + DonutChartFormatterConfiguratorComponent, + DonutContentPercentageConfigurationComponent, + DonutContentPercentageFormatterComponent, + DonutContentSumFormatterComponent, + IDashboard, + IFormatterDefinition, + IHasChangeDetector, + IProperties, + IProportionalWidgetChartOptions, + IProportionalWidgetConfig, + IProviderConfiguration, + IWidget, + IWidgets, + LegendPlacement, + PizzagnaLayer, + ProportionalWidgetChartTypes, + ProviderRegistryService, + WellKnownPathKey, + WellKnownProviders, + WidgetTypesService, +} from "@nova-ui/dashboards"; + +export enum Units { + Days = "Day(s)", + Weeks = "Week(s)", + Hours = "Hour(s)", +} + +@Component({ + selector: "custom-donut-content-formatter", + host: { class: "d-flex flex-column align-items-center" }, + template: \\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\` +
+ + {{ chartMetric || properties?.currentMetric || data[0].id }} +
+
+ {{ chartContent }} +
+
+ {{ units }} +
+
\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\`, + styleUrls: ["./custom-donut-content-formatter-example.component.less"], + standalone: false, +}) +export class CustomDonutContentFormatterComponent + implements IHasChangeDetector, OnInit, OnChanges +{ + public static lateLoadKey = "CustomDonutContentFormatterComponent"; + + // Used to emphasize the chart series when user interacts either with the chart legend, or chart segments. + public emphasizedSeriesData: IChartAssistSeries | undefined; + + // Current raw value of the metric to display + public currentMetricData: number; + + // Metric value rendered inside the template, when user selects a metric, and gets automatically recalculated depending on selected units + public chartContent: number; + + // Metric value rendered inside the template, when user interacts with either chart legend, or chart segments + public chartMetric: number; + + // Units which user can select from the configuration + public units: Units = Units.Days; + + private readonly destroy$ = new Subject(); + + constructor(public changeDetector: ChangeDetectorRef) {} + + // The data we receive from the chart, including metrics names and their values + @Input() data: IChartAssistSeries[]; + + // We use this chart assist instance to subscribe to the events triggered when an interaction with the chart occurs + @Input() chartAssist: ChartAssist; + + // These are the current properties from pizzagna. Used to use data set at the configuration layer + @Input() properties: IProperties; + + public ngOnChanges(changes: SimpleChanges): void { + if (changes.properties || !this.properties) { + // If current metric is not in the list of metrics any more we fall back to the very first one from the list we get from the datasource + this.currentMetricData = + this.data.find( + (item) => item.id === this.properties?.currentMetric + )?.data[0] || this.data[0].data[0]; + + // We either take the selected value, or fall back to the preselected default one + this.units = this.properties?.units || this.units; + } + + this.setContentValue(); + } + + public ngOnInit(): void { + // Here 'chartAssistSubject' is the entity that emits events every time user interacts with either chart legend, or chart segments. + // Subscribing to properly react on these kind of events + this.chartAssist.chartAssistSubject + .pipe( + tap( + (data: IChartAssistEvent) => + (this.emphasizedSeriesData = this.data.find( + (item) => item.id === data.payload.seriesId + )) + ), + tap(() => this.setContentValue()), + tap(() => this.setMetricValue()), + takeUntil(this.destroy$) + ) + .subscribe(); + } + + public getConvertedData(emphData: number): number { + // Recalculating data depending on the units user selected from the configuration view + switch (this.units) { + case Units.Weeks: + return this.emphasizedSeriesData + ? this.convertToWeeks(emphData) + : this.convertToWeeks(this.currentMetricData); + + case Units.Hours: + return this.emphasizedSeriesData + ? this.convertToHours(emphData) + : this.convertToHours(this.currentMetricData); + + default: + return this.emphasizedSeriesData + ? emphData + : this.currentMetricData; + } + } + + public setContentValue(): void { + this.chartContent = this.getConvertedData( + this.emphasizedSeriesData?.data[0] + ); + } + + public setMetricValue(): void { + this.chartMetric = this.emphasizedSeriesData + ? this.data.find( + (item) => + this.getConvertedData(item.data[0]) === + this.getConvertedData(this.emphasizedSeriesData?.data[0]) + )?.id + : // if metric was not initially selected we fall back to the very first one + this.properties?.currentMetric || this.data[0].id; + } + + private convertToWeeks(days: number | undefined): number { + return days ? Number((days / 7).toFixed(2)) : 0; + } + + private convertToHours(days: number | undefined): number { + return days ? Number((days * 24).toFixed(2)) : 0; + } +} + +@Component({ + selector: "custom-donut-content-formatter-configurator", + styleUrls: ["./custom-donut-content-formatter-example.component.less"], + template: \\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\` +
+
+ + + + {{ itemValue?.name }} + + + + This field is required + + +
+
+ + + + {{ itemValue }} + + + + This field is required + + +
+
+ \\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\`, + standalone: false, +}) +export class CustomDonutContentFormatterConfiguratorComponent + extends DonutChartFormatterConfiguratorComponent + implements OnChanges, OnInit, IHasChangeDetector +{ + public static lateLoadKey = "CustomFormatterConfiguratorComponent"; + + constructor( + changeDetector: ChangeDetectorRef, + formBuilder: FormBuilder, + logger: LoggerService, + public iconService: IconService, + public configuratorHeading: ConfiguratorHeadingService + ) { + super(changeDetector, formBuilder, logger); + } + + public availableUnits: Units[] = [Units.Days, Units.Hours, Units.Weeks]; + + protected addCustomFormControls(form: FormGroup): void { + form.addControl( + "units", + this.formBuilder.control(Units.Days, Validators.required) + ); + } +} + +/** + * A component that instantiates the dashboard + */ +@Component({ + selector: "custom-donut-content-formatter-example", + templateUrl: "./custom-donut-content-formatter-example.component.html", + styleUrls: ["./custom-donut-content-formatter-example.component.less"], + standalone: false, +}) +export class CustomDonutContentFormatterExampleComponent implements OnInit { + public editMode: boolean = false; + // This variable will hold all the data needed to define the layout and behavior of the widgets. + // Pass this to the dashboard component's dashboard input in the template. + public dashboard: IDashboard | undefined; + + // Angular gridster requires a configuration object even if it's empty. + // Pass this to the dashboard component's gridsterConfig input in the template. + public gridsterConfig: GridsterConfig = {}; + + constructor( + // WidgetTypesService provides the widget's necessary structure information + private widgetTypesService: WidgetTypesService, + // In general, the ProviderRegistryService is used for making entities available for injection into dynamically loaded components. + private providerRegistry: ProviderRegistryService, + // Inject the ComponentRegistryService to make our custom component available for late loading by the dashboards framework + private componentRegistry: ComponentRegistryService, + private changeDetectorRef: ChangeDetectorRef + ) { + // Register the custom configurator component with the component registry to make it available + // for late loading by the dashboard framework. + this.componentRegistry.registerByLateLoadKey( + CustomDonutContentFormatterConfiguratorComponent + ); + // Register the custom formatter component with the component registry to make it available + // for late loading by the dashboard framework. + this.componentRegistry.registerByLateLoadKey( + CustomDonutContentFormatterComponent + ); + + // Grab the widget's default template which will be needed as a parameter for setNode below. + const proportional = this.widgetTypesService.getWidgetType( + "proportional", + 1 + ); + + const donutFormatters: IFormatterDefinition[] = [ + { + componentType: DonutContentSumFormatterComponent.lateLoadKey, + label: $localize\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\`Sum\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\`, + } as IFormatterDefinition, + { + componentType: + DonutContentPercentageFormatterComponent.lateLoadKey, + label: $localize\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\`Percentage\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\`, + configurationComponent: + DonutContentPercentageConfigurationComponent.lateLoadKey, + } as IFormatterDefinition, + { + componentType: CustomDonutContentFormatterComponent.lateLoadKey, + label: $localize\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\`Custom\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\`, + // This is a custom configurator that will pop up below the formatter once it gets selected + configurationComponent: + CustomDonutContentFormatterConfiguratorComponent.lateLoadKey, + } as IFormatterDefinition, + ]; + + this.widgetTypesService.setNode( + // This is the template we grabbed above with getWidgetType + proportional, + // We are setting the editor/configurator part of the widget template + "configurator", + // This indicates which node you are changing and we want to change the formatters available for selection in the editor. + WellKnownPathKey.Formatters, + // We are setting the available formatters with the array we created above. + donutFormatters + ); + + // This sets the donut chart's datasource to have the StatusesExampleDatasource so the drop down is filled similar to the line above. + this.widgetTypesService.setNode( + proportional, + "configurator", + WellKnownPathKey.DataSourceProviders, + [StatusesExampleDatasource.providerId] + ); + + // Registering the data source for injection into the widget. + this.providerRegistry.setProviders({ + [StatusesExampleDatasource.providerId]: { + provide: DATA_SOURCE, + useClass: StatusesExampleDatasource, + // Any dependencies that need to be injected into the provider must be listed here + deps: [], + }, + }); + } + + public ngOnInit(): void { + this.initializeDashboard(); + } + + /** Used for restoring widgets state */ + public reInitializeDashboard(): void { + // destroys the components and their providers so the dashboard can re init data + this.dashboard = undefined; + this.changeDetectorRef.detectChanges(); + + this.initializeDashboard(); + } + + public initializeDashboard(): void { + // We're using a static configuration object for this example, but this is where + // the widget's configuration could potentially be populated from a database + const proportionalWidget = widgetConfig; + const widgetIndex: IWidgets = { + // Enhance the widget with information coming from it's type definition + [proportionalWidget.id]: + this.widgetTypesService.mergeWithWidgetType(proportionalWidget), + }; + + // Setting the widget dimensions and position (this is for gridster) + const positions: Record = { + [proportionalWidget.id]: { + cols: 12, + rows: 6, + y: 0, + x: 0, + }, + }; + + // Finally, assigning the variables we created above to the dashboard + this.dashboard = { + positions, + widgets: widgetIndex, + }; + } +} + +export interface IStatusesWidgetData { + id: string; + name: string; + data: number[]; +} + +export const randomStatusesWidgetData: IStatusesWidgetData[] = [ + { + id: "Down", + name: "Down", + data: [Math.round(Math.random() * 100)], + }, + { + id: "Critical", + name: "Critical", + data: [Math.round(Math.random() * 100)], + }, + { + id: "Warning", + name: "Warning", + data: [Math.round(Math.random() * 100)], + }, + { + id: "Unknown", + name: "Unknown", + data: [Math.round(Math.random() * 100)], + }, + { + id: "Up", + name: "Up", + data: [Math.round(Math.random() * 100)], + }, + { + id: "Unmanaged", + name: "Unmanaged", + data: [Math.round(Math.random() * 100)], + }, +]; + +@Injectable() +export class StatusesExampleDatasource + extends DataSourceService + implements IDataSource, OnDestroy +{ + public static providerId = "StatusesExampleDatasource"; + + public busy = new Subject(); + + constructor(private http: HttpClient) { + super(); + } + + public async getFilteredData(): Promise { + this.busy.next(true); + + return new Promise((resolve) => { + setTimeout(() => { + resolve({ + result: randomStatusesWidgetData, + }); + this.busy.next(false); + }, 1000); + }); + } + + public ngOnDestroy(): void { + this.outputsSubject.complete(); + } +} + +export const widgetConfig: IWidget = { + id: "proportionalWidgetId", + type: "proportional", + pizzagna: { + [PizzagnaLayer.Configuration]: { + header: { + properties: { + title: "Proportional Widget!", + subtitle: "Proportional widget with legend formatters", + }, + }, + chart: { + providers: { + [WellKnownProviders.DataSource]: { + providerId: StatusesExampleDatasource.providerId, + } as IProviderConfiguration, + }, + properties: { + configuration: { + interactive: true, + chartOptions: { + type: ProportionalWidgetChartTypes.DonutChart, + legendPlacement: LegendPlacement.Right, + contentFormatter: { + componentType: + CustomDonutContentFormatterComponent.lateLoadKey, + properties: { + // here you can set the default value for the metric you receive. If not set the first one from the list will be taken + currentMetric: "Down", + // here you set the default value for your custom controls. If not set the first one from the list will be taken + units: Units.Weeks, + }, + }, + } as IProportionalWidgetChartOptions, + chartColors: [ + "var(--nui-color-chart-eight)", + "var(--nui-color-chart-nine)", + "var(--nui-color-chart-ten)", + ], + } as IProportionalWidgetConfig, + }, + }, + }, + }, +}; +\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\`, + "tutorials/customization/formatter/formatter-example/custom-formatter-docs.component.ts": \\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\`// © 2022 SolarWinds Worldwide, LLC. All rights reserved. +// +// Permission is hereby granted, free of charge, to any person obtaining a copy +// of this software and associated documentation files (the "Software"), to +// deal in the Software without restriction, including without limitation the +// rights to use, copy, modify, merge, publish, distribute, sublicense, and/or +// sell copies of the Software, and to permit persons to whom the Software is +// furnished to do so, subject to the following conditions: +// +// The above copyright notice and this permission notice shall be included in +// all copies or substantial portions of the Software. +// +// THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR +// IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, +// FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE +// AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER +// LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, +// OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN +// THE SOFTWARE. + +import { Component } from "@angular/core"; + +@Component({ + selector: "nui-custom-formatter-docs", + templateUrl: "./custom-formatter-docs.component.html", + standalone: false, +}) +export class CustomFormatterDocComponent {} +\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\`, + "tutorials/customization/formatter/formatter-example/custom-formatter-example.component.ts": \\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\`// © 2022 SolarWinds Worldwide, LLC. All rights reserved. +// +// Permission is hereby granted, free of charge, to any person obtaining a copy +// of this software and associated documentation files (the "Software"), to +// deal in the Software without restriction, including without limitation the +// rights to use, copy, modify, merge, publish, distribute, sublicense, and/or +// sell copies of the Software, and to permit persons to whom the Software is +// furnished to do so, subject to the following conditions: +// +// The above copyright notice and this permission notice shall be included in +// all copies or substantial portions of the Software. +// +// THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR +// IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, +// FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE +// AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER +// LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, +// OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN +// THE SOFTWARE. + +import { ListRange } from "@angular/cdk/collections"; +import { ChangeDetectorRef, Component, Input, OnInit } from "@angular/core"; +import { FormBuilder, FormGroup, Validators } from "@angular/forms"; +import { GridsterConfig, GridsterItem } from "angular-gridster2"; +import isEqual from "lodash/isEqual"; +import orderBy from "lodash/orderBy"; +import { BehaviorSubject } from "rxjs"; + +import { + DataSourceService, + IconService, + IDataField, + INovaFilteringOutputs, + INovaFilters, + ISorterFilter, + LoggerService, +} from "@nova-ui/bits"; +import { + ComponentRegistryService, + ConfiguratorHeadingService, + DATA_SOURCE, + FormatterConfiguratorComponent, + IDashboard, + IDataSourceOutput, + IFormatterDefinition, + IHasChangeDetector, + ITableWidgetColumnConfig, + ITableWidgetSorterConfig, + IWidget, + IWidgets, + PizzagnaLayer, + ProviderRegistryService, + RawFormatterComponent, + TableFormatterRegistryService, + WellKnownPathKey, + WellKnownProviders, + WidgetTypesService, +} from "@nova-ui/dashboards"; + +export const BREW_API_URL = "https://api.punkapi.com/v2/beers"; + +@Component({ + selector: "custom-formatter", + host: { class: "d-flex" }, + template: \\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\` +
+
+ +
+
+ {{ data.value }} +
+
+ \\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\`, + styleUrls: ["./custom-formatter-example.component.less"], + standalone: false, +}) +export class CustomFormatterComponent implements IHasChangeDetector { + public static lateLoadKey = "CustomFormatterComponent"; + + constructor(public changeDetector: ChangeDetectorRef) {} + + @Input() public data: any; + @Input() public icon: string; + @Input() public threshold: string; + + public isAboveThreshold(): boolean { + return parseFloat(this.threshold) <= this.data.value; + } +} + +@Component({ + selector: "custom-formatter-configurator", + styleUrls: ["./custom-formatter-example.component.less"], + template: \\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\` +
+
+ + + + {{ item.label }} + + + + This field is required + + +
+
+ + + + + + + + This field is required + + +
+
+ + + + + This field is required + + +
+
+ +
+
+ +
+ + +
+ + + Select Item + +
+ \\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\`, + standalone: false, +}) +export class CustomFormatterConfiguratorComponent + extends FormatterConfiguratorComponent + implements OnInit, IHasChangeDetector +{ + public static lateLoadKey = "CustomFormatterConfiguratorComponent"; + + constructor( + changeDetector: ChangeDetectorRef, + configuratorHeading: ConfiguratorHeadingService, + formBuilder: FormBuilder, + logger: LoggerService, + public iconService: IconService + ) { + super(changeDetector, configuratorHeading, formBuilder, logger); + } + + public formatterFormGroup: FormGroup; + // This array is where the icon names will be stored + public options: string[] = []; + + public ngOnInit(): void { + for (const icon of this.iconService.icons) { + if (icon.category === "severity") { + this.options.push(icon.name); + } + } + } + + protected addCustomFormControls(form: FormGroup): void { + form.addControl( + "icon", + this.formBuilder.control("", Validators.required) + ); + form.addControl( + "threshold", + this.formBuilder.control(null, Validators.required) + ); + } +} + +/** + * A component that instantiates the dashboard + */ +@Component({ + selector: "custom-formatter-example", + templateUrl: "./custom-formatter-example.component.html", + styleUrls: ["./custom-formatter-example.component.less"], + standalone: false, +}) +export class CustomFormatterExampleComponent implements OnInit { + public editMode: boolean = false; + // This variable will hold all the data needed to define the layout and behavior of the widgets. + // Pass this to the dashboard component's dashboard input in the template. + public dashboard: IDashboard | undefined; + + // Angular gridster requires a configuration object even if it's empty. + // Pass this to the dashboard component's gridsterConfig input in the template. + public gridsterConfig: GridsterConfig = {}; + + constructor( + // WidgetTypesService provides the widget's necessary structure information + private widgetTypesService: WidgetTypesService, + // In general, the ProviderRegistryService is used for making entities available for injection into dynamically loaded components. + private providerRegistry: ProviderRegistryService, + // Inject the ComponentRegistryService to make our custom component available for late loading by the dashboards framework + private componentRegistry: ComponentRegistryService, + private tableFormatterRegistryService: TableFormatterRegistryService, + private changeDetectorRef: ChangeDetectorRef + ) { + // Register the custom configurator component with the component registry to make it available + // for late loading by the dashboard framework. + this.componentRegistry.registerByLateLoadKey( + CustomFormatterConfiguratorComponent + ); + // Register the custom formatter component with the component registry to make it available + // for late loading by the dashboard framework. + this.componentRegistry.registerByLateLoadKey(CustomFormatterComponent); + + // Grab the widget's default template which will be needed as a parameter for setNode below. + const table = this.widgetTypesService.getWidgetType("table", 1); + + const tableFormatters: IFormatterDefinition[] = [ + { + // This will be the component that will format the data + componentType: RawFormatterComponent.lateLoadKey, + // This is the label for what the formatter is selected in the drop down + label: $localize\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\`:table formatter|:No formatter\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\`, + // This says what datatype the formatter supports. If the value node is null, it accepts any data type. + dataTypes: { + // @ts-ignore: Ignoring compiler error to keep the same flow + value: null, + }, + }, + { + componentType: CustomFormatterComponent.lateLoadKey, + label: $localize\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\`:table formatter|:Custom formatter\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\`, + // This is a custom configurator that will pop up below the formatter once it gets selected + configurationComponent: + CustomFormatterConfiguratorComponent.lateLoadKey, + // This says what data types the formatter supports. + // In this case, it supports abv values only. + // If you look below in the table data source you'll see where we define our column's data types. + dataTypes: { + value: ["abv"], + }, + }, + ]; + + // Registering the formatters + this.tableFormatterRegistryService.addItems(tableFormatters); + + // This sets the table's datasource to have the BeerDataSource so the drop down is filled similar to the line above. + this.widgetTypesService.setNode( + table, + "configurator", + WellKnownPathKey.DataSourceProviders, + [BeerDataSource.providerId] + ); + + // Registering the data source for injection into the widget. + this.providerRegistry.setProviders({ + [BeerDataSource.providerId]: { + provide: DATA_SOURCE, + useClass: BeerDataSource, + // Any dependencies that need to be injected into the provider must be listed here + deps: [], + }, + }); + } + + public ngOnInit(): void { + this.initializeDashboard(); + } + + /** Used for restoring widgets state */ + public reInitializeDashboard(): void { + // destroys the components and their providers so the dashboard can re init data + this.dashboard = undefined; + this.changeDetectorRef.detectChanges(); + + this.initializeDashboard(); + } + + public initializeDashboard(): void { + // We're using a static configuration object for this example, but this is where + // the widget's configuration could potentially be populated from a database + const tableWidget = widgetConfig; + const widgetIndex: IWidgets = { + // Enhance the widget with information coming from it's type definition + [tableWidget.id]: + this.widgetTypesService.mergeWithWidgetType(tableWidget), + }; + + // Setting the widget dimensions and position (this is for gridster) + const positions: Record = { + [tableWidget.id]: { + cols: 12, + rows: 6, + y: 0, + x: 0, + }, + }; + + // Finally, assigning the variables we created above to the dashboard + this.dashboard = { + positions, + widgets: widgetIndex, + }; + } +} + +export interface IBrewInfo { + id: number; + name: string; + tagline: string; + first_brewed: string; + description: string; + brewers_tips: string; + abv: number; +} + +export interface IBrewDatasourceResponse { + brewInfo: IBrewInfo[]; + total: number; +} + +export class BeerDataSource extends DataSourceService { + public static providerId = "BeerDataSource"; + + private cache = Array.from({ length: 0 }); + private lastSortValue?: ISorterFilter; + private lastVirtualScroll?: ListRange; + // For simplicity, the totalItems value is hard-coded here, but in a real-world scenario the value would likely be retrieved via an async backend call + private totalItems: number = 325; + + public page: number = 1; + public busy = new BehaviorSubject(false); + + public dataFields: Array = [ + { id: "id", label: "No", dataType: "number" }, + { id: "name", label: "Name", dataType: "string" }, + { id: "tagline", label: "Tagline", dataType: "string" }, + { id: "first_brewed", label: "First Brewed", dataType: "string" }, + { id: "description", label: "Description", dataType: "string" }, + { id: "brewers_tips", label: "Brewer's Tips", dataType: "string" }, + // We are giving this field a custom data type of 'abv' so the dropdown in the custom formatter configurator can use it to filter out other data types + { id: "abv", label: "Alcohol By Volume", dataType: "abv" }, + ]; + + constructor(private logger: LoggerService) { + super(); + } + + public async getFilteredData( + filters: INovaFilters + ): Promise> { + const start = filters.virtualScroll?.value?.start ?? 0; + const end = filters.virtualScroll?.value?.end ?? 0; + const delta = end - start; + + // Note: We should start with a clean cache every time first page is requested + if (start === 0) { + this.cache = []; + } + + // This condition handles sorting. We want to sort columns without fetching another chunk of data. + // Since the data is being fetched when scrolled, we compare virtual scroll indexes here in the condition as well. + if (filters.sorter?.value) { + if ( + !isEqual(this.lastSortValue, filters.sorter.value) && + filters.virtualScroll?.value.start === 0 && + !!this.lastVirtualScroll + ) { + const totalPages = Math.ceil( + delta ? this.totalItems / delta : 1 + ); + const itemsPerPage: number = Math.max( + delta < 80 ? delta : 80, + 1 + ); + let response: Array | null = null; + let map: IBrewDatasourceResponse; + + if (filters.sorter?.value?.direction === "desc") { + this.cache = []; + for (let i = 0; i < this.page; ++i) { + response = await ( + await fetch( + \\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\`\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\${BREW_API_URL}/?page=\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\${ + totalPages - i || 1 + }&per_page=\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\${itemsPerPage}\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\` + ) + ).json(); + + // since the last page contains only 5 items we need to fetch another page to give virtual scroll enough space to work + if (response && response.length < itemsPerPage) { + this.page++; + } + map = { + brewInfo: response?.map((result: IBrewInfo) => ({ + id: result.id, + name: result.name, + tagline: result.tagline, + first_brewed: result.first_brewed, + description: result.description, + brewers_tips: result.brewers_tips, + })), + total: response?.length, + } as IBrewDatasourceResponse; + this.cache = + totalPages - i !== 0 + ? this.cache.concat(map.brewInfo) + : this.cache; + } + } + + if (filters.sorter?.value?.direction === "asc") { + this.cache = []; + for (let i = 0; i < this.page; i++) { + response = await ( + await fetch( + \\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\`\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\${BREW_API_URL}/?page=\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\${ + i + 1 + }&per_page=\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\${itemsPerPage}\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\` + ) + ).json(); + map = { + brewInfo: response?.map((result: IBrewInfo) => ({ + id: result.id, + name: result.name, + tagline: result.tagline, + first_brewed: result.first_brewed, + description: result.description, + brewers_tips: result.brewers_tips, + })), + total: response?.length, + } as IBrewDatasourceResponse; + this.cache = this.cache.concat(map.brewInfo); + } + } + + this.lastSortValue = filters.sorter?.value; + this.lastVirtualScroll = filters.virtualScroll?.value; + + return { + result: { + repeat: { + itemsSource: this.sortData(this.cache, filters), + }, + paginator: { total: this.totalItems }, + dataFields: this.dataFields, + }, + }; + } + } + + this.busy.next(true); + return new Promise((resolve) => { + setTimeout(() => { + this.getData(start, end, filters).then( + (response: INovaFilteringOutputs) => { + if (!response) { + return; + } + + this.cache = this.cache.concat(response.brewInfo); + + this.dataSubject.next(this.cache); + resolve({ + result: { + repeat: { + itemsSource: this.sortData( + this.cache, + filters + ), + }, + paginator: { total: this.totalItems }, + dataFields: this.dataFields, + }, + }); + + this.lastSortValue = filters.sorter?.value; + this.lastVirtualScroll = filters.virtualScroll?.value; + this.busy.next(false); + } + ); + }, 500); + }); + } + + public async getData( + start: number = 0, + end: number = 20, + filters: INovaFilters + ): Promise { + const delta = end - start; + const totalPages = Math.ceil(delta ? this.totalItems / delta : 1); + let response: Array | null = null; + // The api.punk.com is able to return only 80 items per page + const itemsPerPage: number = Math.max(delta < 80 ? delta : 80, 1); + + if (filters.sorter?.value?.direction === "asc") { + response = await ( + await fetch( + \\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\`\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\${BREW_API_URL}/?page=\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\${this.page}&per_page=\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\${itemsPerPage}\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\` + ) + ).json(); + } + + if (filters.sorter?.value?.direction === "desc") { + response = await ( + await fetch( + \\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\`\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\${BREW_API_URL}/?page=\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\${ + totalPages - this.page + }&per_page=\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\${itemsPerPage}\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\` + ) + ).json(); + } + + if (!filters.sorter) { + response = await ( + await fetch( + \\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\`\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\${BREW_API_URL}/?page=\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\${this.page}&per_page=\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\${itemsPerPage}\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\` + ) + ).json(); + } + return { + brewInfo: response?.map((result: IBrewInfo, i: number) => ({ + id: result.id, + abv: result.abv, + name: result.name, + tagline: result.tagline, + first_brewed: result.first_brewed, + description: result.description, + brewers_tips: result.brewers_tips, + })), + total: response?.length, + } as IBrewDatasourceResponse; + } + + private sortData(data: IBrewInfo[], filters: INovaFilters) { + return orderBy( + data, + filters.sorter?.value?.sortBy, + filters.sorter?.value?.direction as "desc" | "asc" + ); + } +} + +export const widgetConfig: IWidget = { + id: "tableWidgetId", + type: "table", + pizzagna: { + [PizzagnaLayer.Configuration]: { + header: { + properties: { + title: "Stupendous Suds", + subtitle: "Try These Brilliant Brews", + }, + }, + table: { + providers: { + [WellKnownProviders.DataSource]: { + providerId: BeerDataSource.providerId, + }, + }, + properties: { + configuration: { + columns: [ + { + id: "column1", + label: "Beer Name", + isActive: true, + width: 185, + formatter: { + componentType: + RawFormatterComponent.lateLoadKey, + properties: { + dataFieldIds: { + value: "name", + }, + }, + }, + }, + { + id: "column2", + label: "Tagline", + isActive: true, + width: 250, + formatter: { + componentType: + RawFormatterComponent.lateLoadKey, + properties: { + dataFieldIds: { + value: "tagline", + }, + }, + }, + }, + { + id: "column3", + label: "Alcohol By Volume", + isActive: true, + width: 150, + formatter: { + componentType: + CustomFormatterComponent.lateLoadKey, + properties: { + dataFieldIds: { + value: "abv", + }, + icon: "severity_error", + threshold: "5", + }, + }, + }, + { + id: "column4", + label: "Description", + isActive: true, + formatter: { + componentType: + RawFormatterComponent.lateLoadKey, + properties: { + dataFieldIds: { + value: "description", + }, + }, + }, + }, + ] as ITableWidgetColumnConfig[], + sorterConfiguration: { + descendantSorting: false, + sortBy: "", + } as ITableWidgetSorterConfig, + hasVirtualScroll: true, + }, + }, + }, + }, + }, +}; +\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\`, + "tutorials/customization/widget/custom-widget-docs.component.ts": \\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\`// © 2022 SolarWinds Worldwide, LLC. All rights reserved. +// +// Permission is hereby granted, free of charge, to any person obtaining a copy +// of this software and associated documentation files (the "Software"), to +// deal in the Software without restriction, including without limitation the +// rights to use, copy, modify, merge, publish, distribute, sublicense, and/or +// sell copies of the Software, and to permit persons to whom the Software is +// furnished to do so, subject to the following conditions: +// +// The above copyright notice and this permission notice shall be included in +// all copies or substantial portions of the Software. +// +// THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR +// IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, +// FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE +// AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER +// LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, +// OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN +// THE SOFTWARE. + +import { Component } from "@angular/core"; + +@Component({ + selector: "custom-widget-docs", + templateUrl: "./custom-widget-docs.component.html", + standalone: false, +}) +export class CustomWidgetDocsComponent {} +\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\`, + "tutorials/customization/widget/custom-widget.component.ts": \\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\`// © 2022 SolarWinds Worldwide, LLC. All rights reserved. +// +// Permission is hereby granted, free of charge, to any person obtaining a copy +// of this software and associated documentation files (the "Software"), to +// deal in the Software without restriction, including without limitation the +// rights to use, copy, modify, merge, publish, distribute, sublicense, and/or +// sell copies of the Software, and to permit persons to whom the Software is +// furnished to do so, subject to the following conditions: +// +// The above copyright notice and this permission notice shall be included in +// all copies or substantial portions of the Software. +// +// THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR +// IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, +// FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE +// AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER +// LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, +// OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN +// THE SOFTWARE. + +import { + ChangeDetectionStrategy, + ChangeDetectorRef, + Component, + EventEmitter, + HostBinding, + Input, + OnChanges, + OnInit, + Output, + SimpleChanges, +} from "@angular/core"; +import { FormBuilder, FormGroup, Validators } from "@angular/forms"; +import { GridsterConfig, GridsterItem } from "angular-gridster2"; + +import { IMenuItem } from "@nova-ui/bits"; +import { + ComponentRegistryService, + ConfiguratorHeadingService, + DEFAULT_PIZZAGNA_ROOT, + EVENT_PROXY, + FormStackComponent, + IConverterFormPartsProperties, + IDashboard, + IHasChangeDetector, + IHasForm, + IProviderConfiguration, + IWidget, + IWidgets, + IWidgetTypeDefinition, + NOVA_GENERIC_CONVERTER, + NOVA_TITLE_AND_DESCRIPTION_CONVERTER, + PizzagnaLayer, + refresher, + StackComponent, + TitleAndDescriptionConfigurationComponent, + WellKnownPathKey, + WellKnownProviders, + widgetBodyContentNodes, + WidgetConfiguratorSectionComponent, + WidgetTypesService, + WIDGET_BODY, + WIDGET_HEADER, + WIDGET_LOADING, +} from "@nova-ui/dashboards"; + +// The custom widget type name we'll use +const CUSTOM_WIDGET_TYPENAME = "example-custom-widget"; +// The path key we'll use for image selection in the configurator definition +const IMAGE_SELECTION_CONFIGURATOR_PATH_KEY = "imageSelection"; + +@Component({ + selector: "custom-widget-body", + // A simple template for our custom widget + template: \\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\`\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\`, + styleUrls: ["./custom-widget.component.less"], + changeDetection: ChangeDetectionStrategy.OnPush, + standalone: false, +}) +// Remember to declare this class in the parent module +export class CustomWidgetBodyContentComponent implements IHasChangeDetector { + // Ensure that the lateLoadKey value matches class name + public static lateLoadKey = "CustomWidgetBodyContentComponent"; + + // Optionally, providing an input for styling of the host element + @Input() @HostBinding("class") public elementClass = ""; + + // We'll map this input with the configurator form using the NOVA_GENERIC_CONVERTER. + // See the customWidget definition at the bottom of the file. + @Input() public imageSource: string; + + // Injecting the ChangeDetectorRef to implement IHasChangeDetector. + // This allows the dashboard framework to reliably propagate component property changes to the DOM. + constructor(public changeDetector: ChangeDetectorRef) {} +} + +/** + * A custom configurator section component for selecting the image source for the custom widget + */ +@Component({ + selector: "custom-configurator-section", + template: \\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\` + + + + +
+ + +
+ Image Selection +
+ {{ imageDisplayValue }} +
+
+
+
+ + + + + {{ item.title }} + + + +
+
+ \\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\`, + styleUrls: ["./custom-widget.component.less"], + changeDetection: ChangeDetectionStrategy.OnPush, + standalone: false, +}) +// Remember to declare this class in the parent module +export class CustomConfiguratorSectionComponent + implements OnInit, OnChanges, IHasChangeDetector, IHasForm +{ + // Ensure that the lateLoadKey value matches the class name + public static lateLoadKey = "CustomConfiguratorSectionComponent"; + + /** + * This input serves as the itemsSource a user can select an image from. + */ + @Input() imageItems: IMenuItem[] = []; + /** + * This property holds the currently selected image source string. + */ + @Input() imageSource: string; + + /** + * An output for emitting formReady to allow the immediate parent formGroup component to register us as a form control + * in the larger form. In this case, the immediate parent would be the WidgetConfiguratorSectionComponent as specified + * in the customWidget configurator definition at the bottom of this file. + */ + @Output() formReady = new EventEmitter(); + + public form: FormGroup; + public imageDisplayValue: string; + + constructor( + public changeDetector: ChangeDetectorRef, + private formBuilder: FormBuilder, + public configuratorHeading: ConfiguratorHeadingService + ) {} + + public ngOnInit(): void { + // Initializing the form + this.form = this.formBuilder.group({ + // Note: When using the NOVA_GENERIC_CONVERTER, the form control name, in this case 'imageSource', must match the input name on + // this component as well as that of the corresponding property on the custom widget body component. + imageSource: [{}, [Validators.required]], + }); + + // Emitting the formReady as described above. + this.formReady.emit(this.form); + } + + public ngOnChanges(changes: SimpleChanges): void { + if (changes.imageSource && !changes.imageSource.isFirstChange()) { + const previousValue: string = changes.imageSource.previousValue; + if (previousValue !== this.imageSource) { + // Setting the display value according to the current imageSource value + this.imageDisplayValue = this.imageItems.find( + (item: IMenuItem) => item.url === this.imageSource + )?.title; + + // Updating the form when the imageSource input gets updated + this.form.get("imageSource")?.setValue(this.imageSource); + } + } + } + + public onChanged(newValue: string): void { + // Keeping the display value updated as the user changes the dropdown selection + this.imageDisplayValue = this.imageItems.find( + (item: IMenuItem) => item.url === newValue + )?.title; + } +} + +/** + * The component that instantiates the dashboard + */ +@Component({ + selector: "custom-widget", + templateUrl: "./custom-widget.component.html", + styleUrls: ["./custom-widget.component.less"], + standalone: false, +}) +export class CustomWidgetComponent implements OnInit { + // This variable will hold all the data needed to define the layout and behavior of the widgets. + // Pass this to the dashboard component's dashboard input in the template. + public dashboard: IDashboard | undefined; + + // Angular gridster requires a configuration object even if it's empty. + // Pass this to the dashboard component's gridsterConfig input in the template. + public gridsterConfig: GridsterConfig = {}; + + // Boolean which dashboard takes in as an input if its true it allows you to move widgets around. + public editMode: boolean = false; + + constructor( + // WidgetTypesService provides the widget's necessary structure information + private widgetTypesService: WidgetTypesService, + + // Inject the ComponentRegistryService to make our custom component available for late loading by the dashboards framework + private componentRegistry: ComponentRegistryService, + private changeDetectorRef: ChangeDetectorRef + ) {} + + public ngOnInit(): void { + // Register the custom widget type and custom components + // Note: This could also be done in the parent module's constructor so that + // multiple dashboards could have access to the same registrations. + this.prepareNovaDashboards(); + + // Register some image items as dropdown options in the widget editor/configurator + // Note: This could also be done in the parent module's constructor so that + // multiple dashboards could have access to the same dropdown options. + this.registerImageOptions(); + + // Initialize our current instance of a dashboard with an instance of our custom widget + this.initializeDashboard(); + } + + /** Used for restoring widgets state */ + public reInitializeDashboard(): void { + // destroys the components and their providers so the dashboard can re init data + this.dashboard = undefined; + this.changeDetectorRef.detectChanges(); + + this.initializeDashboard(); + } + + public initializeDashboard(): void { + // We're using a static configuration object for this example (see widgetConfig at the bottom of the file), + // but this is where the widget's configuration could potentially be populated from a database + const widget = widgetConfig; + + // Create an index of widgets complete with structure and configuration to assign to the dashboard + const widgets: IWidgets = { + // Complete the custom widget with structure information coming from its type definition + [widget.id]: this.widgetTypesService.mergeWithWidgetType(widget), + }; + + // Setting the widget dimensions and position (this is for gridster) + const positions: Record = { + [widget.id]: { + cols: 4, + rows: 11, + y: 0, + x: 0, + }, + }; + + // Finally, assigning the variables we created above to the dashboard + this.dashboard = { positions, widgets }; + } + + private prepareNovaDashboards() { + // Register the custom widget type + this.widgetTypesService.registerWidgetType( + CUSTOM_WIDGET_TYPENAME, + 1, + customWidget + ); + + // Register the custom widget body component with the component registry to make it available + // for late loading by the dashboard framework. + this.componentRegistry.registerByLateLoadKey( + CustomWidgetBodyContentComponent + ); + + // Register the custom configurator section with the component registry to make it available + // for late loading by the dashboard framework. + this.componentRegistry.registerByLateLoadKey( + CustomConfiguratorSectionComponent + ); + } + + private registerImageOptions() { + // Grab the widget's default template which will be needed as a parameter for setNode below. + const widgetTemplate = this.widgetTypesService.getWidgetType( + CUSTOM_WIDGET_TYPENAME, + 1 + ); + + // Register some image items as dropdown options in the widget editor/configurator + this.widgetTypesService.setNode( + // This is the template we grabbed above with getWidgetType + widgetTemplate, + // We are setting the editor/configurator part of the widget template + "configurator", + // This indicates which node you are changing and we want to change the image items available for selection in the editor. + // For reference, see the 'paths' property of the custom widget's IWidgetTypeDefinition at the bottom of this file. + IMAGE_SELECTION_CONFIGURATOR_PATH_KEY, + // We are setting the image items available for selection in the editor. 'imageItems' is defined + // at the bottom of this file. + imageItems + ); + } +} + +/*************************************************************************************************** + * This is the type definition of our custom widget + ***************************************************************************************************/ +const customWidget: IWidgetTypeDefinition = { + /*************************************************************************************************** + * Paths to important settings in this type definition + ***************************************************************************************************/ + paths: { + widget: { + [WellKnownPathKey.Root]: DEFAULT_PIZZAGNA_ROOT, + }, + configurator: { + [WellKnownPathKey.Root]: DEFAULT_PIZZAGNA_ROOT, + // for the custom configuration component, this is the path for the list of image items available for selection + [IMAGE_SELECTION_CONFIGURATOR_PATH_KEY]: + "imageSelection.properties.imageItems", + }, + }, + /*************************************************************************************************** + * Widget section describes the structural part of the custom widget + ***************************************************************************************************/ + widget: { + [PizzagnaLayer.Structure]: { + [DEFAULT_PIZZAGNA_ROOT]: { + id: DEFAULT_PIZZAGNA_ROOT, + // base layout of the widget - all components referenced herein will be stacked in a column + componentType: StackComponent.lateLoadKey, + providers: { + // When enabled, this provider emits the REFRESH event on the pizzagna event bus every X seconds + [WellKnownProviders.Refresher]: refresher(), + // event proxy manages the transmission of events between widget and dashboard such as the WIDGET_EDIT and WIDGET_REMOVE events + [WellKnownProviders.EventProxy]: EVENT_PROXY, + }, + properties: { + // these values reference child components in the widget structure defined below + nodes: ["header", "loading", "body"], + }, + }, + // standard widget header + header: WIDGET_HEADER, + // this is the loading bar below the header + loading: WIDGET_LOADING, + // the body node + body: WIDGET_BODY, + + // retrieving the definitions for the body content nodes. the argument corresponds to the main content node key + ...widgetBodyContentNodes("mainContent"), + + // the component that supplies the content of our custom widget + mainContent: { + id: "mainContent", + componentType: CustomWidgetBodyContentComponent.lateLoadKey, + properties: { + elementClass: "d-flex w-100 justify-content-center", + }, + }, + }, + [PizzagnaLayer.Configuration]: { + [DEFAULT_PIZZAGNA_ROOT]: { + id: DEFAULT_PIZZAGNA_ROOT, + providers: { + // default refresher configuration + [WellKnownProviders.Refresher]: refresher(false, 60), + }, + }, + // default header configuration + header: { + properties: { + title: $localize\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\`Empty Custom Widget\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\`, + }, + }, + }, + }, + /*************************************************************************************************** + * Configurator section describes the form that's used to configure the widget + ***************************************************************************************************/ + configurator: { + [PizzagnaLayer.Structure]: { + [DEFAULT_PIZZAGNA_ROOT]: { + id: DEFAULT_PIZZAGNA_ROOT, + // base layout of the configurator - all form components referenced herein will be stacked in a column + componentType: FormStackComponent.lateLoadKey, + properties: { + elementClass: + "flex-grow-1 overflow-auto nui-scroll-shadows", + // these values reference child components laid out in this form (defined below) + nodes: ["presentation", "customConfig"], + }, + }, + // /presentation + presentation: { + id: "presentation", + componentType: WidgetConfiguratorSectionComponent.lateLoadKey, + properties: { + headerText: $localize\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\`Presentation\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\`, + nodes: ["titleAndDescription"], + }, + }, + // /presentation/titleAndDescription + titleAndDescription: { + id: "titleAndDescription", + componentType: + TitleAndDescriptionConfigurationComponent.lateLoadKey, + providers: { + converter: { + providerId: NOVA_TITLE_AND_DESCRIPTION_CONVERTER, + } as IProviderConfiguration, + }, + }, + // /customConfig + customConfig: { + id: "customConfig", + componentType: WidgetConfiguratorSectionComponent.lateLoadKey, + properties: { + headerText: $localize\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\`Custom Widget Configuration\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\`, + nodes: ["imageSelection"], + }, + }, + // /customConfig/imageSelection + imageSelection: { + id: "imageSelection", + // Here's where we set the configurator to use our custom configurator section + componentType: CustomConfiguratorSectionComponent.lateLoadKey, + properties: { + // This corresponds to the 'imageItems' input on the custom configurator section component + // which defines the list of image items to pick from. The empty value shown here is overridden + // in the 'registerImageOptions' method above. + imageItems: [] as IMenuItem[], + }, + providers: { + // Using the generic converter to map the selected image source between the widget and the form + [WellKnownProviders.Converter]: { + providerId: NOVA_GENERIC_CONVERTER, + properties: { + formParts: [ + { + // Setting up the generic converter to update the 'imageSource' property of the custom widget 'mainContent' component + previewPath: "mainContent.properties", + // Note: To use the NOVA_GENERIC_CONVERTER, the linked properties must have the same name between the configurator + // section component and the widget 'mainContent' component. Additionally, the property name must match the formControl + // name used in the configurator section component. In this case, the common name among all three is 'imageSource'. + keys: ["imageSource"], + }, + ] as IConverterFormPartsProperties[], + }, + } as IProviderConfiguration, + }, + }, + }, + }, +}; + +// For this example, we're using static items for the image selection dropdown. In a more realistic scenario, +// the items available for selection might come from a backend database. +const imageItems = [ + { + title: "Harry Potter Book Cover", + url: "https://imgc.allpostersimages.com/img/print/u-g-F8PQ9I0.jpg?w=550&h=550&p=0", + }, + { + title: "Harry Potter Movie Poster", + url: "https://images-na.ssl-images-amazon.com/images/I/81gpmMdKOHL._AC_SY741_.jpg", + }, +] as IMenuItem[]; + +// We're using a static configuration object for this example. In a more realistic scenario, +// a widget's configuration would likely be stored in a database. +const widgetConfig: IWidget = { + id: "widget1", + // This custom type is registered in the 'prepareNovaDashboards' method above. + type: CUSTOM_WIDGET_TYPENAME, + pizzagna: { + [PizzagnaLayer.Configuration]: { + header: { + // Setting the initial property values for the WidgetHeaderComponent + properties: { + title: "Harry Potter and the Sorcerer's Stone", + subtitle: "By J. K. Rowling", + }, + }, + mainContent: { + properties: { + // Setting the initial value for the 'imageSource' property on our custom widget body + imageSource: imageItems[0].url, + }, + }, + }, + }, +}; +\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\`, + "tutorials/customization/widget/custom-widget.module.ts": \\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\`// © 2022 SolarWinds Worldwide, LLC. All rights reserved. +// +// Permission is hereby granted, free of charge, to any person obtaining a copy +// of this software and associated documentation files (the "Software"), to +// deal in the Software without restriction, including without limitation the +// rights to use, copy, modify, merge, publish, distribute, sublicense, and/or +// sell copies of the Software, and to permit persons to whom the Software is +// furnished to do so, subject to the following conditions: +// +// The above copyright notice and this permission notice shall be included in +// all copies or substantial portions of the Software. +// +// THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR +// IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, +// FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE +// AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER +// LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, +// OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN +// THE SOFTWARE. + +import { CommonModule } from "@angular/common"; +import { HttpClientModule } from "@angular/common/http"; +import { NgModule } from "@angular/core"; +import { ReactiveFormsModule } from "@angular/forms"; +import { RouterModule } from "@angular/router"; + +import { + NuiButtonModule, + NuiDocsModule, + NuiFormFieldModule, + NuiIconModule, + NuiImageModule, + NuiMessageModule, + NuiSelectV2Module, + NuiSwitchModule, + DEMO_PATH_TOKEN, +} from "@nova-ui/bits"; +import { + NuiDashboardConfiguratorModule, + NuiDashboardsModule, +} from "@nova-ui/dashboards"; + +import { CustomWidgetDocsComponent } from "./custom-widget-docs.component"; +import { + CustomConfiguratorSectionComponent, + CustomWidgetBodyContentComponent, + CustomWidgetComponent, +} from "./custom-widget.component"; +import { getDemoFiles } from "../../../../../demo-files-factory"; + +const routes = [ + { + path: "", + component: CustomWidgetDocsComponent, + data: { + srlc: { + hideIndicator: true, + }, + showThemeSwitcher: true, + }, + }, + { + path: "example", + component: CustomWidgetComponent, + data: { + srlc: { + hideIndicator: true, + }, + }, + }, +]; + +@NgModule({ + imports: [ + CommonModule, + ReactiveFormsModule, + HttpClientModule, + NuiDashboardsModule, + NuiDashboardConfiguratorModule, + NuiDocsModule, + NuiFormFieldModule, + NuiIconModule, + NuiImageModule, + NuiMessageModule, + NuiSelectV2Module, + NuiSwitchModule, + NuiButtonModule, + RouterModule.forChild(routes), + ], + declarations: [ + CustomWidgetDocsComponent, + CustomConfiguratorSectionComponent, + CustomWidgetBodyContentComponent, + CustomWidgetComponent, + ], + providers: [ + { + provide: DEMO_PATH_TOKEN, + useValue: getDemoFiles("widget"), + }, + ], +}) +export default class CustomWidgetModule {} +\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\`, + "tutorials/data-source-setup/data-source-setup-docs.component.ts": \\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\`// © 2022 SolarWinds Worldwide, LLC. All rights reserved. +// +// Permission is hereby granted, free of charge, to any person obtaining a copy +// of this software and associated documentation files (the "Software"), to +// deal in the Software without restriction, including without limitation the +// rights to use, copy, modify, merge, publish, distribute, sublicense, and/or +// sell copies of the Software, and to permit persons to whom the Software is +// furnished to do so, subject to the following conditions: +// +// The above copyright notice and this permission notice shall be included in +// all copies or substantial portions of the Software. +// +// THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR +// IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, +// FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE +// AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER +// LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, +// OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN +// THE SOFTWARE. + +import { Component } from "@angular/core"; + +@Component({ + selector: "nui-dashboard-data-source-docs", + templateUrl: "./data-source-setup-docs.component.html", + standalone: false, +}) +export class DataSourceDocsComponent {} +\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\`, + "tutorials/data-source-setup/data-source-setup.component.ts": \\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\`// © 2022 SolarWinds Worldwide, LLC. All rights reserved. +// +// Permission is hereby granted, free of charge, to any person obtaining a copy +// of this software and associated documentation files (the "Software"), to +// deal in the Software without restriction, including without limitation the +// rights to use, copy, modify, merge, publish, distribute, sublicense, and/or +// sell copies of the Software, and to permit persons to whom the Software is +// furnished to do so, subject to the following conditions: +// +// The above copyright notice and this permission notice shall be included in +// all copies or substantial portions of the Software. +// +// THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR +// IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, +// FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE +// AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER +// LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, +// OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN +// THE SOFTWARE. + +import { HttpClient, HttpErrorResponse } from "@angular/common/http"; +import { Component, Injectable, OnDestroy, OnInit } from "@angular/core"; +import { GridsterConfig, GridsterItem } from "angular-gridster2"; +import { BehaviorSubject } from "rxjs"; +import { finalize } from "rxjs/operators"; + +import { DataSourceService, IFilteringOutputs } from "@nova-ui/bits"; +import { + DATA_SOURCE, + DEFAULT_PIZZAGNA_ROOT, + IDashboard, + IKpiData, + IProviderConfiguration, + IRefresherProperties, + IWidget, + IWidgets, + KpiComponent, + NOVA_KPI_DATASOURCE_ADAPTER, + PizzagnaLayer, + ProviderRegistryService, + WellKnownProviders, + WidgetTypesService, +} from "@nova-ui/dashboards"; + +/** + * A simple KPI data source to retrieve the average rating of Harry Potter and the Sorcerer's Stone (book) via googleapis + */ +@Injectable() +export class AverageRatingKpiDataSource + extends DataSourceService + implements OnDestroy +{ + // This is the ID we'll use to identify the provider + public static providerId = "AverageRatingKpiDataSource"; + + // Use this subject to communicate the data source's busy state + public busy = new BehaviorSubject(false); + + constructor(private http: HttpClient) { + super(); + } + + // In this example, getFilteredData is invoked every 10 minutes (Take a look at the refresher + // provider definition in the widget configuration below to see how the interval is set) + public async getFilteredData(): Promise { + this.busy.next(true); + return new Promise((resolve) => { + // *** Make a resource request to an external API (if needed) + this.http + .get("https://www.googleapis.com/books/v1/volumes/5MQFrgEACAAJ") + .pipe(finalize(() => this.busy.next(false))) + .subscribe({ + next: (data: any) => { + resolve({ + result: { + value: data.volumeInfo.averageRating, + }, + }); + }, + error: (error: HttpErrorResponse) => { + resolve({ + result: null, + error: { + type: error.status, + }, + }); + }, + }); + }); + } + + public ngOnDestroy(): void { + this.outputsSubject.complete(); + } +} + +/** + * A component that instantiates the dashboard + */ +@Component({ + selector: "data-source-setup", + templateUrl: "./data-source-setup.component.html", + styleUrls: ["./data-source-setup.component.less"], + standalone: false, +}) +export class DataSourceSetupComponent implements OnInit { + // This variable will hold all the data needed to define the layout and behavior of the widgets. + // Pass this to the dashboard component's dashboard input in the template. + public dashboard: IDashboard; + + // Angular gridster requires a configuration object even if it's empty. + // Pass this to the dashboard component's gridsterConfig input in the template. + public gridsterConfig: GridsterConfig = {}; + + constructor( + // WidgetTypesService provides the widget's necessary structure information + private widgetTypesService: WidgetTypesService, + + // In general, the ProviderRegistryService is used for making entities available for injection into dynamically loaded components. + private providerRegistry: ProviderRegistryService + ) {} + + public ngOnInit(): void { + // Registering the data source for injection into the KPI tile. + // Note: Each tile of a KPI widget is assigned its own instance of the data source + this.providerRegistry.setProviders({ + [AverageRatingKpiDataSource.providerId]: { + provide: DATA_SOURCE, + useClass: AverageRatingKpiDataSource, + // Any dependencies that need to be injected into the provider must be listed here + deps: [HttpClient], + }, + }); + // We're using a static configuration object for this example, but this is where + // the widget's configuration could potentially be populated from a database + const kpiWidget = widgetConfig; + const widgetIndex: IWidgets = { + // Complete the KPI widget with information coming from its type definition + [kpiWidget.id]: + this.widgetTypesService.mergeWithWidgetType(kpiWidget), + }; + + // Setting the widget dimensions and position (this is for gridster) + const positions: Record = { + [kpiWidget.id]: { + cols: 4, + rows: 6, + y: 0, + x: 0, + }, + }; + + // Finally, assigning the variables we created above to the dashboard + this.dashboard = { + positions, + widgets: widgetIndex, + }; + } +} + +const widgetConfig: IWidget = { + id: "widget1", + type: "kpi", + pizzagna: { + [PizzagnaLayer.Configuration]: { + [DEFAULT_PIZZAGNA_ROOT]: { + providers: { + [WellKnownProviders.Refresher]: { + properties: { + // Configuring the refresher interval so that our data source is invoked every ten minutes + interval: 60 * 10, + enabled: true, + } as IRefresherProperties, + } as Partial, + }, + }, + header: { + properties: { + title: "Harry Potter and the Sorcerer's Stone", + subtitle: "By J. K. Rowling", + }, + }, + tiles: { + properties: { + nodes: ["kpi1"], + }, + }, + kpi1: { + id: "kpi1", + componentType: KpiComponent.lateLoadKey, + properties: { + widgetData: { + units: "out of 5 Stars", + label: "Average Rating", + }, + }, + providers: { + [WellKnownProviders.DataSource]: { + // Setting the data source providerId for the tile with id "kpi1" + providerId: AverageRatingKpiDataSource.providerId, + } as IProviderConfiguration, + [WellKnownProviders.Adapter]: { + providerId: NOVA_KPI_DATASOURCE_ADAPTER, + properties: { + componentId: "kpi1", + propertyPath: "widgetData", + }, + } as IProviderConfiguration, + }, + }, + }, + }, +}; +\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\`, + "tutorials/data-source-setup/data-source-setup.module.ts": \\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\`// © 2022 SolarWinds Worldwide, LLC. All rights reserved. +// +// Permission is hereby granted, free of charge, to any person obtaining a copy +// of this software and associated documentation files (the "Software"), to +// deal in the Software without restriction, including without limitation the +// rights to use, copy, modify, merge, publish, distribute, sublicense, and/or +// sell copies of the Software, and to permit persons to whom the Software is +// furnished to do so, subject to the following conditions: +// +// The above copyright notice and this permission notice shall be included in +// all copies or substantial portions of the Software. +// +// THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR +// IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, +// FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE +// AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER +// LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, +// OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN +// THE SOFTWARE. + +import { CommonModule } from "@angular/common"; +import { HttpClientModule } from "@angular/common/http"; +import { NgModule } from "@angular/core"; +import { RouterModule } from "@angular/router"; + +import { + NuiDocsModule, + NuiMessageModule, + DEMO_PATH_TOKEN, +} from "@nova-ui/bits"; +import { NuiDashboardsModule } from "@nova-ui/dashboards"; + +import { DataSourceDocsComponent } from "./data-source-setup-docs.component"; +import { DataSourceSetupComponent } from "./data-source-setup.component"; +import { getDemoFiles } from "../../../../demo-files-factory"; + +const routes = [ + { + path: "", + component: DataSourceDocsComponent, + data: { + srlc: { + hideIndicator: true, + }, + showThemeSwitcher: true, + }, + }, + { + path: "example", + component: DataSourceSetupComponent, + data: { + srlc: { + hideIndicator: true, + }, + }, + }, +]; + +@NgModule({ + imports: [ + CommonModule, + HttpClientModule, + NuiDashboardsModule, + NuiDocsModule, + NuiMessageModule, + RouterModule.forChild(routes), + ], + declarations: [DataSourceDocsComponent, DataSourceSetupComponent], + providers: [ + { + provide: DEMO_PATH_TOKEN, + useValue: getDemoFiles("data-source-setup"), + }, + ], +}) +export default class DataSourceSetupModule {} +\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\`, + "tutorials/dynamic-header-links/dynamic-header-links-docs.component.ts": \\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\`// © 2022 SolarWinds Worldwide, LLC. All rights reserved. +// +// Permission is hereby granted, free of charge, to any person obtaining a copy +// of this software and associated documentation files (the "Software"), to +// deal in the Software without restriction, including without limitation the +// rights to use, copy, modify, merge, publish, distribute, sublicense, and/or +// sell copies of the Software, and to permit persons to whom the Software is +// furnished to do so, subject to the following conditions: +// +// The above copyright notice and this permission notice shall be included in +// all copies or substantial portions of the Software. +// +// THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR +// IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, +// FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE +// AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER +// LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, +// OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN +// THE SOFTWARE. + +import { Component } from "@angular/core"; + +@Component({ + selector: "nui-dynamic-header-links-docs", + templateUrl: "./dynamic-header-links-docs.component.html", + standalone: false, +}) +export class DynamicHeaderLinksDocsComponent {} +\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\`, + "tutorials/dynamic-header-links/dynamic-header-links-docs.module.ts": \\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\`// © 2022 SolarWinds Worldwide, LLC. All rights reserved. +// +// Permission is hereby granted, free of charge, to any person obtaining a copy +// of this software and associated documentation files (the "Software"), to +// deal in the Software without restriction, including without limitation the +// rights to use, copy, modify, merge, publish, distribute, sublicense, and/or +// sell copies of the Software, and to permit persons to whom the Software is +// furnished to do so, subject to the following conditions: +// +// The above copyright notice and this permission notice shall be included in +// all copies or substantial portions of the Software. +// +// THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR +// IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, +// FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE +// AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER +// LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, +// OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN +// THE SOFTWARE. + +import { NgModule } from "@angular/core"; +import { RouterModule } from "@angular/router"; + +import { DynamicHeaderLinksDocsComponent } from "./dynamic-header-links-docs.component"; + +const routes = [ + { + path: "", + component: DynamicHeaderLinksDocsComponent, + data: { + srlc: { + hideIndicator: true, + }, + showThemeSwitcher: true, + }, + }, +]; + +@NgModule({ + imports: [RouterModule.forChild(routes)], + declarations: [DynamicHeaderLinksDocsComponent], +}) +export default class DynamicHeaderLinksDocsModule {} +\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\`, + "tutorials/hello-dashboards/hello-dashboards-docs.component.ts": \\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\`// © 2022 SolarWinds Worldwide, LLC. All rights reserved. +// +// Permission is hereby granted, free of charge, to any person obtaining a copy +// of this software and associated documentation files (the "Software"), to +// deal in the Software without restriction, including without limitation the +// rights to use, copy, modify, merge, publish, distribute, sublicense, and/or +// sell copies of the Software, and to permit persons to whom the Software is +// furnished to do so, subject to the following conditions: +// +// The above copyright notice and this permission notice shall be included in +// all copies or substantial portions of the Software. +// +// THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR +// IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, +// FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE +// AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER +// LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, +// OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN +// THE SOFTWARE. + +import { Component } from "@angular/core"; + +@Component({ + selector: "nui-dashboard-hello-dashboards-docs", + templateUrl: "./hello-dashboards-docs.component.html", + standalone: false, +}) +export class HelloDashboardsDocsComponent {} +\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\`, + "tutorials/hello-dashboards/hello-dashboards-example/hello-dashboards-example.component.ts": \\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\`// © 2022 SolarWinds Worldwide, LLC. All rights reserved. +// +// Permission is hereby granted, free of charge, to any person obtaining a copy +// of this software and associated documentation files (the "Software"), to +// deal in the Software without restriction, including without limitation the +// rights to use, copy, modify, merge, publish, distribute, sublicense, and/or +// sell copies of the Software, and to permit persons to whom the Software is +// furnished to do so, subject to the following conditions: +// +// The above copyright notice and this permission notice shall be included in +// all copies or substantial portions of the Software. +// +// THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR +// IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, +// FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE +// AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER +// LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, +// OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN +// THE SOFTWARE. + +import { Component, OnInit } from "@angular/core"; +import { GridsterConfig, GridsterItem } from "angular-gridster2"; + +import { + IDashboard, + IWidget, + IWidgets, + KpiComponent, + PizzagnaLayer, + WidgetTypesService, +} from "@nova-ui/dashboards"; + +/** + * A component that instantiates the dashboard + */ +@Component({ + selector: "hello-dashboards-example", + templateUrl: "./hello-dashboards-example.component.html", + styleUrls: ["./hello-dashboards-example.component.less"], + standalone: false, +}) +export class HelloDashboardsExampleComponent implements OnInit { + // This variable will have all the data needed to render the widgets widgets. + // Pass this to the dashboard component's dashboard input. + public dashboard: IDashboard; + // Angular gridster requires a configuration object even if its empty. + // Pass this to the dashboard component's gridsterConfig input. + public gridsterConfig: GridsterConfig = {}; + + // WidgetTypesService provides the widget's necessary structure information + constructor(private widgetTypesService: WidgetTypesService) {} + + public ngOnInit(): void { + // Here we are hard-coding the widget config for this example, but this is where you + // could potentially populate the widget's configuration from a database + const kpiWidget = widgetConfig; + const widgetIndex: IWidgets = { + // Complete the KPI widget with information coming from its type definition + [kpiWidget.id]: + this.widgetTypesService.mergeWithWidgetType(kpiWidget), + }; + // Setting widget position and dimensions (this is for gridster) + const positions: Record = { + [kpiWidget.id]: { + cols: 4, + rows: 6, + y: 0, + x: 0, + }, + }; + // Finally, assigning the variables we created above to the dashboard + this.dashboard = { + positions, + widgets: widgetIndex, + }; + } +} + +// In a real-world scenario, this configuration would typically be fetched from a database or at least live in another file +const widgetConfig: IWidget = { + id: "widget1", + type: "kpi", + pizzagna: { + [PizzagnaLayer.Configuration]: { + header: { + properties: { + title: "Hello, KPI Widget!", + subtitle: "A Venue for Meaningful Values", + }, + }, + tiles: { + properties: { + nodes: ["kpi1"], + }, + }, + kpi1: { + id: "kpi1", + componentType: KpiComponent.lateLoadKey, + properties: { + widgetData: { + id: "totalStorage", + value: 1, + label: "Total storage", + units: "TB", + }, + }, + }, + }, + }, +}; +\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\`, + "tutorials/hello-dashboards/hello-dashboards.module.ts": \\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\`// © 2022 SolarWinds Worldwide, LLC. All rights reserved. +// +// Permission is hereby granted, free of charge, to any person obtaining a copy +// of this software and associated documentation files (the "Software"), to +// deal in the Software without restriction, including without limitation the +// rights to use, copy, modify, merge, publish, distribute, sublicense, and/or +// sell copies of the Software, and to permit persons to whom the Software is +// furnished to do so, subject to the following conditions: +// +// The above copyright notice and this permission notice shall be included in +// all copies or substantial portions of the Software. +// +// THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR +// IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, +// FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE +// AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER +// LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, +// OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN +// THE SOFTWARE. + +import { CommonModule } from "@angular/common"; +import { NgModule } from "@angular/core"; +import { RouterModule } from "@angular/router"; + +import { + NuiDocsModule, + NuiMessageModule, + DEMO_PATH_TOKEN, +} from "@nova-ui/bits"; +import { NuiDashboardsModule } from "@nova-ui/dashboards"; + +import { HelloDashboardsDocsComponent } from "./hello-dashboards-docs.component"; +import { HelloDashboardsExampleComponent } from "./hello-dashboards-example/hello-dashboards-example.component"; +import { getDemoFiles } from "../../../../demo-files-factory"; + +const routes = [ + { + path: "", + component: HelloDashboardsDocsComponent, + data: { + srlc: { + hideIndicator: true, + }, + showThemeSwitcher: true, + }, + }, + { + path: "example", + component: HelloDashboardsExampleComponent, + data: { + srlc: { + hideIndicator: true, + }, + }, + }, +]; + +@NgModule({ + imports: [ + CommonModule, + NuiDashboardsModule, + NuiDocsModule, + NuiMessageModule, + RouterModule.forChild(routes), + ], + declarations: [ + HelloDashboardsDocsComponent, + HelloDashboardsExampleComponent, + ], + providers: [ + { + provide: DEMO_PATH_TOKEN, + useValue: getDemoFiles("hello-dashboards"), + }, + ], +}) +export default class HelloDashboardsModule {} +\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\`, + "tutorials/persistence-handler-setup/persistence-handler-setup-docs.component.ts": \\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\`// © 2022 SolarWinds Worldwide, LLC. All rights reserved. +// +// Permission is hereby granted, free of charge, to any person obtaining a copy +// of this software and associated documentation files (the "Software"), to +// deal in the Software without restriction, including without limitation the +// rights to use, copy, modify, merge, publish, distribute, sublicense, and/or +// sell copies of the Software, and to permit persons to whom the Software is +// furnished to do so, subject to the following conditions: +// +// The above copyright notice and this permission notice shall be included in +// all copies or substantial portions of the Software. +// +// THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR +// IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, +// FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE +// AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER +// LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, +// OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN +// THE SOFTWARE. + +import { Component } from "@angular/core"; + +@Component({ + selector: "nui-dashboard-persistence-handler-setup-docs", + templateUrl: "./persistence-handler-setup-docs.component.html", + standalone: false, +}) +export class PersistenceHandlerSetupDocsComponent {} +\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\`, + "tutorials/persistence-handler-setup/persistence-handler-setup.component.ts": \\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\`// © 2022 SolarWinds Worldwide, LLC. All rights reserved. +// +// Permission is hereby granted, free of charge, to any person obtaining a copy +// of this software and associated documentation files (the "Software"), to +// deal in the Software without restriction, including without limitation the +// rights to use, copy, modify, merge, publish, distribute, sublicense, and/or +// sell copies of the Software, and to permit persons to whom the Software is +// furnished to do so, subject to the following conditions: +// +// The above copyright notice and this permission notice shall be included in +// all copies or substantial portions of the Software. +// +// THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR +// IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, +// FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE +// AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER +// LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, +// OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN +// THE SOFTWARE. + +import { HttpClient, HttpErrorResponse } from "@angular/common/http"; +import { + ChangeDetectorRef, + Component, + Injectable, + OnDestroy, + OnInit, +} from "@angular/core"; +import { GridsterConfig, GridsterItem } from "angular-gridster2"; +import { BehaviorSubject, Observable, Subject } from "rxjs"; +import { finalize } from "rxjs/operators"; + +import { + DataSourceService, + IFilteringOutputs, + ToastService, + uuid, +} from "@nova-ui/bits"; +import { + DATA_SOURCE, + DEFAULT_PIZZAGNA_ROOT, + IDashboard, + IDashboardPersistenceHandler, + IKpiData, + IProviderConfiguration, + IRefresherProperties, + IWidget, + IWidgets, + KpiComponent, + NOVA_KPI_DATASOURCE_ADAPTER, + PizzagnaLayer, + ProviderRegistryService, + WellKnownPathKey, + WellKnownProviders, + WidgetTypesService, +} from "@nova-ui/dashboards"; + +/** + * A simple persistence handler that is tied to the widget editor directive + */ +@Injectable() +// The realizer of IDashboardPersistenceHandler may implement a trySubmit and/or a tryRemove method. +export class PersistenceHandler implements IDashboardPersistenceHandler { + // This variable is just to show how to handle error handling. + private persistenceSucceeded: boolean = true; + + // The example uses the toast service to demonstrate the + // invocation of each of the persistence handler callbacks + constructor(private toastService: ToastService) { + // toastService options to let it sit on the page for 2 seconds. + this.toastService.setConfig({ + timeOut: 2000, + }); + } + + // This method will be invoked anytime the widget editor form gets submitted. + public trySubmit = (widget: IWidget): Observable => { + // Since we are working asynchronously, we'll return a subject. So, after the submit attempt + // succeeds or fails, we can let the subscriber know the result. + const subject = new Subject(); + + if (!widget.id) { + // Creates an id if the widget has no id. + // (This step will make more sense in the context of the widget cloning tutorial + // in which we handle the persistence of a newly created widget.) + widget.id = uuid(); + } + + // For this example, we're using a setTimeout to mock an asynchronous persistence request to a backend + setTimeout(() => { + if (this.persistenceSucceeded) { + // Passes along the new widget after one second. + subject.next(widget); + // Toast on the page on success. + this.toastService.success({ + title: $localize\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\`Submit succeeded.\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\`, + }); + } else { + const errorText = $localize\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\`Submit failed.\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\`; + // Toast on the page on failure. + this.toastService.error({ title: errorText }); + // Makes the subject say there is an error. + subject.error(errorText); + } + // Completes the subject so whoever subscribes to it knows its finished. + subject.complete(); + }, 1000); + + // Returns the subject as an observable. + return subject.asObservable(); + }; + + // This method will be invoked anytime there's a widget removal attempt. + public tryRemove = (widgetId: string): Observable => { + const subject = new Subject(); + + setTimeout(() => { + if (this.persistenceSucceeded) { + // Pass through the id of the widget that was removed. + subject.next(widgetId); + this.toastService.success({ + title: $localize\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\`Removal succeeded.\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\`, + }); + } else { + const errorText = $localize\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\`Removal failed.\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\`; + this.toastService.error({ title: errorText }); + subject.error(errorText); + } + subject.complete(); + }, 1000); + + return subject.asObservable(); + }; +} + +/** + * A component that instantiates the dashboard + */ +@Component({ + selector: "persistence-handler-setup", + templateUrl: "./persistence-handler-setup.component.html", + styleUrls: ["./persistence-handler-setup.component.less"], + // Here we provide our persistence handler at the component level; this can also be done in the module. + providers: [PersistenceHandler], + standalone: false, +}) +export class PersistenceHandlerSetupComponent implements OnInit { + // This variable will hold all the data needed to define the layout and behavior of the widgets. + // Pass this to the dashboard component's dashboard input in the template. + public dashboard: IDashboard | undefined; + + // Angular gridster requires a configuration object even if it's empty. + // Pass this to the dashboard component's gridsterConfig input in the template. + public gridsterConfig: GridsterConfig = {}; + + // Boolean which dashboard takes in as an input if its true it allows you to move widgets around. + public editMode: boolean = false; + + constructor( + // WidgetTypesService provides the widget's necessary structure information + private widgetTypesService: WidgetTypesService, + + // In general, the ProviderRegistryService is used for making entities available for injection into dynamically loaded components. + private providerRegistry: ProviderRegistryService, + + // We are injecting the PersistenceHandler we created and assigning it to a property we use in the template. + public persistenceHandler: PersistenceHandler, + private changeDetectorRef: ChangeDetectorRef + ) {} + + public ngOnInit(): void { + // Grabbing the widget's default template which will be needed as a parameter for setNode + const widgetTemplate = this.widgetTypesService.getWidgetType("kpi", 1); + // Registering our data sources as dropdown options in the widget editor/configurator + // Note: This could also be done in the parent module's constructor so that + // multiple dashboards could have access to the same widget template modification. + this.widgetTypesService.setNode( + // This is the template we grabbed above with getWidgetType + widgetTemplate, + // We are setting the editor/configurator part of the widget template + "configurator", + // This indicates which node you are changing and we want to change + // the data source providers available for selection in the editor. + WellKnownPathKey.DataSourceProviders, + // We are setting the data sources available for selection in the editor + [ + AverageRatingKpiDataSource.providerId, + RatingsCountKpiDataSource.providerId, + ] + ); + + // Registering the data sources available for injection into the KPI tiles. + // Note: Each tile of a KPI widget is assigned its own instance of a data source + this.providerRegistry.setProviders({ + [AverageRatingKpiDataSource.providerId]: { + provide: DATA_SOURCE, + useClass: AverageRatingKpiDataSource, + // Any dependencies that need to be injected into the provider must be listed here + deps: [HttpClient], + }, + [RatingsCountKpiDataSource.providerId]: { + provide: DATA_SOURCE, + useClass: RatingsCountKpiDataSource, + deps: [HttpClient], + }, + }); + + this.initializeDashboard(); + } + + /** Used for restoring widgets state */ + public reInitializeDashboard(): void { + // destroys the components and their providers so the dashboard can re init data + this.dashboard = undefined; + this.changeDetectorRef.detectChanges(); + + this.initializeDashboard(); + } + + public initializeDashboard(): void { + // We're using a static configuration object for this example (see widgetConfig at the bottom of the file), + // but this is where the widget's configuration could potentially be populated from a database + const kpiWidget = widgetConfig; + const widgetIndex: IWidgets = { + // Complete the KPI widget with information coming from its type definition + [kpiWidget.id]: + this.widgetTypesService.mergeWithWidgetType(kpiWidget), + }; + + // Setting the widget dimensions and position (this is for gridster) + const positions: Record = { + [kpiWidget.id]: { + cols: 4, + rows: 6, + y: 0, + x: 0, + }, + }; + + // Finally, assigning the variables we created above to the dashboard + this.dashboard = { + positions, + widgets: widgetIndex, + }; + } +} + +/** + * A simple KPI data source to retrieve the average rating of Harry Potter and the Sorcerer's Stone (book) via googleapis + */ +@Injectable() +export class AverageRatingKpiDataSource + extends DataSourceService + implements OnDestroy +{ + // This is the ID we'll use to identify the provider + public static providerId = "AverageRatingKpiDataSource"; + + // Use this subject to communicate the data source's busy state + public busy = new BehaviorSubject(false); + + constructor(private http: HttpClient) { + super(); + } + + // In this example, getFilteredData is invoked every 10 minutes (Take a look at the refresher + // provider definition in the widget configuration below to see how the interval is set) + public async getFilteredData(): Promise { + this.busy.next(true); + return new Promise((resolve) => { + // *** Make a resource request to an external API (if needed) + this.http + .get("https://www.googleapis.com/books/v1/volumes/5MQFrgEACAAJ") + .pipe(finalize(() => this.busy.next(false))) + .subscribe({ + next: (data: any) => { + resolve({ + result: { + value: data.volumeInfo.averageRating, + }, + }); + }, + error: (error: HttpErrorResponse) => { + resolve({ + result: null, + error: { + type: error.status, + }, + }); + }, + }); + }); + } + + public ngOnDestroy(): void { + this.outputsSubject.complete(); + } +} + +/** + * A simple KPI data source to retrieve the ratings count of Harry Potter and the Sorcerer's Stone (book) via googleapis + */ +@Injectable() +export class RatingsCountKpiDataSource + extends DataSourceService + implements OnDestroy +{ + public static providerId = "RatingsCountKpiDataSource"; + + // Use this subject to communicate the data source's busy state + public busy = new BehaviorSubject(false); + + constructor(private http: HttpClient) { + super(); + } + + public async getFilteredData(): Promise { + this.busy.next(true); + return new Promise((resolve) => { + this.http + .get("https://www.googleapis.com/books/v1/volumes/5MQFrgEACAAJ") + .pipe(finalize(() => this.busy.next(false))) + .subscribe({ + next: (data: any) => { + resolve({ + result: { + value: data.volumeInfo.ratingsCount, + }, + }); + }, + error: (error: HttpErrorResponse) => { + resolve({ + result: null, + error: { + type: error.status, + }, + }); + }, + }); + }); + } + + public ngOnDestroy(): void { + this.outputsSubject.complete(); + } +} + +const widgetConfig: IWidget = { + id: "widget1", + type: "kpi", + pizzagna: { + [PizzagnaLayer.Configuration]: { + [DEFAULT_PIZZAGNA_ROOT]: { + providers: { + [WellKnownProviders.Refresher]: { + properties: { + // Configuring the refresher interval so that our data source is invoked every ten minutes + interval: 60 * 10, + enabled: true, + } as IRefresherProperties, + } as Partial, + }, + }, + header: { + properties: { + title: "Harry Potter and the Sorcerer's Stone", + subtitle: "By J. K. Rowling", + }, + }, + tiles: { + properties: { + nodes: ["kpi1"], + }, + }, + kpi1: { + id: "kpi1", + componentType: KpiComponent.lateLoadKey, + properties: { + widgetData: { + units: "out of 5 Stars", + label: "Average Rating", + }, + }, + providers: { + [WellKnownProviders.DataSource]: { + // Setting the data source providerId for the tile with id "kpi1" + providerId: AverageRatingKpiDataSource.providerId, + } as IProviderConfiguration, + [WellKnownProviders.Adapter]: { + providerId: NOVA_KPI_DATASOURCE_ADAPTER, + properties: { + componentId: "kpi1", + propertyPath: "widgetData", + }, + } as IProviderConfiguration, + }, + }, + }, + }, +}; +\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\`, + "tutorials/persistence-handler-setup/persistence-handler-setup.module.ts": \\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\`// © 2022 SolarWinds Worldwide, LLC. All rights reserved. +// +// Permission is hereby granted, free of charge, to any person obtaining a copy +// of this software and associated documentation files (the "Software"), to +// deal in the Software without restriction, including without limitation the +// rights to use, copy, modify, merge, publish, distribute, sublicense, and/or +// sell copies of the Software, and to permit persons to whom the Software is +// furnished to do so, subject to the following conditions: +// +// The above copyright notice and this permission notice shall be included in +// all copies or substantial portions of the Software. +// +// THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR +// IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, +// FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE +// AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER +// LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, +// OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN +// THE SOFTWARE. + +import { CommonModule } from "@angular/common"; +import { HttpClientModule } from "@angular/common/http"; +import { NgModule } from "@angular/core"; +import { RouterModule } from "@angular/router"; + +import { + NuiButtonModule, + NuiDocsModule, + NuiMessageModule, + NuiSwitchModule, + NuiToastModule, + DEMO_PATH_TOKEN, +} from "@nova-ui/bits"; +import { NuiDashboardsModule } from "@nova-ui/dashboards"; + +import { PersistenceHandlerSetupDocsComponent } from "./persistence-handler-setup-docs.component"; +import { PersistenceHandlerSetupComponent } from "./persistence-handler-setup.component"; +import { getDemoFiles } from "../../../../demo-files-factory"; + +const routes = [ + { + path: "", + component: PersistenceHandlerSetupDocsComponent, + data: { + srlc: { + hideIndicator: true, + }, + showThemeSwitcher: true, + }, + }, + { + path: "example", + component: PersistenceHandlerSetupComponent, + data: { + srlc: { + hideIndicator: true, + }, + }, + }, +]; + +@NgModule({ + imports: [ + CommonModule, + HttpClientModule, + NuiDashboardsModule, + NuiDocsModule, + NuiMessageModule, + NuiSwitchModule, + NuiToastModule, + NuiButtonModule, + RouterModule.forChild(routes), + ], + declarations: [ + PersistenceHandlerSetupDocsComponent, + PersistenceHandlerSetupComponent, + ], + providers: [ + { + provide: DEMO_PATH_TOKEN, + useValue: getDemoFiles("persistence-handler-setup"), + }, + ], +}) +export default class PersistenceHandlerSetupModule {} +\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\`, + "tutorials/tutorials.module.ts": \\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\`// © 2022 SolarWinds Worldwide, LLC. All rights reserved. +// +// Permission is hereby granted, free of charge, to any person obtaining a copy +// of this software and associated documentation files (the "Software"), to +// deal in the Software without restriction, including without limitation the +// rights to use, copy, modify, merge, publish, distribute, sublicense, and/or +// sell copies of the Software, and to permit persons to whom the Software is +// furnished to do so, subject to the following conditions: +// +// The above copyright notice and this permission notice shall be included in +// all copies or substantial portions of the Software. +// +// THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR +// IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, +// FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE +// AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER +// LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, +// OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN +// THE SOFTWARE. + +import { NgModule, Type } from "@angular/core"; +import { RouterModule, Routes } from "@angular/router"; + +import { ConfiguratorHeadingService } from "@nova-ui/dashboards"; + +export enum TutorialsModuleRoute { + HelloDashboards = "hello-dashboards", + DataSource = "data-source-setup", + WidgetEditor = "widget-editor-setup", + SubmitHandler = "persistence-handler-setup", + WidgetCreation = "widget-creation", + Customization = "customization", + WidgetErrorHandling = "widget-error-handling", + DynamicHeaderLinks = "dynamic-header-links", +} + +const routes: Routes = [ + { + path: TutorialsModuleRoute.HelloDashboards, + loadChildren: async () => + import( + "./hello-dashboards/hello-dashboards.module" + ) as object as Promise>, + }, + { + path: TutorialsModuleRoute.DataSource, + loadChildren: async () => + import( + "./data-source-setup/data-source-setup.module" + ) as object as Promise>, + }, + { + path: TutorialsModuleRoute.WidgetEditor, + loadChildren: async () => + import( + "./widget-editor-setup/widget-editor-setup.module" + ) as object as Promise>, + }, + { + path: TutorialsModuleRoute.SubmitHandler, + loadChildren: async () => + import( + "./persistence-handler-setup/persistence-handler-setup.module" + ) as object as Promise>, + }, + { + path: TutorialsModuleRoute.WidgetCreation, + loadChildren: async () => + import( + "./widget-creation/widget-creation.module" + ) as object as Promise>, + }, + { + path: TutorialsModuleRoute.Customization, + loadChildren: async () => + import("./customization/customization.module") as object as Promise< + Type + >, + }, + { + path: TutorialsModuleRoute.WidgetErrorHandling, + loadChildren: async () => + import( + "./widget-error-handling/widget-error-handling.module" + ) as object as Promise>, + }, + { + path: TutorialsModuleRoute.DynamicHeaderLinks, + loadChildren: async () => + import( + "./dynamic-header-links/dynamic-header-links-docs.module" + ) as object as Promise>, + }, +]; + +@NgModule({ + imports: [RouterModule.forChild(routes)], + providers: [ConfiguratorHeadingService], +}) +export default class TutorialsModule {} +\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\`, + "tutorials/widget-creation/widget-creation-docs.component.ts": \\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\`// © 2022 SolarWinds Worldwide, LLC. All rights reserved. +// +// Permission is hereby granted, free of charge, to any person obtaining a copy +// of this software and associated documentation files (the "Software"), to +// deal in the Software without restriction, including without limitation the +// rights to use, copy, modify, merge, publish, distribute, sublicense, and/or +// sell copies of the Software, and to permit persons to whom the Software is +// furnished to do so, subject to the following conditions: +// +// The above copyright notice and this permission notice shall be included in +// all copies or substantial portions of the Software. +// +// THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR +// IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, +// FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE +// AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER +// LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, +// OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN +// THE SOFTWARE. + +import { Component } from "@angular/core"; + +@Component({ + selector: "nui-dashboard-widget-creation-docs", + templateUrl: "./widget-creation-docs.component.html", + standalone: false, +}) +export class WidgetCreationDocsComponent {} +\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\`, + "tutorials/widget-creation/widget-creation.component.ts": \\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\`// © 2022 SolarWinds Worldwide, LLC. All rights reserved. +// +// Permission is hereby granted, free of charge, to any person obtaining a copy +// of this software and associated documentation files (the "Software"), to +// deal in the Software without restriction, including without limitation the +// rights to use, copy, modify, merge, publish, distribute, sublicense, and/or +// sell copies of the Software, and to permit persons to whom the Software is +// furnished to do so, subject to the following conditions: +// +// The above copyright notice and this permission notice shall be included in +// all copies or substantial portions of the Software. +// +// THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR +// IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, +// FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE +// AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER +// LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, +// OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN +// THE SOFTWARE. + +import { HttpClient, HttpErrorResponse } from "@angular/common/http"; +import { + Component, + EventEmitter, + Injectable, + OnDestroy, + OnInit, + Output, + ViewChild, +} from "@angular/core"; +import { GridsterConfig, GridsterItem } from "angular-gridster2"; +import { BehaviorSubject, Observable, Subject } from "rxjs"; +import { finalize, take, takeUntil } from "rxjs/operators"; + +import { + DataSourceService, + IFilteringOutputs, + ToastService, + uuid, +} from "@nova-ui/bits"; +import { + DashboardComponent, + DATA_SOURCE, + DEFAULT_PIZZAGNA_ROOT, + IDashboard, + IDashboardPersistenceHandler, + IDataSourceOutput, + IKpiData, + IProviderConfiguration, + IRefresherProperties, + IWidget, + IWidgets, + IWidgetSelector, + IWidgetTemplateSelector, + KpiComponent, + NOVA_KPI_DATASOURCE_ADAPTER, + PizzagnaLayer, + ProviderRegistryService, + WellKnownPathKey, + WellKnownProviders, + WidgetClonerService, + WidgetTypesService, +} from "@nova-ui/dashboards"; + +// Interface of a widget item +interface IWidgetItem { + name: string; + widget: IWidget; +} + +// This component acts as the first step, or page, in the wizard where the user selects a wizard type to create. +// It's recommended to have this component in a different file. For this tutorial, it's included in the same +// file for simplicity. +@Component({ + selector: "widget-template-selection", + styleUrls: ["./widget-creation.component.less"], + template: \\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\` +
+ + +
+ + +
+
{{ item.name }}
+
+
+ \\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\`, + standalone: false, +}) +export class WidgetTemplateSelectionComponent + implements IWidgetTemplateSelector, OnInit +{ + // This output will notify the wizard that a widget has been selected. + @Output() public widgetSelected = new EventEmitter(); + + public widgetItems: IWidgetItem[] = []; + public widgetSelection: IWidgetItem[]; + + constructor(private widgetTypesService: WidgetTypesService) {} + + public ngOnInit(): void { + // Here we combine the widget structure from the WidgetTypesService with the corresponding widget + // configuration to create an array of widget objects for the itemSource on the repeat component. + this.widgetItems = [ + { + name: "Fully Configured KPI Widget", + widget: this.widgetTypesService.mergeWithWidgetType( + fullKpiWidgetConfig + ), + }, + { + name: "Unconfigured Proportional Widget", + // Note that 'partialPropWidgetConfig' sets 'metadata.needsConfiguration' to true. + // When this widget is selected in the wizard, the 'Create Widget' button will be hidden + // to guide the user to the second step where they can complete the configuration. + widget: this.widgetTypesService.mergeWithWidgetType( + partialPropWidgetConfig + ), + }, + ]; + + // You can optionally auto-select a widget by doing the following + // this.onSelect([this.widgetItems[0]]); + } + + public onSelect(selectedItems: any[]): void { + // We emit the selected widget to communicate the selection to the configurator + this.widgetSelected.emit(selectedItems[0].widget); + this.widgetSelection = selectedItems; + } +} + +/** + * A simple persistence handler that is tied to the widget editor directive + */ +@Injectable() +// The realizer of IDashboardPersistenceHandler may implement a trySubmit and/or a tryRemove method. +export class PersistenceHandler implements IDashboardPersistenceHandler { + // This variable is just to show how to handle error handling. + private persistenceSucceeded: boolean = true; + + // The example uses the toast service to demonstrate the + // invocation of each of the persistence handler callbacks + constructor(private toastService: ToastService) { + // toastService options to let it sit on the page for 2 seconds. + this.toastService.setConfig({ + timeOut: 2000, + }); + } + + // This method will be invoked anytime the widget editor form gets submitted. + public trySubmit = (widget: IWidget): Observable => { + // Since we are working asynchronously, we'll return a subject. So, after the submit attempt + // succeeds or fails, we can let the subscriber know the result. + const subject = new Subject(); + + if (!widget.id) { + // Creates an id if the widget has no id. + // (This step will make more sense in the context of the widget cloning tutorial + // in which we handle the persistence of a newly created widget.) + widget.id = uuid(); + } + + // For this example, we're using a setTimeout to mock an asynchronous persistence request to a backend + setTimeout(() => { + if (this.persistenceSucceeded) { + // Passes along the new widget after one second. + subject.next(widget); + // Toast on the page on success. + this.toastService.success({ + title: $localize\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\`Submit succeeded.\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\`, + }); + } else { + const errorText = $localize\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\`Submit failed.\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\`; + // Toast on the page on failure. + this.toastService.error({ title: errorText }); + // Makes the subject say there is an error. + subject.error(errorText); + } + // Completes the subject so whoever subscribes to it knows its finished. + subject.complete(); + }, 1000); + + // Returns the subject as an observable. + return subject.asObservable(); + }; + + // This method will be invoked anytime there's a widget removal attempt. + public tryRemove = (widgetId: string): Observable => { + const subject = new Subject(); + + setTimeout(() => { + if (this.persistenceSucceeded) { + // Pass through the id of the widget that was removed. + subject.next(widgetId); + this.toastService.success({ + title: $localize\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\`Removal success\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\`, + }); + } else { + const errorText = $localize\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\`Removal failed.\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\`; + this.toastService.error({ title: errorText }); + subject.error(errorText); + } + subject.complete(); + }, 1000); + + return subject.asObservable(); + }; +} + +/** + * A component that instantiates the dashboard + */ +@Component({ + selector: "widget-creation", + templateUrl: "./widget-creation.component.html", + styleUrls: ["./widget-creation.component.less"], + // Here we provide our persistence handler at the component level; this can also be done in the module. + providers: [PersistenceHandler], + standalone: false, +}) +export class WidgetCreationComponent implements OnInit { + // The WidgetClonerService will need this for updating the dashboard + @ViewChild(DashboardComponent, { static: true }) + dashboardComponent: DashboardComponent; + // This variable will hold all the data needed to define the layout and behavior of the widgets. + // Pass this to the dashboard component's dashboard input in the template. + public dashboard: IDashboard; + + // Angular gridster requires a configuration object even if it's empty. + // Pass this to the dashboard component's gridsterConfig input in the template. + public gridsterConfig: GridsterConfig = { + // These values will be used to set the initial widget dimensions on creation. + // If not set, they each default to 6. + defaultItemCols: 3, + defaultItemRows: 5, + }; + + // Boolean the dashboard takes in as an input; if it's set to true + // the dashboard allows you to resize widgets and move them around. + public editMode: boolean = false; + + // Subject used for auto-unsubscribing from subscriptions on component destruction + private readonly destroy$ = new Subject(); + + constructor( + // WidgetTypesService provides the widget's necessary structure information + private widgetTypesService: WidgetTypesService, + + // In general, the ProviderRegistryService is used for making entities available for injection into dynamically loaded components. + private providerRegistry: ProviderRegistryService, + + // Injecting the PersistenceHandler we created and assigning it to a property we use in the template. + public persistenceHandler: PersistenceHandler, + + // Injecting the cloner service which is needed for opening up the cloner wizard. + private widgetClonerService: WidgetClonerService + ) {} + + public ngOnInit(): void { + // Grabbing the widget's default template which will be needed as a parameter for setNode + const kpiTemplate = this.widgetTypesService.getWidgetType("kpi", 1); + const proportionalTemplate = this.widgetTypesService.getWidgetType( + "proportional", + 1 + ); + + // Registering our data sources as dropdown options in the widget editor/configurator + // Note: This could also be done in the parent module's constructor so that + // multiple dashboards could have access to the same widget template modification. + this.widgetTypesService.setNode( + // This is the template we grabbed above with getWidgetType + proportionalTemplate, + // Setting the editor/configurator part of the widget template + "configurator", + // This indicates which node you are changing and we want to change + // the data source providers available for selection in the editor. + WellKnownPathKey.DataSourceProviders, + // Setting the data sources available for selection in the editor + [RandomCitiesProportionalDataSource.providerId] + ); + + // Same as above, but for the KPI data sources + this.widgetTypesService.setNode( + kpiTemplate, + "configurator", + WellKnownPathKey.DataSourceProviders, + [ + AverageRatingKpiDataSource.providerId, + RatingsCountKpiDataSource.providerId, + ] + ); + + // Registering the data sources available for injection into the KPI tiles and proportional widget. + // Note: Each tile of a KPI widget is assigned its own instance of a data source. + this.providerRegistry.setProviders({ + [AverageRatingKpiDataSource.providerId]: { + provide: DATA_SOURCE, + useClass: AverageRatingKpiDataSource, + // Any dependencies that need to be injected into the provider must be listed here + deps: [HttpClient], + }, + [RatingsCountKpiDataSource.providerId]: { + provide: DATA_SOURCE, + useClass: RatingsCountKpiDataSource, + deps: [HttpClient], + }, + [RandomCitiesProportionalDataSource.providerId]: { + provide: DATA_SOURCE, + useClass: RandomCitiesProportionalDataSource, + deps: [], + }, + }); + + this.initializeDashboard(); + } + + public onCreateWidget(): void { + const widgetSelector: IWidgetSelector = { + // Template ref of the dashboard component. + dashboardComponent: this.dashboardComponent, + // A trySubmit function; in this case, we use the trySubmit from the PersistenceHandler created in the previous tutorial. + trySubmit: this.persistenceHandler.trySubmit, + // WidgetTemplateSelectionComponent will act as step one of the wizard to allow the user to select which widget will be cloned. + widgetSelectionComponentType: WidgetTemplateSelectionComponent, + }; + this.widgetClonerService + .open(widgetSelector) + .pipe( + // Auto-unsubscribe after one emission or on component destruction + take(1), + takeUntil(this.destroy$) + ) + .subscribe(); + } + + public initializeDashboard(): void { + // We're using a static configuration object for this example (see widgetConfig at the bottom of the file), + // but this is where the widget's configuration could potentially be populated from a database + const kpiWidget = fullKpiWidgetConfig; + const widgetIndex: IWidgets = { + // Complete the KPI widget with information coming from its type definition + [kpiWidget.id]: + this.widgetTypesService.mergeWithWidgetType(kpiWidget), + }; + + // Setting the widget dimensions and position (this is for gridster) + // Note: If no position is given for a widget the 'defaultItemCols' and 'defaultItemRows' properties + // from the gridsterConfig will be used for the dimensions + const positions: Record = { + [kpiWidget.id]: { + cols: 3, + rows: 5, + y: 0, + x: 0, + }, + }; + + // Finally, assigning the variables we created above to the dashboard + this.dashboard = { + positions, + widgets: widgetIndex, + }; + } +} + +// Interface for each data point in a proportional widget. +interface IProportionalWidgetData { + id: string; + name: string; + data: number[]; + icon: string; + link: string; + value: string; +} + +@Injectable() +export class RandomCitiesProportionalDataSource implements OnDestroy { + public static providerId = "RandomCitiesProportionalDataSource"; + + public outputsSubject = new Subject< + IDataSourceOutput + >(); + + // Every time applyFilters gets ran we are changing the data source. + public applyFilters(): void { + setTimeout(() => { + this.outputsSubject.next({ + result: this.getRandomProportionalWidgetData(), + }); + }, 1000); + } + + public ngOnDestroy(): void { + this.outputsSubject.complete(); + } + + private getRandomProportionalWidgetData(): IProportionalWidgetData[] { + return [ + { + id: "Down", + name: "Down", + data: [Math.round(Math.random() * 100)], + icon: "status_down", + link: "https://en.wikipedia.org/wiki/Brno", + value: "Brno", + }, + { + id: "Critical", + name: "Critical", + data: [Math.round(Math.random() * 100)], + icon: "status_critical", + link: "https://en.wikipedia.org/wiki/Kyiv", + value: "Kyiv", + }, + { + id: "Warning", + name: "Warning", + data: [Math.round(Math.random() * 100)], + icon: "status_warning", + link: "https://en.wikipedia.org/wiki/Austin", + value: "Austin", + }, + { + id: "Unknown", + name: "Unknown", + data: [Math.round(Math.random() * 100)], + icon: "status_unknown", + link: "https://en.wikipedia.org/wiki/Lisbon", + value: "Lisbon", + }, + { + id: "Up", + name: "Up", + data: [Math.round(Math.random() * 100)], + icon: "status_up", + link: "https://en.wikipedia.org/wiki/Sydney", + value: "Sydney", + }, + { + id: "Unmanaged", + name: "Unmanaged", + data: [Math.round(Math.random() * 100)], + icon: "status_unmanaged", + link: "https://en.wikipedia.org/wiki/Nur-Sultan", + value: "Nur-Sultan", + }, + ]; + } +} + +/** + * A simple KPI data source to retrieve the average rating of Harry Potter and the Sorcerer's Stone (book) via googleapis + */ +@Injectable() +export class AverageRatingKpiDataSource + extends DataSourceService + implements OnDestroy +{ + // This is the ID we'll use to identify the provider + public static providerId = "AverageRatingKpiDataSource"; + + // Use this subject to communicate the data source's busy state + public busy = new BehaviorSubject(false); + + constructor(private http: HttpClient) { + super(); + } + + // In this example, getFilteredData is invoked every 10 minutes (Take a look at the refresher + // provider definition in the widget configuration below to see how the interval is set) + public async getFilteredData(): Promise { + this.busy.next(true); + return new Promise((resolve) => { + // *** Make a resource request to an external API (if needed) + this.http + .get("https://www.googleapis.com/books/v1/volumes/5MQFrgEACAAJ") + .pipe(finalize(() => this.busy.next(false))) + .subscribe({ + next: (data: any) => { + resolve({ + result: { + value: data.volumeInfo.averageRating, + }, + }); + }, + error: (error: HttpErrorResponse) => { + resolve({ + result: null, + error: { + type: error.status, + }, + }); + }, + }); + }); + } + + public ngOnDestroy(): void { + this.outputsSubject.complete(); + } +} + +/** + * A simple KPI data source to retrieve the ratings count of Harry Potter and the Sorcerer's Stone (book) via googleapis + */ +@Injectable() +export class RatingsCountKpiDataSource + extends DataSourceService + implements OnDestroy +{ + public static providerId = "RatingsCountKpiDataSource"; + + // Use this subject to communicate the data source's busy state + public busy = new BehaviorSubject(false); + + constructor(private http: HttpClient) { + super(); + } + + public async getFilteredData(): Promise { + this.busy.next(true); + return new Promise((resolve) => { + this.http + .get("https://www.googleapis.com/books/v1/volumes/5MQFrgEACAAJ") + .pipe(finalize(() => this.busy.next(false))) + .subscribe({ + next: (data: any) => { + resolve({ + result: { + value: data.volumeInfo.ratingsCount, + }, + }); + }, + error: (error: HttpErrorResponse) => { + resolve({ + result: null, + error: { + type: error.status, + }, + }); + }, + }); + }); + } + + public ngOnDestroy(): void { + this.outputsSubject.complete(); + } +} + +const fullKpiWidgetConfig: IWidget = { + id: "widget1", + type: "kpi", + pizzagna: { + [PizzagnaLayer.Configuration]: { + [DEFAULT_PIZZAGNA_ROOT]: { + providers: { + [WellKnownProviders.Refresher]: { + properties: { + // Configuring the refresher interval so that our data source is invoked every ten minutes + interval: 60 * 10, + enabled: true, + } as IRefresherProperties, + } as Partial, + }, + }, + header: { + properties: { + title: "Harry Potter and the Sorcerer's Stone", + subtitle: "By J. K. Rowling", + }, + }, + tiles: { + properties: { + nodes: ["kpi1"], + }, + }, + kpi1: { + id: "kpi1", + componentType: KpiComponent.lateLoadKey, + properties: { + widgetData: { + units: \\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\`out of 5 Stars\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\`, + label: \\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\`Average Rating\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\`, + }, + }, + providers: { + [WellKnownProviders.DataSource]: { + // Setting the data source providerId for the tile with id "kpi1" + providerId: AverageRatingKpiDataSource.providerId, + } as IProviderConfiguration, + [WellKnownProviders.Adapter]: { + providerId: NOVA_KPI_DATASOURCE_ADAPTER, + properties: { + componentId: "kpi1", + propertyPath: "widgetData", + }, + } as IProviderConfiguration, + }, + }, + }, + }, +}; + +const partialPropWidgetConfig: IWidget = { + id: "widget2", + type: "proportional", + metadata: { + // Set 'needsConfiguration' to true if the widget needs further configuration before it can be + // placed on the dashboard. The "Create Widget" button will be hidden in the wizard when this + // widget is selected. + needsConfiguration: true, + }, + pizzagna: { + [PizzagnaLayer.Configuration]: { + header: { + properties: { + title: "*New Proportional Widget*", + }, + }, + }, + }, +}; +\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\`, + "tutorials/widget-creation/widget-creation.module.ts": \\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\`// © 2022 SolarWinds Worldwide, LLC. All rights reserved. +// +// Permission is hereby granted, free of charge, to any person obtaining a copy +// of this software and associated documentation files (the "Software"), to +// deal in the Software without restriction, including without limitation the +// rights to use, copy, modify, merge, publish, distribute, sublicense, and/or +// sell copies of the Software, and to permit persons to whom the Software is +// furnished to do so, subject to the following conditions: +// +// The above copyright notice and this permission notice shall be included in +// all copies or substantial portions of the Software. +// +// THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR +// IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, +// FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE +// AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER +// LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, +// OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN +// THE SOFTWARE. + +import { CommonModule } from "@angular/common"; +import { HttpClientModule } from "@angular/common/http"; +import { NgModule } from "@angular/core"; +import { RouterModule } from "@angular/router"; + +import { + NuiButtonModule, + NuiDocsModule, + NuiImageModule, + NuiMessageModule, + NuiRepeatModule, + NuiSwitchModule, + NuiToastModule, + DEMO_PATH_TOKEN, +} from "@nova-ui/bits"; +import { NuiDashboardsModule } from "@nova-ui/dashboards"; + +import { WidgetCreationDocsComponent } from "./widget-creation-docs.component"; +import { + WidgetCreationComponent, + WidgetTemplateSelectionComponent, +} from "./widget-creation.component"; +import { getDemoFiles } from "../../../../demo-files-factory"; + +const routes = [ + { + path: "", + component: WidgetCreationDocsComponent, + data: { + srlc: { + hideIndicator: true, + }, + showThemeSwitcher: true, + }, + }, + { + path: "example", + component: WidgetCreationComponent, + data: { + srlc: { + hideIndicator: true, + }, + }, + }, +]; + +@NgModule({ + imports: [ + CommonModule, + HttpClientModule, + NuiDashboardsModule, + NuiDocsModule, + NuiMessageModule, + NuiSwitchModule, + NuiToastModule, + NuiButtonModule, + NuiRepeatModule, + NuiImageModule, + RouterModule.forChild(routes), + ], + declarations: [ + WidgetCreationDocsComponent, + WidgetCreationComponent, + WidgetTemplateSelectionComponent, + ], + providers: [ + { + provide: DEMO_PATH_TOKEN, + useValue: getDemoFiles("widget-creation"), + }, + ], +}) +export default class WidgetCreationModule {} +\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\`, + "tutorials/widget-editor-setup/widget-editor-setup-docs.component.ts": \\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\`// © 2022 SolarWinds Worldwide, LLC. All rights reserved. +// +// Permission is hereby granted, free of charge, to any person obtaining a copy +// of this software and associated documentation files (the "Software"), to +// deal in the Software without restriction, including without limitation the +// rights to use, copy, modify, merge, publish, distribute, sublicense, and/or +// sell copies of the Software, and to permit persons to whom the Software is +// furnished to do so, subject to the following conditions: +// +// The above copyright notice and this permission notice shall be included in +// all copies or substantial portions of the Software. +// +// THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR +// IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, +// FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE +// AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER +// LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, +// OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN +// THE SOFTWARE. + +import { Component } from "@angular/core"; + +@Component({ + selector: "nui-dashboard-widget-editor-docs", + templateUrl: "./widget-editor-setup-docs.component.html", + standalone: false, +}) +export class WidgetEditorDocsComponent {} +\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\`, + "tutorials/widget-editor-setup/widget-editor-setup.component.ts": \\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\`// © 2022 SolarWinds Worldwide, LLC. All rights reserved. +// +// Permission is hereby granted, free of charge, to any person obtaining a copy +// of this software and associated documentation files (the "Software"), to +// deal in the Software without restriction, including without limitation the +// rights to use, copy, modify, merge, publish, distribute, sublicense, and/or +// sell copies of the Software, and to permit persons to whom the Software is +// furnished to do so, subject to the following conditions: +// +// The above copyright notice and this permission notice shall be included in +// all copies or substantial portions of the Software. +// +// THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR +// IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, +// FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE +// AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER +// LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, +// OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN +// THE SOFTWARE. + +import { HttpClient, HttpErrorResponse } from "@angular/common/http"; +import { + ChangeDetectorRef, + Component, + Injectable, + OnDestroy, + OnInit, +} from "@angular/core"; +import { GridsterConfig, GridsterItem } from "angular-gridster2"; +import { BehaviorSubject } from "rxjs"; +import { finalize } from "rxjs/operators"; + +import { DataSourceService, IFilteringOutputs } from "@nova-ui/bits"; +import { + DATA_SOURCE, + DEFAULT_PIZZAGNA_ROOT, + IDashboard, + IKpiData, + IProviderConfiguration, + IRefresherProperties, + IWidget, + IWidgets, + KpiComponent, + NOVA_KPI_DATASOURCE_ADAPTER, + PizzagnaLayer, + ProviderRegistryService, + WellKnownPathKey, + WellKnownProviders, + WidgetTypesService, +} from "@nova-ui/dashboards"; + +/** + * A simple KPI data source to retrieve the average rating of Harry Potter and the Sorcerer's Stone (book) via googleapis + */ +@Injectable() +export class AverageRatingKpiDataSource + extends DataSourceService + implements OnDestroy +{ + // This is the ID we'll use to identify the provider + public static providerId = "AverageRatingKpiDataSource"; + + // Use this subject to communicate the data source's busy state + public busy = new BehaviorSubject(false); + + constructor(private http: HttpClient) { + super(); + } + + // In this example, getFilteredData is invoked every 10 minutes (Take a look at the refresher + // provider definition in the widget configuration below to see how the interval is set) + public async getFilteredData(): Promise { + this.busy.next(true); + return new Promise((resolve) => { + // *** Make a resource request to an external API (if needed) + this.http + .get("https://www.googleapis.com/books/v1/volumes/5MQFrgEACAAJ") + .pipe(finalize(() => this.busy.next(false))) + .subscribe({ + next: (data: any) => { + resolve({ + result: { + value: data.volumeInfo.averageRating, + }, + }); + }, + error: (error: HttpErrorResponse) => { + resolve({ + result: null, + error: { + type: error.status, + }, + }); + }, + }); + }); + } + + public ngOnDestroy(): void { + this.outputsSubject.complete(); + } +} + +/** + * A simple KPI data source to retrieve the ratings count of Harry Potter and the Sorcerer's Stone (book) via googleapis + */ +@Injectable() +export class RatingsCountKpiDataSource + extends DataSourceService + implements OnDestroy +{ + public static providerId = "RatingsCountKpiDataSource"; + + // Use this subject to communicate the data source's busy state + public busy = new BehaviorSubject(false); + + constructor(private http: HttpClient) { + super(); + } + + public async getFilteredData(): Promise { + this.busy.next(true); + return new Promise((resolve) => { + this.http + .get("https://www.googleapis.com/books/v1/volumes/5MQFrgEACAAJ") + .pipe(finalize(() => this.busy.next(false))) + .subscribe({ + next: (data: any) => { + resolve({ + result: { + value: data.volumeInfo.ratingsCount, + }, + }); + }, + error: (error: HttpErrorResponse) => { + resolve({ + result: null, + error: { + type: error.status, + }, + }); + }, + }); + }); + } + + public ngOnDestroy(): void { + this.outputsSubject.complete(); + } +} + +/** + * A component that instantiates the dashboard + */ +@Component({ + selector: "widget-editor-setup", + templateUrl: "./widget-editor-setup.component.html", + styleUrls: ["./widget-editor-setup.component.less"], + standalone: false, +}) +export class WidgetEditorSetupComponent implements OnInit { + // This variable will hold all the data needed to define the layout and behavior of the widgets. + // Pass this to the dashboard component's dashboard input in the template. + public dashboard: IDashboard | undefined; + + // Angular gridster requires a configuration object even if it's empty. + // Pass this to the dashboard component's gridsterConfig input in the template. + public gridsterConfig: GridsterConfig = {}; + + // Boolean which dashboard takes in as an input if its true it allows you to move widgets around. + public editMode: boolean = false; + + constructor( + // WidgetTypesService provides the widget's necessary structure information + private widgetTypesService: WidgetTypesService, + + // In general, the ProviderRegistryService is used for making entities available for injection into dynamically loaded components. + private providerRegistry: ProviderRegistryService, + private changeDetectorRef: ChangeDetectorRef + ) {} + + public ngOnInit(): void { + // Grabbing the widget's default template which will be needed as a parameter for setNode + const widgetTemplate = this.widgetTypesService.getWidgetType("kpi", 1); + // Registering our data sources as dropdown options in the widget editor/configurator + // Note: This could also be done in the parent module's constructor so that + // multiple dashboards could have access to the same widget template modification. + this.widgetTypesService.setNode( + // This is the template we grabbed above with getWidgetType + widgetTemplate, + // We are setting the editor/configurator part of the widget template + "configurator", + // This indicates which node you are changing and we want to change + // the data source providers available for selection in the editor. + WellKnownPathKey.DataSourceProviders, + // We are setting the data sources available for selection in the editor + [ + AverageRatingKpiDataSource.providerId, + RatingsCountKpiDataSource.providerId, + ] + ); + + // Registering the data sources available for injection into the KPI tiles. + // Note: Each tile of a KPI widget is assigned its own instance of a data source + this.providerRegistry.setProviders({ + [AverageRatingKpiDataSource.providerId]: { + provide: DATA_SOURCE, + useClass: AverageRatingKpiDataSource, + // Any dependencies that need to be injected into the provider must be listed here + deps: [HttpClient], + }, + [RatingsCountKpiDataSource.providerId]: { + provide: DATA_SOURCE, + useClass: RatingsCountKpiDataSource, + deps: [HttpClient], + }, + }); + + this.initializeDashboard(); + } + + /** Used for restoring widgets state */ + public reInitializeDashboard(): void { + // destroys the components and their providers so the dashboard can re init data + this.dashboard = undefined; + this.changeDetectorRef.detectChanges(); + + this.initializeDashboard(); + } + + public initializeDashboard(): void { + // We're using a static configuration object for this example (see widgetConfig at the bottom of the file), + // but this is where the widget's configuration could potentially be populated from a database + const kpiWidget = widgetConfig; + const widgetIndex: IWidgets = { + // Complete the KPI widget with information coming from its type definition + [kpiWidget.id]: + this.widgetTypesService.mergeWithWidgetType(kpiWidget), + }; + + // Setting the widget dimensions and position (this is for gridster) + const positions: Record = { + [kpiWidget.id]: { + cols: 4, + rows: 6, + y: 0, + x: 0, + }, + }; + + // Finally, assigning the variables we created above to the dashboard + this.dashboard = { + positions, + widgets: widgetIndex, + }; + } +} + +const widgetConfig: IWidget = { + id: "widget1", + type: "kpi", + pizzagna: { + [PizzagnaLayer.Configuration]: { + [DEFAULT_PIZZAGNA_ROOT]: { + providers: { + [WellKnownProviders.Refresher]: { + properties: { + // Configuring the refresher interval so that our data source is invoked every ten minutes + interval: 60 * 10, + enabled: true, + } as IRefresherProperties, + } as Partial, + }, + }, + header: { + properties: { + title: "Harry Potter and the Sorcerer's Stone", + subtitle: "By J. K. Rowling", + }, + }, + tiles: { + properties: { + nodes: ["kpi1"], + }, + }, + kpi1: { + id: "kpi1", + componentType: KpiComponent.lateLoadKey, + properties: { + widgetData: { + units: "out of 5 Stars", + label: "Average Rating", + }, + }, + providers: { + [WellKnownProviders.DataSource]: { + // Setting the data source providerId for the tile with id "kpi1" + providerId: AverageRatingKpiDataSource.providerId, + } as IProviderConfiguration, + [WellKnownProviders.Adapter]: { + providerId: NOVA_KPI_DATASOURCE_ADAPTER, + properties: { + componentId: "kpi1", + propertyPath: "widgetData", + }, + } as IProviderConfiguration, + }, + }, + }, + }, +}; +\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\`, + "tutorials/widget-editor-setup/widget-editor-setup.module.ts": \\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\`// © 2022 SolarWinds Worldwide, LLC. All rights reserved. +// +// Permission is hereby granted, free of charge, to any person obtaining a copy +// of this software and associated documentation files (the "Software"), to +// deal in the Software without restriction, including without limitation the +// rights to use, copy, modify, merge, publish, distribute, sublicense, and/or +// sell copies of the Software, and to permit persons to whom the Software is +// furnished to do so, subject to the following conditions: +// +// The above copyright notice and this permission notice shall be included in +// all copies or substantial portions of the Software. +// +// THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR +// IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, +// FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE +// AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER +// LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, +// OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN +// THE SOFTWARE. + +import { CommonModule } from "@angular/common"; +import { HttpClientModule } from "@angular/common/http"; +import { NgModule } from "@angular/core"; +import { RouterModule } from "@angular/router"; + +import { + NuiButtonModule, + NuiDocsModule, + NuiMessageModule, + NuiSwitchModule, + DEMO_PATH_TOKEN, +} from "@nova-ui/bits"; +import { NuiDashboardsModule } from "@nova-ui/dashboards"; + +import { WidgetEditorDocsComponent } from "./widget-editor-setup-docs.component"; +import { WidgetEditorSetupComponent } from "./widget-editor-setup.component"; +import { getDemoFiles } from "../../../../demo-files-factory"; + +const routes = [ + { + path: "", + component: WidgetEditorDocsComponent, + data: { + srlc: { + hideIndicator: true, + }, + showThemeSwitcher: true, + }, + }, + { + path: "example", + component: WidgetEditorSetupComponent, + data: { + srlc: { + hideIndicator: true, + }, + }, + }, +]; + +@NgModule({ + imports: [ + CommonModule, + HttpClientModule, + NuiDashboardsModule, + NuiDocsModule, + NuiMessageModule, + NuiSwitchModule, + NuiButtonModule, + RouterModule.forChild(routes), + ], + declarations: [WidgetEditorDocsComponent, WidgetEditorSetupComponent], + providers: [ + { + provide: DEMO_PATH_TOKEN, + useValue: getDemoFiles("widget-editor-setup"), + }, + ], +}) +export default class WidgetEditorSetupModule {} +\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\`, + "tutorials/widget-error-handling/widget-error-handling-docs.component.ts": \\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\`// © 2022 SolarWinds Worldwide, LLC. All rights reserved. +// +// Permission is hereby granted, free of charge, to any person obtaining a copy +// of this software and associated documentation files (the "Software"), to +// deal in the Software without restriction, including without limitation the +// rights to use, copy, modify, merge, publish, distribute, sublicense, and/or +// sell copies of the Software, and to permit persons to whom the Software is +// furnished to do so, subject to the following conditions: +// +// The above copyright notice and this permission notice shall be included in +// all copies or substantial portions of the Software. +// +// THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR +// IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, +// FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE +// AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER +// LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, +// OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN +// THE SOFTWARE. + +import { Component } from "@angular/core"; + +@Component({ + selector: "nui-widget-error-handling-docs", + templateUrl: "./widget-error-handling-docs.component.html", + standalone: false, +}) +export class WidgetErrorHandlingDocsComponent { + public fallbackAdapter = \\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\` +@Injectable() +export class StatusContentFallbackAdapter implements OnDestroy, IHasComponent { + + protected readonly destroy$ = new Subject(); + protected componentId: string; + + constructor(@Inject(PIZZAGNA_EVENT_BUS) protected eventBus: EventBus, + protected pizzagnaService: PizzagnaService) { + this.eventBus.getStream(DATA_SOURCE_OUTPUT) + .pipe(takeUntil(this.destroy$)).subscribe((event: IEvent>) => { + this.handleDataSourceOutput(event); + }); + } + + public ngOnDestroy(): void { + this.destroy$.next(); + this.destroy$.complete(); + } + + public setComponent(component: any, componentId: string) { + this.componentId = componentId; + } + + protected handleDataSourceOutput(event: IEvent>) { + this.pizzagnaService.setProperty({ + componentId: this.componentId, + propertyPath: ["fallbackKey"], + pizzagnaKey: PizzagnaLayer.Data, + }, typeof event.payload?.error?.type !== "undefined" ? event.payload?.error?.type.toString() : undefined); + } +}\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\`; + public errorsMap = \\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\` +export const ERROR_FALLBACK_MAP: Record = { + [HttpStatusCode.Unknown]: ErrorNodeKey.ErrorUnknown, + [HttpStatusCode.Forbidden]: ErrorNodeKey.ErrorForbidden, + [HttpStatusCode.NotFound]: ErrorNodeKey.ErrorNotFound, +}; +\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\`; + public errorNodes = \\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\` +export const ERROR_NODES: Record = { + [ErrorNodeKey.ErrorUnknown]: { + id: ErrorNodeKey.ErrorUnknown, + componentType: WidgetErrorComponent.lateLoadKey, + properties: { + image: "no-data-to-show", + title: $localize\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\`Whoops, something went wrong\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\`, + description: $localize\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\`There was an unexpected error.\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\`, + } as IWidgetErrorDisplayProperties, + }, + [ErrorNodeKey.ErrorForbidden]: { + id: ErrorNodeKey.ErrorForbidden, + componentType: WidgetErrorComponent.lateLoadKey, + properties: { + image: "no-data-to-show", + title: $localize\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\`403 - Forbidden\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\`, + description: $localize\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\`The requested action was forbidden.\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\`, + } as IWidgetErrorDisplayProperties, + }, + [ErrorNodeKey.ErrorNotFound]: { + id: ErrorNodeKey.ErrorNotFound, + componentType: WidgetErrorComponent.lateLoadKey, + properties: { + image: "no-data-to-show", + title: $localize\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\`404 - Not Found\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\`, + description: $localize\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\`The requested resource could not be found.\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\`, + } as IWidgetErrorDisplayProperties, + }, +};\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\`; + public widgetBodyContentNodesSignature = \\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\` +/** + * Retrieves an index of the basic widget body content nodes including fallback nodes + * + * @param mainContentNodeKey The key corresponding to the main body content node + * @param fallbackAdapterId The id for the adapter responsible for activating fallback content in case of an error + * @param fallbackMap A map of node keys to fallback content definitions + * @param fallbackNodes An index of fallback content definitions + * + * @returns An index of component configurations + */ +export function widgetBodyContentNodes( + mainContentNodeKey: string, + fallbackAdapterId = NOVA_STATUS_CONTENT_FALLBACK_ADAPTER, + fallbackMap: Record = ERROR_FALLBACK_MAP, + fallbackNodes: Record = ERROR_NODES +): Record { ... } +\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\`; +} +\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\`, + "tutorials/widget-error-handling/widget-error-handling.component.ts": \\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\`// © 2022 SolarWinds Worldwide, LLC. All rights reserved. +// +// Permission is hereby granted, free of charge, to any person obtaining a copy +// of this software and associated documentation files (the "Software"), to +// deal in the Software without restriction, including without limitation the +// rights to use, copy, modify, merge, publish, distribute, sublicense, and/or +// sell copies of the Software, and to permit persons to whom the Software is +// furnished to do so, subject to the following conditions: +// +// The above copyright notice and this permission notice shall be included in +// all copies or substantial portions of the Software. +// +// THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR +// IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, +// FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE +// AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER +// LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, +// OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN +// THE SOFTWARE. + +import { HttpClient, HttpErrorResponse } from "@angular/common/http"; +import { + ChangeDetectorRef, + Component, + Injectable, + OnDestroy, + OnInit, +} from "@angular/core"; +import { GridsterConfig, GridsterItem } from "angular-gridster2"; +import { BehaviorSubject } from "rxjs"; +import { finalize } from "rxjs/operators"; + +import { DataSourceService, IFilteringOutputs } from "@nova-ui/bits"; +import { + DATA_SOURCE, + DEFAULT_PIZZAGNA_ROOT, + HttpStatusCode, + IDashboard, + IKpiData, + IProviderConfiguration, + IRefresherProperties, + IWidget, + IWidgets, + KpiComponent, + NOVA_KPI_DATASOURCE_ADAPTER, + PizzagnaLayer, + ProviderRegistryService, + WellKnownPathKey, + WellKnownProviders, + WidgetTypesService, +} from "@nova-ui/dashboards"; + +/** + * A simple KPI data source to retrieve the average rating of Harry Potter and the Sorcerer's Stone (book) via googleapis + */ +@Injectable() +export class AverageRatingKpiDataSource + extends DataSourceService + implements OnDestroy +{ + // This is the ID we'll use to identify the provider + public static providerId = "AverageRatingKpiDataSource"; + + // Use this subject to communicate the data source's busy state + public busy = new BehaviorSubject(false); + + constructor(private http: HttpClient) { + super(); + } + + // In this example, getFilteredData is invoked every 10 minutes (Take a look at the refresher + // provider definition in the widget configuration below to see how the interval is set) + public async getFilteredData(): Promise { + this.busy.next(true); + return new Promise((resolve) => { + // *** Make a resource request to an external API (if needed) + this.http + .get("https://www.googleapis.com/books/v1/volumes/5MQFrgEACAAJ") + .pipe(finalize(() => this.busy.next(false))) + .subscribe({ + next: (data: any) => { + resolve({ + result: { + value: data.volumeInfo.averageRating, + }, + }); + }, + error: (error: HttpErrorResponse) => { + resolve({ + result: null, + error: { + type: error.status, + }, + }); + }, + }); + }); + } + + public ngOnDestroy(): void { + this.outputsSubject.complete(); + } +} + +/** + * A simple KPI data source to retrieve the average rating of Harry Potter and the Sorcerer's Stone (book) via googleapis + */ +@Injectable() +export class ErrorUnknownDataSource + extends DataSourceService + implements OnDestroy +{ + // This is the ID we'll use to identify the provider + public static providerId = "ErrorUnknownDataSource"; + + // Use this subject to communicate the data source's busy state + public busy = new BehaviorSubject(false); + + // In this example, getFilteredData is invoked every 10 minutes (Take a look at the refresher + // provider definition in the widget configuration below to see how the interval is set) + public async getFilteredData(): Promise { + this.busy.next(true); + const mockError = { + result: null, + error: { type: HttpStatusCode.Unknown }, + }; + this.busy.next(false); + return mockError; + } + + public ngOnDestroy(): void { + this.outputsSubject.complete(); + } +} + +/** + * A simple KPI data source to retrieve the ratings count of Harry Potter and the Sorcerer's Stone (book) via googleapis + */ +@Injectable() +export class ErrorForbiddenDataSource + extends DataSourceService + implements OnDestroy +{ + public static providerId = "ErrorForbiddenDataSource"; + + // Use this subject to communicate the data source's busy state + public busy = new BehaviorSubject(false); + + constructor(private http: HttpClient) { + super(); + } + + public async getFilteredData(): Promise { + this.busy.next(true); + // generate a 403 + return new Promise((resolve) => { + this.http + .get( + "http://www.mocky.io/v2/5ecc724a3200000f0023614a?mocky-delay=4000ms" + ) + .pipe(finalize(() => this.busy.next(false))) + .subscribe({ + error: (error: HttpErrorResponse) => { + resolve({ + result: null, + error: { + type: error.status, + }, + }); + }, + }); + }); + } + + public ngOnDestroy(): void { + this.outputsSubject.complete(); + } +} + +/** + * A simple KPI data source to retrieve the ratings count of Harry Potter and the Sorcerer's Stone (book) via googleapis + */ +@Injectable() +export class ErrorNotFoundDataSource + extends DataSourceService + implements OnDestroy +{ + public static providerId = "ErrorNotFoundDataSource"; + + // Use this subject to communicate the data source's busy state + public busy = new BehaviorSubject(false); + + constructor(private http: HttpClient) { + super(); + } + + public async getFilteredData(): Promise { + this.busy.next(true); + // generate a 404 + return new Promise((resolve) => { + this.http + .get( + "http://www.mocky.io/v2/5ec6bfd93200007800d75100?mocky-delay=1000ms" + ) + .pipe(finalize(() => this.busy.next(false))) + .subscribe({ + error: (error: HttpErrorResponse) => { + resolve({ + result: null, + error: { + type: error.status, + }, + }); + }, + }); + }); + } + + public ngOnDestroy(): void { + this.outputsSubject.complete(); + } +} + +/** + * A component that instantiates the dashboard + */ +@Component({ + selector: "widget-error-handling", + templateUrl: "./widget-error-handling.component.html", + styleUrls: ["./widget-error-handling.component.less"], + standalone: false, +}) +export class WidgetErrorHandlingComponent implements OnInit { + // This variable will hold all the data needed to define the layout and behavior of the widgets. + // Pass this to the dashboard component's dashboard input in the template. + public dashboard: IDashboard | undefined; + + // Angular gridster requires a configuration object even if it's empty. + // Pass this to the dashboard component's gridsterConfig input in the template. + public gridsterConfig: GridsterConfig = {}; + + // Boolean which dashboard takes in as an input if its true it allows you to move widgets around. + public editMode: boolean = false; + + constructor( + // WidgetTypesService provides the widget's necessary structure information + private widgetTypesService: WidgetTypesService, + + // In general, the ProviderRegistryService is used for making entities available for injection into dynamically loaded components. + private providerRegistry: ProviderRegistryService, + private changeDetectorRef: ChangeDetectorRef + ) {} + + public ngOnInit(): void { + // Grab the widget's default template which will be needed as a parameter for setNode. + const widgetTemplate = this.widgetTypesService.getWidgetType("kpi", 1); + // Register our data sources as dropdown options in the widget editor/configurator + // Note: This could also be done in the parent module's constructor so that + // multiple dashboards could have access to the same widget template modification. + this.widgetTypesService.setNode( + // This is the template we grabbed above with getWidgetType + widgetTemplate, + // We are setting the editor/configurator part of the widget template + "configurator", + // This indicates which node you are changing and we want to change + // the data source providers available for selection in the editor. + WellKnownPathKey.DataSourceProviders, + // We are setting the data sources available for selection in the editor + [ + ErrorUnknownDataSource.providerId, + ErrorForbiddenDataSource.providerId, + ErrorNotFoundDataSource.providerId, + AverageRatingKpiDataSource.providerId, + ] + ); + + // Register the data sources available for injection into the KPI tiles. + // Note: Each tile of a KPI widget is assigned its own instance of a data source + this.providerRegistry.setProviders({ + [ErrorUnknownDataSource.providerId]: { + provide: DATA_SOURCE, + useClass: ErrorUnknownDataSource, + // Any dependencies that need to be injected into the provider must be listed here + deps: [HttpClient], + }, + [ErrorForbiddenDataSource.providerId]: { + provide: DATA_SOURCE, + useClass: ErrorForbiddenDataSource, + deps: [HttpClient], + }, + [ErrorNotFoundDataSource.providerId]: { + provide: DATA_SOURCE, + useClass: ErrorNotFoundDataSource, + deps: [HttpClient], + }, + [AverageRatingKpiDataSource.providerId]: { + provide: DATA_SOURCE, + useClass: AverageRatingKpiDataSource, + // Any dependencies that need to be injected into the provider must be listed here + deps: [HttpClient], + }, + }); + + this.initializeDashboard(); + } + + /** Used for restoring widgets state */ + public reInitializeDashboard(): void { + // destroys the components and their providers so the dashboard can re init data + this.dashboard = undefined; + this.changeDetectorRef.detectChanges(); + + this.initializeDashboard(); + } + + public initializeDashboard(): void { + // We're using a static configuration object for this example (see widgetConfig at the bottom of the file), + // but this is where the widget's configuration could potentially be populated from a database + const kpiWidget = widgetConfig; + const widgetIndex: IWidgets = { + // Complete the KPI widget with information coming from its type definition + [kpiWidget.id]: + this.widgetTypesService.mergeWithWidgetType(kpiWidget), + }; + + // Setting the widget dimensions and position (this is for gridster) + const positions: Record = { + [kpiWidget.id]: { + cols: 4, + rows: 6, + y: 0, + x: 0, + }, + }; + + // Finally, assigning the variables we created above to the dashboard + this.dashboard = { + positions, + widgets: widgetIndex, + }; + } +} + +const widgetConfig: IWidget = { + id: "widget1", + type: "kpi", + pizzagna: { + [PizzagnaLayer.Configuration]: { + [DEFAULT_PIZZAGNA_ROOT]: { + providers: { + [WellKnownProviders.Refresher]: { + properties: { + // Configuring the refresher interval so that our data source is invoked every ten minutes + interval: 60 * 10, + enabled: true, + } as IRefresherProperties, + } as Partial, + }, + }, + header: { + properties: { + title: "Harry Potter and the Sorcerer's Stone", + subtitle: "By J. K. Rowling", + }, + }, + tiles: { + properties: { + nodes: ["kpi1"], + }, + }, + kpi1: { + id: "kpi1", + componentType: KpiComponent.lateLoadKey, + properties: { + widgetData: { + units: "out of 5 Stars", + label: "Average Rating", + }, + }, + providers: { + [WellKnownProviders.DataSource]: { + // Setting the data source providerId for the tile with id "kpi1" + providerId: ErrorUnknownDataSource.providerId, + } as IProviderConfiguration, + [WellKnownProviders.Adapter]: { + providerId: NOVA_KPI_DATASOURCE_ADAPTER, + properties: { + componentId: "kpi1", + propertyPath: "widgetData", + }, + } as IProviderConfiguration, + }, + }, + }, + }, +}; +\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\`, + "tutorials/widget-error-handling/widget-error-handling.module.ts": \\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\`// © 2022 SolarWinds Worldwide, LLC. All rights reserved. +// +// Permission is hereby granted, free of charge, to any person obtaining a copy +// of this software and associated documentation files (the "Software"), to +// deal in the Software without restriction, including without limitation the +// rights to use, copy, modify, merge, publish, distribute, sublicense, and/or +// sell copies of the Software, and to permit persons to whom the Software is +// furnished to do so, subject to the following conditions: +// +// The above copyright notice and this permission notice shall be included in +// all copies or substantial portions of the Software. +// +// THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR +// IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, +// FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE +// AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER +// LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, +// OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN +// THE SOFTWARE. + +import { CommonModule } from "@angular/common"; +import { HttpClientModule } from "@angular/common/http"; +import { NgModule } from "@angular/core"; +import { ReactiveFormsModule } from "@angular/forms"; +import { RouterModule } from "@angular/router"; + +import { + NuiButtonModule, + NuiDocsModule, + NuiFormFieldModule, + NuiIconModule, + NuiMessageModule, + NuiSwitchModule, + NuiTextboxModule, + DEMO_PATH_TOKEN, +} from "@nova-ui/bits"; +import { + NuiDashboardConfiguratorModule, + NuiDashboardsModule, +} from "@nova-ui/dashboards"; + +import { WidgetErrorHandlingDocsComponent } from "./widget-error-handling-docs.component"; +import { WidgetErrorHandlingComponent } from "./widget-error-handling.component"; +import { getDemoFiles } from "../../../../demo-files-factory"; + +const routes = [ + { + path: "", + component: WidgetErrorHandlingDocsComponent, + data: { + srlc: { + hideIndicator: true, + }, + showThemeSwitcher: true, + }, + }, + { + path: "example", + component: WidgetErrorHandlingComponent, + data: { + srlc: { + hideIndicator: true, + }, + }, + }, +]; + +@NgModule({ + imports: [ + CommonModule, + ReactiveFormsModule, + HttpClientModule, + NuiButtonModule, + NuiDashboardsModule, + NuiDashboardConfiguratorModule, + NuiDocsModule, + NuiFormFieldModule, + NuiIconModule, + NuiMessageModule, + NuiIconModule, + NuiTextboxModule, + NuiIconModule, + NuiSwitchModule, + RouterModule.forChild(routes), + ], + declarations: [ + WidgetErrorHandlingDocsComponent, + WidgetErrorHandlingComponent, + ], + providers: [ + { + provide: DEMO_PATH_TOKEN, + useValue: getDemoFiles("widget-error-handling"), + }, + ], +}) +export default class WidgetErrorHandlingModule {} +\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\`, + "types.ts": \\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\`// © 2022 SolarWinds Worldwide, LLC. All rights reserved. +// +// Permission is hereby granted, free of charge, to any person obtaining a copy +// of this software and associated documentation files (the "Software"), to +// deal in the Software without restriction, including without limitation the +// rights to use, copy, modify, merge, publish, distribute, sublicense, and/or +// sell copies of the Software, and to permit persons to whom the Software is +// furnished to do so, subject to the following conditions: +// +// The above copyright notice and this permission notice shall be included in +// all copies or substantial portions of the Software. +// +// THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR +// IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, +// FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE +// AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER +// LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, +// OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN +// THE SOFTWARE. + +export enum APOLLO_API_NAMESPACE { + COUNTRIES = "countries", +} +\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\`, + "widget-types/drilldown/drilldown-multi-request-widget/drilldown-multi-request-widget-example.component.ts": \\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\`// © 2022 SolarWinds Worldwide, LLC. All rights reserved. +// +// Permission is hereby granted, free of charge, to any person obtaining a copy +// of this software and associated documentation files (the "Software"), to +// deal in the Software without restriction, including without limitation the +// rights to use, copy, modify, merge, publish, distribute, sublicense, and/or +// sell copies of the Software, and to permit persons to whom the Software is +// furnished to do so, subject to the following conditions: +// +// The above copyright notice and this permission notice shall be included in +// all copies or substantial portions of the Software. +// +// THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR +// IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, +// FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE +// AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER +// LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, +// OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN +// THE SOFTWARE. + +import { HttpClient } from "@angular/common/http"; +import { + ChangeDetectorRef, + Component, + Injectable, + OnDestroy, + OnInit, +} from "@angular/core"; +import { GridsterConfig, GridsterItem } from "angular-gridster2"; +import { Apollo, gql } from "apollo-angular"; +import { BehaviorSubject, Observable, of, Subject } from "rxjs"; +// eslint-disable-next-line import/no-deprecated +import { finalize, map, switchMap, tap } from "rxjs/operators"; + +import { + DataSourceService, + IconStatus, + IDataField, + IFilters, + INovaFilters, +} from "@nova-ui/bits"; +import { + DATA_SOURCE, + DEFAULT_PIZZAGNA_ROOT, + IDashboard, + IDrilldownComponentsConfiguration, + IListWidgetConfiguration, + IProviderConfiguration, + IWidget, + IWidgets, + ListGroupItemComponent, + ListLeafItemComponent, + NOVA_DRILLDOWN_DATASOURCE_ADAPTER, + PizzagnaLayer, + ProviderRegistryService, + WellKnownPathKey, + WellKnownProviders, + WidgetTypesService, +} from "@nova-ui/dashboards"; + +import { APOLLO_API_NAMESPACE } from "../../../types"; + +/** + * A simple KPI data source to retrieve the average rating of Harry Potter and the Sorcerer's Stone (book) via googleapis + */ +@Injectable() +export class DrilldownDataSource + extends DataSourceService + implements OnDestroy +{ + // This is the ID we'll use to identify the provider + public static providerId = "DrilldownDataSource"; + + // Use this subject to communicate the data source's busy state + public busy = new BehaviorSubject(false); + + public dataFields: Partial[] = [ + { id: "Region", label: "Region name" }, + { id: "Subregion", label: "Subregion name" }, + ]; + + private drillState: string[] = []; + private groupBy: string[]; + private cache: any; + private lastDrillState: string[] = []; + private leafGroup: string = "country"; + private applyFilters$ = new Subject(); + + constructor(private http: HttpClient, private apollo: Apollo) { + super(); + + // TODO: remove Partial in vNext after marking dataType field as optional - NUI-5838 + ( + this.dataFieldsConfig.dataFields$ as BehaviorSubject< + Partial[] + > + ).next(this.dataFields); + + this.applyFilters$ + // eslint-disable-next-line import/no-deprecated + .pipe(switchMap((filters) => this.getData(filters))) + .subscribe(async (res) => { + this.outputsSubject.next(await this.getFilteredData(res)); + }); + } + + private groupedDataHistory: any[] = []; + + // In this example, getFilteredData is invoked every 10 minutes (Take a look at the refresher + // provider definition in the widget configuration below to see how the interval is set) + public async getFilteredData(data: any): Promise { + return of(data) + .pipe( + map((entries) => { + if (this.isDrillDown()) { + const activeDrillLvl = this.drillState.length; + const group = this.groupBy[activeDrillLvl]; + const lastGroupedValue = + this.getTransformedDataForGroup( + entries, + group, + getLast(this.drillState) + ); + + this.groupedDataHistory.push(lastGroupedValue); + + return lastGroupedValue; + } + + const mapIconsToEntries = entries.map((item: any) => ({ + ...item, + icon: "virtual-host", + icon_status: IconStatus.Up, + })); + this.groupedDataHistory.push(mapIconsToEntries); + const widgetInput = this.getOutput(entries); + + return widgetInput; + }) + ) + .toPromise(); + } + + public ngOnDestroy(): void { + this.outputsSubject.complete(); + } + + // redefine parent method + public async applyFilters(): Promise { + this.applyFilters$.next(this.getFilters()); + } + + private getQuery(key: string, value: string) { + const groupToRequestMap: Record = { + Region: \\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\`{ Region { name } }\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\`, + Subregion: \\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\`{ Subregion(filter: { region: { name: "\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\${value}" } } ) { name } }\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\`, + Country: \\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\`{ Country(filter: { subregion: { name: "\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\${value}" } } ) { name capital } }\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\`, + }; + + return gql\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\` + \\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\${groupToRequestMap[key]} + \\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\`; + } + + private getData(filters: INovaFilters): Observable { + this.drillState = filters.drillstate?.value; + this.groupBy = filters.group?.value; + const group = this.groupBy[this.drillState.length]; + const isDrillUp = this.drillState.length < this.lastDrillState.length; + + this.lastDrillState = [...this.drillState]; + + if (!this.drillState.length) { + this.groupedDataHistory.length = 0; + } + + this.busy.next(true); + + if (this.cache && (isDrillUp || this.isHome())) { + return of(this.cache).pipe( + map((data) => data.data[group]), + finalize(() => this.busy.next(false)) + ); + } else { + return this.apollo + .use(APOLLO_API_NAMESPACE.COUNTRIES) + .query({ + query: this.getQuery( + group || this.leafGroup, + getLast(this.drillState) + ), + }) + .pipe( + tap( + (data) => + (this.cache = { + data: { ...this.cache?.data, ...data?.data }, + }) + ), + map((data) => data.data[group || this.leafGroup]), + finalize(() => this.busy.next(false)) + ); + } + } + + private getTransformedDataForGroup( + data: any, + group: string, + drillStateValue: string + ) { + const fallback: string = \\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\`No \\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\${group} for \\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\${drillStateValue}\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\`; + const dataArr = Object.values(data).map((val: any) => ({ + id: val.name || fallback, + label: val.name || fallback, + statuses: [ + { key: "state_ok", value: val.name?.length }, + { + key: "status_unreachable", + value: generateNumberUpTo(100000), + }, + { key: "status_warning", value: generateNumberUpTo(10000) }, + { key: "status_unknown", value: generateNumberUpTo(1000) }, + ], + })); + + return dataArr; + } + + private isHome(): boolean { + return this.drillState.length === 0; + } + + private isDrillDown(): boolean { + return this.drillState.length !== this.groupBy.length; + } + + private getOutput(data: any) { + if (this.isHome()) { + this.groupedDataHistory.length = 0; + } + + const lastHistoryValue = getLast(this.groupedDataHistory); + + if (!lastHistoryValue) { + return data; + } + + return lastHistoryValue[getLast(this.drillState)] || lastHistoryValue; + } +} + +@Component({ + selector: "drilldown-multi-request-widget-example", + templateUrl: "./drilldown-multi-request-widget-example.component.html", + styleUrls: ["./drilldown-multi-request-widget-example.component.less"], + standalone: false, +}) +export class DrilldownMultiRequestWidgetExampleComponent implements OnInit { + // This variable will hold all the data needed to define the layout and behavior of the widgets. + // Pass this to the dashboard component's dashboard input in the template. + public dashboard: IDashboard | undefined; + + // Angular gridster requires a configuration object even if it's empty. + // Pass this to the dashboard component's gridsterConfig input in the template. + public gridsterConfig: GridsterConfig = {}; + + // Boolean passed as an input to the dashboard. When true, widgets can be moved, resized, removed, or edited + public editMode: boolean = false; + + constructor( + // WidgetTypesService provides the widget's necessary structure information + private widgetTypesService: WidgetTypesService, + private providerRegistry: ProviderRegistryService, + private changeDetectorRef: ChangeDetectorRef + ) {} + + public ngOnInit(): void { + // this.prepareNovaDashboards(); + this.initializeDashboard(); + const widgetTemplate = this.widgetTypesService.getWidgetType( + "drilldown", + 1 + ); + this.widgetTypesService.setNode( + widgetTemplate, + "configurator", + WellKnownPathKey.DataSourceProviders, + [DrilldownDataSource.providerId] + ); + + // Registering the data source for injection into the KPI tile. + // Note: Each tile of a KPI widget is assigned its own instance of the data source + this.providerRegistry.setProviders({ + [DrilldownDataSource.providerId]: { + provide: DATA_SOURCE, + useClass: DrilldownDataSource, + // Any dependencies that need to be injected into the provider must be listed here + deps: [HttpClient, Apollo], + }, + }); + } + + /** Used for restoring widgets state */ + public reInitializeDashboard(): void { + // destroys the components and their providers so the dashboard can re init data + this.dashboard = undefined; + this.changeDetectorRef.detectChanges(); + + this.initializeDashboard(); + } + + public initializeDashboard(): void { + // We're using a static configuration object for this example, but this is where + // the widget's configuration could potentially be populated from a database + const drilldownWidget = widgetConfig; + const widgets: IWidgets = { + // Complete the widget with information coming from its type definition + [drilldownWidget.id]: + this.widgetTypesService.mergeWithWidgetType(drilldownWidget), + }; + + // Setting the widget dimensions and position (this is for gridster) + const positions: Record = { + [drilldownWidget.id]: { + cols: 10, + rows: 10, + y: 0, + x: 0, + }, + }; + + // Finally, assigning the variables we created above to the dashboard + this.dashboard = { positions, widgets }; + } +} + +const widgetConfig: IWidget = { + id: "drilldown", + type: "drilldown", + pizzagna: { + [PizzagnaLayer.Configuration]: { + header: { + properties: { + title: "Drilldown Widget", + subtitle: "Countries BY continent THEN currency", + }, + }, + [DEFAULT_PIZZAGNA_ROOT]: { + providers: { + [WellKnownProviders.DataSource]: { + // Setting the data source providerId for the tile with id "kpi1" + providerId: DrilldownDataSource.providerId, + properties: {}, + } as IProviderConfiguration, + }, + }, + listWidget: { + providers: { + [WellKnownProviders.Adapter]: { + providerId: NOVA_DRILLDOWN_DATASOURCE_ADAPTER, + properties: { + // widget + navigationBarId: "navigationBar", + componentId: "listWidget", + dataPath: "data", + + // adapter props + drillstate: [], + groups: ["Region", "Subregion"], + groupBy: ["Region", "Subregion"], + + // components + componentsConfig: { + group: { + componentType: + ListGroupItemComponent.lateLoadKey, + properties: { + dataFieldIds: { + id: "id", + label: "label", + statuses: "statuses", + }, + }, + itemProperties: { + canNavigate: true, + }, + }, + leaf: { + componentType: + ListLeafItemComponent.lateLoadKey, + properties: { + dataFieldIds: { + icon: "icon", + status: "icon_status", + detailedUrl: "capital", + label: "name", + }, + }, + itemProperties: { + canNavigate: false, + }, + }, + } as IDrilldownComponentsConfiguration, + }, + }, + }, + properties: { + configuration: { + // FORMAT: + // componentType: ListLeafItemComponent.lateLoadKey, + // properties: { + // dataFieldIds: { + // icon: "", + // status: "code", + // detailedUrl: "capital", + // label: "name", + // }, + // }, + // + } as IListWidgetConfiguration, + }, + }, + }, + }, +}; + +const getLast = (arr: any[]) => arr[arr.length - 1]; + +const generateNumberUpTo = (upperLimit: number): number => + Math.floor(Math.random() * upperLimit + 1); +\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\`, + "widget-types/drilldown/drilldown-widget/data-mock.ts": \\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\`// © 2022 SolarWinds Worldwide, LLC. All rights reserved. +// +// Permission is hereby granted, free of charge, to any person obtaining a copy +// of this software and associated documentation files (the "Software"), to +// deal in the Software without restriction, including without limitation the +// rights to use, copy, modify, merge, publish, distribute, sublicense, and/or +// sell copies of the Software, and to permit persons to whom the Software is +// furnished to do so, subject to the following conditions: +// +// The above copyright notice and this permission notice shall be included in +// all copies or substantial portions of the Software. +// +// THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR +// IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, +// FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE +// AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER +// LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, +// OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN +// THE SOFTWARE. + +import { IconStatus } from "@nova-ui/bits"; + +export const GRAPH_DATA_MOCK = { + data: { + countries: [ + { + name: "Andorra", + code: "AD", + icon: "virtual-host", + icon_status: IconStatus.Up, + capital: "Andorra la Vella", + continent: { + name: "Europe", + }, + currency: "EUR", + languages: [ + { + name: "Catalan", + }, + ], + url: "https://en.wikipedia.org/wiki/Andorra", + }, + { + name: "United Arab Emirates", + code: "AE", + icon: "virtual-host", + icon_status: IconStatus.Up, + capital: "Abu Dhabi", + continent: { + name: "Asia", + }, + currency: "AED", + languages: [ + { + name: "Arabic", + }, + ], + url: "https://en.wikipedia.org/wiki/United_Arab_Emirates", + }, + { + name: "Afghanistan", + code: "AF", + icon: "virtual-host", + icon_status: IconStatus.Up, + capital: "Kabul", + continent: { + name: "Asia", + }, + currency: "AFN", + languages: [ + { + name: "Pashto", + }, + { + name: "Uzbek", + }, + { + name: "Turkmen", + }, + ], + url: "https://en.wikipedia.org/wiki/Afghanistan", + }, + { + name: "Antigua and Barbuda", + code: "AG", + icon: "virtual-host", + icon_status: IconStatus.Up, + capital: "Saint John's", + continent: { + name: "North America", + }, + currency: "XCD", + languages: [ + { + name: "English", + }, + ], + url: "https://en.wikipedia.org/wiki/Antigua_and_Barbuda", + }, + { + name: "Anguilla", + code: "AI", + icon: "virtual-host", + icon_status: IconStatus.Up, + capital: "The Valley", + continent: { + name: "North America", + }, + currency: "XCD", + languages: [ + { + name: "English", + }, + ], + url: "https://en.wikipedia.org/wiki/Anguilla", + }, + { + name: "Albania", + code: "AL", + icon: "virtual-host", + icon_status: IconStatus.Up, + capital: "Tirana", + continent: { + name: "Europe", + }, + currency: "ALL", + languages: [ + { + name: "Albanian", + }, + ], + url: "https://en.wikipedia.org/wiki/Albania", + }, + { + name: "Armenia", + code: "AM", + icon: "virtual-host", + icon_status: IconStatus.Up, + capital: "Yerevan", + continent: { + name: "Asia", + }, + currency: "AMD", + languages: [ + { + name: "Armenian", + }, + { + name: "Russian", + }, + ], + url: "https://en.wikipedia.org/wiki/Armenia", + }, + { + name: "Angola", + code: "AO", + icon: "virtual-host", + icon_status: IconStatus.Up, + capital: "Luanda", + continent: { + name: "Africa", + }, + currency: "AOA", + languages: [ + { + name: "Portuguese", + }, + ], + }, + { + name: "Antarctica", + code: "AQ", + icon: "virtual-host", + icon_status: IconStatus.Up, + capital: null, + continent: { + name: "Antarctica", + }, + currency: null, + languages: [], + }, + { + name: "Argentina", + code: "AR", + icon: "virtual-host", + icon_status: IconStatus.Up, + capital: "Buenos Aires", + continent: { + name: "South America", + }, + currency: "ARS", + languages: [ + { + name: "Spanish", + }, + { + name: "Guarani", + }, + ], + }, + { + name: "American Samoa", + code: "AS", + icon: "virtual-host", + icon_status: IconStatus.Up, + capital: "Pago Pago", + continent: { + name: "Oceania", + }, + currency: "USD", + languages: [ + { + name: "English", + }, + { + name: "Samoan", + }, + ], + }, + { + name: "Austria", + code: "AT", + icon: "virtual-host", + icon_status: IconStatus.Up, + capital: "Vienna", + continent: { + name: "Europe", + }, + currency: "EUR", + languages: [ + { + name: "German", + }, + ], + }, + { + name: "Australia", + code: "AU", + icon: "virtual-host", + icon_status: IconStatus.Up, + capital: "Canberra", + continent: { + name: "Oceania", + }, + currency: "AUD", + languages: [ + { + name: "English", + }, + ], + }, + { + name: "Aruba", + code: "AW", + icon: "virtual-host", + icon_status: IconStatus.Up, + capital: "Oranjestad", + continent: { + name: "North America", + }, + currency: "AWG", + languages: [ + { + name: "Dutch", + }, + { + name: "Panjabi / Punjabi", + }, + ], + }, + { + name: "Åland", + code: "AX", + icon: "virtual-host", + icon_status: IconStatus.Up, + capital: "Mariehamn", + continent: { + name: "Europe", + }, + currency: "EUR", + languages: [ + { + name: "Swedish", + }, + ], + }, + { + name: "Azerbaijan", + code: "AZ", + icon: "virtual-host", + icon_status: IconStatus.Up, + capital: "Baku", + continent: { + name: "Asia", + }, + currency: "AZN", + languages: [ + { + name: "Azerbaijani", + }, + ], + }, + { + name: "Bosnia and Herzegovina", + code: "BA", + icon: "virtual-host", + icon_status: IconStatus.Up, + capital: "Sarajevo", + continent: { + name: "Europe", + }, + currency: "BAM", + languages: [ + { + name: "Bosnian", + }, + { + name: "Croatian", + }, + { + name: "Serbian", + }, + ], + }, + { + name: "Barbados", + code: "BB", + icon: "virtual-host", + icon_status: IconStatus.Up, + capital: "Bridgetown", + continent: { + name: "North America", + }, + currency: "BBD", + languages: [ + { + name: "English", + }, + ], + }, + { + name: "Bangladesh", + code: "BD", + icon: "virtual-host", + icon_status: IconStatus.Up, + capital: "Dhaka", + continent: { + name: "Asia", + }, + currency: "BDT", + languages: [ + { + name: "Bengali", + }, + ], + }, + { + name: "Belgium", + code: "BE", + icon: "virtual-host", + icon_status: IconStatus.Up, + capital: "Brussels", + continent: { + name: "Europe", + }, + currency: "EUR", + languages: [ + { + name: "Dutch", + }, + { + name: "French", + }, + { + name: "German", + }, + ], + }, + { + name: "Burkina Faso", + code: "BF", + icon: "virtual-host", + icon_status: IconStatus.Up, + capital: "Ouagadougou", + continent: { + name: "Africa", + }, + currency: "XOF", + languages: [ + { + name: "French", + }, + { + name: "Peul", + }, + ], + }, + { + name: "Bulgaria", + code: "BG", + icon: "virtual-host", + icon_status: IconStatus.Up, + capital: "Sofia", + continent: { + name: "Europe", + }, + currency: "BGN", + languages: [ + { + name: "Bulgarian", + }, + ], + }, + { + name: "Bahrain", + code: "BH", + icon: "virtual-host", + icon_status: IconStatus.Up, + capital: "Manama", + continent: { + name: "Asia", + }, + currency: "BHD", + languages: [ + { + name: "Arabic", + }, + ], + }, + { + name: "Burundi", + code: "BI", + icon: "virtual-host", + icon_status: IconStatus.Up, + capital: "Bujumbura", + continent: { + name: "Africa", + }, + currency: "BIF", + languages: [ + { + name: "French", + }, + { + name: "Kirundi", + }, + ], + }, + { + name: "Benin", + code: "BJ", + icon: "virtual-host", + icon_status: IconStatus.Up, + capital: "Porto-Novo", + continent: { + name: "Africa", + }, + currency: "XOF", + languages: [ + { + name: "French", + }, + ], + }, + { + name: "Saint Barthélemy", + code: "BL", + icon: "virtual-host", + icon_status: IconStatus.Up, + capital: "Gustavia", + continent: { + name: "North America", + }, + currency: "EUR", + languages: [ + { + name: "French", + }, + ], + }, + { + name: "Bermuda", + code: "BM", + icon: "virtual-host", + icon_status: IconStatus.Up, + capital: "Hamilton", + continent: { + name: "North America", + }, + currency: "BMD", + languages: [ + { + name: "English", + }, + ], + }, + { + name: "Brunei", + code: "BN", + icon: "virtual-host", + icon_status: IconStatus.Up, + capital: "Bandar Seri Begawan", + continent: { + name: "Asia", + }, + currency: "BND", + languages: [ + { + name: "Malay", + }, + ], + }, + { + name: "Bolivia", + code: "BO", + icon: "virtual-host", + icon_status: IconStatus.Up, + capital: "Sucre", + continent: { + name: "South America", + }, + currency: "BOB,BOV", + languages: [ + { + name: "Spanish", + }, + { + name: "Aymara", + }, + { + name: "Quechua", + }, + ], + }, + { + name: "Bonaire", + code: "BQ", + icon: "virtual-host", + icon_status: IconStatus.Up, + capital: "Kralendijk", + continent: { + name: "North America", + }, + currency: "USD", + languages: [ + { + name: "Dutch", + }, + ], + }, + { + name: "Brazil", + code: "BR", + icon: "virtual-host", + icon_status: IconStatus.Up, + capital: "Brasília", + continent: { + name: "South America", + }, + currency: "BRL", + languages: [ + { + name: "Portuguese", + }, + ], + }, + { + name: "Bahamas", + code: "BS", + icon: "virtual-host", + icon_status: IconStatus.Up, + capital: "Nassau", + continent: { + name: "North America", + }, + currency: "BSD", + languages: [ + { + name: "English", + }, + ], + }, + { + name: "Bhutan", + code: "BT", + icon: "virtual-host", + icon_status: IconStatus.Up, + capital: "Thimphu", + continent: { + name: "Asia", + }, + currency: "BTN,INR", + languages: [ + { + name: "Dzongkha", + }, + ], + }, + { + name: "Bouvet Island", + code: "BV", + icon: "virtual-host", + icon_status: IconStatus.Up, + capital: null, + continent: { + name: "Antarctica", + }, + currency: "NOK", + languages: [ + { + name: "Norwegian", + }, + { + name: "Norwegian Bokmål", + }, + { + name: "Norwegian Nynorsk", + }, + ], + }, + { + name: "Botswana", + code: "BW", + icon: "virtual-host", + icon_status: IconStatus.Up, + capital: "Gaborone", + continent: { + name: "Africa", + }, + currency: "BWP", + languages: [ + { + name: "English", + }, + { + name: "Tswana", + }, + ], + }, + { + name: "Belarus", + code: "BY", + icon: "virtual-host", + icon_status: IconStatus.Up, + capital: "Minsk", + continent: { + name: "Europe", + }, + currency: "BYN", + languages: [ + { + name: "Belarusian", + }, + { + name: "Russian", + }, + ], + }, + { + name: "Belize", + code: "BZ", + icon: "virtual-host", + icon_status: IconStatus.Up, + capital: "Belmopan", + continent: { + name: "North America", + }, + currency: "BZD", + languages: [ + { + name: "English", + }, + { + name: "Spanish", + }, + ], + }, + { + name: "Canada", + code: "CA", + icon: "virtual-host", + icon_status: IconStatus.Up, + capital: "Ottawa", + continent: { + name: "North America", + }, + currency: "CAD", + languages: [ + { + name: "English", + }, + { + name: "French", + }, + ], + }, + { + name: "Cocos [Keeling] Islands", + code: "CC", + icon: "virtual-host", + icon_status: IconStatus.Up, + capital: "West Island", + continent: { + name: "Asia", + }, + currency: "AUD", + languages: [ + { + name: "English", + }, + ], + }, + { + name: "Democratic Republic of the Congo", + code: "CD", + icon: "virtual-host", + icon_status: IconStatus.Up, + capital: "Kinshasa", + continent: { + name: "Africa", + }, + currency: "CDF", + languages: [ + { + name: "French", + }, + { + name: "Lingala", + }, + { + name: "Kongo", + }, + { + name: "Swahili", + }, + { + name: "Luba-Katanga", + }, + ], + }, + { + name: "Central African Republic", + code: "CF", + icon: "virtual-host", + icon_status: IconStatus.Up, + capital: "Bangui", + continent: { + name: "Africa", + }, + currency: "XAF", + languages: [ + { + name: "French", + }, + { + name: "Sango", + }, + ], + }, + { + name: "Republic of the Congo", + code: "CG", + icon: "virtual-host", + icon_status: IconStatus.Up, + capital: "Brazzaville", + continent: { + name: "Africa", + }, + currency: "XAF", + languages: [ + { + name: "French", + }, + { + name: "Lingala", + }, + ], + }, + { + name: "Switzerland", + code: "CH", + icon: "virtual-host", + icon_status: IconStatus.Up, + capital: "Bern", + continent: { + name: "Europe", + }, + currency: "CHE,CHF,CHW", + languages: [ + { + name: "German", + }, + { + name: "French", + }, + { + name: "Italian", + }, + ], + }, + { + name: "Ivory Coast", + code: "CI", + icon: "virtual-host", + icon_status: IconStatus.Up, + capital: "Yamoussoukro", + continent: { + name: "Africa", + }, + currency: "XOF", + languages: [ + { + name: "French", + }, + ], + }, + { + name: "Cook Islands", + code: "CK", + icon: "virtual-host", + icon_status: IconStatus.Up, + capital: "Avarua", + continent: { + name: "Oceania", + }, + currency: "NZD", + languages: [ + { + name: "English", + }, + ], + }, + { + name: "Chile", + code: "CL", + icon: "virtual-host", + icon_status: IconStatus.Up, + capital: "Santiago", + continent: { + name: "South America", + }, + currency: "CLF,CLP", + languages: [ + { + name: "Spanish", + }, + ], + }, + { + name: "Cameroon", + code: "CM", + icon: "virtual-host", + icon_status: IconStatus.Up, + capital: "Yaoundé", + continent: { + name: "Africa", + }, + currency: "XAF", + languages: [ + { + name: "English", + }, + { + name: "French", + }, + ], + }, + { + name: "China", + code: "CN", + icon: "virtual-host", + icon_status: IconStatus.Up, + capital: "Beijing", + continent: { + name: "Asia", + }, + currency: "CNY", + languages: [ + { + name: "Chinese", + }, + ], + }, + { + name: "Colombia", + code: "CO", + icon: "virtual-host", + icon_status: IconStatus.Up, + capital: "Bogotá", + continent: { + name: "South America", + }, + currency: "COP", + languages: [ + { + name: "Spanish", + }, + ], + }, + { + name: "Costa Rica", + code: "CR", + icon: "virtual-host", + icon_status: IconStatus.Up, + capital: "San José", + continent: { + name: "North America", + }, + currency: "CRC", + languages: [ + { + name: "Spanish", + }, + ], + }, + { + name: "Cuba", + code: "CU", + icon: "virtual-host", + icon_status: IconStatus.Up, + capital: "Havana", + continent: { + name: "North America", + }, + currency: "CUC,CUP", + languages: [ + { + name: "Spanish", + }, + ], + }, + { + name: "Cape Verde", + code: "CV", + icon: "virtual-host", + icon_status: IconStatus.Up, + capital: "Praia", + continent: { + name: "Africa", + }, + currency: "CVE", + languages: [ + { + name: "Portuguese", + }, + ], + }, + { + name: "Curacao", + code: "CW", + icon: "virtual-host", + icon_status: IconStatus.Up, + capital: "Willemstad", + continent: { + name: "North America", + }, + currency: "ANG", + languages: [ + { + name: "Dutch", + }, + { + name: "Panjabi / Punjabi", + }, + { + name: "English", + }, + ], + }, + { + name: "Christmas Island", + code: "CX", + icon: "virtual-host", + icon_status: IconStatus.Up, + capital: "Flying Fish Cove", + continent: { + name: "Asia", + }, + currency: "AUD", + languages: [ + { + name: "English", + }, + ], + }, + { + name: "Cyprus", + code: "CY", + icon: "virtual-host", + icon_status: IconStatus.Up, + capital: "Nicosia", + continent: { + name: "Europe", + }, + currency: "EUR", + languages: [ + { + name: "Greek", + }, + { + name: "Turkish", + }, + { + name: "Armenian", + }, + ], + }, + { + name: "Czech Republic", + code: "CZ", + icon: "virtual-host", + icon_status: IconStatus.Up, + capital: "Prague", + continent: { + name: "Europe", + }, + currency: "CZK", + languages: [ + { + name: "Czech", + }, + { + name: "Slovak", + }, + ], + }, + { + name: "Germany", + code: "DE", + icon: "virtual-host", + icon_status: IconStatus.Up, + capital: "Berlin", + continent: { + name: "Europe", + }, + currency: "EUR", + languages: [ + { + name: "German", + }, + ], + }, + { + name: "Djibouti", + code: "DJ", + icon: "virtual-host", + icon_status: IconStatus.Up, + capital: "Djibouti", + continent: { + name: "Africa", + }, + currency: "DJF", + languages: [ + { + name: "French", + }, + { + name: "Arabic", + }, + ], + }, + { + name: "Denmark", + code: "DK", + icon: "virtual-host", + icon_status: IconStatus.Up, + capital: "Copenhagen", + continent: { + name: "Europe", + }, + currency: "DKK", + languages: [ + { + name: "Danish", + }, + ], + }, + { + name: "Dominica", + code: "DM", + icon: "virtual-host", + icon_status: IconStatus.Up, + capital: "Roseau", + continent: { + name: "North America", + }, + currency: "XCD", + languages: [ + { + name: "English", + }, + ], + }, + { + name: "Dominican Republic", + code: "DO", + icon: "virtual-host", + icon_status: IconStatus.Up, + capital: "Santo Domingo", + continent: { + name: "North America", + }, + currency: "DOP", + languages: [ + { + name: "Spanish", + }, + ], + }, + { + name: "Algeria", + code: "DZ", + icon: "virtual-host", + icon_status: IconStatus.Up, + capital: "Algiers", + continent: { + name: "Africa", + }, + currency: "DZD", + languages: [ + { + name: "Arabic", + }, + ], + }, + { + name: "Ecuador", + code: "EC", + icon: "virtual-host", + icon_status: IconStatus.Up, + capital: "Quito", + continent: { + name: "South America", + }, + currency: "USD", + languages: [ + { + name: "Spanish", + }, + ], + }, + { + name: "Estonia", + code: "EE", + icon: "virtual-host", + icon_status: IconStatus.Up, + capital: "Tallinn", + continent: { + name: "Europe", + }, + currency: "EUR", + languages: [ + { + name: "Estonian", + }, + ], + }, + { + name: "Egypt", + code: "EG", + icon: "virtual-host", + icon_status: IconStatus.Up, + capital: "Cairo", + continent: { + name: "Africa", + }, + currency: "EGP", + languages: [ + { + name: "Arabic", + }, + ], + }, + { + name: "Western Sahara", + code: "EH", + icon: "virtual-host", + icon_status: IconStatus.Up, + capital: "El Aaiún", + continent: { + name: "Africa", + }, + currency: "MAD,DZD,MRU", + languages: [ + { + name: "Spanish", + }, + ], + }, + { + name: "Eritrea", + code: "ER", + icon: "virtual-host", + icon_status: IconStatus.Up, + capital: "Asmara", + continent: { + name: "Africa", + }, + currency: "ERN", + languages: [ + { + name: "Tigrinya", + }, + { + name: "Arabic", + }, + { + name: "English", + }, + ], + }, + { + name: "Spain", + code: "ES", + icon: "virtual-host", + icon_status: IconStatus.Up, + capital: "Madrid", + continent: { + name: "Europe", + }, + currency: "EUR", + languages: [ + { + name: "Spanish", + }, + { + name: "Basque", + }, + { + name: "Catalan", + }, + { + name: "Galician", + }, + { + name: "Occitan", + }, + ], + }, + { + name: "Ethiopia", + code: "ET", + icon: "virtual-host", + icon_status: IconStatus.Up, + capital: "Addis Ababa", + continent: { + name: "Africa", + }, + currency: "ETB", + languages: [ + { + name: "Amharic", + }, + ], + }, + { + name: "Finland", + code: "FI", + icon: "virtual-host", + icon_status: IconStatus.Up, + capital: "Helsinki", + continent: { + name: "Europe", + }, + currency: "EUR", + languages: [ + { + name: "Finnish", + }, + { + name: "Swedish", + }, + ], + }, + { + name: "Fiji", + code: "FJ", + icon: "virtual-host", + icon_status: IconStatus.Up, + capital: "Suva", + continent: { + name: "Oceania", + }, + currency: "FJD", + languages: [ + { + name: "English", + }, + { + name: "Fijian", + }, + { + name: "Hindi", + }, + { + name: "Urdu", + }, + ], + }, + { + name: "Falkland Islands", + code: "FK", + icon: "virtual-host", + icon_status: IconStatus.Up, + capital: "Stanley", + continent: { + name: "South America", + }, + currency: "FKP", + languages: [ + { + name: "English", + }, + ], + }, + { + name: "Micronesia", + code: "FM", + icon: "virtual-host", + icon_status: IconStatus.Up, + capital: "Palikir", + continent: { + name: "Oceania", + }, + currency: "USD", + languages: [ + { + name: "English", + }, + ], + }, + { + name: "Faroe Islands", + code: "FO", + icon: "virtual-host", + icon_status: IconStatus.Up, + capital: "Tórshavn", + continent: { + name: "Europe", + }, + currency: "DKK", + languages: [ + { + name: "Faroese", + }, + ], + }, + { + name: "France", + code: "FR", + icon: "virtual-host", + icon_status: IconStatus.Up, + capital: "Paris", + continent: { + name: "Europe", + }, + currency: "EUR", + languages: [ + { + name: "French", + }, + ], + }, + { + name: "Gabon", + code: "GA", + icon: "virtual-host", + icon_status: IconStatus.Up, + capital: "Libreville", + continent: { + name: "Africa", + }, + currency: "XAF", + languages: [ + { + name: "French", + }, + ], + }, + { + name: "United Kingdom", + code: "GB", + icon: "virtual-host", + icon_status: IconStatus.Up, + capital: "London", + continent: { + name: "Europe", + }, + currency: "GBP", + languages: [ + { + name: "English", + }, + ], + }, + { + name: "Grenada", + code: "GD", + icon: "virtual-host", + icon_status: IconStatus.Up, + capital: "St. George's", + continent: { + name: "North America", + }, + currency: "XCD", + languages: [ + { + name: "English", + }, + ], + }, + { + name: "Georgia", + code: "GE", + icon: "virtual-host", + icon_status: IconStatus.Up, + capital: "Tbilisi", + continent: { + name: "Asia", + }, + currency: "GEL", + languages: [ + { + name: "Georgian", + }, + ], + }, + { + name: "French Guiana", + code: "GF", + icon: "virtual-host", + icon_status: IconStatus.Up, + capital: "Cayenne", + continent: { + name: "South America", + }, + currency: "EUR", + languages: [ + { + name: "French", + }, + ], + }, + { + name: "Guernsey", + code: "GG", + icon: "virtual-host", + icon_status: IconStatus.Up, + capital: "St. Peter Port", + continent: { + name: "Europe", + }, + currency: "GBP", + languages: [ + { + name: "English", + }, + { + name: "French", + }, + ], + }, + { + name: "Ghana", + code: "GH", + icon: "virtual-host", + icon_status: IconStatus.Up, + capital: "Accra", + continent: { + name: "Africa", + }, + currency: "GHS", + languages: [ + { + name: "English", + }, + ], + }, + { + name: "Gibraltar", + code: "GI", + icon: "virtual-host", + icon_status: IconStatus.Up, + capital: "Gibraltar", + continent: { + name: "Europe", + }, + currency: "GIP", + languages: [ + { + name: "English", + }, + ], + }, + { + name: "Greenland", + code: "GL", + icon: "virtual-host", + icon_status: IconStatus.Up, + capital: "Nuuk", + continent: { + name: "North America", + }, + currency: "DKK", + languages: [ + { + name: "Greenlandic", + }, + ], + }, + { + name: "Gambia", + code: "GM", + icon: "virtual-host", + icon_status: IconStatus.Up, + capital: "Banjul", + continent: { + name: "Africa", + }, + currency: "GMD", + languages: [ + { + name: "English", + }, + ], + }, + { + name: "Guinea", + code: "GN", + icon: "virtual-host", + icon_status: IconStatus.Up, + capital: "Conakry", + continent: { + name: "Africa", + }, + currency: "GNF", + languages: [ + { + name: "French", + }, + { + name: "Peul", + }, + ], + }, + { + name: "Guadeloupe", + code: "GP", + icon: "virtual-host", + icon_status: IconStatus.Up, + capital: "Basse-Terre", + continent: { + name: "North America", + }, + currency: "EUR", + languages: [ + { + name: "French", + }, + ], + }, + { + name: "Equatorial Guinea", + code: "GQ", + icon: "virtual-host", + icon_status: IconStatus.Up, + capital: "Malabo", + continent: { + name: "Africa", + }, + currency: "XAF", + languages: [ + { + name: "Spanish", + }, + { + name: "French", + }, + ], + }, + { + name: "Greece", + code: "GR", + icon: "virtual-host", + icon_status: IconStatus.Up, + capital: "Athens", + continent: { + name: "Europe", + }, + currency: "EUR", + languages: [ + { + name: "Greek", + }, + ], + }, + { + name: "South Georgia and the South Sandwich Islands", + code: "GS", + icon: "virtual-host", + icon_status: IconStatus.Up, + capital: "King Edward Point", + continent: { + name: "Antarctica", + }, + currency: "GBP", + languages: [ + { + name: "English", + }, + ], + }, + { + name: "Guatemala", + code: "GT", + icon: "virtual-host", + icon_status: IconStatus.Up, + capital: "Guatemala City", + continent: { + name: "North America", + }, + currency: "GTQ", + languages: [ + { + name: "Spanish", + }, + ], + }, + { + name: "Guam", + code: "GU", + icon: "virtual-host", + icon_status: IconStatus.Up, + capital: "Hagåtña", + continent: { + name: "Oceania", + }, + currency: "USD", + languages: [ + { + name: "English", + }, + { + name: "Chamorro", + }, + { + name: "Spanish", + }, + ], + }, + { + name: "Guinea-Bissau", + code: "GW", + icon: "virtual-host", + icon_status: IconStatus.Up, + capital: "Bissau", + continent: { + name: "Africa", + }, + currency: "XOF", + languages: [ + { + name: "Portuguese", + }, + ], + }, + { + name: "Guyana", + code: "GY", + icon: "virtual-host", + icon_status: IconStatus.Up, + capital: "Georgetown", + continent: { + name: "South America", + }, + currency: "GYD", + languages: [ + { + name: "English", + }, + ], + }, + { + name: "Hong Kong", + code: "HK", + icon: "virtual-host", + icon_status: IconStatus.Up, + capital: "City of Victoria", + continent: { + name: "Asia", + }, + currency: "HKD", + languages: [ + { + name: "Chinese", + }, + { + name: "English", + }, + ], + }, + { + name: "Heard Island and McDonald Islands", + code: "HM", + icon: "virtual-host", + icon_status: IconStatus.Up, + capital: null, + continent: { + name: "Antarctica", + }, + currency: "AUD", + languages: [ + { + name: "English", + }, + ], + }, + { + name: "Honduras", + code: "HN", + icon: "virtual-host", + icon_status: IconStatus.Up, + capital: "Tegucigalpa", + continent: { + name: "North America", + }, + currency: "HNL", + languages: [ + { + name: "Spanish", + }, + ], + }, + { + name: "Croatia", + code: "HR", + icon: "virtual-host", + icon_status: IconStatus.Up, + capital: "Zagreb", + continent: { + name: "Europe", + }, + currency: "HRK", + languages: [ + { + name: "Croatian", + }, + ], + }, + { + name: "Haiti", + code: "HT", + icon: "virtual-host", + icon_status: IconStatus.Up, + capital: "Port-au-Prince", + continent: { + name: "North America", + }, + currency: "HTG,USD", + languages: [ + { + name: "French", + }, + { + name: "Haitian", + }, + ], + }, + { + name: "Hungary", + code: "HU", + icon: "virtual-host", + icon_status: IconStatus.Up, + capital: "Budapest", + continent: { + name: "Europe", + }, + currency: "HUF", + languages: [ + { + name: "Hungarian", + }, + ], + }, + { + name: "Indonesia", + code: "ID", + icon: "virtual-host", + icon_status: IconStatus.Up, + capital: "Jakarta", + continent: { + name: "Asia", + }, + currency: "IDR", + languages: [ + { + name: "Indonesian", + }, + ], + }, + { + name: "Ireland", + code: "IE", + icon: "virtual-host", + icon_status: IconStatus.Up, + capital: "Dublin", + continent: { + name: "Europe", + }, + currency: "EUR", + languages: [ + { + name: "Irish", + }, + { + name: "English", + }, + ], + }, + { + name: "Israel", + code: "IL", + icon: "virtual-host", + icon_status: IconStatus.Up, + capital: "Jerusalem", + continent: { + name: "Asia", + }, + currency: "ILS", + languages: [ + { + name: "Hebrew", + }, + { + name: "Arabic", + }, + ], + }, + { + name: "Isle of Man", + code: "IM", + icon: "virtual-host", + icon_status: IconStatus.Up, + capital: "Douglas", + continent: { + name: "Europe", + }, + currency: "GBP", + languages: [ + { + name: "English", + }, + { + name: "Manx", + }, + ], + }, + { + name: "India", + code: "IN", + icon: "virtual-host", + icon_status: IconStatus.Up, + capital: "New Delhi", + continent: { + name: "Asia", + }, + currency: "INR", + languages: [ + { + name: "Hindi", + }, + { + name: "English", + }, + ], + }, + { + name: "British Indian Ocean Territory", + code: "IO", + icon: "virtual-host", + icon_status: IconStatus.Up, + capital: "Diego Garcia", + continent: { + name: "Asia", + }, + currency: "USD", + languages: [ + { + name: "English", + }, + ], + }, + { + name: "Iraq", + code: "IQ", + icon: "virtual-host", + icon_status: IconStatus.Up, + capital: "Baghdad", + continent: { + name: "Asia", + }, + currency: "IQD", + languages: [ + { + name: "Arabic", + }, + { + name: "Kurdish", + }, + ], + }, + { + name: "Iran", + code: "IR", + icon: "virtual-host", + icon_status: IconStatus.Up, + capital: "Tehran", + continent: { + name: "Asia", + }, + currency: "IRR", + languages: [ + { + name: "Persian", + }, + ], + }, + { + name: "Iceland", + code: "IS", + icon: "virtual-host", + icon_status: IconStatus.Up, + capital: "Reykjavik", + continent: { + name: "Europe", + }, + currency: "ISK", + languages: [ + { + name: "Icelandic", + }, + ], + }, + { + name: "Italy", + code: "IT", + icon: "virtual-host", + icon_status: IconStatus.Up, + capital: "Rome", + continent: { + name: "Europe", + }, + currency: "EUR", + languages: [ + { + name: "Italian", + }, + ], + }, + { + name: "Jersey", + code: "JE", + icon: "virtual-host", + icon_status: IconStatus.Up, + capital: "Saint Helier", + continent: { + name: "Europe", + }, + currency: "GBP", + languages: [ + { + name: "English", + }, + { + name: "French", + }, + ], + }, + { + name: "Jamaica", + code: "JM", + icon: "virtual-host", + icon_status: IconStatus.Up, + capital: "Kingston", + continent: { + name: "North America", + }, + currency: "JMD", + languages: [ + { + name: "English", + }, + ], + }, + { + name: "Jordan", + code: "JO", + icon: "virtual-host", + icon_status: IconStatus.Up, + capital: "Amman", + continent: { + name: "Asia", + }, + currency: "JOD", + languages: [ + { + name: "Arabic", + }, + ], + }, + { + name: "Japan", + code: "JP", + icon: "virtual-host", + icon_status: IconStatus.Up, + capital: "Tokyo", + continent: { + name: "Asia", + }, + currency: "JPY", + languages: [ + { + name: "Japanese", + }, + ], + }, + { + name: "Kenya", + code: "KE", + icon: "virtual-host", + icon_status: IconStatus.Up, + capital: "Nairobi", + continent: { + name: "Africa", + }, + currency: "KES", + languages: [ + { + name: "English", + }, + { + name: "Swahili", + }, + ], + }, + { + name: "Kyrgyzstan", + code: "KG", + icon: "virtual-host", + icon_status: IconStatus.Up, + capital: "Bishkek", + continent: { + name: "Asia", + }, + currency: "KGS", + languages: [ + { + name: "Kirghiz", + }, + { + name: "Russian", + }, + ], + }, + { + name: "Cambodia", + code: "KH", + icon: "virtual-host", + icon_status: IconStatus.Up, + capital: "Phnom Penh", + continent: { + name: "Asia", + }, + currency: "KHR", + languages: [ + { + name: "Cambodian", + }, + ], + }, + { + name: "Kiribati", + code: "KI", + icon: "virtual-host", + icon_status: IconStatus.Up, + capital: "South Tarawa", + continent: { + name: "Oceania", + }, + currency: "AUD", + languages: [ + { + name: "English", + }, + ], + }, + { + name: "Comoros", + code: "KM", + icon: "virtual-host", + icon_status: IconStatus.Up, + capital: "Moroni", + continent: { + name: "Africa", + }, + currency: "KMF", + languages: [ + { + name: "Arabic", + }, + { + name: "French", + }, + ], + }, + { + name: "Saint Kitts and Nevis", + code: "KN", + icon: "virtual-host", + icon_status: IconStatus.Up, + capital: "Basseterre", + continent: { + name: "North America", + }, + currency: "XCD", + languages: [ + { + name: "English", + }, + ], + }, + { + name: "North Korea", + code: "KP", + icon: "virtual-host", + icon_status: IconStatus.Up, + capital: "Pyongyang", + continent: { + name: "Asia", + }, + currency: "KPW", + languages: [ + { + name: "Korean", + }, + ], + }, + { + name: "South Korea", + code: "KR", + icon: "virtual-host", + icon_status: IconStatus.Up, + capital: "Seoul", + continent: { + name: "Asia", + }, + currency: "KRW", + languages: [ + { + name: "Korean", + }, + ], + }, + { + name: "Kuwait", + code: "KW", + icon: "virtual-host", + icon_status: IconStatus.Up, + capital: "Kuwait City", + continent: { + name: "Asia", + }, + currency: "KWD", + languages: [ + { + name: "Arabic", + }, + ], + }, + { + name: "Cayman Islands", + code: "KY", + icon: "virtual-host", + icon_status: IconStatus.Up, + capital: "George Town", + continent: { + name: "North America", + }, + currency: "KYD", + languages: [ + { + name: "English", + }, + ], + }, + { + name: "Kazakhstan", + code: "KZ", + icon: "virtual-host", + icon_status: IconStatus.Up, + capital: "Astana", + continent: { + name: "Asia", + }, + currency: "KZT", + languages: [ + { + name: "Kazakh", + }, + { + name: "Russian", + }, + ], + }, + { + name: "Laos", + code: "LA", + icon: "virtual-host", + icon_status: IconStatus.Up, + capital: "Vientiane", + continent: { + name: "Asia", + }, + currency: "LAK", + languages: [ + { + name: "Laotian", + }, + ], + }, + { + name: "Lebanon", + code: "LB", + icon: "virtual-host", + icon_status: IconStatus.Up, + capital: "Beirut", + continent: { + name: "Asia", + }, + currency: "LBP", + languages: [ + { + name: "Arabic", + }, + { + name: "French", + }, + ], + }, + { + name: "Saint Lucia", + code: "LC", + icon: "virtual-host", + icon_status: IconStatus.Up, + capital: "Castries", + continent: { + name: "North America", + }, + currency: "XCD", + languages: [ + { + name: "English", + }, + ], + }, + { + name: "Liechtenstein", + code: "LI", + icon: "virtual-host", + icon_status: IconStatus.Up, + capital: "Vaduz", + continent: { + name: "Europe", + }, + currency: "CHF", + languages: [ + { + name: "German", + }, + ], + }, + { + name: "Sri Lanka", + code: "LK", + icon: "virtual-host", + icon_status: IconStatus.Up, + capital: "Colombo", + continent: { + name: "Asia", + }, + currency: "LKR", + languages: [ + { + name: "Sinhalese", + }, + { + name: "Tamil", + }, + ], + }, + { + name: "Liberia", + code: "LR", + icon: "virtual-host", + icon_status: IconStatus.Up, + capital: "Monrovia", + continent: { + name: "Africa", + }, + currency: "LRD", + languages: [ + { + name: "English", + }, + ], + }, + { + name: "Lesotho", + code: "LS", + icon: "virtual-host", + icon_status: IconStatus.Up, + capital: "Maseru", + continent: { + name: "Africa", + }, + currency: "LSL,ZAR", + languages: [ + { + name: "English", + }, + { + name: "Southern Sotho", + }, + ], + }, + { + name: "Lithuania", + code: "LT", + icon: "virtual-host", + icon_status: IconStatus.Up, + capital: "Vilnius", + continent: { + name: "Europe", + }, + currency: "EUR", + languages: [ + { + name: "Lithuanian", + }, + ], + }, + { + name: "Luxembourg", + code: "LU", + icon: "virtual-host", + icon_status: IconStatus.Up, + capital: "Luxembourg", + continent: { + name: "Europe", + }, + currency: "EUR", + languages: [ + { + name: "French", + }, + { + name: "German", + }, + { + name: "Luxembourgish", + }, + ], + }, + { + name: "Latvia", + code: "LV", + icon: "virtual-host", + icon_status: IconStatus.Up, + capital: "Riga", + continent: { + name: "Europe", + }, + currency: "EUR", + languages: [ + { + name: "Latvian", + }, + ], + }, + { + name: "Libya", + code: "LY", + icon: "virtual-host", + icon_status: IconStatus.Up, + capital: "Tripoli", + continent: { + name: "Africa", + }, + currency: "LYD", + languages: [ + { + name: "Arabic", + }, + ], + }, + { + name: "Morocco", + code: "MA", + icon: "virtual-host", + icon_status: IconStatus.Up, + capital: "Rabat", + continent: { + name: "Africa", + }, + currency: "MAD", + languages: [ + { + name: "Arabic", + }, + ], + }, + { + name: "Monaco", + code: "MC", + icon: "virtual-host", + icon_status: IconStatus.Up, + capital: "Monaco", + continent: { + name: "Europe", + }, + currency: "EUR", + languages: [ + { + name: "French", + }, + ], + }, + { + name: "Moldova", + code: "MD", + icon: "virtual-host", + icon_status: IconStatus.Up, + capital: "Chișinău", + continent: { + name: "Europe", + }, + currency: "MDL", + languages: [ + { + name: "Romanian", + }, + ], + }, + { + name: "Montenegro", + code: "ME", + icon: "virtual-host", + icon_status: IconStatus.Up, + capital: "Podgorica", + continent: { + name: "Europe", + }, + currency: "EUR", + languages: [ + { + name: "Serbian", + }, + { + name: "Bosnian", + }, + { + name: "Albanian", + }, + { + name: "Croatian", + }, + ], + }, + { + name: "Saint Martin", + code: "MF", + icon: "virtual-host", + icon_status: IconStatus.Up, + capital: "Marigot", + continent: { + name: "North America", + }, + currency: "EUR", + languages: [ + { + name: "English", + }, + { + name: "French", + }, + { + name: "Dutch", + }, + ], + }, + { + name: "Madagascar", + code: "MG", + icon: "virtual-host", + icon_status: IconStatus.Up, + capital: "Antananarivo", + continent: { + name: "Africa", + }, + currency: "MGA", + languages: [ + { + name: "French", + }, + { + name: "Malagasy", + }, + ], + }, + { + name: "Marshall Islands", + code: "MH", + icon: "virtual-host", + icon_status: IconStatus.Up, + capital: "Majuro", + continent: { + name: "Oceania", + }, + currency: "USD", + languages: [ + { + name: "English", + }, + { + name: "Marshallese", + }, + ], + }, + { + name: "North Macedonia", + code: "MK", + icon: "virtual-host", + icon_status: IconStatus.Up, + capital: "Skopje", + continent: { + name: "Europe", + }, + currency: "MKD", + languages: [ + { + name: "Macedonian", + }, + ], + }, + { + name: "Mali", + code: "ML", + icon: "virtual-host", + icon_status: IconStatus.Up, + capital: "Bamako", + continent: { + name: "Africa", + }, + currency: "XOF", + languages: [ + { + name: "French", + }, + ], + }, + { + name: "Myanmar [Burma]", + code: "MM", + icon: "virtual-host", + icon_status: IconStatus.Up, + capital: "Naypyidaw", + continent: { + name: "Asia", + }, + currency: "MMK", + languages: [ + { + name: "Burmese", + }, + ], + }, + { + name: "Mongolia", + code: "MN", + icon: "virtual-host", + icon_status: IconStatus.Up, + capital: "Ulan Bator", + continent: { + name: "Asia", + }, + currency: "MNT", + languages: [ + { + name: "Mongolian", + }, + ], + }, + { + name: "Macao", + code: "MO", + icon: "virtual-host", + icon_status: IconStatus.Up, + capital: null, + continent: { + name: "Asia", + }, + currency: "MOP", + languages: [ + { + name: "Chinese", + }, + { + name: "Portuguese", + }, + ], + }, + { + name: "Northern Mariana Islands", + code: "MP", + icon: "virtual-host", + icon_status: IconStatus.Up, + capital: "Saipan", + continent: { + name: "Oceania", + }, + currency: "USD", + languages: [ + { + name: "English", + }, + { + name: "Chamorro", + }, + ], + }, + { + name: "Martinique", + code: "MQ", + icon: "virtual-host", + icon_status: IconStatus.Up, + capital: "Fort-de-France", + continent: { + name: "North America", + }, + currency: "EUR", + languages: [ + { + name: "French", + }, + ], + }, + { + name: "Mauritania", + code: "MR", + icon: "virtual-host", + icon_status: IconStatus.Up, + capital: "Nouakchott", + continent: { + name: "Africa", + }, + currency: "MRU", + languages: [ + { + name: "Arabic", + }, + ], + }, + { + name: "Montserrat", + code: "MS", + icon: "virtual-host", + icon_status: IconStatus.Up, + capital: "Plymouth", + continent: { + name: "North America", + }, + currency: "XCD", + languages: [ + { + name: "English", + }, + ], + }, + { + name: "Malta", + code: "MT", + icon: "virtual-host", + icon_status: IconStatus.Up, + capital: "Valletta", + continent: { + name: "Europe", + }, + currency: "EUR", + languages: [ + { + name: "Maltese", + }, + { + name: "English", + }, + ], + }, + { + name: "Mauritius", + code: "MU", + icon: "virtual-host", + icon_status: IconStatus.Up, + capital: "Port Louis", + continent: { + name: "Africa", + }, + currency: "MUR", + languages: [ + { + name: "English", + }, + ], + }, + { + name: "Maldives", + code: "MV", + icon: "virtual-host", + icon_status: IconStatus.Up, + capital: "Malé", + continent: { + name: "Asia", + }, + currency: "MVR", + languages: [ + { + name: "Divehi", + }, + ], + }, + { + name: "Malawi", + code: "MW", + icon: "virtual-host", + icon_status: IconStatus.Up, + capital: "Lilongwe", + continent: { + name: "Africa", + }, + currency: "MWK", + languages: [ + { + name: "English", + }, + { + name: "Chichewa", + }, + ], + }, + { + name: "Mexico", + code: "MX", + icon: "virtual-host", + icon_status: IconStatus.Up, + capital: "Mexico City", + continent: { + name: "North America", + }, + currency: "MXN", + languages: [ + { + name: "Spanish", + }, + ], + }, + { + name: "Malaysia", + code: "MY", + icon: "virtual-host", + icon_status: IconStatus.Up, + capital: "Kuala Lumpur", + continent: { + name: "Asia", + }, + currency: "MYR", + languages: [ + { + name: "Malay", + }, + ], + }, + { + name: "Mozambique", + code: "MZ", + icon: "virtual-host", + icon_status: IconStatus.Up, + capital: "Maputo", + continent: { + name: "Africa", + }, + currency: "MZN", + languages: [ + { + name: "Portuguese", + }, + ], + }, + { + name: "Namibia", + code: "NA", + icon: "virtual-host", + icon_status: IconStatus.Up, + capital: "Windhoek", + continent: { + name: "Africa", + }, + currency: "NAD,ZAR", + languages: [ + { + name: "English", + }, + { + name: "Afrikaans", + }, + ], + }, + { + name: "New Caledonia", + code: "NC", + icon: "virtual-host", + icon_status: IconStatus.Up, + capital: "Nouméa", + continent: { + name: "Oceania", + }, + currency: "XPF", + languages: [ + { + name: "French", + }, + ], + }, + { + name: "Niger", + code: "NE", + icon: "virtual-host", + icon_status: IconStatus.Up, + capital: "Niamey", + continent: { + name: "Africa", + }, + currency: "XOF", + languages: [ + { + name: "French", + }, + ], + }, + { + name: "Norfolk Island", + code: "NF", + icon: "virtual-host", + icon_status: IconStatus.Up, + capital: "Kingston", + continent: { + name: "Oceania", + }, + currency: "AUD", + languages: [ + { + name: "English", + }, + ], + }, + { + name: "Nigeria", + code: "NG", + icon: "virtual-host", + icon_status: IconStatus.Up, + capital: "Abuja", + continent: { + name: "Africa", + }, + currency: "NGN", + languages: [ + { + name: "English", + }, + ], + }, + { + name: "Nicaragua", + code: "NI", + icon: "virtual-host", + icon_status: IconStatus.Up, + capital: "Managua", + continent: { + name: "North America", + }, + currency: "NIO", + languages: [ + { + name: "Spanish", + }, + ], + }, + { + name: "Netherlands", + code: "NL", + icon: "virtual-host", + icon_status: IconStatus.Up, + capital: "Amsterdam", + continent: { + name: "Europe", + }, + currency: "EUR", + languages: [ + { + name: "Dutch", + }, + ], + }, + { + name: "Norway", + code: "NO", + icon: "virtual-host", + icon_status: IconStatus.Up, + capital: "Oslo", + continent: { + name: "Europe", + }, + currency: "NOK", + languages: [ + { + name: "Norwegian", + }, + { + name: "Norwegian Bokmål", + }, + { + name: "Norwegian Nynorsk", + }, + ], + }, + { + name: "Nepal", + code: "NP", + icon: "virtual-host", + icon_status: IconStatus.Up, + capital: "Kathmandu", + continent: { + name: "Asia", + }, + currency: "NPR", + languages: [ + { + name: "Nepali", + }, + ], + }, + { + name: "Nauru", + code: "NR", + icon: "virtual-host", + icon_status: IconStatus.Up, + capital: "Yaren", + continent: { + name: "Oceania", + }, + currency: "AUD", + languages: [ + { + name: "English", + }, + { + name: "Nauruan", + }, + ], + }, + { + name: "Niue", + code: "NU", + icon: "virtual-host", + icon_status: IconStatus.Up, + capital: "Alofi", + continent: { + name: "Oceania", + }, + currency: "NZD", + languages: [ + { + name: "English", + }, + ], + }, + { + name: "New Zealand", + code: "NZ", + icon: "virtual-host", + icon_status: IconStatus.Up, + capital: "Wellington", + continent: { + name: "Oceania", + }, + currency: "NZD", + languages: [ + { + name: "English", + }, + { + name: "Maori", + }, + ], + }, + { + name: "Oman", + code: "OM", + icon: "virtual-host", + icon_status: IconStatus.Up, + capital: "Muscat", + continent: { + name: "Asia", + }, + currency: "OMR", + languages: [ + { + name: "Arabic", + }, + ], + }, + { + name: "Panama", + code: "PA", + icon: "virtual-host", + icon_status: IconStatus.Up, + capital: "Panama City", + continent: { + name: "North America", + }, + currency: "PAB,USD", + languages: [ + { + name: "Spanish", + }, + ], + }, + { + name: "Peru", + code: "PE", + icon: "virtual-host", + icon_status: IconStatus.Up, + capital: "Lima", + continent: { + name: "South America", + }, + currency: "PEN", + languages: [ + { + name: "Spanish", + }, + ], + }, + { + name: "French Polynesia", + code: "PF", + icon: "virtual-host", + icon_status: IconStatus.Up, + capital: "Papeetē", + continent: { + name: "Oceania", + }, + currency: "XPF", + languages: [ + { + name: "French", + }, + ], + }, + { + name: "Papua New Guinea", + code: "PG", + icon: "virtual-host", + icon_status: IconStatus.Up, + capital: "Port Moresby", + continent: { + name: "Oceania", + }, + currency: "PGK", + languages: [ + { + name: "English", + }, + ], + }, + { + name: "Philippines", + code: "PH", + icon: "virtual-host", + icon_status: IconStatus.Up, + capital: "Manila", + continent: { + name: "Asia", + }, + currency: "PHP", + languages: [ + { + name: "English", + }, + ], + }, + { + name: "Pakistan", + code: "PK", + icon: "virtual-host", + icon_status: IconStatus.Up, + capital: "Islamabad", + continent: { + name: "Asia", + }, + currency: "PKR", + languages: [ + { + name: "English", + }, + { + name: "Urdu", + }, + ], + }, + { + name: "Poland", + code: "PL", + icon: "virtual-host", + icon_status: IconStatus.Up, + capital: "Warsaw", + continent: { + name: "Europe", + }, + currency: "PLN", + languages: [ + { + name: "Polish", + }, + ], + }, + { + name: "Saint Pierre and Miquelon", + code: "PM", + icon: "virtual-host", + icon_status: IconStatus.Up, + capital: "Saint-Pierre", + continent: { + name: "North America", + }, + currency: "EUR", + languages: [ + { + name: "French", + }, + ], + }, + { + name: "Pitcairn Islands", + code: "PN", + icon: "virtual-host", + icon_status: IconStatus.Up, + capital: "Adamstown", + continent: { + name: "Oceania", + }, + currency: "NZD", + languages: [ + { + name: "English", + }, + ], + }, + { + name: "Puerto Rico", + code: "PR", + icon: "virtual-host", + icon_status: IconStatus.Up, + capital: "San Juan", + continent: { + name: "North America", + }, + currency: "USD", + languages: [ + { + name: "Spanish", + }, + { + name: "English", + }, + ], + }, + { + name: "Palestine", + code: "PS", + icon: "virtual-host", + icon_status: IconStatus.Up, + capital: "Ramallah", + continent: { + name: "Asia", + }, + currency: "ILS", + languages: [ + { + name: "Arabic", + }, + ], + }, + { + name: "Portugal", + code: "PT", + icon: "virtual-host", + icon_status: IconStatus.Up, + capital: "Lisbon", + continent: { + name: "Europe", + }, + currency: "EUR", + languages: [ + { + name: "Portuguese", + }, + ], + }, + { + name: "Palau", + code: "PW", + icon: "virtual-host", + icon_status: IconStatus.Up, + capital: "Ngerulmud", + continent: { + name: "Oceania", + }, + currency: "USD", + languages: [ + { + name: "English", + }, + ], + }, + { + name: "Paraguay", + code: "PY", + icon: "virtual-host", + icon_status: IconStatus.Up, + capital: "Asunción", + continent: { + name: "South America", + }, + currency: "PYG", + languages: [ + { + name: "Spanish", + }, + { + name: "Guarani", + }, + ], + }, + { + name: "Qatar", + code: "QA", + icon: "virtual-host", + icon_status: IconStatus.Up, + capital: "Doha", + continent: { + name: "Asia", + }, + currency: "QAR", + languages: [ + { + name: "Arabic", + }, + ], + }, + { + name: "Réunion", + code: "RE", + icon: "virtual-host", + icon_status: IconStatus.Up, + capital: "Saint-Denis", + continent: { + name: "Africa", + }, + currency: "EUR", + languages: [ + { + name: "French", + }, + ], + }, + { + name: "Romania", + code: "RO", + icon: "virtual-host", + icon_status: IconStatus.Up, + capital: "Bucharest", + continent: { + name: "Europe", + }, + currency: "RON", + languages: [ + { + name: "Romanian", + }, + ], + }, + { + name: "Serbia", + code: "RS", + icon: "virtual-host", + icon_status: IconStatus.Up, + capital: "Belgrade", + continent: { + name: "Europe", + }, + currency: "RSD", + languages: [ + { + name: "Serbian", + }, + ], + }, + { + name: "Russia", + code: "RU", + icon: "virtual-host", + icon_status: IconStatus.Up, + capital: "Moscow", + continent: { + name: "Europe", + }, + currency: "RUB", + languages: [ + { + name: "Russian", + }, + ], + }, + { + name: "Rwanda", + code: "RW", + icon: "virtual-host", + icon_status: IconStatus.Up, + capital: "Kigali", + continent: { + name: "Africa", + }, + currency: "RWF", + languages: [ + { + name: "Rwandi", + }, + { + name: "English", + }, + { + name: "French", + }, + ], + }, + { + name: "Saudi Arabia", + code: "SA", + icon: "virtual-host", + icon_status: IconStatus.Up, + capital: "Riyadh", + continent: { + name: "Asia", + }, + currency: "SAR", + languages: [ + { + name: "Arabic", + }, + ], + }, + { + name: "Solomon Islands", + code: "SB", + icon: "virtual-host", + icon_status: IconStatus.Up, + capital: "Honiara", + continent: { + name: "Oceania", + }, + currency: "SBD", + languages: [ + { + name: "English", + }, + ], + }, + { + name: "Seychelles", + code: "SC", + icon: "virtual-host", + icon_status: IconStatus.Up, + capital: "Victoria", + continent: { + name: "Africa", + }, + currency: "SCR", + languages: [ + { + name: "French", + }, + { + name: "English", + }, + ], + }, + { + name: "Sudan", + code: "SD", + icon: "virtual-host", + icon_status: IconStatus.Up, + capital: "Khartoum", + continent: { + name: "Africa", + }, + currency: "SDG", + languages: [ + { + name: "Arabic", + }, + { + name: "English", + }, + ], + }, + { + name: "Sweden", + code: "SE", + icon: "virtual-host", + icon_status: IconStatus.Up, + capital: "Stockholm", + continent: { + name: "Europe", + }, + currency: "SEK", + languages: [ + { + name: "Swedish", + }, + ], + }, + { + name: "Singapore", + code: "SG", + icon: "virtual-host", + icon_status: IconStatus.Up, + capital: "Singapore", + continent: { + name: "Asia", + }, + currency: "SGD", + languages: [ + { + name: "English", + }, + { + name: "Malay", + }, + { + name: "Tamil", + }, + { + name: "Chinese", + }, + ], + }, + { + name: "Saint Helena", + code: "SH", + icon: "virtual-host", + icon_status: IconStatus.Up, + capital: "Jamestown", + continent: { + name: "Africa", + }, + currency: "SHP", + languages: [ + { + name: "English", + }, + ], + }, + { + name: "Slovenia", + code: "SI", + icon: "virtual-host", + icon_status: IconStatus.Up, + capital: "Ljubljana", + continent: { + name: "Europe", + }, + currency: "EUR", + languages: [ + { + name: "Slovenian", + }, + ], + }, + { + name: "Svalbard and Jan Mayen", + code: "SJ", + icon: "virtual-host", + icon_status: IconStatus.Up, + capital: "Longyearbyen", + continent: { + name: "Europe", + }, + currency: "NOK", + languages: [ + { + name: "Norwegian", + }, + ], + }, + { + name: "Slovakia", + code: "SK", + icon: "virtual-host", + icon_status: IconStatus.Up, + capital: "Bratislava", + continent: { + name: "Europe", + }, + currency: "EUR", + languages: [ + { + name: "Slovak", + }, + ], + }, + { + name: "Sierra Leone", + code: "SL", + icon: "virtual-host", + icon_status: IconStatus.Up, + capital: "Freetown", + continent: { + name: "Africa", + }, + currency: "SLL", + languages: [ + { + name: "English", + }, + ], + }, + { + name: "San Marino", + code: "SM", + icon: "virtual-host", + icon_status: IconStatus.Up, + capital: "City of San Marino", + continent: { + name: "Europe", + }, + currency: "EUR", + languages: [ + { + name: "Italian", + }, + ], + }, + { + name: "Senegal", + code: "SN", + icon: "virtual-host", + icon_status: IconStatus.Up, + capital: "Dakar", + continent: { + name: "Africa", + }, + currency: "XOF", + languages: [ + { + name: "French", + }, + ], + }, + { + name: "Somalia", + code: "SO", + icon: "virtual-host", + icon_status: IconStatus.Up, + capital: "Mogadishu", + continent: { + name: "Africa", + }, + currency: "SOS", + languages: [ + { + name: "Somalia", + }, + { + name: "Arabic", + }, + ], + }, + { + name: "Suriname", + code: "SR", + icon: "virtual-host", + icon_status: IconStatus.Up, + capital: "Paramaribo", + continent: { + name: "South America", + }, + currency: "SRD", + languages: [ + { + name: "Dutch", + }, + ], + }, + { + name: "South Sudan", + code: "SS", + icon: "virtual-host", + icon_status: IconStatus.Up, + capital: "Juba", + continent: { + name: "Africa", + }, + currency: "SSP", + languages: [ + { + name: "English", + }, + ], + }, + { + name: "São Tomé and Príncipe", + code: "ST", + icon: "virtual-host", + icon_status: IconStatus.Up, + capital: "São Tomé", + continent: { + name: "Africa", + }, + currency: "STN", + languages: [ + { + name: "Portuguese", + }, + ], + }, + { + name: "El Salvador", + code: "SV", + icon: "virtual-host", + icon_status: IconStatus.Up, + capital: "San Salvador", + continent: { + name: "North America", + }, + currency: "SVC,USD", + languages: [ + { + name: "Spanish", + }, + ], + }, + { + name: "Sint Maarten", + code: "SX", + icon: "virtual-host", + icon_status: IconStatus.Up, + capital: "Philipsburg", + continent: { + name: "North America", + }, + currency: "ANG", + languages: [ + { + name: "Dutch", + }, + { + name: "English", + }, + ], + }, + { + name: "Syria", + code: "SY", + icon: "virtual-host", + icon_status: IconStatus.Up, + capital: "Damascus", + continent: { + name: "Asia", + }, + currency: "SYP", + languages: [ + { + name: "Arabic", + }, + ], + }, + { + name: "Swaziland", + code: "SZ", + icon: "virtual-host", + icon_status: IconStatus.Up, + capital: "Lobamba", + continent: { + name: "Africa", + }, + currency: "SZL", + languages: [ + { + name: "English", + }, + { + name: "Swati", + }, + ], + }, + { + name: "Turks and Caicos Islands", + code: "TC", + icon: "virtual-host", + icon_status: IconStatus.Up, + capital: "Cockburn Town", + continent: { + name: "North America", + }, + currency: "USD", + languages: [ + { + name: "English", + }, + ], + }, + { + name: "Chad", + code: "TD", + icon: "virtual-host", + icon_status: IconStatus.Up, + capital: "N'Djamena", + continent: { + name: "Africa", + }, + currency: "XAF", + languages: [ + { + name: "French", + }, + { + name: "Arabic", + }, + ], + }, + { + name: "French Southern Territories", + code: "TF", + icon: "virtual-host", + icon_status: IconStatus.Up, + capital: "Port-aux-Français", + continent: { + name: "Antarctica", + }, + currency: "EUR", + languages: [ + { + name: "French", + }, + ], + }, + { + name: "Togo", + code: "TG", + icon: "virtual-host", + icon_status: IconStatus.Up, + capital: "Lomé", + continent: { + name: "Africa", + }, + currency: "XOF", + languages: [ + { + name: "French", + }, + ], + }, + { + name: "Thailand", + code: "TH", + icon: "virtual-host", + icon_status: IconStatus.Up, + capital: "Bangkok", + continent: { + name: "Asia", + }, + currency: "THB", + languages: [ + { + name: "Thai", + }, + ], + }, + { + name: "Tajikistan", + code: "TJ", + icon: "virtual-host", + icon_status: IconStatus.Up, + capital: "Dushanbe", + continent: { + name: "Asia", + }, + currency: "TJS", + languages: [ + { + name: "Tajik", + }, + { + name: "Russian", + }, + ], + }, + { + name: "Tokelau", + code: "TK", + icon: "virtual-host", + icon_status: IconStatus.Up, + capital: "Fakaofo", + continent: { + name: "Oceania", + }, + currency: "NZD", + languages: [ + { + name: "English", + }, + ], + }, + { + name: "East Timor", + code: "TL", + icon: "virtual-host", + icon_status: IconStatus.Up, + capital: "Dili", + continent: { + name: "Oceania", + }, + currency: "USD", + languages: [ + { + name: "Portuguese", + }, + ], + }, + { + name: "Turkmenistan", + code: "TM", + icon: "virtual-host", + icon_status: IconStatus.Up, + capital: "Ashgabat", + continent: { + name: "Asia", + }, + currency: "TMT", + languages: [ + { + name: "Turkmen", + }, + { + name: "Russian", + }, + ], + }, + { + name: "Tunisia", + code: "TN", + icon: "virtual-host", + icon_status: IconStatus.Up, + capital: "Tunis", + continent: { + name: "Africa", + }, + currency: "TND", + languages: [ + { + name: "Arabic", + }, + ], + }, + { + name: "Tonga", + code: "TO", + icon: "virtual-host", + icon_status: IconStatus.Up, + capital: "Nuku'alofa", + continent: { + name: "Oceania", + }, + currency: "TOP", + languages: [ + { + name: "English", + }, + { + name: "Tonga", + }, + ], + }, + { + name: "Turkey", + code: "TR", + icon: "virtual-host", + icon_status: IconStatus.Up, + capital: "Ankara", + continent: { + name: "Asia", + }, + currency: "TRY", + languages: [ + { + name: "Turkish", + }, + ], + }, + { + name: "Trinidad and Tobago", + code: "TT", + icon: "virtual-host", + icon_status: IconStatus.Up, + capital: "Port of Spain", + continent: { + name: "North America", + }, + currency: "TTD", + languages: [ + { + name: "English", + }, + ], + }, + { + name: "Tuvalu", + code: "TV", + icon: "virtual-host", + icon_status: IconStatus.Up, + capital: "Funafuti", + continent: { + name: "Oceania", + }, + currency: "AUD", + languages: [ + { + name: "English", + }, + ], + }, + { + name: "Taiwan", + code: "TW", + icon: "virtual-host", + icon_status: IconStatus.Up, + capital: "Taipei", + continent: { + name: "Asia", + }, + currency: "TWD", + languages: [ + { + name: "Chinese", + }, + ], + }, + { + name: "Tanzania", + code: "TZ", + icon: "virtual-host", + icon_status: IconStatus.Up, + capital: "Dodoma", + continent: { + name: "Africa", + }, + currency: "TZS", + languages: [ + { + name: "Swahili", + }, + { + name: "English", + }, + ], + }, + { + name: "Ukraine", + code: "UA", + icon: "virtual-host", + icon_status: IconStatus.Up, + capital: "Kyiv", + continent: { + name: "Europe", + }, + currency: "UAH", + languages: [ + { + name: "Ukrainian", + }, + ], + }, + { + name: "Uganda", + code: "UG", + icon: "virtual-host", + icon_status: IconStatus.Up, + capital: "Kampala", + continent: { + name: "Africa", + }, + currency: "UGX", + languages: [ + { + name: "English", + }, + { + name: "Swahili", + }, + ], + }, + { + name: "U.S. Minor Outlying Islands", + code: "UM", + icon: "virtual-host", + icon_status: IconStatus.Up, + capital: null, + continent: { + name: "Oceania", + }, + currency: "USD", + languages: [ + { + name: "English", + }, + ], + }, + { + name: "United States", + code: "US", + icon: "virtual-host", + icon_status: IconStatus.Up, + capital: "Washington D.C.", + continent: { + name: "North America", + }, + currency: "USD,USN,USS", + languages: [ + { + name: "English", + }, + ], + }, + { + name: "Uruguay", + code: "UY", + icon: "virtual-host", + icon_status: IconStatus.Up, + capital: "Montevideo", + continent: { + name: "South America", + }, + currency: "UYI,UYU", + languages: [ + { + name: "Spanish", + }, + ], + }, + { + name: "Uzbekistan", + code: "UZ", + icon: "virtual-host", + icon_status: IconStatus.Up, + capital: "Tashkent", + continent: { + name: "Asia", + }, + currency: "UZS", + languages: [ + { + name: "Uzbek", + }, + { + name: "Russian", + }, + ], + }, + { + name: "Vatican City", + code: "VA", + icon: "virtual-host", + icon_status: IconStatus.Up, + capital: "Vatican City", + continent: { + name: "Europe", + }, + currency: "EUR", + languages: [ + { + name: "Italian", + }, + { + name: "Latin", + }, + ], + }, + { + name: "Saint Vincent and the Grenadines", + code: "VC", + icon: "virtual-host", + icon_status: IconStatus.Up, + capital: "Kingstown", + continent: { + name: "North America", + }, + currency: "XCD", + languages: [ + { + name: "English", + }, + ], + }, + { + name: "Venezuela", + code: "VE", + icon: "virtual-host", + icon_status: IconStatus.Up, + capital: "Caracas", + continent: { + name: "South America", + }, + currency: "VES", + languages: [ + { + name: "Spanish", + }, + ], + }, + { + name: "British Virgin Islands", + code: "VG", + icon: "virtual-host", + icon_status: IconStatus.Up, + capital: "Road Town", + continent: { + name: "North America", + }, + currency: "USD", + languages: [ + { + name: "English", + }, + ], + }, + { + name: "U.S. Virgin Islands", + code: "VI", + icon: "virtual-host", + icon_status: IconStatus.Up, + capital: "Charlotte Amalie", + continent: { + name: "North America", + }, + currency: "USD", + languages: [ + { + name: "English", + }, + ], + }, + { + name: "Vietnam", + code: "VN", + icon: "virtual-host", + icon_status: IconStatus.Up, + capital: "Hanoi", + continent: { + name: "Asia", + }, + currency: "VND", + languages: [ + { + name: "Vietnamese", + }, + ], + }, + { + name: "Vanuatu", + code: "VU", + icon: "virtual-host", + icon_status: IconStatus.Up, + capital: "Port Vila", + continent: { + name: "Oceania", + }, + currency: "VUV", + languages: [ + { + name: "Bislama", + }, + { + name: "English", + }, + { + name: "French", + }, + ], + }, + { + name: "Wallis and Futuna", + code: "WF", + icon: "virtual-host", + icon_status: IconStatus.Up, + capital: "Mata-Utu", + continent: { + name: "Oceania", + }, + currency: "XPF", + languages: [ + { + name: "French", + }, + ], + }, + { + name: "Samoa", + code: "WS", + icon: "virtual-host", + icon_status: IconStatus.Up, + capital: "Apia", + continent: { + name: "Oceania", + }, + currency: "WST", + languages: [ + { + name: "Samoan", + }, + { + name: "English", + }, + ], + }, + { + name: "Kosovo", + code: "XK", + icon: "virtual-host", + icon_status: IconStatus.Up, + capital: "Pristina", + continent: { + name: "Europe", + }, + currency: "EUR", + languages: [ + { + name: "Albanian", + }, + { + name: "Serbian", + }, + ], + }, + { + name: "Yemen", + code: "YE", + icon: "virtual-host", + icon_status: IconStatus.Up, + capital: "Sana'a", + continent: { + name: "Asia", + }, + currency: "YER", + languages: [ + { + name: "Arabic", + }, + ], + }, + { + name: "Mayotte", + code: "YT", + icon: "virtual-host", + icon_status: IconStatus.Up, + capital: "Mamoudzou", + continent: { + name: "Africa", + }, + currency: "EUR", + languages: [ + { + name: "French", + }, + ], + }, + { + name: "South Africa", + code: "ZA", + icon: "virtual-host", + icon_status: IconStatus.Up, + capital: "Pretoria", + continent: { + name: "Africa", + }, + currency: "ZAR", + languages: [ + { + name: "Afrikaans", + }, + { + name: "English", + }, + { + name: "South Ndebele", + }, + { + name: "Southern Sotho", + }, + { + name: "Swati", + }, + { + name: "Tswana", + }, + { + name: "Tsonga", + }, + { + name: "Venda", + }, + { + name: "Xhosa", + }, + { + name: "Zulu", + }, + ], + }, + { + name: "Zambia", + code: "ZM", + icon: "virtual-host", + icon_status: IconStatus.Up, + capital: "Lusaka", + continent: { + name: "Africa", + }, + currency: "ZMW", + languages: [ + { + name: "English", + }, + ], + }, + { + name: "Zimbabwe", + code: "ZW", + icon: "virtual-host", + icon_status: IconStatus.Up, + capital: "Harare", + continent: { + name: "Africa", + }, + currency: "USD,ZAR,BWP,GBP,AUD,CNY,INR,JPY", + languages: [ + { + name: "English", + }, + { + name: "Shona", + }, + { + name: "North Ndebele", + }, + ], + }, + ], + }, +}; +\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\`, + "widget-types/drilldown/drilldown-widget/drilldown-widget-example.component.ts": \\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\`// © 2022 SolarWinds Worldwide, LLC. All rights reserved. +// +// Permission is hereby granted, free of charge, to any person obtaining a copy +// of this software and associated documentation files (the "Software"), to +// deal in the Software without restriction, including without limitation the +// rights to use, copy, modify, merge, publish, distribute, sublicense, and/or +// sell copies of the Software, and to permit persons to whom the Software is +// furnished to do so, subject to the following conditions: +// +// The above copyright notice and this permission notice shall be included in +// all copies or substantial portions of the Software. +// +// THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR +// IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, +// FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE +// AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER +// LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, +// OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN +// THE SOFTWARE. + +import { HttpClient } from "@angular/common/http"; +import { + ChangeDetectorRef, + Component, + Injectable, + OnDestroy, + OnInit, +} from "@angular/core"; +import { GridsterConfig, GridsterItem } from "angular-gridster2"; +import { Apollo, gql } from "apollo-angular"; +import groupBy from "lodash/groupBy"; +import { BehaviorSubject, Observable, of } from "rxjs"; +import { catchError, delay, filter, map } from "rxjs/operators"; + +import { + DataSourceFeatures, + IconStatus, + IDataField, + IDataSource, + IDataSourceFeatures, + IDataSourceFeaturesConfiguration, + INovaFilters, + LoggerService, + ServerSideDataSource, + IFilters, +} from "@nova-ui/bits"; +import { + DATA_SOURCE, + DEFAULT_PIZZAGNA_ROOT, + IDashboard, + IDrilldownComponentsConfiguration, + IListWidgetConfiguration, + IProviderConfiguration, + IWidget, + IWidgets, + ListGroupItemComponent, + ListLeafItemComponent, + NOVA_DRILLDOWN_DATASOURCE_ADAPTER, + PizzagnaLayer, + ProviderRegistryService, + WellKnownPathKey, + WellKnownProviders, + WidgetTypesService, +} from "@nova-ui/dashboards"; + +import { DrilldownDataSource } from "./mock-data-source"; + +/** + * A simple KPI data source to retrieve the average rating of Harry Potter and the Sorcerer's Stone (book) via googleapis + */ +@Injectable() +export class DrilldownDataSourceRealApi + extends ServerSideDataSource + implements OnDestroy, IDataSource +{ + // This is the ID we'll use to identify the provider + public static providerId = "DrilldownDataSourceRealApi"; + + // Use this subject to communicate the data source's busy state + public busy = new BehaviorSubject(false); + public dataFields: Partial[] = [ + { id: "regionName", label: "Region name" }, + { id: "subregionName", label: "Subregion name" }, + ]; + + public features: IDataSourceFeaturesConfiguration; + private supportedFeatures: IDataSourceFeatures = { + search: { enabled: true }, + }; + + private drillState: string[] = []; + private groupBy: string[]; + + constructor( + private logger: LoggerService, + private http: HttpClient, + private apollo: Apollo + ) { + super(); + this.features = new DataSourceFeatures(this.supportedFeatures); + // TODO: remove Partial in vNext after marking dataType field as optional - NUI-5838 + ( + this.dataFieldsConfig.dataFields$ as BehaviorSubject< + Partial[] + > + ).next(this.dataFields); + } + + private groupedDataHistory: Array> = []; + + // In this example, getFilteredData is invoked every 10 minutes (Take a look at the refresher + // provider definition in the widget configuration below to see how the interval is set) + public async getFilteredData(data: IFilters): Promise { + return of(data) + .pipe( + filter(() => !!this.drillState), + map((countries) => { + const lastHistory = () => getLast(this.groupedDataHistory); + + if (!this.drillState.length && !this.groupBy.length) { + return countries; + } + + // adding "ROOT" as a root level for drilling + const fullDrillState = ["ROOT", ...this.drillState]; + const activeDrillLvl = fullDrillState.length; + const historyLvl = this.groupedDataHistory.length; + + // checking how many lvls we have to group for drilling, in case some are missed + const drillLvlDiff = activeDrillLvl - historyLvl; + + if (!drillLvlDiff) { + return lastHistory() || countries; + } + + const drillToGroup = fullDrillState.slice( + fullDrillState.length - drillLvlDiff + ); + + for (const drill of drillToGroup) { + const drillIdx = fullDrillState.findIndex( + (v) => v === drill + ); + const group = this.groupBy[drillIdx]; + + if (group) { + const dataToGroup = lastHistory() + ? lastHistory()[drill] + : countries; + const lastGroupedValue = groupBy( + dataToGroup, + group + ); + + this.groupedDataHistory.push(lastGroupedValue); + } + } + + // take last if we have all data grouped + if (this.groupBy.length === this.drillState.length) { + return lastHistory()[getLast(this.drillState)]; + } + + // get groping and transform to raw data format + return this.getGroupsWidgetData(lastHistory()); + }) + ) + .toPromise(); + } + + public ngOnDestroy(): void { + this.outputsSubject.complete(); + } + + // This method is expected to return all data needed for repeat/paginator/filterGroups in order to work. + // In case of custom filtering participants feel free to extend INovaFilteringOutputs. + protected getBackendData(filters: INovaFilters): Observable { + const mainRequest = this.apollo.watchQuery<{ countries: any }>({ + query: this.generateQuery(filters), + }); + + return mainRequest.valueChanges.pipe( + // mock delay + delay(300), + // data mapping, !DS specific! + map((res) => res.data.countries), + // adds mock icons to be displayed on leaf nodes !DS specific! + map((res: any[]) => + res.map((v) => ({ + ...v, + icon: "virtual-host", + icon_status: IconStatus.Up, + subregionName: + v.subregion?.name || "No Subregion Specified", + regionName: + v.subregion?.region?.name || "No Region Specified", + })) + ), + catchError((e) => { + this.logger.error(e); + return of({} as any); + }) + ); + } + + private generateQuery(filters: INovaFilters) { + const { search } = filters; + const searchValue = search?.value ? \\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\`^[\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\${search.value}]*\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\` : ""; + + const queryString = \\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\` + query { + countries(filter: {name: {regex: "\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\${searchValue}"} }) { + name + native + capital + languages { + name + } + currencies + subdivisions { + name + } + } + } + \\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\`; + + return gql\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\` + \\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\${queryString} + \\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\`; + } + + // Overrides default ServerSideDataSource.beforeApplyFilters implementation + // to save some filters that are used internally + // -- !DS specific + protected beforeApplyFilters(filters: INovaFilters): void { + this.busy.next(true); + + this.drillState = filters.drillstate?.value; + this.groupBy = filters.group?.value; + + if (this.isHome()) { + this.groupedDataHistory.length = 0; + } + + if (this.isBack()) { + this.groupedDataHistory.length = this.groupedDataHistory.length - 1; + } + + if (this.getFilters()["search"] && this.filterChanged("search")) { + this.groupedDataHistory.length = 0; + } + } + + private getGroupsWidgetData(groupByObj: Record) { + return Object.keys(groupByObj).map((property) => ({ + id: property, + label: property, + // statuses that will be displayed on group item + statuses: [ + { key: "virtual-host", value: groupByObj[property].length }, + { + key: "acknowledge", + value: this.getPopulation(groupByObj[property]), + }, + ], + })); + } + + private isHome(): boolean { + return this.drillState?.length === 0; + } + + private isBack(): boolean { + return ( + this.groupedDataHistory?.length > this.drillState?.length && + !this.isHome() + ); + } + + /** + * Gets population for the country(ies) + */ + private getPopulation(countries: any[]) { + const totalPopulation = countries.reduce( + (acc, next) => (acc += next.population), + 0 + ); + return \\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\`\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\${totalPopulation * Math.pow(10, -3)} k\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\`; + } +} + +@Component({ + selector: "drilldown-widget-example", + templateUrl: "./drilldown-widget-example.component.html", + styleUrls: ["./drilldown-widget-example.component.less"], + standalone: false, +}) +export class DrilldownWidgetExampleComponent implements OnInit { + // This variable will hold all the data needed to define the layout and behavior of the widgets. + // Pass this to the dashboard component's dashboard input in the template. + public dashboard: IDashboard | undefined; + + // Angular gridster requires a configuration object even if it's empty. + // Pass this to the dashboard component's gridsterConfig input in the template. + public gridsterConfig: GridsterConfig = {}; + + // Boolean passed as an input to the dashboard. When true, widgets can be moved, resized, removed, or edited + public editMode: boolean = false; + + constructor( + // WidgetTypesService provides the widget's necessary structure information + private widgetTypesService: WidgetTypesService, + private providerRegistry: ProviderRegistryService, + private changeDetectorRef: ChangeDetectorRef + ) {} + + public ngOnInit(): void { + // Registering the data source for injection into the KPI tile. + // Note: Each tile of a KPI widget is assigned its own instance of the data source + this.providerRegistry.setProviders({ + [DrilldownDataSource.providerId]: { + provide: DATA_SOURCE, + useClass: DrilldownDataSource, + // Any dependencies that need to be injected into the provider must be listed here + deps: [HttpClient], + }, + [DrilldownDataSourceRealApi.providerId]: { + provide: DATA_SOURCE, + useClass: DrilldownDataSourceRealApi, + // Any dependencies that need to be injected into the provider must be listed here + deps: [LoggerService, HttpClient, Apollo], + }, + }); + + this.initializeDashboard(); + const widgetTemplate = this.widgetTypesService.getWidgetType( + "drilldown", + 1 + ); + this.widgetTypesService.setNode( + widgetTemplate, + "configurator", + WellKnownPathKey.DataSourceProviders, + [ + DrilldownDataSourceRealApi.providerId, + DrilldownDataSource.providerId, + ] + ); + } + + public initializeDashboard(): void { + // We're using a static configuration object for this example, but this is where + // the widget's configuration could potentially be populated from a database + const drilldownWidget = widgetConfig; + const widgets: IWidgets = { + // Complete the widget with information coming from its type definition + [drilldownWidget.id]: + this.widgetTypesService.mergeWithWidgetType(drilldownWidget), + }; + + // Setting the widget dimensions and position (this is for gridster) + const positions: Record = { + [drilldownWidget.id]: { + cols: 10, + rows: 10, + y: 0, + x: 0, + }, + }; + + // Finally, assigning the variables we created above to the dashboard + this.dashboard = { positions, widgets }; + } + + public reInitializeDashboard(): void { + // destroys the components and their providers so the dashboard can re init data + this.dashboard = undefined; + this.changeDetectorRef.detectChanges(); + + const adapterProperties = + widgetConfig.pizzagna[PizzagnaLayer.Configuration].listWidget + .providers?.adapter?.properties; + + if (adapterProperties) { + adapterProperties.drillstate = []; + } + + this.initializeDashboard(); + } +} + +const widgetConfig: IWidget = { + id: "drilldown", + type: "drilldown", + pizzagna: { + [PizzagnaLayer.Configuration]: { + [DEFAULT_PIZZAGNA_ROOT]: { + providers: { + [WellKnownProviders.DataSource]: { + // Setting the data source providerId for the tile with id "kpi1" + providerId: DrilldownDataSourceRealApi.providerId, + properties: {}, + } as IProviderConfiguration, + }, + }, + header: { + properties: { + title: "Drilldown Widget", + subtitle: "Search is case sensitive!", + }, + }, + listWidget: { + providers: { + [WellKnownProviders.Adapter]: { + providerId: NOVA_DRILLDOWN_DATASOURCE_ADAPTER, + properties: { + // widget + navigationBarId: "navigationBar", + componentId: "listWidget", + dataPath: "data", + + // adapter props + drillstate: [], + groupBy: ["regionName", "subregionName"], + groups: ["regionName", "subregionName"], + + // components + componentsConfig: { + group: { + componentType: + ListGroupItemComponent.lateLoadKey, + properties: { + dataFieldIds: { + id: "id", + label: "label", + statuses: "statuses", + }, + }, + itemProperties: { + canNavigate: true, + }, + }, + leaf: { + componentType: + ListLeafItemComponent.lateLoadKey, + properties: { + dataFieldIds: { + icon: "icon", + status: "icon_status", + detailedUrl: "capital", + label: "name", + }, + }, + itemProperties: { + canNavigate: false, + }, + }, + } as IDrilldownComponentsConfiguration, + }, + }, + }, + properties: { + configuration: { + // FORMAT: + // componentType: ListLeafItemComponent.lateLoadKey, + // properties: { + // dataFieldIds: { + // icon: "", + // status: "code", + // detailedUrl: "capital", + // label: "name", + // }, + // }, + // + } as IListWidgetConfiguration, + }, + }, + }, + }, +}; + +const getLast = (arr: any[]) => arr[arr.length - 1]; +\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\`, + "widget-types/drilldown/drilldown-widget/mock-data-source.ts": \\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\`// © 2022 SolarWinds Worldwide, LLC. All rights reserved. +// +// Permission is hereby granted, free of charge, to any person obtaining a copy +// of this software and associated documentation files (the "Software"), to +// deal in the Software without restriction, including without limitation the +// rights to use, copy, modify, merge, publish, distribute, sublicense, and/or +// sell copies of the Software, and to permit persons to whom the Software is +// furnished to do so, subject to the following conditions: +// +// The above copyright notice and this permission notice shall be included in +// all copies or substantial portions of the Software. +// +// THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR +// IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, +// FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE +// AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER +// LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, +// OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN +// THE SOFTWARE. + +import { Injectable, OnDestroy } from "@angular/core"; +import groupBy from "lodash/groupBy"; +import { BehaviorSubject, Observable, of, Subject } from "rxjs"; +import { + catchError, + delay, + finalize, + map, + // eslint-disable-next-line import/no-deprecated + switchMap, + tap, +} from "rxjs/operators"; + +import { + DataSourceService, + IDataField, + IDataSource, + IFilters, + INovaFilters, +} from "@nova-ui/bits"; + +import { GRAPH_DATA_MOCK } from "./data-mock"; + +/** + * A simple KPI data source to retrieve the average rating of Harry Potter and the Sorcerer's Stone (book) via googleapis + */ +@Injectable() +export class DrilldownDataSource + extends DataSourceService + implements IDataSource, OnDestroy +{ + // This is the ID we'll use to identify the provider + public static providerId = "DrilldownDataSource"; + + // Use this subject to communicate the data source's busy state + public busy = new BehaviorSubject(false); + public dataFields: Partial[] = [ + { id: "continent.name", label: "Continent name" }, + { id: "currency", label: "Currency" }, + ]; + + private drillState: string[] = []; + private groupBy: string[]; + private cache: any; + private applyFilters$ = new Subject(); + + constructor() { + super(); + + // TODO: remove Partial in vNext after marking dataType field as optional - NUI-5838 + ( + this.dataFieldsConfig.dataFields$ as BehaviorSubject< + Partial[] + > + ).next(this.dataFields); + + this.applyFilters$ + // eslint-disable-next-line import/no-deprecated + .pipe(switchMap((filters) => this.getData(filters))) + .subscribe(async (res) => { + this.outputsSubject.next(await this.getFilteredData(res)); + }); + } + + private groupedDataHistory: any[] = []; + + // In this example, getFilteredData is invoked every 10 minutes (Take a look at the refresher + // provider definition in the widget configuration below to see how the interval is set) + public async getFilteredData(data: any): Promise { + return of(data) + .pipe( + map((countries) => { + const widgetInput = this.getOutput(countries); + + if (this.isDrillDown()) { + const activeDrillLvl = this.drillState.length; + const group = this.groupBy[activeDrillLvl]; + const [lastGroupedValue, groupedData] = + this.getTransformedDataForGroup(widgetInput, group); + + this.groupedDataHistory.push(lastGroupedValue); + + return groupedData; + } + + return widgetInput; + }) + ) + .toPromise(); + } + + public ngOnDestroy(): void { + this.outputsSubject.complete(); + } + + // redefine parent method + public async applyFilters(): Promise { + this.applyFilters$.next(this.getFilters()); + } + + private getData(filters: INovaFilters): Observable { + this.drillState = filters.drillstate?.value; + this.groupBy = filters.group?.value; + + this.busy.next(true); + + return of(this.cache || GRAPH_DATA_MOCK).pipe( + delay(1000), + tap((data) => (this.cache = data)), + map((data) => data.data.countries), + catchError((e) => of([])), + finalize(() => this.busy.next(false)) + ); + } + + private getTransformedDataForGroup(data: any, groupName: string) { + const groupedDict = groupBy(data, groupName); + const dataArr = Object.keys(groupedDict).map((property) => ({ + id: property, + label: property, + // TODO: apply groups mapping here + statuses: [ + { key: "state_ok", value: groupedDict[property].length }, + { + key: "status_unreachable", + value: generateNumberUpTo(100000), + }, + { key: "status_warning", value: generateNumberUpTo(10000) }, + { key: "status_unknown", value: generateNumberUpTo(1000) }, + ], + })); + + return [groupedDict, dataArr]; + } + + private isHome(): boolean { + return !this.drillState || this.drillState.length === 0; + } + + private isBack(): boolean { + return ( + this.groupedDataHistory.length > this.drillState?.length && + !this.isHome() + ); + } + + private isDrillDown(): boolean { + return this.drillState?.length !== this.groupBy?.length; + } + + private getOutput(data: any) { + if (this.isHome()) { + this.groupedDataHistory.length = 0; + } + + if (this.isBack()) { + this.groupedDataHistory.length = this.groupedDataHistory.length - 1; + } + + const lastHistoryValue = getLast(this.groupedDataHistory); + + if (!lastHistoryValue) { + return data; + } + + return lastHistoryValue[getLast(this.drillState)] || lastHistoryValue; + } +} + +const getLast = (arr: any[]) => arr[arr.length - 1]; +const generateNumberUpTo = (upperLimit: number): number => + Math.floor(Math.random() * upperLimit + 1); +\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\`, + "widget-types/drilldown/drilldown-widget-docs.component.ts": \\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\`// © 2022 SolarWinds Worldwide, LLC. All rights reserved. +// +// Permission is hereby granted, free of charge, to any person obtaining a copy +// of this software and associated documentation files (the "Software"), to +// deal in the Software without restriction, including without limitation the +// rights to use, copy, modify, merge, publish, distribute, sublicense, and/or +// sell copies of the Software, and to permit persons to whom the Software is +// furnished to do so, subject to the following conditions: +// +// The above copyright notice and this permission notice shall be included in +// all copies or substantial portions of the Software. +// +// THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR +// IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, +// FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE +// AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER +// LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, +// OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN +// THE SOFTWARE. + +import { Component, OnInit } from "@angular/core"; + +import { mapContentFile } from "../../../../demo-files-factory"; + +@Component({ + selector: "nui-drilldown-docs", + templateUrl: "./drilldown-widget-docs.component.html", + standalone: false, +}) +export class DrilldownDocsComponent implements OnInit { + public widgetFileText = ""; + public configuratorFileText = ""; + + public predefinedGroping = \\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\` +listWidget: { + providers: { + [WellKnownProviders.Adapter]: { + providerId: NOVA_DRILLDOWN_DATASOURCE_ADAPTER, + properties: { + ... + // adapter props + drillstate: [], + groupBy: ["regionName", "subregionName"], + groups: ["regionName", "subregionName"], + ... + }, + }, + }, +}, +\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\`; + public featuredDeclaredText = \\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\` + private supportedFeatures: IDataSourceFeatures = { + search: { enabled: true }, + };\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\`; + public featuresUsedText = \\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\` + this.features = new DataSourceFeatures(this.supportedFeatures); + \\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\`; + + public async ngOnInit(): Promise { + this.widgetFileText = await import( + "./../../../../../../src/lib/widget-types/drilldown/drilldown-widget" + ).then(mapContentFile); + this.configuratorFileText = await import( + "./../../../../../../src/lib/widget-types/drilldown/drilldown-configurator" + ).then(mapContentFile); + } +} +\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\`, + "widget-types/drilldown/drilldown-widget-docs.module.ts": \\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\`// © 2022 SolarWinds Worldwide, LLC. All rights reserved. +// +// Permission is hereby granted, free of charge, to any person obtaining a copy +// of this software and associated documentation files (the "Software"), to +// deal in the Software without restriction, including without limitation the +// rights to use, copy, modify, merge, publish, distribute, sublicense, and/or +// sell copies of the Software, and to permit persons to whom the Software is +// furnished to do so, subject to the following conditions: +// +// The above copyright notice and this permission notice shall be included in +// all copies or substantial portions of the Software. +// +// THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR +// IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, +// FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE +// AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER +// LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, +// OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN +// THE SOFTWARE. + +import { NgModule } from "@angular/core"; +import { RouterModule, Routes } from "@angular/router"; + +// eslint-disable-next-line max-len +import { + NuiButtonModule, + NuiDocsModule, + NuiMessageModule, + NuiSwitchModule, + DEMO_PATH_TOKEN, +} from "@nova-ui/bits"; +import { NuiDashboardsModule } from "@nova-ui/dashboards"; + +import { DrilldownMultiRequestWidgetExampleComponent } from "./drilldown-multi-request-widget/drilldown-multi-request-widget-example.component"; +import { DrilldownWidgetExampleComponent } from "./drilldown-widget/drilldown-widget-example.component"; +import { DrilldownDocsComponent } from "./drilldown-widget-docs.component"; +import { getDemoFiles } from "../../../../demo-files-factory"; + +const routes: Routes = [ + { + path: "", + component: DrilldownDocsComponent, + data: { + srlc: { + hideIndicator: true, + }, + }, + }, + { + path: "example", + component: DrilldownWidgetExampleComponent, + data: { + srlc: { + hideIndicator: true, + }, + }, + }, + { + path: "multiple-requests", + component: DrilldownMultiRequestWidgetExampleComponent, + data: { + srlc: { + hideIndicator: true, + }, + }, + }, +]; + +@NgModule({ + imports: [ + RouterModule.forChild(routes), + NuiButtonModule, + NuiDocsModule, + NuiMessageModule, + NuiDashboardsModule, + NuiSwitchModule, + ], + declarations: [ + DrilldownDocsComponent, + DrilldownWidgetExampleComponent, + DrilldownMultiRequestWidgetExampleComponent, + ], + providers: [ + { + provide: DEMO_PATH_TOKEN, + useValue: getDemoFiles("drilldown"), + }, + ], +}) +export default class DrilldownDocsModule {} +\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\`, + "widget-types/embedded-content/embedded-content-docs.component.ts": \\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\`// © 2022 SolarWinds Worldwide, LLC. All rights reserved. +// +// Permission is hereby granted, free of charge, to any person obtaining a copy +// of this software and associated documentation files (the "Software"), to +// deal in the Software without restriction, including without limitation the +// rights to use, copy, modify, merge, publish, distribute, sublicense, and/or +// sell copies of the Software, and to permit persons to whom the Software is +// furnished to do so, subject to the following conditions: +// +// The above copyright notice and this permission notice shall be included in +// all copies or substantial portions of the Software. +// +// THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR +// IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, +// FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE +// AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER +// LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, +// OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN +// THE SOFTWARE. + +import { Component, OnInit } from "@angular/core"; + +import { mapContentFile } from "../../../../demo-files-factory"; + +@Component({ + selector: "nui-embedded-content-docs", + templateUrl: "./embedded-content-docs.component.html", + standalone: false, +}) +export class EmbeddedContentDocsComponent implements OnInit { + public embeddedContentWidgetFileText = ""; + public embeddedContentConfiguratorFileText = ""; + + public async ngOnInit(): Promise { + this.embeddedContentWidgetFileText = await import( + "./../../../../../../src/lib/widget-types/embedded-content/embedded-content-widget" + ).then(mapContentFile); + this.embeddedContentWidgetFileText = await import( + "./../../../../../../src/lib/widget-types/embedded-content/embedded-content-configurator" + ).then(mapContentFile); + } +} +\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\`, + "widget-types/embedded-content/embedded-content-docs.module.ts": \\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\`// © 2022 SolarWinds Worldwide, LLC. All rights reserved. +// +// Permission is hereby granted, free of charge, to any person obtaining a copy +// of this software and associated documentation files (the "Software"), to +// deal in the Software without restriction, including without limitation the +// rights to use, copy, modify, merge, publish, distribute, sublicense, and/or +// sell copies of the Software, and to permit persons to whom the Software is +// furnished to do so, subject to the following conditions: +// +// The above copyright notice and this permission notice shall be included in +// all copies or substantial portions of the Software. +// +// THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR +// IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, +// FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE +// AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER +// LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, +// OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN +// THE SOFTWARE. + +import { NgModule } from "@angular/core"; +import { RouterModule, Routes } from "@angular/router"; + +// eslint-disable-next-line max-len +import { + NuiButtonModule, + NuiDocsModule, + NuiMessageModule, + NuiSwitchModule, + DEMO_PATH_TOKEN, +} from "@nova-ui/bits"; +import { NuiDashboardsModule } from "@nova-ui/dashboards"; + +import { EmbeddedContentDocsComponent } from "./embedded-content-docs.component"; +import { EmbeddedContentWidgetExampleComponent } from "./embedded-content-widget-example/embedded-content-widget-example.component"; +import { getDemoFiles } from "../../../../demo-files-factory"; + +const routes: Routes = [ + { + path: "", + component: EmbeddedContentDocsComponent, + data: { + srlc: { + hideIndicator: true, + }, + }, + }, + { + path: "example", + component: EmbeddedContentWidgetExampleComponent, + data: { + srlc: { + hideIndicator: true, + }, + }, + }, +]; + +@NgModule({ + imports: [ + RouterModule.forChild(routes), + NuiButtonModule, + NuiDocsModule, + NuiMessageModule, + NuiDashboardsModule, + NuiSwitchModule, + ], + declarations: [ + EmbeddedContentDocsComponent, + EmbeddedContentWidgetExampleComponent, + ], + providers: [ + { + provide: DEMO_PATH_TOKEN, + useValue: getDemoFiles("embedded-content"), + }, + ], +}) +export default class EmbeddedContentDocsModule {} +\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\`, + "widget-types/embedded-content/embedded-content-widget-example/embedded-content-widget-example.component.ts": \\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\`// © 2022 SolarWinds Worldwide, LLC. All rights reserved. +// +// Permission is hereby granted, free of charge, to any person obtaining a copy +// of this software and associated documentation files (the "Software"), to +// deal in the Software without restriction, including without limitation the +// rights to use, copy, modify, merge, publish, distribute, sublicense, and/or +// sell copies of the Software, and to permit persons to whom the Software is +// furnished to do so, subject to the following conditions: +// +// The above copyright notice and this permission notice shall be included in +// all copies or substantial portions of the Software. +// +// THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR +// IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, +// FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE +// AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER +// LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, +// OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN +// THE SOFTWARE. + +import { ChangeDetectorRef, Component, OnInit } from "@angular/core"; +import { GridsterConfig, GridsterItem } from "angular-gridster2"; + +import { + ComponentRegistryService, + EmbeddedContentComponent, + EmbeddedContentConfigurationComponent, + EmbeddedContentMode, + IDashboard, + IWidget, + IWidgets, + PizzagnaLayer, + WidgetTypesService, +} from "@nova-ui/dashboards"; + +@Component({ + selector: "embedded-content-widget-example", + templateUrl: "./embedded-content-widget-example.component.html", + styleUrls: ["./embedded-content-widget-example.component.less"], + standalone: false, +}) +export class EmbeddedContentWidgetExampleComponent implements OnInit { + // This variable will hold all the data needed to define the layout and behavior of the widgets. + // Pass this to the dashboard component's dashboard input in the template. + public dashboard: IDashboard | undefined; + + // Angular gridster requires a configuration object even if it's empty. + // Pass this to the dashboard component's gridsterConfig input in the template. + public gridsterConfig: GridsterConfig = {}; + + // Boolean passed as an input to the dashboard. When true, widgets can be moved, resized, removed, or edited + public editMode: boolean = false; + + constructor( + // WidgetTypesService provides the widget's necessary structure information + private widgetTypesService: WidgetTypesService, + + private componentRegistry: ComponentRegistryService, + private changeDetectorRef: ChangeDetectorRef + ) {} + + public ngOnInit(): void { + this.prepareNovaDashboards(); + this.initializeDashboard(); + } + + /** Used for restoring widgets state */ + public reInitializeDashboard(): void { + // destroys the components and their providers so the dashboard can re init data + this.dashboard = undefined; + this.changeDetectorRef.detectChanges(); + + this.initializeDashboard(); + } + + public initializeDashboard(): void { + // We're using a static configuration object for this example, but this is where + // the widget's configuration could potentially be populated from a database + const embeddedContentWidget = widgetConfig; + const widgets: IWidgets = { + // Complete the widget with information coming from its type definition + [embeddedContentWidget.id]: + this.widgetTypesService.mergeWithWidgetType( + embeddedContentWidget + ), + }; + + // Setting the widget dimensions and position (this is for gridster) + const positions: Record = { + [embeddedContentWidget.id]: { + cols: 10, + rows: 10, + y: 0, + x: 0, + }, + }; + + // Finally, assigning the variables we created above to the dashboard + this.dashboard = { positions, widgets }; + } + + private prepareNovaDashboards() { + this.componentRegistry.registerByLateLoadKey(EmbeddedContentComponent); + this.componentRegistry.registerByLateLoadKey( + EmbeddedContentConfigurationComponent + ); + } +} + +const widgetConfig: IWidget = { + id: "embeddedContentWidgetId", + type: "embedded-content", + pizzagna: { + [PizzagnaLayer.Configuration]: { + header: { + properties: { + title: "Embedded Content Widget", + subtitle: "", + }, + }, + mainContent: { + properties: { + sanitized: true, + mode: EmbeddedContentMode.URL, + customEmbeddedContent: "https://www.ventusky.com/", + }, + }, + }, + }, +}; +\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\`, + "widget-types/kpi/kpi-docs.component.ts": \\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\`// © 2022 SolarWinds Worldwide, LLC. All rights reserved. +// +// Permission is hereby granted, free of charge, to any person obtaining a copy +// of this software and associated documentation files (the "Software"), to +// deal in the Software without restriction, including without limitation the +// rights to use, copy, modify, merge, publish, distribute, sublicense, and/or +// sell copies of the Software, and to permit persons to whom the Software is +// furnished to do so, subject to the following conditions: +// +// The above copyright notice and this permission notice shall be included in +// all copies or substantial portions of the Software. +// +// THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR +// IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, +// FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE +// AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER +// LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, +// OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN +// THE SOFTWARE. + +import { Component, OnInit } from "@angular/core"; + +import { mapContentFile } from "../../../../demo-files-factory"; + +@Component({ + selector: "nui-kpi-docs", + templateUrl: "./kpi-docs.component.html", + standalone: false, +}) +export class KpiDocsComponent implements OnInit { + public kpiWidgetFileText = ""; + public kpiConfiguratorFileText = ""; + + public async ngOnInit(): Promise { + this.kpiWidgetFileText = await import( + "./../../../../../../src/lib/widget-types/kpi/kpi-widget" + ).then(mapContentFile); + this.kpiConfiguratorFileText = await import( + "./../../../../../../src/lib/widget-types/kpi/kpi-configurator" + ).then(mapContentFile); + } +} +\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\`, + "widget-types/kpi/kpi-docs.module.ts": \\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\`// © 2022 SolarWinds Worldwide, LLC. All rights reserved. +// +// Permission is hereby granted, free of charge, to any person obtaining a copy +// of this software and associated documentation files (the "Software"), to +// deal in the Software without restriction, including without limitation the +// rights to use, copy, modify, merge, publish, distribute, sublicense, and/or +// sell copies of the Software, and to permit persons to whom the Software is +// furnished to do so, subject to the following conditions: +// +// The above copyright notice and this permission notice shall be included in +// all copies or substantial portions of the Software. +// +// THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR +// IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, +// FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE +// AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER +// LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, +// OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN +// THE SOFTWARE. + +import { NgModule } from "@angular/core"; +import { RouterModule, Routes } from "@angular/router"; + +import { + NuiButtonModule, + NuiDocsModule, + NuiMessageModule, + NuiSwitchModule, + DEMO_PATH_TOKEN, +} from "@nova-ui/bits"; +import { + KpiColorComparatorsRegistryService, + NuiDashboardsModule, +} from "@nova-ui/dashboards"; + +import { KpiDocsComponent } from "./kpi-docs.component"; +import { KpiSyncBrokerExampleComponent } from "./kpi-sync-broker/kpi-sync-broker-example.component"; +import { KpiSyncBrokerDocsComponent } from "./kpi-sync-broker-docs.component"; +import { KpiSyncBrokerForAllTilesExampleComponent } from "./kpi-sync-broker-for-all-tiles/kpi-sync-broker-for-all-tiles-example.component"; +import { KpiWidgetExampleComponent } from "./kpi-widget/kpi-widget-example.component"; +import { KpiWidgetBackgroundColorExampleComponent } from "./kpi-widget-background-color/kpi-widget-background-color-example.component"; +import { KpiWidgetBackgroundColorDocsComponent } from "./kpi-widget-background-color-docs.component"; +import { KpiWidgetInteractiveExampleComponent } from "./kpi-widget-interactive/kpi-widget-interactive-example.component"; +import { getDemoFiles } from "../../../../demo-files-factory"; + +const routes: Routes = [ + { + path: "", + component: KpiDocsComponent, + data: { + srlc: { + hideIndicator: true, + }, + showThemeSwitcher: true, + }, + }, + { + path: "example", + component: KpiWidgetExampleComponent, + data: { + srlc: { + hideIndicator: true, + }, + }, + }, + { + path: "background-color", + component: KpiWidgetBackgroundColorDocsComponent, + data: { + srlc: { + hideIndicator: true, + }, + }, + }, + { + path: "sync-broker", + component: KpiSyncBrokerDocsComponent, + data: { + srlc: { + hideIndicator: true, + }, + }, + }, +]; + +@NgModule({ + imports: [ + RouterModule.forChild(routes), + NuiButtonModule, + NuiDocsModule, + NuiMessageModule, + NuiDashboardsModule, + NuiSwitchModule, + ], + declarations: [ + KpiDocsComponent, + KpiWidgetExampleComponent, + KpiWidgetInteractiveExampleComponent, + KpiWidgetBackgroundColorDocsComponent, + KpiWidgetBackgroundColorExampleComponent, + KpiSyncBrokerDocsComponent, + KpiSyncBrokerExampleComponent, + KpiSyncBrokerForAllTilesExampleComponent, + ], + providers: [ + KpiColorComparatorsRegistryService, + { + provide: DEMO_PATH_TOKEN, + useValue: getDemoFiles("kpi"), + }, + ], +}) +export default class KpiDocsModule { + constructor( + private comparatorsRegistry: KpiColorComparatorsRegistryService + ) { + this.backgroundColorDocsSetup(); + } + + private backgroundColorDocsSetup() { + this.comparatorsRegistry.registerComparators({ + "!=": { + comparatorFn: (actual: any, reference: any) => + // eslint-disable-next-line eqeqeq + actual != reference, + label: "Not equal", + }, + }); + } +} +\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\`, + "widget-types/kpi/kpi-sync-broker/kpi-sync-broker-example.component.ts": \\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\`// © 2022 SolarWinds Worldwide, LLC. All rights reserved. +// +// Permission is hereby granted, free of charge, to any person obtaining a copy +// of this software and associated documentation files (the "Software"), to +// deal in the Software without restriction, including without limitation the +// rights to use, copy, modify, merge, publish, distribute, sublicense, and/or +// sell copies of the Software, and to permit persons to whom the Software is +// furnished to do so, subject to the following conditions: +// +// The above copyright notice and this permission notice shall be included in +// all copies or substantial portions of the Software. +// +// THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR +// IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, +// FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE +// AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER +// LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, +// OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN +// THE SOFTWARE. + +import { HttpClient, HttpErrorResponse } from "@angular/common/http"; +import { + ChangeDetectorRef, + Component, + Injectable, + OnDestroy, + OnInit, +} from "@angular/core"; +import { GridsterConfig, GridsterItem } from "angular-gridster2"; +import keyBy from "lodash/keyBy"; +import { BehaviorSubject, of } from "rxjs"; +import { delay, finalize, take } from "rxjs/operators"; + +import { DataSourceService, IFilteringOutputs } from "@nova-ui/bits"; +import { + DATA_SOURCE, + IDashboard, + IKpiData, + IProviderConfiguration, + IWidget, + KpiComponent, + NOVA_KPI_DATASOURCE_ADAPTER, + NOVA_KPI_SCALE_SYNC_BROKER, + PizzagnaLayer, + ProviderRegistryService, + WellKnownPathKey, + WellKnownProviders, + WidgetTypesService, +} from "@nova-ui/dashboards"; + +/** + * A simple KPI data source to retrieve the average rating of Harry Potter and the Sorcerer's Stone (book) via googleapis + */ +@Injectable() +export class AverageRatingKpiDataSource + extends DataSourceService + implements OnDestroy +{ + public static providerId = "AverageRatingKpiDataSource"; + public busy = new BehaviorSubject(false); + + constructor(private http: HttpClient) { + super(); + } + + public async getFilteredData(): Promise { + this.busy.next(true); + return new Promise((resolve) => { + // *** Make a resource request to an external API (if needed) + this.http + .get("https://www.googleapis.com/books/v1/volumes/5MQFrgEACAAJ") + .pipe(finalize(() => this.busy.next(false))) + .subscribe({ + next: (data: any) => { + resolve({ + result: { + value: data.volumeInfo.averageRating, + }, + }); + }, + error: (error: HttpErrorResponse) => { + resolve({ + result: null, + error: { + type: error.status, + }, + }); + }, + }); + }); + } + + public ngOnDestroy(): void { + this.outputsSubject.complete(); + } +} + +/** + * A simple KPI data source to retrieve the ratings count of Harry Potter and the Sorcerer's Stone (book) via googleapis + */ +@Injectable() +export class RatingsCountKpiDataSource + extends DataSourceService + implements OnDestroy +{ + public static providerId = "RatingsCountKpiDataSource"; + + // Use this subject to communicate the data source's busy state + public busy = new BehaviorSubject(false); + + constructor(private http: HttpClient) { + super(); + } + + public async getFilteredData(): Promise { + this.busy.next(true); + return new Promise((resolve) => { + this.http + .get("https://www.googleapis.com/books/v1/volumes/5MQFrgEACAAJ") + .pipe( + delay(2000), + finalize(() => this.busy.next(false)) + ) + .subscribe({ + next: (data: any) => { + resolve({ + result: { + value: data.volumeInfo.ratingsCount, + }, + }); + }, + error: (error: HttpErrorResponse) => { + resolve({ + result: null, + error: { + type: error.status, + }, + }); + }, + }); + }); + } + + public ngOnDestroy(): void { + this.outputsSubject.complete(); + } +} +/** + * A simple KPI data source to retrieve the ratings count of Harry Potter and the Sorcerer's Stone (book) via googleapis + */ +@Injectable() +export class MockKpiDataSource + extends DataSourceService + implements OnDestroy +{ + public static providerId = "MockKpiDataSource"; + + // Use this subject to communicate the data source's busy state + public busy = new BehaviorSubject(false); + + constructor() { + super(); + } + + public async getFilteredData(): Promise { + this.busy.next(true); + return new Promise((resolve) => { + of(3381342) + .pipe( + delay(5000), + take(1), + finalize(() => this.busy.next(false)) + ) + .subscribe({ + next: (data: any) => { + resolve({ + result: { + value: data, + }, + }); + }, + }); + }); + } + + public ngOnDestroy(): void { + this.outputsSubject.complete(); + } +} + +/** + * A component that instantiates the dashboard + */ +@Component({ + selector: "kpi-sync-broker-example", + templateUrl: "./kpi-sync-broker-example.component.html", + styleUrls: ["./kpi-sync-broker-example.component.less"], + standalone: false, +}) +export class KpiSyncBrokerExampleComponent implements OnInit { + public dashboard: IDashboard | undefined; + public gridsterConfig: GridsterConfig = {}; + public editMode: boolean = false; + + constructor( + private widgetTypesService: WidgetTypesService, + private providerRegistry: ProviderRegistryService, + private changeDetectorRef: ChangeDetectorRef + ) {} + + public ngOnInit(): void { + this.setupDashboard(); + + this.initializeDashboard(); + } + + private setupDashboard() { + const widgetTemplate = this.widgetTypesService.getWidgetType("kpi", 1); + + this.widgetTypesService.setNode( + widgetTemplate, + "configurator", + WellKnownPathKey.DataSourceProviders, + [ + AverageRatingKpiDataSource.providerId, + RatingsCountKpiDataSource.providerId, + MockKpiDataSource.providerId, + ] + ); + + this.providerRegistry.setProviders({ + [AverageRatingKpiDataSource.providerId]: { + provide: DATA_SOURCE, + useClass: AverageRatingKpiDataSource, + deps: [HttpClient], + }, + [RatingsCountKpiDataSource.providerId]: { + provide: DATA_SOURCE, + useClass: RatingsCountKpiDataSource, + deps: [HttpClient], + }, + [MockKpiDataSource.providerId]: { + provide: DATA_SOURCE, + useClass: MockKpiDataSource, + deps: [], + }, + }); + } + + /** Used for restoring widgets state */ + public reInitializeDashboard(): void { + // destroys the components and their providers so the dashboard can re init data + this.dashboard = undefined; + this.changeDetectorRef.detectChanges(); + + this.initializeDashboard(); + } + + private initializeDashboard(): void { + const widgetsWithStructure = widgetsConfig.map((w) => + this.widgetTypesService.mergeWithWidgetType(w) + ); + const widgetsIndex = keyBy(widgetsWithStructure, (w: IWidget) => w.id); + + const positions: Record = { + kpiWidgetId: { + cols: 3, + rows: 6, + y: 0, + x: 0, + }, + kpiWidgetId2: { + cols: 3, + rows: 6, + y: 0, + x: 0, + }, + }; + + this.dashboard = { + positions, + widgets: widgetsIndex, + }; + } +} + +const widgetsConfig: IWidget[] = [ + { + id: "kpiWidgetId", + type: "kpi", + pizzagna: { + [PizzagnaLayer.Configuration]: { + header: { + properties: { + title: "NO Sync Broker", + subtitle: "Values sizes are being not synced", + }, + }, + tiles: { + properties: { + nodes: ["kpi1", "kpi2", "kpi3"], + }, + }, + kpi1: { + id: "kpi1", + componentType: KpiComponent.lateLoadKey, + properties: { + widgetData: { + units: \\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\`out of 5 Stars\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\`, + label: \\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\`Average Rating\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\`, + backgroundColor: "lightpink", + }, + }, + providers: { + [WellKnownProviders.DataSource]: { + providerId: AverageRatingKpiDataSource.providerId, + } as IProviderConfiguration, + [WellKnownProviders.Adapter]: { + providerId: NOVA_KPI_DATASOURCE_ADAPTER, + properties: { + componentId: "kpi1", + propertyPath: "widgetData", + }, + } as IProviderConfiguration, + }, + }, + kpi2: { + id: "kpi2", + componentType: KpiComponent.lateLoadKey, + properties: { + widgetData: { + label: \\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\`Another label which might be a pretty long one\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\`, + units: \\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\`Which comes from somewhere\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\`, + backgroundColor: "skyblue", + }, + }, + providers: { + [WellKnownProviders.DataSource]: { + providerId: RatingsCountKpiDataSource.providerId, + } as IProviderConfiguration, + [WellKnownProviders.Adapter]: { + providerId: NOVA_KPI_DATASOURCE_ADAPTER, + properties: { + componentId: "kpi2", + propertyPath: "widgetData", + }, + } as IProviderConfiguration, + }, + }, + kpi3: { + id: "kpi3", + componentType: KpiComponent.lateLoadKey, + properties: { + widgetData: { + label: \\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\`Random\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\`, + units: \\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\`Data\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\`, + }, + }, + providers: { + [WellKnownProviders.DataSource]: { + providerId: MockKpiDataSource.providerId, + } as IProviderConfiguration, + [WellKnownProviders.Adapter]: { + providerId: NOVA_KPI_DATASOURCE_ADAPTER, + properties: { + componentId: "kpi3", + propertyPath: "widgetData", + }, + } as IProviderConfiguration, + }, + }, + }, + }, + }, + { + id: "kpiWidgetId2", + type: "kpi", + pizzagna: { + [PizzagnaLayer.Configuration]: { + header: { + properties: { + title: "WITH Sync Broker", + subtitle: + "Now the values of label, units, and value are being synced", + }, + }, + tiles: { + properties: { + nodes: ["kpi4", "kpi5", "kpi6"], + }, + providers: { + // This is where and how you set the sync broker provider + kpiScaleSyncBroker: { + providerId: NOVA_KPI_SCALE_SYNC_BROKER, + properties: { + scaleSyncConfig: [ + // You can decide which values to keep in sync. For instance, you can leave only 'label' id in the array below + { id: "value" }, + { id: "label" }, + { id: "units" }, + ], + }, + }, + }, + }, + kpi4: { + id: "kpi4", + componentType: KpiComponent.lateLoadKey, + properties: { + widgetData: { + units: \\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\`out of 5 Stars\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\`, + label: \\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\`Average Rating\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\`, + backgroundColor: "lightpink", + }, + }, + providers: { + [WellKnownProviders.DataSource]: { + providerId: AverageRatingKpiDataSource.providerId, + } as IProviderConfiguration, + [WellKnownProviders.Adapter]: { + providerId: NOVA_KPI_DATASOURCE_ADAPTER, + properties: { + componentId: "kpi4", + propertyPath: "widgetData", + }, + } as IProviderConfiguration, + }, + }, + kpi5: { + id: "kpi5", + componentType: KpiComponent.lateLoadKey, + properties: { + widgetData: { + label: \\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\`Another label which might be a pretty long one\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\`, + units: \\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\`Which comes from somewhere\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\`, + backgroundColor: "skyblue", + }, + }, + providers: { + [WellKnownProviders.DataSource]: { + providerId: RatingsCountKpiDataSource.providerId, + } as IProviderConfiguration, + [WellKnownProviders.Adapter]: { + providerId: NOVA_KPI_DATASOURCE_ADAPTER, + properties: { + componentId: "kpi5", + propertyPath: "widgetData", + }, + } as IProviderConfiguration, + }, + }, + kpi6: { + id: "kpi6", + componentType: KpiComponent.lateLoadKey, + properties: { + widgetData: { + label: \\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\`Random\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\`, + units: \\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\`Data\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\`, + }, + }, + providers: { + [WellKnownProviders.DataSource]: { + providerId: MockKpiDataSource.providerId, + } as IProviderConfiguration, + [WellKnownProviders.Adapter]: { + providerId: NOVA_KPI_DATASOURCE_ADAPTER, + properties: { + componentId: "kpi6", + propertyPath: "widgetData", + }, + } as IProviderConfiguration, + }, + }, + }, + }, + }, +]; +\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\`, + "widget-types/kpi/kpi-sync-broker-docs.component.ts": \\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\`// © 2022 SolarWinds Worldwide, LLC. All rights reserved. +// +// Permission is hereby granted, free of charge, to any person obtaining a copy +// of this software and associated documentation files (the "Software"), to +// deal in the Software without restriction, including without limitation the +// rights to use, copy, modify, merge, publish, distribute, sublicense, and/or +// sell copies of the Software, and to permit persons to whom the Software is +// furnished to do so, subject to the following conditions: +// +// The above copyright notice and this permission notice shall be included in +// all copies or substantial portions of the Software. +// +// THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR +// IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, +// FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE +// AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER +// LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, +// OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN +// THE SOFTWARE. + +import { Component } from "@angular/core"; + +@Component({ + selector: "kpi-sync-broker-docs", + templateUrl: "./kpi-sync-broker-docs.component.html", + standalone: false, +}) +export class KpiSyncBrokerDocsComponent { + public kpiScaleSyncBroker = \\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\` +"tiles": { + "providers": { + kpiScaleSyncBroker: { + providerId: NOVA_KPI_SCALE_SYNC_BROKER, + properties: { + scaleSyncConfig: [ + { id: "value" }, + { id: "label" }, + { id: "units" }, + ], + }, + }, + }, +}, +\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\`; + + public defineScaleBrokerOnDashboardSetup = \\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\` +// To add the sync broker globally to all the kpi tiles you may start with setting up the broker config +// Here you define which values to keep in sync +const brokerConfig = { + providerId: NOVA_KPI_SCALE_SYNC_BROKER, + properties: { + scaleSyncConfig: [ + { id: "value" }, + { id: "label" }, + { id: "units" }, + ], + }, + }; + +// And here is how you set the sync broker for every KPI widget in the dashboard. +// Later, you will be able to override this setting for each separate KPI widget in the configuration (just like it is shown in the third +// width of the example with the 'kpiWidgetId3') +this.widgetTypesService.setNode( + widgetTemplate, + "widget", + "tiles.providers.kpiScaleSyncBroker", + brokerConfig +); +\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\`; +} +\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\`, + "widget-types/kpi/kpi-sync-broker-for-all-tiles/kpi-sync-broker-for-all-tiles-example.component.ts": \\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\`// © 2022 SolarWinds Worldwide, LLC. All rights reserved. +// +// Permission is hereby granted, free of charge, to any person obtaining a copy +// of this software and associated documentation files (the "Software"), to +// deal in the Software without restriction, including without limitation the +// rights to use, copy, modify, merge, publish, distribute, sublicense, and/or +// sell copies of the Software, and to permit persons to whom the Software is +// furnished to do so, subject to the following conditions: +// +// The above copyright notice and this permission notice shall be included in +// all copies or substantial portions of the Software. +// +// THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR +// IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, +// FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE +// AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER +// LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, +// OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN +// THE SOFTWARE. + +import { HttpClient, HttpErrorResponse } from "@angular/common/http"; +import { + ChangeDetectorRef, + Component, + Injectable, + OnDestroy, + OnInit, +} from "@angular/core"; +import { GridsterConfig, GridsterItem } from "angular-gridster2"; +import keyBy from "lodash/keyBy"; +import { BehaviorSubject, of } from "rxjs"; +import { delay, finalize, take } from "rxjs/operators"; + +import { DataSourceService, IFilteringOutputs } from "@nova-ui/bits"; +import { + DATA_SOURCE, + IDashboard, + IKpiData, + IProviderConfiguration, + IWidget, + KpiComponent, + NOVA_KPI_DATASOURCE_ADAPTER, + NOVA_KPI_SCALE_SYNC_BROKER, + PizzagnaLayer, + ProviderRegistryService, + WellKnownPathKey, + WellKnownProviders, + WidgetTypesService, +} from "@nova-ui/dashboards"; + +/** + * A simple KPI data source to retrieve the average rating of Harry Potter and the Sorcerer's Stone (book) via googleapis + */ +@Injectable() +export class AverageRatingKpiDataSource + extends DataSourceService + implements OnDestroy +{ + public static providerId = "AverageRatingKpiDataSource"; + public busy = new BehaviorSubject(false); + + constructor(private http: HttpClient) { + super(); + } + + public async getFilteredData(): Promise { + this.busy.next(true); + return new Promise((resolve) => { + // *** Make a resource request to an external API (if needed) + this.http + .get("https://www.googleapis.com/books/v1/volumes/5MQFrgEACAAJ") + .pipe(finalize(() => this.busy.next(false))) + .subscribe({ + next: (data: any) => { + resolve({ + result: { + value: data.volumeInfo.averageRating, + }, + }); + }, + error: (error: HttpErrorResponse) => { + resolve({ + result: null, + error: { + type: error.status, + }, + }); + }, + }); + }); + } + + public ngOnDestroy(): void { + this.outputsSubject.complete(); + } +} + +/** + * A simple KPI data source to retrieve the ratings count of Harry Potter and the Sorcerer's Stone (book) via googleapis + */ +@Injectable() +export class RatingsCountKpiDataSource + extends DataSourceService + implements OnDestroy +{ + public static providerId = "RatingsCountKpiDataSource"; + + // Use this subject to communicate the data source's busy state + public busy = new BehaviorSubject(false); + + constructor(private http: HttpClient) { + super(); + } + + public async getFilteredData(): Promise { + this.busy.next(true); + return new Promise((resolve) => { + this.http + .get("https://www.googleapis.com/books/v1/volumes/5MQFrgEACAAJ") + .pipe( + delay(2000), + finalize(() => this.busy.next(false)) + ) + .subscribe({ + next: (data: any) => { + resolve({ + result: { + value: data.volumeInfo.ratingsCount, + }, + }); + }, + error: (error: HttpErrorResponse) => { + resolve({ + result: null, + error: { + type: error.status, + }, + }); + }, + }); + }); + } + + public ngOnDestroy(): void { + this.outputsSubject.complete(); + } +} +/** + * A simple KPI data source to retrieve the ratings count of Harry Potter and the Sorcerer's Stone (book) via googleapis + */ +@Injectable() +export class MockKpiDataSource + extends DataSourceService + implements OnDestroy +{ + public static providerId = "MockKpiDataSource"; + + // Use this subject to communicate the data source's busy state + public busy = new BehaviorSubject(false); + public value: number = 3381342; + + constructor() { + super(); + } + + public async getFilteredData(): Promise { + this.busy.next(true); + return new Promise((resolve) => { + of(this.value) + .pipe( + delay(5000), + take(1), + finalize(() => this.busy.next(false)) + ) + .subscribe({ + next: (data: any) => { + resolve({ + result: { + value: data, + }, + }); + }, + }); + }); + } + + public ngOnDestroy(): void { + this.outputsSubject.complete(); + } +} + +/** + * A component that instantiates the dashboard + */ +@Component({ + selector: "kpi-sync-broker-for-all-tiles-example", + templateUrl: "./kpi-sync-broker-for-all-tiles-example.component.html", + styleUrls: ["./kpi-sync-broker-for-all-tiles-example.component.less"], + standalone: false, +}) +export class KpiSyncBrokerForAllTilesExampleComponent implements OnInit { + public dashboard: IDashboard | undefined; + public gridsterConfig: GridsterConfig = {}; + public editMode: boolean = false; + + constructor( + private widgetTypesService: WidgetTypesService, + private providerRegistry: ProviderRegistryService, + private changeDetectorRef: ChangeDetectorRef + ) {} + + public ngOnInit(): void { + this.setupDashboard(); + + this.initializeDashboard(); + } + + /** Used for restoring widgets state */ + public reInitializeDashboard(): void { + // destroys the components and their providers so the dashboard can re init data + this.dashboard = undefined; + this.changeDetectorRef.detectChanges(); + + this.initializeDashboard(); + } + + private setupDashboard() { + // To add the sync broker globally to all the kpi tiles you may start with setting up the broker config + // Here you define which values to keep in sync + const brokerConfig = { + providerId: NOVA_KPI_SCALE_SYNC_BROKER, + properties: { + scaleSyncConfig: [ + { id: "value" }, + { id: "label" }, + { id: "units" }, + ], + }, + }; + const widgetTemplate = this.widgetTypesService.getWidgetType("kpi", 1); + + this.widgetTypesService.setNode( + widgetTemplate, + "configurator", + WellKnownPathKey.DataSourceProviders, + [ + AverageRatingKpiDataSource.providerId, + RatingsCountKpiDataSource.providerId, + MockKpiDataSource.providerId, + ] + ); + + // And here is how you set the sync broker for every KPI widget in the dashboard. + // Later, you will be able to override this setting for each separate KPI widget in the configuration (just like it is shown in the third + // width of the example with the 'kpiWidgetId3') + this.widgetTypesService.setNode( + widgetTemplate, + "widget", + "tiles.providers.kpiScaleSyncBroker", + brokerConfig + ); + + this.providerRegistry.setProviders({ + [AverageRatingKpiDataSource.providerId]: { + provide: DATA_SOURCE, + useClass: AverageRatingKpiDataSource, + deps: [HttpClient], + }, + [RatingsCountKpiDataSource.providerId]: { + provide: DATA_SOURCE, + useClass: RatingsCountKpiDataSource, + deps: [HttpClient], + }, + [MockKpiDataSource.providerId]: { + provide: DATA_SOURCE, + useClass: MockKpiDataSource, + deps: [], + }, + }); + } + + private initializeDashboard(): void { + const widgetsWithStructure = widgetsConfig.map((w) => + this.widgetTypesService.mergeWithWidgetType(w) + ); + const widgetsIndex = keyBy(widgetsWithStructure, (w: IWidget) => w.id); + + const positions: Record = { + kpiWidgetId: { + cols: 3, + rows: 6, + y: 0, + x: 0, + }, + kpiWidgetId2: { + cols: 3, + rows: 6, + y: 0, + x: 3, + }, + kpiWidgetId3: { + cols: 3, + rows: 6, + y: 0, + x: 6, + }, + }; + + this.dashboard = { + positions, + widgets: widgetsIndex, + }; + } +} + +const widgetsConfig: IWidget[] = [ + { + id: "kpiWidgetId", + type: "kpi", + pizzagna: { + [PizzagnaLayer.Configuration]: { + header: { + properties: { + title: "Sync Broker Applied for ALL Widgets", + subtitle: "Values are being synced", + }, + }, + tiles: { + properties: { + nodes: ["kpi1", "kpi2", "kpi3"], + }, + }, + kpi1: { + id: "kpi1", + componentType: KpiComponent.lateLoadKey, + properties: { + widgetData: { + units: \\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\`out of 5 Stars\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\`, + label: \\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\`Average Rating\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\`, + backgroundColor: "lightpink", + }, + }, + providers: { + [WellKnownProviders.DataSource]: { + providerId: AverageRatingKpiDataSource.providerId, + } as IProviderConfiguration, + [WellKnownProviders.Adapter]: { + providerId: NOVA_KPI_DATASOURCE_ADAPTER, + properties: { + componentId: "kpi1", + propertyPath: "widgetData", + }, + } as IProviderConfiguration, + }, + }, + kpi2: { + id: "kpi2", + componentType: KpiComponent.lateLoadKey, + properties: { + widgetData: { + label: \\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\`Another label which might be a pretty long one\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\`, + units: \\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\`Which comes from somewhere\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\`, + backgroundColor: "skyblue", + }, + }, + providers: { + [WellKnownProviders.DataSource]: { + providerId: RatingsCountKpiDataSource.providerId, + } as IProviderConfiguration, + [WellKnownProviders.Adapter]: { + providerId: NOVA_KPI_DATASOURCE_ADAPTER, + properties: { + componentId: "kpi2", + propertyPath: "widgetData", + }, + } as IProviderConfiguration, + }, + }, + kpi3: { + id: "kpi3", + componentType: KpiComponent.lateLoadKey, + properties: { + widgetData: { + label: \\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\`Random\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\`, + units: \\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\`Data\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\`, + }, + }, + providers: { + [WellKnownProviders.DataSource]: { + providerId: MockKpiDataSource.providerId, + } as IProviderConfiguration, + [WellKnownProviders.Adapter]: { + providerId: NOVA_KPI_DATASOURCE_ADAPTER, + properties: { + componentId: "kpi3", + propertyPath: "widgetData", + }, + } as IProviderConfiguration, + }, + }, + }, + }, + }, + { + id: "kpiWidgetId2", + type: "kpi", + pizzagna: { + [PizzagnaLayer.Configuration]: { + header: { + properties: { + title: "Sync Broker Applied for ALL Widgets", + subtitle: + "Now the values of label, units, and value are being synced", + }, + }, + tiles: { + properties: { + nodes: ["kpi1", "kpi2", "kpi3"], + }, + }, + kpi1: { + id: "kpi1", + componentType: KpiComponent.lateLoadKey, + properties: { + widgetData: { + units: \\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\`out of 5 Stars\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\`, + label: \\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\`Average Rating\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\`, + backgroundColor: "lightpink", + }, + }, + providers: { + [WellKnownProviders.DataSource]: { + providerId: AverageRatingKpiDataSource.providerId, + } as IProviderConfiguration, + [WellKnownProviders.Adapter]: { + providerId: NOVA_KPI_DATASOURCE_ADAPTER, + properties: { + componentId: "kpi1", + propertyPath: "widgetData", + }, + } as IProviderConfiguration, + }, + }, + kpi2: { + id: "kpi2", + componentType: KpiComponent.lateLoadKey, + properties: { + widgetData: { + label: \\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\`Another label which might be a pretty long one\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\`, + units: \\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\`Which comes from somewhere\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\`, + backgroundColor: "skyblue", + }, + }, + providers: { + [WellKnownProviders.DataSource]: { + providerId: RatingsCountKpiDataSource.providerId, + } as IProviderConfiguration, + [WellKnownProviders.Adapter]: { + providerId: NOVA_KPI_DATASOURCE_ADAPTER, + properties: { + componentId: "kpi2", + propertyPath: "widgetData", + }, + } as IProviderConfiguration, + }, + }, + kpi3: { + id: "kpi3", + componentType: KpiComponent.lateLoadKey, + properties: { + widgetData: { + label: \\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\`Random\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\`, + units: \\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\`Data\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\`, + }, + }, + providers: { + [WellKnownProviders.DataSource]: { + providerId: MockKpiDataSource.providerId, + } as IProviderConfiguration, + [WellKnownProviders.Adapter]: { + providerId: NOVA_KPI_DATASOURCE_ADAPTER, + properties: { + componentId: "kpi3", + propertyPath: "widgetData", + }, + } as IProviderConfiguration, + }, + }, + }, + }, + }, + { + id: "kpiWidgetId3", + type: "kpi", + pizzagna: { + [PizzagnaLayer.Configuration]: { + header: { + properties: { + title: "Here We Sync Only Labels and Units", + subtitle: + "Now only the label, and units are being synced", + }, + }, + tiles: { + properties: { + nodes: ["kpi1", "kpi2", "kpi3"], + }, + providers: { + // This is where and how you can override the globally set broker config + kpiScaleSyncBroker: { + providerId: NOVA_KPI_SCALE_SYNC_BROKER, + properties: { + scaleSyncConfig: [ + { id: "label" }, + { id: "units" }, + ], + }, + }, + }, + }, + kpi1: { + id: "kpi1", + componentType: KpiComponent.lateLoadKey, + properties: { + widgetData: { + units: \\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\`out of 5 Stars\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\`, + label: \\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\`Average Rating\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\`, + backgroundColor: "lightpink", + }, + }, + providers: { + [WellKnownProviders.DataSource]: { + providerId: AverageRatingKpiDataSource.providerId, + } as IProviderConfiguration, + [WellKnownProviders.Adapter]: { + providerId: NOVA_KPI_DATASOURCE_ADAPTER, + properties: { + componentId: "kpi1", + propertyPath: "widgetData", + }, + } as IProviderConfiguration, + }, + }, + kpi2: { + id: "kpi2", + componentType: KpiComponent.lateLoadKey, + properties: { + widgetData: { + label: \\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\`Another label which might be a pretty long one\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\`, + units: \\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\`Which comes from somewhere\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\`, + backgroundColor: "skyblue", + }, + }, + providers: { + [WellKnownProviders.DataSource]: { + providerId: RatingsCountKpiDataSource.providerId, + } as IProviderConfiguration, + [WellKnownProviders.Adapter]: { + providerId: NOVA_KPI_DATASOURCE_ADAPTER, + properties: { + componentId: "kpi2", + propertyPath: "widgetData", + }, + } as IProviderConfiguration, + }, + }, + kpi3: { + id: "kpi3", + componentType: KpiComponent.lateLoadKey, + properties: { + widgetData: { + label: \\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\`Random\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\`, + units: \\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\`Data\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\`, + }, + }, + providers: { + [WellKnownProviders.DataSource]: { + providerId: MockKpiDataSource.providerId, + } as IProviderConfiguration, + [WellKnownProviders.Adapter]: { + providerId: NOVA_KPI_DATASOURCE_ADAPTER, + properties: { + componentId: "kpi3", + propertyPath: "widgetData", + }, + } as IProviderConfiguration, + }, + }, + }, + }, + }, +]; +\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\`, + "widget-types/kpi/kpi-widget/kpi-widget-example.component.ts": \\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\`// © 2022 SolarWinds Worldwide, LLC. All rights reserved. +// +// Permission is hereby granted, free of charge, to any person obtaining a copy +// of this software and associated documentation files (the "Software"), to +// deal in the Software without restriction, including without limitation the +// rights to use, copy, modify, merge, publish, distribute, sublicense, and/or +// sell copies of the Software, and to permit persons to whom the Software is +// furnished to do so, subject to the following conditions: +// +// The above copyright notice and this permission notice shall be included in +// all copies or substantial portions of the Software. +// +// THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR +// IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, +// FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE +// AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER +// LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, +// OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN +// THE SOFTWARE. + +import { HttpClient, HttpErrorResponse } from "@angular/common/http"; +import { Component, Injectable, OnDestroy, OnInit } from "@angular/core"; +import { GridsterConfig, GridsterItem } from "angular-gridster2"; +import { BehaviorSubject } from "rxjs"; +import { finalize } from "rxjs/operators"; + +import { DataSourceService, IFilteringOutputs } from "@nova-ui/bits"; +import { + DATA_SOURCE, + DEFAULT_PIZZAGNA_ROOT, + IDashboard, + IKpiData, + IProviderConfiguration, + IRefresherProperties, + IWidget, + IWidgets, + KpiComponent, + NOVA_KPI_DATASOURCE_ADAPTER, + PizzagnaLayer, + ProviderRegistryService, + WellKnownPathKey, + WellKnownProviders, + WidgetTypesService, +} from "@nova-ui/dashboards"; + +/** + * A simple KPI data source to retrieve the average rating of Harry Potter and the Sorcerer's Stone (book) via googleapis + */ +@Injectable() +export class AverageRatingKpiDataSource + extends DataSourceService + implements OnDestroy +{ + // This is the ID we'll use to identify the provider + public static providerId = "AverageRatingKpiDataSource"; + + // Use this subject to communicate the data source's busy state + public busy = new BehaviorSubject(false); + + constructor(private http: HttpClient) { + super(); + } + + // In this example, getFilteredData is invoked every 10 minutes (Take a look at the refresher + // provider definition in the widget configuration below to see how the interval is set) + public async getFilteredData(): Promise { + this.busy.next(true); + return new Promise((resolve) => { + // *** Make a resource request to an external API (if needed) + this.http + .get("https://www.googleapis.com/books/v1/volumes/5MQFrgEACAAJ") + .pipe(finalize(() => this.busy.next(false))) + .subscribe({ + next: (data: any) => { + resolve({ + result: { + value: data.volumeInfo.averageRating, + }, + }); + }, + error: (error: HttpErrorResponse) => { + resolve({ + result: null, + error: { + type: error.status, + }, + }); + }, + }); + }); + } + + public ngOnDestroy(): void { + this.outputsSubject.complete(); + } +} + +/** + * A component that instantiates the dashboard + */ +@Component({ + selector: "kpi-widget-example", + templateUrl: "./kpi-widget-example.component.html", + styleUrls: ["./kpi-widget-example.component.less"], + standalone: false, +}) +export class KpiWidgetExampleComponent implements OnInit { + // This variable will hold all the data needed to define the layout and behavior of the widgets. + // Pass this to the dashboard component's dashboard input in the template. + public dashboard: IDashboard; + + // Angular gridster requires a configuration object even if it's empty. + // Pass this to the dashboard component's gridsterConfig input in the template. + public gridsterConfig: GridsterConfig = {}; + + // Boolean passed as an input to the dashboard. When true, widgets can be moved, resized, removed, or edited + public editMode: boolean = false; + + constructor( + // WidgetTypesService provides the widget's necessary structure information + private widgetTypesService: WidgetTypesService, + + // In general, the ProviderRegistryService is used for making entities available for injection into dynamically loaded components. + private providerRegistry: ProviderRegistryService + ) {} + + public ngOnInit(): void { + // Grabbing the widget's default template which will be needed as a parameter for setNode + const widgetTemplate = this.widgetTypesService.getWidgetType("kpi", 1); + // Registering our data sources as dropdown options in the widget editor/configurator + // Note: This could also be done in the parent module's constructor so that + // multiple dashboards could have access to the same widget template modification. + this.widgetTypesService.setNode( + // This is the template we grabbed above with getWidgetType + widgetTemplate, + // We are setting the editor/configurator part of the widget template + "configurator", + // This indicates which node you are changing and we want to change + // the data source providers available for selection in the editor. + WellKnownPathKey.DataSourceProviders, + // We are setting the data sources available for selection in the editor + [AverageRatingKpiDataSource.providerId] + ); + + // Registering the data source for injection into the KPI tile. + // Note: Each tile of a KPI widget is assigned its own instance of the data source + this.providerRegistry.setProviders({ + [AverageRatingKpiDataSource.providerId]: { + provide: DATA_SOURCE, + useClass: AverageRatingKpiDataSource, + // Any dependencies that need to be injected into the provider must be listed here + deps: [HttpClient], + }, + }); + + this.initializeDashboard(); + } + + public initializeDashboard(): void { + // We're using a static configuration object for this example, but this is where + // the widget's configuration could potentially be populated from a database + const kpiWidget = widgetConfig; + const widgetIndex: IWidgets = { + // Complete the KPI widget with information coming from its type definition + [kpiWidget.id]: + this.widgetTypesService.mergeWithWidgetType(kpiWidget), + }; + + // Setting the widget dimensions and position (this is for gridster) + const positions: Record = { + [kpiWidget.id]: { + cols: 4, + rows: 6, + y: 0, + x: 0, + }, + }; + + // Finally, assigning the variables we created above to the dashboard + this.dashboard = { + positions, + widgets: widgetIndex, + }; + } +} + +const widgetConfig: IWidget = { + id: "kpiWidgetId", + type: "kpi", + pizzagna: { + [PizzagnaLayer.Configuration]: { + [DEFAULT_PIZZAGNA_ROOT]: { + providers: { + [WellKnownProviders.Refresher]: { + properties: { + // Configuring the refresher interval so that our data source is invoked every ten minutes + interval: 60 * 10, + enabled: true, + } as IRefresherProperties, + } as Partial, + }, + }, + header: { + properties: { + title: "Harry Potter and the Sorcerer's Stone", + subtitle: "By J. K. Rowling", + }, + }, + tiles: { + properties: { + nodes: ["kpi1"], + }, + }, + kpi1: { + id: "kpi1", + componentType: KpiComponent.lateLoadKey, + properties: { + widgetData: { + units: \\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\`out of 5 Stars\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\`, + label: \\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\`Average Rating\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\`, + }, + }, + providers: { + [WellKnownProviders.DataSource]: { + // Setting the data source providerId for the tile with id "kpi1" + providerId: AverageRatingKpiDataSource.providerId, + } as IProviderConfiguration, + [WellKnownProviders.Adapter]: { + providerId: NOVA_KPI_DATASOURCE_ADAPTER, + properties: { + componentId: "kpi1", + propertyPath: "widgetData", + }, + } as IProviderConfiguration, + }, + }, + }, + }, +}; +\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\`, + "widget-types/kpi/kpi-widget-background-color/kpi-widget-background-color-example.component.ts": \\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\`// © 2022 SolarWinds Worldwide, LLC. All rights reserved. +// +// Permission is hereby granted, free of charge, to any person obtaining a copy +// of this software and associated documentation files (the "Software"), to +// deal in the Software without restriction, including without limitation the +// rights to use, copy, modify, merge, publish, distribute, sublicense, and/or +// sell copies of the Software, and to permit persons to whom the Software is +// furnished to do so, subject to the following conditions: +// +// The above copyright notice and this permission notice shall be included in +// all copies or substantial portions of the Software. +// +// THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR +// IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, +// FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE +// AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER +// LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, +// OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN +// THE SOFTWARE. + +import { HttpClient, HttpErrorResponse } from "@angular/common/http"; +import { + ChangeDetectorRef, + Component, + Injectable, + OnDestroy, + OnInit, +} from "@angular/core"; +import { GridsterConfig, GridsterItem } from "angular-gridster2"; +import { BehaviorSubject } from "rxjs"; +import { finalize } from "rxjs/operators"; + +import { DataSourceService, IFilteringOutputs } from "@nova-ui/bits"; +import { + DATA_SOURCE, + DEFAULT_KPI_BACKGROUND_COLORS, + IDashboard, + IKpiColorRules, + IKpiData, + IProviderConfiguration, + IWidget, + IWidgets, + KpiComponent, + NOVA_KPI_COLOR_PRIORITIZER, + NOVA_KPI_DATASOURCE_ADAPTER, + PizzagnaLayer, + ProviderRegistryService, + WellKnownPathKey, + WellKnownProviders, + WidgetTypesService, +} from "@nova-ui/dashboards"; + +/** + * A simple KPI data source to retrieve the average rating of Harry Potter and the Sorcerer's Stone (book) via googleapis + */ +@Injectable() +export class AverageRatingKpiDataSource + extends DataSourceService + implements OnDestroy +{ + public static providerId = "AverageRatingKpiDataSource"; + public busy = new BehaviorSubject(false); + + constructor(private http: HttpClient) { + super(); + } + + public async getFilteredData(): Promise { + this.busy.next(true); + return new Promise((resolve) => { + // *** Make a resource request to an external API (if needed) + this.http + .get("https://www.googleapis.com/books/v1/volumes/5MQFrgEACAAJ") + .pipe(finalize(() => this.busy.next(false))) + .subscribe({ + next: (data: any) => { + resolve({ + result: { + value: data.volumeInfo.averageRating, + // setting the color on the dataSource "Sea Green", + // uncomment to get the background color update from the "Data" layer + // backgroundColor: "var(--nui-color-chart-three)", + }, + }); + }, + error: (error: HttpErrorResponse) => { + resolve({ + result: null, + error: { + type: error.status, + }, + }); + }, + }); + }); + } + + public ngOnDestroy(): void { + this.outputsSubject.complete(); + } +} + +/** + * A component that instantiates the dashboard + */ +@Component({ + selector: "kpi-widget-background-color-example", + templateUrl: "./kpi-widget-background-color-example.component.html", + styleUrls: ["./kpi-widget-background-color-example.component.less"], + standalone: false, +}) +export class KpiWidgetBackgroundColorExampleComponent implements OnInit { + public dashboard: IDashboard | undefined; + public gridsterConfig: GridsterConfig = {}; + public editMode: boolean = false; + + constructor( + private widgetTypesService: WidgetTypesService, + private providerRegistry: ProviderRegistryService, + private changeDetectorRef: ChangeDetectorRef + ) {} + + public ngOnInit(): void { + this.setupDashboard(); + + // KPI tile default color setup + this.setupDefaultColorStructure(); + + // Sets the custom pallette to the 'Description' section + this.setupCustomPalletteDescription(); + + // Sets the custom pallette to the 'Background color rules' section + this.setupCustomPalletteRules(); + + this.initializeDashboard(); + } + + /** Used for restoring widgets state */ + public reInitializeDashboard(): void { + // destroys the components and their providers so the dashboard can re init data + this.dashboard = undefined; + this.changeDetectorRef.detectChanges(); + + this.initializeDashboard(); + } + + private setupCustomPalletteDescription() { + const kpiWidgetTemplate = this.widgetTypesService.getWidgetType( + "kpi", + 1 + ); + this.widgetTypesService.setNode( + kpiWidgetTemplate, + "configurator", + WellKnownPathKey.TileDescriptionBackgroundColors, + [ + { color: "var(--nui-color-chart-one)", label: "Blue" }, + { + color: "var(--nui-color-chart-one-light)", + label: "Blue Light", + }, + { + color: "var(--nui-color-chart-one-dark)", + label: "Blue Dark", + }, + ] + ); + } + + private setupCustomPalletteRules() { + const kpiWidgetTemplate = this.widgetTypesService.getWidgetType( + "kpi", + 1 + ); + this.widgetTypesService.setNode( + kpiWidgetTemplate, + "configurator", + WellKnownPathKey.TileBackgroundColorRulesBackgroundColors, + [ + { color: "red", label: "Native Red" }, + ...DEFAULT_KPI_BACKGROUND_COLORS, + ] + ); + } + + private setupDefaultColorStructure() { + const widgetTemplate = this.widgetTypesService.getWidgetType("kpi", 1); + this.widgetTypesService.setNode( + widgetTemplate, + "widget", + "tiles.properties.template.properties.widgetData.backgroundColor", + "red" + ); + } + + private setupDashboard() { + const widgetTemplate = this.widgetTypesService.getWidgetType("kpi", 1); + this.widgetTypesService.setNode( + widgetTemplate, + "configurator", + WellKnownPathKey.DataSourceProviders, + [AverageRatingKpiDataSource.providerId] + ); + + this.providerRegistry.setProviders({ + [AverageRatingKpiDataSource.providerId]: { + provide: DATA_SOURCE, + useClass: AverageRatingKpiDataSource, + deps: [HttpClient], + }, + }); + } + + private initializeDashboard(): void { + const kpiWidget = widgetConfig; + const widgetIndex: IWidgets = { + [kpiWidget.id]: + this.widgetTypesService.mergeWithWidgetType(kpiWidget), + }; + + const positions: Record = { + [kpiWidget.id]: { + cols: 4, + rows: 6, + y: 0, + x: 0, + }, + }; + + this.dashboard = { + positions, + widgets: widgetIndex, + }; + } +} + +const widgetConfig: IWidget = { + id: "kpiWidgetId", + type: "kpi", + pizzagna: { + [PizzagnaLayer.Configuration]: { + header: { + properties: { + title: "Harry Potter and the Sorcerer's Stone", + subtitle: "By J. K. Rowling", + }, + }, + tiles: { + properties: { + nodes: ["kpi1"], + }, + }, + kpi1: { + id: "kpi1", + componentType: KpiComponent.lateLoadKey, + properties: { + widgetData: { + units: \\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\`out of 5 Stars\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\`, + label: \\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\`Average Rating\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\`, + // Configuration color "Blue" + backgroundColor: "var(--nui-color-chart-one)", + }, + }, + providers: { + [WellKnownProviders.DataSource]: { + providerId: AverageRatingKpiDataSource.providerId, + } as IProviderConfiguration, + [WellKnownProviders.Adapter]: { + providerId: NOVA_KPI_DATASOURCE_ADAPTER, + properties: { + componentId: "kpi1", + propertyPath: "widgetData", + }, + } as IProviderConfiguration, + [WellKnownProviders.KpiColorPrioritizer]: { + providerId: NOVA_KPI_COLOR_PRIORITIZER, + properties: { + // Color Prioritizer Rules + // settings rules - if the value is more than "2" display "Violet" color + rules: [ + { + comparisonType: ">", + value: 2, + color: "var(--nui-color-chart-four)", + }, + ] as IKpiColorRules[], + }, + } as IProviderConfiguration, + }, + }, + }, + }, +}; +\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\`, + "widget-types/kpi/kpi-widget-background-color-docs.component.ts": \\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\`// © 2022 SolarWinds Worldwide, LLC. All rights reserved. +// +// Permission is hereby granted, free of charge, to any person obtaining a copy +// of this software and associated documentation files (the "Software"), to +// deal in the Software without restriction, including without limitation the +// rights to use, copy, modify, merge, publish, distribute, sublicense, and/or +// sell copies of the Software, and to permit persons to whom the Software is +// furnished to do so, subject to the following conditions: +// +// The above copyright notice and this permission notice shall be included in +// all copies or substantial portions of the Software. +// +// THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR +// IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, +// FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE +// AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER +// LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, +// OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN +// THE SOFTWARE. + +import { Component } from "@angular/core"; + +@Component({ + selector: "nui-kpi-background-color-docs", + templateUrl: "./kpi-widget-background-color-docs.component.html", + standalone: false, +}) +export class KpiWidgetBackgroundColorDocsComponent { + public comparatorsRegistryCode = \\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\` + this.comparatorsRegistry.registerComparators({ + "!=": { + comparatorFn: (actual: any, reference: any) => actual != reference, + label: "Not equal", + }, + }); + \\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\`; +} +\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\`, + "widget-types/kpi/kpi-widget-interactive/kpi-widget-interactive-example.component.ts": \\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\`// © 2022 SolarWinds Worldwide, LLC. All rights reserved. +// +// Permission is hereby granted, free of charge, to any person obtaining a copy +// of this software and associated documentation files (the "Software"), to +// deal in the Software without restriction, including without limitation the +// rights to use, copy, modify, merge, publish, distribute, sublicense, and/or +// sell copies of the Software, and to permit persons to whom the Software is +// furnished to do so, subject to the following conditions: +// +// The above copyright notice and this permission notice shall be included in +// all copies or substantial portions of the Software. +// +// THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR +// IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, +// FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE +// AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER +// LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, +// OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN +// THE SOFTWARE. + +import { HttpClient, HttpErrorResponse } from "@angular/common/http"; +import { Component, Injectable, OnDestroy, OnInit } from "@angular/core"; +import { GridsterConfig, GridsterItem } from "angular-gridster2"; +import { BehaviorSubject } from "rxjs"; +import { finalize } from "rxjs/operators"; + +import { DataSourceService, IFilteringOutputs } from "@nova-ui/bits"; +import { + DATA_SOURCE, + IDashboard, + IKpiData, + IProviderConfiguration, + IWidget, + IWidgets, + KpiComponent, + NOVA_KPI_DATASOURCE_ADAPTER, + NOVA_URL_INTERACTION_HANDLER, + PizzagnaLayer, + ProviderRegistryService, + WellKnownPathKey, + WellKnownProviders, + WidgetTypesService, +} from "@nova-ui/dashboards"; + +/** + * A simple KPI data source to retrieve the average rating of Harry Potter and the Sorcerer's Stone (book) via googleapis + */ +@Injectable() +export class BookRatingDataSource + extends DataSourceService + implements OnDestroy +{ + // This is the ID we'll use to identify the provider + public static providerId = "BookRatingDataSource"; + + // Use this subject to communicate the data source's busy state + public busy = new BehaviorSubject(false); + + constructor(private http: HttpClient) { + super(); + } + + // In this example, getFilteredData is invoked every 10 minutes (Take a look at the refresher + // provider definition in the widget configuration below to see how the interval is set) + public async getFilteredData(): Promise { + this.busy.next(true); + return new Promise((resolve) => { + // *** Make a resource request to an external API (if needed) + this.http + .get("https://www.googleapis.com/books/v1/volumes/zpvysRGsBlwC") + .pipe(finalize(() => this.busy.next(false))) + .subscribe({ + next: (data: any) => { + resolve({ + result: { + value: data.volumeInfo.averageRating, + link: data.volumeInfo.infoLink, + }, + }); + }, + error: (error: HttpErrorResponse) => { + resolve({ + result: null, + error: { + type: error.status, + }, + }); + }, + }); + }); + } + + public ngOnDestroy(): void { + this.outputsSubject.complete(); + } +} + +/** + * A component that instantiates the dashboard + */ +@Component({ + selector: "kpi-widget-interactive-example", + templateUrl: "./kpi-widget-interactive-example.component.html", + styleUrls: ["./kpi-widget-interactive-example.component.less"], + standalone: false, +}) +export class KpiWidgetInteractiveExampleComponent implements OnInit { + // This variable will hold all the data needed to define the layout and behavior of the widgets. + // Pass this to the dashboard component's dashboard input in the template. + public dashboard: IDashboard; + + // Angular gridster requires a configuration object even if it's empty. + // Pass this to the dashboard component's gridsterConfig input in the template. + public gridsterConfig: GridsterConfig = {}; + + // Boolean passed as an input to the dashboard. When true, widgets can be moved, resized, removed, or edited + public editMode: boolean = false; + + constructor( + // WidgetTypesService provides the widget's necessary structure information + private widgetTypesService: WidgetTypesService, + + // In general, the ProviderRegistryService is used for making entities available for injection into dynamically loaded components. + private providerRegistry: ProviderRegistryService + ) {} + + public ngOnInit(): void { + // Grabbing the widget's default template which will be needed as a parameter for setNode + const widgetTemplate = this.widgetTypesService.getWidgetType("kpi", 1); + // Registering our data sources as dropdown options in the widget editor/configurator + // Note: This could also be done in the parent module's constructor so that + // multiple dashboards could have access to the same widget template modification. + this.widgetTypesService.setNode( + // This is the template we grabbed above with getWidgetType + widgetTemplate, + // We are setting the editor/configurator part of the widget template + "configurator", + // This indicates which node you are changing and we want to change + // the data source providers available for selection in the editor. + WellKnownPathKey.DataSourceProviders, + // We are setting the data sources available for selection in the editor + [BookRatingDataSource.providerId] + ); + + // Registering the data source for injection into the KPI tile. + // Note: Each tile of a KPI widget is assigned its own instance of the data source + this.providerRegistry.setProviders({ + [BookRatingDataSource.providerId]: { + provide: DATA_SOURCE, + useClass: BookRatingDataSource, + // Any dependencies that need to be injected into the provider must be listed here + deps: [HttpClient], + }, + }); + + this.initializeDashboard(); + } + + public initializeDashboard(): void { + // We're using a static configuration object for this example, but this is where + // the widget's configuration could potentially be populated from a database + const kpiWidget = widgetConfig; + const widgetIndex: IWidgets = { + // Complete the KPI widget with information coming from its type definition + [kpiWidget.id]: + this.widgetTypesService.mergeWithWidgetType(kpiWidget), + }; + + // Setting the widget dimensions and position (this is for gridster) + const positions: Record = { + [kpiWidget.id]: { + cols: 4, + rows: 6, + y: 0, + x: 0, + }, + }; + + // Finally, assigning the variables we created above to the dashboard + this.dashboard = { + positions, + widgets: widgetIndex, + }; + } +} + +const widgetConfig: IWidget = { + id: "kpiWidgetId", + type: "kpi", + pizzagna: { + [PizzagnaLayer.Configuration]: { + header: { + properties: { + title: "Harry Potter and the Order of the Phoenix", + subtitle: "By: J. K. Rowling", + }, + }, + tiles: { + providers: { + interaction: { + // Configuring the UrlInteractionHandler for interactions on the tiles + providerId: NOVA_URL_INTERACTION_HANDLER, + properties: { + // the 'url' property tells the handler what link to use when interaction occurs on the series + url: "\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\${data.link}", + }, + }, + }, + properties: { + nodes: ["kpi1"], + }, + }, + kpi1: { + id: "kpi1", + componentType: KpiComponent.lateLoadKey, + properties: { + widgetData: { + units: \\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\`out of 5 stars\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\`, + label: \\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\`Average Rating\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\`, + value: 0, + // the link property that is passed to the UrlInteractionHandler when the title is clicked + // this will be updated in BookRatingDataSource's 'getFilteredData' call. + link: "http://www.google.com", + }, + }, + providers: { + [WellKnownProviders.DataSource]: { + // Setting the data source providerId for the tile with id "kpi1" + providerId: BookRatingDataSource.providerId, + } as IProviderConfiguration, + [WellKnownProviders.Adapter]: { + providerId: NOVA_KPI_DATASOURCE_ADAPTER, + properties: { + componentId: "kpi1", + propertyPath: "widgetData", + }, + } as IProviderConfiguration, + }, + }, + }, + }, +}; +\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\`, + "widget-types/proportional/models.ts": \\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\`export interface IMockBeerReview { + id: string; + name: string; + data: number[]; + icon: string; + link?: string; + value: string; + color?: string; +} +\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\`, + "widget-types/proportional/proportional-docs.component.ts": \\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\`// © 2022 SolarWinds Worldwide, LLC. All rights reserved. +// +// Permission is hereby granted, free of charge, to any person obtaining a copy +// of this software and associated documentation files (the "Software"), to +// deal in the Software without restriction, including without limitation the +// rights to use, copy, modify, merge, publish, distribute, sublicense, and/or +// sell copies of the Software, and to permit persons to whom the Software is +// furnished to do so, subject to the following conditions: +// +// The above copyright notice and this permission notice shall be included in +// all copies or substantial portions of the Software. +// +// THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR +// IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, +// FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE +// AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER +// LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, +// OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN +// THE SOFTWARE. + +import { Component, OnInit } from "@angular/core"; + +import { mapContentFile } from "../../../../demo-files-factory"; + +@Component({ + selector: "nui-proportional-docs", + templateUrl: "./proportional-docs.component.html", + standalone: false, +}) +export class ProportionalDocsComponent implements OnInit { + public proportionalWidgetFileText = ""; + public proportionalConfiguratorFileText = ""; + + public async ngOnInit(): Promise { + this.proportionalWidgetFileText = await import( + "./../../../../../../src/lib/widget-types/proportional/proportional-widget" + ).then(mapContentFile); + this.proportionalConfiguratorFileText = await import( + "./../../../../../../src/lib/widget-types/proportional/proportional-configurator" + ).then(mapContentFile); + } +} +\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\`, + "widget-types/proportional/proportional-docs.module.ts": \\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\`// © 2022 SolarWinds Worldwide, LLC. All rights reserved. +// +// Permission is hereby granted, free of charge, to any person obtaining a copy +// of this software and associated documentation files (the "Software"), to +// deal in the Software without restriction, including without limitation the +// rights to use, copy, modify, merge, publish, distribute, sublicense, and/or +// sell copies of the Software, and to permit persons to whom the Software is +// furnished to do so, subject to the following conditions: +// +// The above copyright notice and this permission notice shall be included in +// all copies or substantial portions of the Software. +// +// THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR +// IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, +// FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE +// AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER +// LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, +// OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN +// THE SOFTWARE. + +import { NgModule } from "@angular/core"; +import { RouterModule, Routes } from "@angular/router"; + +import { + NuiButtonModule, + NuiDocsModule, + NuiMessageModule, + NuiSwitchModule, + DEMO_PATH_TOKEN, +} from "@nova-ui/bits"; +import { NuiDashboardsModule } from "@nova-ui/dashboards"; + +import { ProportionalDocsComponent } from "./proportional-docs.component"; +import { ProportionalDonutContentDocsComponent } from "./proportional-donut-content-docs.component"; +import { ProportionalWidgetDonutContentFormattersExampleComponent } from "./proportional-donut-content-formatters/proportional-donut-content-formatters-example.component"; +import { ProportionalWidgetExampleComponent } from "./proportional-widget/proportional-widget-example.component"; +import { ProportionalWidgetInteractiveExampleComponent } from "./proportional-widget-interactive/proportional-widget-interactive-example.component"; +import { getDemoFiles } from "../../../../demo-files-factory"; + +const routes: Routes = [ + { + path: "", + component: ProportionalDocsComponent, + data: { + srlc: { + hideIndicator: true, + }, + showThemeSwitcher: true, + }, + }, + { + path: "example", + component: ProportionalWidgetExampleComponent, + data: { + srlc: { + hideIndicator: true, + }, + }, + }, + { + path: "donut-content-formatters", + component: ProportionalDonutContentDocsComponent, + data: { + srlc: { + hideIndicator: true, + }, + }, + }, + { + path: "donut-content-formatters-example", + component: ProportionalWidgetDonutContentFormattersExampleComponent, + data: { + srlc: { + hideIndicator: true, + }, + }, + }, + { + path: "proportional-widget-interactive-example", + component: ProportionalWidgetInteractiveExampleComponent, + data: { + srlc: { + hideIndicator: true, + }, + }, + }, +]; + +@NgModule({ + imports: [ + RouterModule.forChild(routes), + NuiButtonModule, + NuiDocsModule, + NuiDashboardsModule, + NuiMessageModule, + NuiSwitchModule, + ], + declarations: [ + ProportionalDocsComponent, + ProportionalWidgetExampleComponent, + ProportionalWidgetInteractiveExampleComponent, + ProportionalWidgetDonutContentFormattersExampleComponent, + ProportionalDonutContentDocsComponent, + ], + providers: [ + { + provide: DEMO_PATH_TOKEN, + useValue: getDemoFiles("proportional"), + }, + ], +}) +export default class ProportionalDocsModule {} +\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\`, + "widget-types/proportional/proportional-donut-content-docs.component.ts": \\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\`// © 2022 SolarWinds Worldwide, LLC. All rights reserved. +// +// Permission is hereby granted, free of charge, to any person obtaining a copy +// of this software and associated documentation files (the "Software"), to +// deal in the Software without restriction, including without limitation the +// rights to use, copy, modify, merge, publish, distribute, sublicense, and/or +// sell copies of the Software, and to permit persons to whom the Software is +// furnished to do so, subject to the following conditions: +// +// The above copyright notice and this permission notice shall be included in +// all copies or substantial portions of the Software. +// +// THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR +// IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, +// FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE +// AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER +// LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, +// OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN +// THE SOFTWARE. + +import { Component } from "@angular/core"; + +@Component({ + selector: "nui-proportional-donut-content-docs", + templateUrl: "./proportional-donut-content-docs.component.html", + standalone: false, +}) +export class ProportionalDonutContentDocsComponent { + public dataSourceDataFieldsConfig = \\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\` +public dataFieldsConfig: IProportionalDataFieldsConfig = { + dataFields$: new BehaviorSubject(this.dataFields), + chartSeriesDataFields$: new BehaviorSubject(this.chartSeriesDataFields), +}; + \\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\`; + + public widgetConfigSlice = \\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\` +"properties": { + "configuration": { + "chartOptions": { + donutContentConfig: { + formatter: { + componentType: SiUnitsFormatterComponent.lateLoadKey, + }, + aggregator: { + aggregatorType: sumAggregator.aggregatorType, + }, + }, + } + } +} + + \\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\`; +} +\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\`, + "widget-types/proportional/proportional-donut-content-formatters/proportional-donut-content-formatters-example.component.ts": \\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\`// © 2022 SolarWinds Worldwide, LLC. All rights reserved. +// +// Permission is hereby granted, free of charge, to any person obtaining a copy +// of this software and associated documentation files (the "Software"), to +// deal in the Software without restriction, including without limitation the +// rights to use, copy, modify, merge, publish, distribute, sublicense, and/or +// sell copies of the Software, and to permit persons to whom the Software is +// furnished to do so, subject to the following conditions: +// +// The above copyright notice and this permission notice shall be included in +// all copies or substantial portions of the Software. +// +// THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR +// IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, +// FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE +// AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER +// LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, +// OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN +// THE SOFTWARE. + +import { + ChangeDetectorRef, + Component, + Injectable, + OnDestroy, + OnInit, +} from "@angular/core"; +import { GridsterConfig, GridsterItem } from "angular-gridster2"; +import { BehaviorSubject } from "rxjs"; + +import { + DataSourceService, + IDataField, + IDataSource, + IFilteringOutputs, +} from "@nova-ui/bits"; +import { IAccessors, IChartAssistSeries } from "@nova-ui/charts"; +import { + DATA_SOURCE, + DEFAULT_LEGEND_FORMATTERS, + DEFAULT_PIZZAGNA_ROOT, + DEFAULT_PROPORTIONAL_CONTENT_AGGREGATORS, + DEFAULT_PROPORTIONAL_CONTENT_FORMATTERS, + DONUT_CONTENT_CONFIGURATION_SLICE, + IDashboard, + IDonutContentConfig, + IProportionalDataFieldsConfig, + IProportionalWidgetChartOptions, + IProportionalWidgetConfig, + IProviderConfiguration, + IWidget, + IWidgets, + LegendPlacement, + PizzagnaLayer, + ProportionalContentAggregatorsRegistryService, + ProportionalDonutContentFormattersRegistryService, + ProportionalLegendFormattersRegistryService, + ProportionalWidgetChartTypes, + ProviderRegistryService, + SiUnitsFormatterComponent, + sumAggregator, + WellKnownPathKey, + WellKnownProviders, + WidgetTypesService, +} from "@nova-ui/dashboards"; + +import { IMockBeerReview } from "../models"; + +/** + * A simple proportional data source to retrieve beer review counts by city + */ +@Injectable() +export class BeerReviewCountsByCityMockDataSource + extends DataSourceService> + implements IDataSource>, OnDestroy +{ + public static providerId = "BeerReviewCountsByCityMockDataSource"; + public busy = new BehaviorSubject(false); + + protected dataFields: IDataField[] = [ + { + id: "Brno", + label: "Brno", + // @ts-ignore + dataType: null, + }, + { + id: "kyiv", + label: "Kyiv", + // @ts-ignore + dataType: null, + }, + { + id: "austin", + label: "Austin", + // @ts-ignore + dataType: null, + }, + { + id: "lisbon", + label: "Lisbon", + // @ts-ignore + dataType: null, + }, + { + id: "sydney", + label: "Sydney", + // @ts-ignore + dataType: null, + }, + { + id: "nur-sultan", + label: "Nur-Sultan", + // @ts-ignore + dataType: null, + }, + ]; + protected chartSeriesDataFields: IDataField[] = [ + // default field in the chart series that is used for the aggregation + { + id: "data[0]", + label: "data", + // @ts-ignore + dataType: null, + }, + // any custom field in the chart series that is used for the aggregation + { + id: "customDonutContent", + label: "Custom Donut Content", + // @ts-ignore + dataType: null, + }, + ]; + + /** + * DataSource needs to implement the "IDataFieldsConfig" for this scenario. + * + * It's necessary to provide the "chartSeriesDataFields", + * that's why proportional widget dataSource has it's own interface for that - IProportionalDataFieldsConfig. + * + * dataFields$ - stands for possible series fields + * chartSeriesDataFields$ - stands for the fields IN the series + * + * see declaration of "dataFields" and "chartSeriesDataFields" for the example. + */ + public dataFieldsConfig: IProportionalDataFieldsConfig = { + dataFields$: new BehaviorSubject(this.dataFields), + chartSeriesDataFields$: new BehaviorSubject( + this.chartSeriesDataFields + ), + }; + + public async getFilteredData(): Promise { + this.busy.next(true); + return new Promise((resolve) => { + setTimeout(() => { + this.outputsSubject.next({ + result: getMockBeerReviewCountsByCity(), + }); + this.busy.next(false); + }, 300); + }); + } + + public ngOnDestroy(): void { + this.outputsSubject.complete(); + } +} + +/** + * A component that instantiates the dashboard + */ +@Component({ + selector: "proportional-widget-donut-content-formatters-example", + templateUrl: "./proportional-donut-content-formatters-example.component.html", + styleUrls: [ + "./proportional-donut-content-formatters-example.component.less", + ], + standalone: false, +}) +export class ProportionalWidgetDonutContentFormattersExampleComponent + implements OnInit +{ + public dashboard: IDashboard | undefined; + + // Angular gridster requires a configuration object even if it's empty. + // Pass this to the dashboard component's gridsterConfig input in the template. + public gridsterConfig: GridsterConfig = {}; + + // Boolean passed as an input to the dashboard. When true, widgets can be moved, resized, removed, or edited + public editMode: boolean = false; + + constructor( + // WidgetTypesService provides the widget's necessary structure information + private widgetTypesService: WidgetTypesService, + // In general, the ProviderRegistryService is used for making entities available for injection into dynamically loaded components. + private providerRegistry: ProviderRegistryService, + // registry for adding the formatter for donut content + contentFormattersRegistry: ProportionalDonutContentFormattersRegistryService, + // registry for adding the formatter for proportional legend + legendFormattersRegistry: ProportionalLegendFormattersRegistryService, + // registry for adding the aggregators for donut content + aggregatorRegistry: ProportionalContentAggregatorsRegistryService, + private changeDetectorRef: ChangeDetectorRef + ) { + // on the dashboard startup, it's necessary to add possible content formatters, legend formatters and content aggregators to the registry. + // using registry is a way for setting the available formatters. + legendFormattersRegistry.addItems(DEFAULT_LEGEND_FORMATTERS); + contentFormattersRegistry.addItems( + DEFAULT_PROPORTIONAL_CONTENT_FORMATTERS + ); + aggregatorRegistry.addItems(DEFAULT_PROPORTIONAL_CONTENT_AGGREGATORS); + } + + public ngOnInit(): void { + // Grabbing the widget's default template which will be needed as a parameter for setNode + const widgetTemplate = this.widgetTypesService.getWidgetType( + "proportional", + 1 + ); + + // Registering our data sources as dropdown options in the widget editor/configurator + // Note: This could also be done in the parent module's constructor so that + // multiple dashboards could have access to the same widget template modification. + this.widgetTypesService.setNode( + // This is the template we grabbed above with getWidgetType + widgetTemplate, + // We are setting the editor/configurator part of the widget template + "configurator", + // This indicates which node you are changing and we want to change + // the data source providers available for selection in the editor. + WellKnownPathKey.DataSourceProviders, + // We are setting the data sources available for selection in the editor + [BeerReviewCountsByCityMockDataSource.providerId] + ); + + // Setup of the configurator is done here + this.setupConfigurator(); + + // Registering the data source for injection into the Proportional widget. + this.providerRegistry.setProviders({ + [BeerReviewCountsByCityMockDataSource.providerId]: { + provide: DATA_SOURCE, + useClass: BeerReviewCountsByCityMockDataSource, + deps: [], + }, + }); + + this.initializeDashboard(); + } + + /** Used for restoring widgets state */ + public reInitializeDashboard(): void { + // destroys the components and their providers so the dashboard can re init data + this.dashboard = undefined; + this.changeDetectorRef.detectChanges(); + + this.initializeDashboard(); + } + + private initializeDashboard(): void { + // We're using a static configuration object for this example, but this is where + // the widget's configuration could potentially be populated from a database + const widgetIndex: IWidgets = { + // Complete the proportional widget with information coming from its type definition + [widgetConfig.id]: + this.widgetTypesService.mergeWithWidgetType(widgetConfig), + }; + + // Setting the widget dimensions and position (this is for gridster) + const positions: Record = { + [widgetConfig.id]: { + cols: 6, + rows: 6, + y: 0, + x: 0, + }, + }; + + // Finally, assigning the variables we created above to the dashboard + this.dashboard = { + positions, + widgets: widgetIndex, + }; + } + + /** + * Sets up the configurator sections for proportional donut + */ + private setupConfigurator() { + const widgetTemplate = this.widgetTypesService.getWidgetType( + "proportional", + 1 + ); + + // remove old "presentation", "chartOptionsEditor" and "donutContentConfiguration" sections from the configurator + delete widgetTemplate.configurator?.structure?.presentation; + delete widgetTemplate.configurator?.structure?.chartOptionsEditor; + delete widgetTemplate.configurator?.structure + ?.donutContentConfiguration; + + // add new "presentation" section + this.widgetTypesService.setNode( + widgetTemplate, + "configurator", + "presentation", + DONUT_CONTENT_CONFIGURATION_SLICE.presentation + ); + // add new "chartOptionsEditor" section + this.widgetTypesService.setNode( + widgetTemplate, + "configurator", + "chartOptionsEditor", + DONUT_CONTENT_CONFIGURATION_SLICE.chartOptionsEditor + ); + // add new "donutContentConfiguration" section + this.widgetTypesService.setNode( + widgetTemplate, + "configurator", + "donutContentConfiguration", + DONUT_CONTENT_CONFIGURATION_SLICE.donutContentConfiguration + ); + } +} + +const widgetConfig: IWidget = { + id: "proportionalWidgetId", + type: "proportional", + pizzagna: { + [PizzagnaLayer.Configuration]: { + [DEFAULT_PIZZAGNA_ROOT]: { + providers: {}, + }, + header: { + properties: { + title: "Beer Review Tally by City", + subtitle: "These People Love Beer", + }, + }, + chart: { + providers: { + [WellKnownProviders.DataSource]: { + // Setting the data source providerId for the chart + providerId: + BeerReviewCountsByCityMockDataSource.providerId, + } as IProviderConfiguration, + }, + properties: { + configuration: { + chartOptions: { + type: ProportionalWidgetChartTypes.DonutChart, + legendPlacement: LegendPlacement.Right, + // old configuration looks like this + // contentFormatter: { + // componentType: DonutContentSumFormatterComponent.lateLoadKey, + // }, + + // NEW configuration looks like this + donutContentConfig: { + formatter: { + componentType: + SiUnitsFormatterComponent.lateLoadKey, + }, + aggregator: { + aggregatorType: + sumAggregator.aggregatorType, + properties: { + // example of a default metric to be used for the percentage calculation + // activeMetricId: "austin", + }, + }, + } as IDonutContentConfig, + } as IProportionalWidgetChartOptions, + } as IProportionalWidgetConfig, + }, + }, + }, + }, +}; + +export function getMockBeerReviewCountsByCity(): IMockBeerReview[] { + return [ + { + id: "Brno", + name: "Brno", + data: [Math.round(Math.random() * 1000000)], + icon: "status_down", + link: "https://en.wikipedia.org/wiki/Brno", + value: "Brno", + customDonutContent: "Custom Brno", + }, + { + id: "kyiv", + name: "Kyiv", + data: [Math.round(Math.random() * 1000000)], + icon: "status_critical", + link: "https://en.wikipedia.org/wiki/Kyiv", + value: "Kyiv", + customDonutContent: "Custom Kyiv", + }, + { + id: "austin", + name: "Austin", + data: [Math.round(Math.random() * 1000000)], + icon: "status_warning", + link: "https://en.wikipedia.org/wiki/Austin", + value: "Austin", + customDonutContent: "Custom Austin", + }, + { + id: "lisbon", + name: "Lisbon", + data: [Math.round(Math.random() * 1000000)], + icon: "status_unknown", + link: "https://en.wikipedia.org/wiki/Lisbon", + value: "Lisbon", + customDonutContent: "Custom Lisbon", + }, + { + id: "sydney", + name: "Sydney", + data: [Math.round(Math.random() * 1000000)], + icon: "status_up", + link: "https://en.wikipedia.org/wiki/Sydney", + value: "Sydney", + customDonutContent: "Custom Sydney", + }, + { + id: "nur-sultan", + name: "Nur-Sultan", + data: [Math.round(Math.random() * 1000000)], + icon: "status_unmanaged", + link: "https://en.wikipedia.org/wiki/Nur-Sultan", + value: "Nur-Sultan", + customDonutContent: "Custom Nur-Sultan", + }, + ].sort((a, b) => a.data[0] - b.data[0]); +} +\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\`, + "widget-types/proportional/proportional-widget/proportional-widget-example.component.ts": \\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\`// © 2022 SolarWinds Worldwide, LLC. All rights reserved. +// +// Permission is hereby granted, free of charge, to any person obtaining a copy +// of this software and associated documentation files (the "Software"), to +// deal in the Software without restriction, including without limitation the +// rights to use, copy, modify, merge, publish, distribute, sublicense, and/or +// sell copies of the Software, and to permit persons to whom the Software is +// furnished to do so, subject to the following conditions: +// +// The above copyright notice and this permission notice shall be included in +// all copies or substantial portions of the Software. +// +// THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR +// IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, +// FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE +// AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER +// LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, +// OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN +// THE SOFTWARE. + +import { + ChangeDetectorRef, + Component, + Injectable, + OnDestroy, + OnInit, +} from "@angular/core"; +import { GridsterConfig, GridsterItem } from "angular-gridster2"; +import { BehaviorSubject } from "rxjs"; + +import { + DataSourceService, + IDataSource, + IFilteringOutputs, +} from "@nova-ui/bits"; +import { + DATA_SOURCE, + DEFAULT_PIZZAGNA_ROOT, + IDashboard, + IProportionalWidgetChartOptions, + IProportionalWidgetConfig, + IProportionalWidgetData, + IProviderConfiguration, + IRefresherProperties, + IWidget, + IWidgets, + LegendPlacement, + PizzagnaLayer, + ProportionalWidgetChartTypes, + ProviderRegistryService, + WellKnownPathKey, + WellKnownProviders, + WidgetTypesService, +} from "@nova-ui/dashboards"; + +import { IMockBeerReview } from "../models"; + +/** + * A simple proportional data source to retrieve beer review counts by city + */ +@Injectable() +export class BeerReviewCountsByCityMockDataSource + extends DataSourceService + implements IDataSource, OnDestroy +{ + // This is the ID we'll use to identify the provider + public static providerId = "BeerReviewCountsByCityMockDataSource"; + public busy = new BehaviorSubject(false); + + public async getFilteredData(): Promise { + this.busy.next(true); + return new Promise((resolve) => { + setTimeout(() => { + this.outputsSubject.next({ + result: getMockBeerReviewCountsByCity(), + }); + this.busy.next(false); + }, 300); + }); + } + + public ngOnDestroy(): void { + this.outputsSubject.complete(); + } +} + +/** + * A component that instantiates the dashboard + */ +@Component({ + selector: "proportional-widget-example", + templateUrl: "./proportional-widget-example.component.html", + styleUrls: ["./proportional-widget-example.component.less"], + standalone: false, +}) +export class ProportionalWidgetExampleComponent implements OnInit { + // This variable will hold all the data needed to define the layout and behavior of the widgets. + // Pass this to the dashboard component's dashboard input in the template. + public dashboard: IDashboard | undefined; + + // Angular gridster requires a configuration object even if it's empty. + // Pass this to the dashboard component's gridsterConfig input in the template. + public gridsterConfig: GridsterConfig = {}; + + // Boolean passed as an input to the dashboard. When true, widgets can be moved, resized, removed, or edited + public editMode: boolean = false; + + constructor( + // WidgetTypesService provides the widget's necessary structure information + private widgetTypesService: WidgetTypesService, + // In general, the ProviderRegistryService is used for making entities available for injection into dynamically loaded components. + private providerRegistry: ProviderRegistryService, + private changeDetectorRef: ChangeDetectorRef + ) {} + + public ngOnInit(): void { + // Grabbing the widget's default template which will be needed as a parameter for setNode + const widgetTemplate = this.widgetTypesService.getWidgetType( + "proportional", + 1 + ); + + // Registering our data sources as dropdown options in the widget editor/configurator + // Note: This could also be done in the parent module's constructor so that + // multiple dashboards could have access to the same widget template modification. + this.widgetTypesService.setNode( + // This is the template we grabbed above with getWidgetType + widgetTemplate, + // We are setting the editor/configurator part of the widget template + "configurator", + // This indicates which node you are changing and we want to change + // the data source providers available for selection in the editor. + WellKnownPathKey.DataSourceProviders, + // We are setting the data sources available for selection in the editor + [BeerReviewCountsByCityMockDataSource.providerId] + ); + + // Registering the data source for injection into the Proportional widget. + this.providerRegistry.setProviders({ + [BeerReviewCountsByCityMockDataSource.providerId]: { + provide: DATA_SOURCE, + useClass: BeerReviewCountsByCityMockDataSource, + deps: [], + }, + }); + + this.initializeDashboard(); + } + + /** Used for restoring widgets state */ + public reInitializeDashboard(): void { + // destroys the components and their providers so the dashboard can re init data + this.dashboard = undefined; + this.changeDetectorRef.detectChanges(); + + this.initializeDashboard(); + } + + public initializeDashboard(): void { + // We're using a static configuration object for this example, but this is where + // the widget's configuration could potentially be populated from a database + const widgetIndex: IWidgets = { + // Complete the proportional widget with information coming from its type definition + [widgetConfig.id]: + this.widgetTypesService.mergeWithWidgetType(widgetConfig), + }; + + // Setting the widget dimensions and position (this is for gridster) + const positions: Record = { + [widgetConfig.id]: { + cols: 5, + rows: 6, + y: 0, + x: 0, + }, + }; + + // Finally, assigning the variables we created above to the dashboard + this.dashboard = { + positions, + widgets: widgetIndex, + }; + } +} + +const widgetConfig: IWidget = { + id: "proportionalWidgetId", + type: "proportional", + pizzagna: { + [PizzagnaLayer.Configuration]: { + [DEFAULT_PIZZAGNA_ROOT]: { + providers: { + [WellKnownProviders.Refresher]: { + properties: { + // Configuring the refresher interval so that our data source is invoked every ten minutes + interval: 60 * 10, + enabled: true, + } as IRefresherProperties, + } as Partial, + }, + }, + header: { + properties: { + title: "Beer Review Tally by City", + subtitle: "These People Love Beer", + }, + }, + chart: { + providers: { + [WellKnownProviders.DataSource]: { + // Setting the data source providerId for the chart + providerId: + BeerReviewCountsByCityMockDataSource.providerId, + } as IProviderConfiguration, + }, + properties: { + configuration: { + chartOptions: { + type: ProportionalWidgetChartTypes.DonutChart, + legendPlacement: LegendPlacement.Right, + } as IProportionalWidgetChartOptions, + // You can optionally define custom colors for the chart by setting the 'chartColors' configuration property + // "chartColors": [ + // "var(--nui-color-chart-five)", + // "var(--nui-color-chart-six)", + // "var(--nui-color-chart-seven)", + // "var(--nui-color-chart-eight)", + // "var(--nui-color-chart-nine)", + // "var(--nui-color-chart-ten)", + // ], + // or use-mapped structure + chartColors: { + Brno: "var(--nui-color-chart-five)", + kyiv: "var(--nui-color-chart-six)", + austin: "var(--nui-color-chart-seven)", + lisbon: "var(--nui-color-chart-eight)", + sydney: "var(--nui-color-chart-nine)", + "nur-sultan": "var(--nui-color-chart-ten)", + }, + prioritizeWidgetColors: false, + } as IProportionalWidgetConfig, + }, + }, + }, + }, +}; + +export function getMockBeerReviewCountsByCity(): IMockBeerReview[] { + return [ + { + id: "Brno", + name: "Brno", + data: [Math.round(Math.random() * 100000)], + icon: "status_down", + link: "https://en.wikipedia.org/wiki/Brno", + value: "Brno", + color: "var(--nui-color-chart-one)", + }, + { + id: "kyiv", + name: "Kyiv", + data: [Math.round(Math.random() * 100000)], + icon: "status_critical", + link: "https://en.wikipedia.org/wiki/Kyiv", + value: "Kyiv", + color: "var(--nui-color-chart-two)", + }, + { + id: "austin", + name: "Austin", + data: [Math.round(Math.random() * 100000)], + icon: "status_warning", + link: "https://en.wikipedia.org/wiki/Austin", + value: "Austin", + color: "var(--nui-color-chart-three)", + }, + { + id: "lisbon", + name: "Lisbon", + data: [Math.round(Math.random() * 100000)], + icon: "status_unknown", + link: "https://en.wikipedia.org/wiki/Lisbon", + value: "Lisbon", + color: "var(--nui-color-chart-four)", + }, + { + id: "sydney", + name: "Sydney", + data: [Math.round(Math.random() * 100000)], + icon: "status_up", + link: "https://en.wikipedia.org/wiki/Sydney", + value: "Sydney", + color: "var(--nui-color-chart-five)", + }, + { + id: "nur-sultan", + name: "Nur-Sultan", + data: [Math.round(Math.random() * 100000)], + icon: "status_unmanaged", + link: "https://en.wikipedia.org/wiki/Nur-Sultan", + value: "Nur-Sultan", + color: "var(--nui-color-chart-six)", + }, + ].sort((a, b) => a.data[0] - b.data[0]); +} +\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\`, + "widget-types/proportional/proportional-widget-interactive/proportional-widget-interactive-example.component.ts": \\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\`// © 2022 SolarWinds Worldwide, LLC. All rights reserved. +// +// Permission is hereby granted, free of charge, to any person obtaining a copy +// of this software and associated documentation files (the "Software"), to +// deal in the Software without restriction, including without limitation the +// rights to use, copy, modify, merge, publish, distribute, sublicense, and/or +// sell copies of the Software, and to permit persons to whom the Software is +// furnished to do so, subject to the following conditions: +// +// The above copyright notice and this permission notice shall be included in +// all copies or substantial portions of the Software. +// +// THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR +// IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, +// FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE +// AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER +// LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, +// OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN +// THE SOFTWARE. + +import { + ChangeDetectorRef, + Component, + Injectable, + OnDestroy, + OnInit, +} from "@angular/core"; +import { GridsterConfig, GridsterItem } from "angular-gridster2"; +import keyBy from "lodash/keyBy"; +import { BehaviorSubject } from "rxjs"; + +import { + DataSourceService, + IDataSource, + IFilteringOutputs, +} from "@nova-ui/bits"; +import { + DATA_SOURCE, + DEFAULT_PIZZAGNA_ROOT, + IDashboard, + IProportionalWidgetChartOptions, + IProportionalWidgetConfig, + IProportionalWidgetData, + IProviderConfiguration, + IWidget, + LegendPlacement, + NOVA_URL_INTERACTION_HANDLER, + PizzagnaLayer, + ProportionalWidgetChartTypes, + ProviderRegistryService, + WellKnownPathKey, + WellKnownProviders, + WidgetTypesService, +} from "@nova-ui/dashboards"; + +import { IMockBeerReview } from "../models"; + +/** + * A simple proportional data source to retrieve beer review counts by city + */ +@Injectable() +export class ReviewCountsByCityMockDataSource + extends DataSourceService + implements IDataSource, OnDestroy +{ + // This is the ID we'll use to identify the provider + public static providerId = "ReviewCountsByCityMockDataSource"; + public busy = new BehaviorSubject(false); + + public async getFilteredData(): Promise { + this.busy.next(true); + return new Promise((resolve) => { + setTimeout(() => { + this.outputsSubject.next({ + result: getMockBeerReviewCountsByCity(), + }); + this.busy.next(false); + }, 300); + }); + } + + public ngOnDestroy(): void { + this.outputsSubject.complete(); + } +} + +/** + * A component that instantiates the dashboard + */ +@Component({ + selector: "proportional-widget-interactive-example", + templateUrl: "./proportional-widget-interactive-example.component.html", + styleUrls: ["./proportional-widget-interactive-example.component.less"], + standalone: false, +}) +export class ProportionalWidgetInteractiveExampleComponent implements OnInit { + // This variable will hold all the data needed to define the layout and behavior of the widgets. + // Pass this to the dashboard component's dashboard input in the template. + public dashboard: IDashboard | undefined; + + // Angular gridster requires a configuration object even if it's empty. + // Pass this to the dashboard component's gridsterConfig input in the template. + public gridsterConfig: GridsterConfig = {}; + + // Boolean passed as an input to the dashboard. When true, widgets can be moved, resized, removed, or edited + public editMode: boolean = false; + + constructor( + // WidgetTypesService provides the widget's necessary structure information + private widgetTypesService: WidgetTypesService, + // In general, the ProviderRegistryService is used for making entities available for injection into dynamically loaded components. + private providerRegistry: ProviderRegistryService, + private changeDetectorRef: ChangeDetectorRef + ) {} + + public ngOnInit(): void { + // Grabbing the widget's default template which will be needed as a parameter for setNode + const widgetTemplate = this.widgetTypesService.getWidgetType( + "proportional", + 1 + ); + + // Registering our data sources as dropdown options in the widget editor/configurator + // Note: This could also be done in the parent module's constructor so that + // multiple dashboards could have access to the same widget template modification. + this.widgetTypesService.setNode( + // This is the template we grabbed above with getWidgetType + widgetTemplate, + // We are setting the editor/configurator part of the widget template + "configurator", + // This indicates which node you are changing and we want to change + // the data source providers available for selection in the editor. + WellKnownPathKey.DataSourceProviders, + // We are setting the data sources available for selection in the editor + [ReviewCountsByCityMockDataSource.providerId] + ); + + // Registering the data source for injection into the Proportional widget. + this.providerRegistry.setProviders({ + [ReviewCountsByCityMockDataSource.providerId]: { + provide: DATA_SOURCE, + useClass: ReviewCountsByCityMockDataSource, + deps: [], + }, + }); + + this.initializeDashboard(); + } + + /** Used for restoring widgets state */ + public reInitializeDashboard(): void { + // destroys the components and their providers so the dashboard can re init data + this.dashboard = undefined; + this.changeDetectorRef.detectChanges(); + + this.initializeDashboard(); + } + + public initializeDashboard(): void { + // We're using a static configuration object for this example, but this is where + // the widget's configuration could potentially be populated from a database + const widgetsWithStructure = widgetConfigs.map((w) => + this.widgetTypesService.mergeWithWidgetType(w) + ); + const widgetsIndex = keyBy(widgetsWithStructure, (w: IWidget) => w.id); + + // Setting the widget dimensions and position (this is for gridster) + const positions: Record = { + [widgetConfigs[0].id]: { + cols: 6, + rows: 6, + y: 0, + x: 0, + }, + [widgetConfigs[1].id]: { + cols: 6, + rows: 6, + y: 0, + x: 6, + }, + }; + + // Finally, assigning the variables we created above to the dashboard + this.dashboard = { + positions, + widgets: widgetsIndex, + }; + } +} + +const widgetConfigs: IWidget[] = [ + { + id: "widget1", + type: "proportional", + pizzagna: { + [PizzagnaLayer.Configuration]: { + [DEFAULT_PIZZAGNA_ROOT]: { + providers: { + // Configuring the UrlInteractionHandler to handle interactions + [WellKnownProviders.InteractionHandler]: { + providerId: NOVA_URL_INTERACTION_HANDLER, + properties: { + // the 'url' property tells the handler what link to use when interaction occurs on the series + // if the series does not have a link we are passing one to the handler + url: "\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\${data.link || 'https://en.wikipedia.org/wiki/'+data.id}", + // by default the link is opened in the current window, set 'newWindow' to true to open in a new tab instead + // newWindow: true, + }, + }, + }, + }, + header: { + properties: { + title: "Proportional Widget", + subtitle: "With interaction handler", + }, + }, + chart: { + providers: { + [WellKnownProviders.DataSource]: { + // Setting the data source providerId for the chart + providerId: + ReviewCountsByCityMockDataSource.providerId, + } as IProviderConfiguration, + }, + properties: { + configuration: { + // Setting the interactive to true + interactive: true, + chartOptions: { + type: ProportionalWidgetChartTypes.VerticalBarChart, + legendPlacement: LegendPlacement.Bottom, + } as IProportionalWidgetChartOptions, + prioritizeWidgetColors: false, + } as IProportionalWidgetConfig, + }, + }, + }, + }, + }, + { + id: "widget2", + type: "proportional", + pizzagna: { + [PizzagnaLayer.Configuration]: { + header: { + properties: { + title: "Proportional Widget", + subtitle: "Without interaction handler", + }, + }, + chart: { + providers: { + [WellKnownProviders.DataSource]: { + // Setting the data source providerId for the chart + providerId: + ReviewCountsByCityMockDataSource.providerId, + } as IProviderConfiguration, + }, + properties: { + configuration: { + // interactive set to false so series without links are not styled like a link + interactive: false, + chartOptions: { + type: ProportionalWidgetChartTypes.HorizontalBarChart, + legendPlacement: LegendPlacement.Bottom, + } as IProportionalWidgetChartOptions, + prioritizeWidgetColors: false, + } as IProportionalWidgetConfig, + }, + }, + }, + }, + }, +]; + +export function getMockBeerReviewCountsByCity(): IMockBeerReview[] { + return [ + { + id: "Brno", + name: "Brno", + data: [Math.round(Math.random() * 100000)], + icon: "status_down", + link: "https://en.wikipedia.org/wiki/Brno", + value: "Brno", + color: "var(--nui-color-chart-one)", + }, + { + id: "kyiv", + name: "Kyiv", + data: [Math.round(Math.random() * 100000)], + icon: "status_critical", + link: "https://en.wikipedia.org/wiki/Kyiv", + value: "Kyiv", + color: "var(--nui-color-chart-two)", + }, + { + id: "austin", + name: "Austin", + data: [Math.round(Math.random() * 100000)], + icon: "status_warning", + value: "Austin", + color: "var(--nui-color-chart-three)", + }, + { + id: "lisbon", + name: "Lisbon", + data: [Math.round(Math.random() * 100000)], + icon: "status_unknown", + link: "https://en.wikipedia.org/wiki/Lisbon", + value: "Lisbon", + color: "var(--nui-color-chart-four)", + }, + { + id: "sydney", + name: "Sydney", + data: [Math.round(Math.random() * 100000)], + icon: "status_up", + value: "Sydney", + color: "var(--nui-color-chart-five)", + }, + { + id: "nur-sultan", + name: "Nur-Sultan", + data: [Math.round(Math.random() * 100000)], + icon: "status_unmanaged", + value: "Nur-Sultan", + color: "var(--nui-color-chart-six)", + }, + ].sort((a, b) => a.data[0] - b.data[0]); +} +\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\`, + "widget-types/risk-score/risk-score-docs.component.ts": \\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\`// © 2023 SolarWinds Worldwide, LLC. All rights reserved. +// +// Permission is hereby granted, free of charge, to any person obtaining a copy +// of this software and associated documentation files (the "Software"), to +// deal in the Software without restriction, including without limitation the +// rights to use, copy, modify, merge, publish, distribute, sublicense, and/or +// sell copies of the Software, and to permit persons to whom the Software is +// furnished to do so, subject to the following conditions: +// +// The above copyright notice and this permission notice shall be included in +// all copies or substantial portions of the Software. +// +// THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR +// IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, +// FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE +// AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER +// LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, +// OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN +// THE SOFTWARE. + +import { Component, OnInit } from "@angular/core"; + +import { mapContentFile } from "../../../../demo-files-factory"; + +@Component({ + selector: "nui-risk-score-docs", + templateUrl: "./risk-score-docs.component.html", + standalone: false, +}) +export class RiskScoreDocsComponent implements OnInit { + public riskScoreWidgetFileText = ""; + public riskScoreConfiguratorFileText = ""; + + public async ngOnInit(): Promise { + this.riskScoreWidgetFileText = await import( + "./../../../../../../src/lib/widget-types/risk-score/risk-score-widget" + ).then(mapContentFile); + this.riskScoreConfiguratorFileText = await import( + "./../../../../../../src/lib/widget-types/risk-score/risk-score-configurator" + ).then(mapContentFile); + } +} +\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\`, + "widget-types/risk-score/risk-score-docs.module.ts": \\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\`// © 2023 SolarWinds Worldwide, LLC. All rights reserved. +// +// Permission is hereby granted, free of charge, to any person obtaining a copy +// of this software and associated documentation files (the "Software"), to +// deal in the Software without restriction, including without limitation the +// rights to use, copy, modify, merge, publish, distribute, sublicense, and/or +// sell copies of the Software, and to permit persons to whom the Software is +// furnished to do so, subject to the following conditions: +// +// The above copyright notice and this permission notice shall be included in +// all copies or substantial portions of the Software. +// +// THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR +// IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, +// FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE +// AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER +// LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, +// OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN +// THE SOFTWARE. + +import { NgModule } from "@angular/core"; +import { RouterModule, Routes } from "@angular/router"; + +import { + DEMO_PATH_TOKEN, + NuiButtonModule, + NuiDocsModule, + NuiMessageModule, + NuiSwitchModule, +} from "@nova-ui/bits"; +import { NuiDashboardsModule } from "@nova-ui/dashboards"; + +import { RiskScoreDocsComponent } from "./risk-score-docs.component"; +import { RiskScoreWidgetExampleComponent } from "./risk-score-widget-example/risk-score-widget-example.component"; +import { getDemoFiles } from "../../../../demo-files-factory"; + +const routes: Routes = [ + { + path: "", + component: RiskScoreDocsComponent, + data: { + srlc: { + hideIndicator: true, + }, + showThemeSwitcher: true, + }, + }, + { + path: "example", + component: RiskScoreWidgetExampleComponent, + data: { + srlc: { + hideIndicator: true, + }, + }, + }, +]; + +@NgModule({ + imports: [ + RouterModule.forChild(routes), + NuiButtonModule, + NuiDocsModule, + NuiMessageModule, + NuiDashboardsModule, + NuiSwitchModule, + ], + declarations: [RiskScoreDocsComponent, RiskScoreWidgetExampleComponent], + providers: [ + { + provide: DEMO_PATH_TOKEN, + useValue: getDemoFiles("risk-score"), + }, + ], +}) +export default class RiskScoreDocsModule {} +\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\`, + "widget-types/risk-score/risk-score-widget-example/risk-score-widget-example.component.ts": \\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\`// © 2023 SolarWinds Worldwide, LLC. All rights reserved. +// +// Permission is hereby granted, free of charge, to any person obtaining a copy +// of this software and associated documentation files (the "Software"), to +// deal in the Software without restriction, including without limitation the +// rights to use, copy, modify, merge, publish, distribute, sublicense, and/or +// sell copies of the Software, and to permit persons to whom the Software is +// furnished to do so, subject to the following conditions: +// +// The above copyright notice and this permission notice shall be included in +// all copies or substantial portions of the Software. +// +// THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR +// IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, +// FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE +// AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER +// LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, +// OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN +// THE SOFTWARE. + +import { HttpClient, HttpErrorResponse } from "@angular/common/http"; +import { Component, Injectable, OnDestroy, OnInit } from "@angular/core"; +import { GridsterConfig, GridsterItem } from "angular-gridster2"; +import { BehaviorSubject } from "rxjs"; +import { finalize } from "rxjs/operators"; + +import { DataSourceService, IFilteringOutputs } from "@nova-ui/bits"; +import { + DATA_SOURCE, + DEFAULT_PIZZAGNA_ROOT, + IDashboard, + IRiskScoreData, + IProviderConfiguration, + IRefresherProperties, + IWidget, + IWidgets, + RiskScoreTileComponent, + NOVA_KPI_DATASOURCE_ADAPTER, + PizzagnaLayer, + ProviderRegistryService, + WellKnownPathKey, + WellKnownProviders, + WidgetTypesService, +} from "@nova-ui/dashboards"; + +/** + * A simple KPI data source to retrieve the average rating of Harry Potter and the Sorcerer's Stone (book) via googleapis + */ +@Injectable() +export class AverageRatingRiskScoreDataSource + extends DataSourceService + implements OnDestroy +{ + // This is the ID we'll use to identify the provider + public static providerId = "AverageRatingRiskScoreDataSource"; + + // Use this subject to communicate the data source's busy state + public busy = new BehaviorSubject(false); + + constructor(private http: HttpClient) { + super(); + } + + // In this example, getFilteredData is invoked every 10 minutes (Take a look at the refresher + // provider definition in the widget configuration below to see how the interval is set) + public async getFilteredData(): Promise { + this.busy.next(true); + return new Promise((resolve) => { + // *** Make a resource request to an external API (if needed) + this.http + .get("https://www.googleapis.com/books/v1/volumes/5MQFrgEACAAJ") + .pipe(finalize(() => this.busy.next(false))) + .subscribe({ + next: (data: any) => { + resolve({ + result: { + value: data.volumeInfo.averageRating, + }, + }); + }, + error: (error: HttpErrorResponse) => { + resolve({ + result: null, + error: { + type: error.status, + }, + }); + }, + }); + }); + } + + public ngOnDestroy(): void { + this.outputsSubject.complete(); + } +} + +/** + * A component that instantiates the dashboard + */ +@Component({ + selector: "risk-score-widget-example", + templateUrl: "./risk-score-widget-example.component.html", + styleUrls: ["./risk-score-widget-example.component.less"], + standalone: false, +}) +export class RiskScoreWidgetExampleComponent implements OnInit { + // This variable will hold all the data needed to define the layout and behavior of the widgets. + // Pass this to the dashboard component's dashboard input in the template. + public dashboard: IDashboard; + + // Angular gridster requires a configuration object even if it's empty. + // Pass this to the dashboard component's gridsterConfig input in the template. + public gridsterConfig: GridsterConfig = {}; + + // Boolean passed as an input to the dashboard. When true, widgets can be moved, resized, removed, or edited + public editMode: boolean = false; + + constructor( + // WidgetTypesService provides the widget's necessary structure information + private widgetTypesService: WidgetTypesService, + + // In general, the ProviderRegistryService is used for making entities available for injection into dynamically loaded components. + private providerRegistry: ProviderRegistryService + ) {} + + public ngOnInit(): void { + // Grabbing the widget's default template which will be needed as a parameter for setNode + const widgetTemplate = this.widgetTypesService.getWidgetType( + "risk-score", + 1 + ); + // Registering our data sources as dropdown options in the widget editor/configurator + // Note: This could also be done in the parent module's constructor so that + // multiple dashboards could have access to the same widget template modification. + this.widgetTypesService.setNode( + // This is the template we grabbed above with getWidgetType + widgetTemplate, + // We are setting the editor/configurator part of the widget template + "configurator", + // This indicates which node you are changing and we want to change + // the data source providers available for selection in the editor. + WellKnownPathKey.DataSourceProviders, + // We are setting the data sources available for selection in the editor + [AverageRatingRiskScoreDataSource.providerId] + ); + + // Registering the data source for injection into the KPI tile. + // Note: Each tile of a KPI widget is assigned its own instance of the data source + this.providerRegistry.setProviders({ + [AverageRatingRiskScoreDataSource.providerId]: { + provide: DATA_SOURCE, + useClass: AverageRatingRiskScoreDataSource, + // Any dependencies that need to be injected into the provider must be listed here + deps: [HttpClient], + }, + }); + + this.initializeDashboard(); + } + + public initializeDashboard(): void { + // We're using a static configuration object for this example, but this is where + // the widget's configuration could potentially be populated from a database + const riskScoreWidget = widgetConfig; + const widgetIndex: IWidgets = { + // Complete the KPI widget with information coming from its type definition + [riskScoreWidget.id]: + this.widgetTypesService.mergeWithWidgetType(riskScoreWidget), + }; + + // Setting the widget dimensions and position (this is for gridster) + const positions: Record = { + [riskScoreWidget.id]: { + cols: 4, + rows: 6, + y: 0, + x: 0, + }, + }; + + // Finally, assigning the variables we created above to the dashboard + this.dashboard = { + positions, + widgets: widgetIndex, + }; + } +} + +const widgetConfig: IWidget = { + id: "riskScoreWidgetId", + type: "risk-score", + pizzagna: { + [PizzagnaLayer.Configuration]: { + [DEFAULT_PIZZAGNA_ROOT]: { + providers: { + [WellKnownProviders.Refresher]: { + properties: { + // Configuring the refresher interval so that our data source is invoked every ten minutes + interval: 60 * 10, + enabled: true, + } as IRefresherProperties, + } as Partial, + }, + }, + header: { + properties: { + title: "Harry Potter and the Sorcerer's Stone", + subtitle: "By J. K. Rowling", + }, + }, + tiles: { + properties: { + nodes: ["riskScore1"], + }, + }, + riskScore1: { + id: "riskScore1", + componentType: RiskScoreTileComponent.lateLoadKey, + properties: { + widgetData: { + minValue: 0, + maxValue: 5, + useStaticLabel: false, + staticLabel: undefined, + label: \\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\`Average Rating\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\`, + description: \\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\`Harry Potter and the Sorcerer's Stone By J. K. Rowling Average Rating Risk Score\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\`, + }, + }, + providers: { + [WellKnownProviders.DataSource]: { + // Setting the data source providerId for the tile with id "kpi1" + providerId: AverageRatingRiskScoreDataSource.providerId, + } as IProviderConfiguration, + [WellKnownProviders.Adapter]: { + providerId: NOVA_KPI_DATASOURCE_ADAPTER, + properties: { + componentId: "riskScore1", + propertyPath: "widgetData", + }, + } as IProviderConfiguration, + }, + }, + }, + }, +}; +\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\`, + "widget-types/table/table-docs.component.ts": \\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\`// © 2022 SolarWinds Worldwide, LLC. All rights reserved. +// +// Permission is hereby granted, free of charge, to any person obtaining a copy +// of this software and associated documentation files (the "Software"), to +// deal in the Software without restriction, including without limitation the +// rights to use, copy, modify, merge, publish, distribute, sublicense, and/or +// sell copies of the Software, and to permit persons to whom the Software is +// furnished to do so, subject to the following conditions: +// +// The above copyright notice and this permission notice shall be included in +// all copies or substantial portions of the Software. +// +// THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR +// IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, +// FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE +// AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER +// LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, +// OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN +// THE SOFTWARE. + +import { Component, OnInit } from "@angular/core"; + +import { mapContentFile } from "../../../../demo-files-factory"; + +@Component({ + selector: "nui-table-docs", + templateUrl: "./table-docs.component.html", + standalone: false, +}) +export class TableDocsComponent implements OnInit { + public widgetFileText = ""; + public configuratorFileText = ""; + + public async ngOnInit(): Promise { + this.widgetFileText = await import( + "./../../../../../../src/lib/widget-types/table/table-widget" + ).then(mapContentFile); + this.configuratorFileText = await import( + "./../../../../../../src/lib/widget-types/table/table-configurator" + ).then(mapContentFile); + } +} +\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\`, + "widget-types/table/table-docs.module.ts": \\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\`// © 2022 SolarWinds Worldwide, LLC. All rights reserved. +// +// Permission is hereby granted, free of charge, to any person obtaining a copy +// of this software and associated documentation files (the "Software"), to +// deal in the Software without restriction, including without limitation the +// rights to use, copy, modify, merge, publish, distribute, sublicense, and/or +// sell copies of the Software, and to permit persons to whom the Software is +// furnished to do so, subject to the following conditions: +// +// The above copyright notice and this permission notice shall be included in +// all copies or substantial portions of the Software. +// +// THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR +// IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, +// FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE +// AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER +// LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, +// OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN +// THE SOFTWARE. + +import { NgModule } from "@angular/core"; +import { RouterModule, Routes } from "@angular/router"; + +import { DEMO_PATH_TOKEN } from "@nova-ui/bits"; +import { + NuiButtonModule, + NuiDocsModule, + NuiMessageModule, + NuiSwitchModule, +} from "@nova-ui/bits"; +import { + NuiDashboardsModule, + TableFormatterRegistryService, +} from "@nova-ui/dashboards"; + +import { TableDocsComponent } from "./table-docs.component"; +import { TablePaginatorDocsComponent } from "./table-paginator-docs.component"; +import { TableSelectableDocsComponent } from "./table-selectable-docs.component"; +import { TableWidgetExampleComponent } from "./table-widget/table-widget-example.component"; +import { TableWidgetInteractiveExampleComponent } from "./table-widget-interactive/table-widget-interactive-example.component"; +import { TableWidgetPaginatorExampleComponent } from "./table-widget-paginator/table-widget-paginator-example.component"; +import { TableWidgetSearchExampleComponent } from "./table-widget-search/table-widget-search-example.component"; +import { TableSearchDocsComponent } from "./table-widget-search-docs.component"; +import { TableWidgetSelectableMultiExampleComponent } from "./table-widget-selectable/table-widget-selectable-multi/table-widget-selectable-multi.example.component"; +import { TableWidgetSelectableRadioExampleComponent } from "./table-widget-selectable/table-widget-selectable-radio/table-widget-selectable-radio.example.component"; +import { TableWidgetSelectableSingleExampleComponent } from "./table-widget-selectable/table-widget-selectable-single/table-widget-selectable-single.example.component"; +import { TableWidgetSelectableExampleComponent } from "./table-widget-selectable/table-widget-selectable.example.component"; +import { DEFAULT_TABLE_FORMATTERS } from "../../../../../../src/lib/widget-types/table/default-table-formatters"; +import { getDemoFiles } from "../../../../demo-files-factory"; + +const routes: Routes = [ + { + path: "", + component: TableDocsComponent, + data: { + srlc: { + hideIndicator: true, + }, + showThemeSwitcher: true, + }, + }, + { + path: "example", + component: TableWidgetExampleComponent, + data: { + srlc: { + hideIndicator: true, + }, + }, + }, + { + path: "table-search", + component: TableSearchDocsComponent, + data: { + srlc: { + hideIndicator: true, + }, + showThemeSwitcher: true, + }, + }, + { + path: "table-paginator", + component: TablePaginatorDocsComponent, + data: { + srlc: { + hideIndicator: true, + }, + showThemeSwitcher: true, + }, + }, + { + path: "table-select", + component: TableSelectableDocsComponent, + data: { + srlc: { + hideIndicator: true, + }, + showThemeSwitcher: true, + }, + }, +]; + +@NgModule({ + imports: [ + RouterModule.forChild(routes), + NuiButtonModule, + NuiDocsModule, + NuiMessageModule, + NuiSwitchModule, + NuiDashboardsModule, + ], + declarations: [ + TableDocsComponent, + TableSearchDocsComponent, + TablePaginatorDocsComponent, + TableWidgetPaginatorExampleComponent, + TableSelectableDocsComponent, + TableWidgetInteractiveExampleComponent, + TableWidgetExampleComponent, + TableWidgetSearchExampleComponent, + TableWidgetSelectableExampleComponent, + TableWidgetSelectableMultiExampleComponent, + TableWidgetSelectableSingleExampleComponent, + TableWidgetSelectableRadioExampleComponent, + ], + providers: [ + { + provide: DEMO_PATH_TOKEN, + useValue: getDemoFiles("table"), + }, + ], +}) +export default class TableDocsModule { + constructor(tableFormattersRegistryService: TableFormatterRegistryService) { + tableFormattersRegistryService.addItems(DEFAULT_TABLE_FORMATTERS); + } +} +\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\`, + "widget-types/table/table-paginator-docs.component.ts": \\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\`// © 2022 SolarWinds Worldwide, LLC. All rights reserved. +// +// Permission is hereby granted, free of charge, to any person obtaining a copy +// of this software and associated documentation files (the "Software"), to +// deal in the Software without restriction, including without limitation the +// rights to use, copy, modify, merge, publish, distribute, sublicense, and/or +// sell copies of the Software, and to permit persons to whom the Software is +// furnished to do so, subject to the following conditions: +// +// The above copyright notice and this permission notice shall be included in +// all copies or substantial portions of the Software. +// +// THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR +// IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, +// FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE +// AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER +// LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, +// OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN +// THE SOFTWARE. + +import { Component } from "@angular/core"; + +@Component({ + selector: "nui-table-paginator-docs", + templateUrl: "./table-paginator-docs.component.html", + standalone: false, +}) +export class TablePaginatorDocsComponent { + public tableConfigurationText = \\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\` + "table": { + ... + properties: { + configuration: { + // define paginator configuration here + scrollType: ScrollType.paginator, + paginatorConfiguration: { + pageSize: 10, // Value have to be one of pageSizeSet values + pageSizeSet: [10, 20, 30], + }, + // If not specified, default is set to + // pageSize: 10, + // pageSizeSet: [10, 20, 50], + hasVirtualScroll: false, // Has to be speciefied because of backward compatibility + } as ITableWidgetConfig, + }, + }, + \\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\`; +} +\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\`, + "widget-types/table/table-selectable-docs.component.ts": \\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\`// © 2022 SolarWinds Worldwide, LLC. All rights reserved. +// +// Permission is hereby granted, free of charge, to any person obtaining a copy +// of this software and associated documentation files (the "Software"), to +// deal in the Software without restriction, including without limitation the +// rights to use, copy, modify, merge, publish, distribute, sublicense, and/or +// sell copies of the Software, and to permit persons to whom the Software is +// furnished to do so, subject to the following conditions: +// +// The above copyright notice and this permission notice shall be included in +// all copies or substantial portions of the Software. +// +// THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR +// IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, +// FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE +// AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER +// LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, +// OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN +// THE SOFTWARE. + +import { Component } from "@angular/core"; + +@Component({ + selector: "nui-table-selectable-docs", + templateUrl: "./table-selectable-docs.component.html", + standalone: false, +}) +export class TableSelectableDocsComponent { + public tableConfigurationText = \\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\` + "table": { + ... + properties: { + // enabling selection here + selectionConfiguration: { + // whether the selection is enabled or disabled + enabled: true, + // can be Multi | Radio | Single + selectionMode: TableSelectionMode.Multi, + // property that uniquely identifies row in a table + trackByProperty: "id", + // whether clicking on row should select it + clickableRow: true, + }, + }, + }, + \\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\`; + + public eventSubscriptionText = \\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\` +... +constructor(Inject(PIZZAGNA_EVENT_BUS) eventBus: EventBus) { + eventBus + .getStream(SELECTION) + // don't forget to unsubscribe! + .subscribe((selection: ISelection) => ...) +} +... + \\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\`; +} +\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\`, + "widget-types/table/table-widget/table-widget-example.component.ts": \\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\`// © 2022 SolarWinds Worldwide, LLC. All rights reserved. +// +// Permission is hereby granted, free of charge, to any person obtaining a copy +// of this software and associated documentation files (the "Software"), to +// deal in the Software without restriction, including without limitation the +// rights to use, copy, modify, merge, publish, distribute, sublicense, and/or +// sell copies of the Software, and to permit persons to whom the Software is +// furnished to do so, subject to the following conditions: +// +// The above copyright notice and this permission notice shall be included in +// all copies or substantial portions of the Software. +// +// THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR +// IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, +// FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE +// AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER +// LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, +// OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN +// THE SOFTWARE. + +import { ChangeDetectorRef, Component, OnInit } from "@angular/core"; +import { GridsterConfig, GridsterItem } from "angular-gridster2"; +import orderBy from "lodash/orderBy"; +import { BehaviorSubject, firstValueFrom, from } from "rxjs"; +import { map, tap } from "rxjs/operators"; + +import { + DataSourceService, + IDataField, + INovaFilteringOutputs, + INovaFilters, + nameof, +} from "@nova-ui/bits"; +import { + DATA_SOURCE, + IDashboard, + ITableWidgetColumnConfig, + IWidget, + IWidgets, + ProviderRegistryService, + WellKnownPathKey, + WellKnownProviders, + WidgetTypesService, +} from "@nova-ui/dashboards"; + +export const BREW_API_URL = "https://api.punkapi.com/v2/beers"; + +export interface IBrewInfo { + id: string; + name: string; + tagline: string; + first_brewed: string; + description: string; + brewers_tips: string; +} + +export interface IBrewDatasourceResponse { + brewInfo: IBrewInfo[]; + total: number; +} + +export class BeerDataSource extends DataSourceService { + public static providerId = "BeerDataSource"; + + private cache: IBrewInfo[] = []; + + public busy = new BehaviorSubject(false); + + public dataFields: Array = [ + { + id: nameof("id"), + label: "No", + dataType: "number", + sortable: true, + }, + // To indicate that a column should not be sortable, set the optional IDataField 'sortable' property to false + { + id: nameof("name"), + label: "Name", + dataType: "string", + sortable: true, + }, + { + id: nameof("tagline"), + label: "Tagline", + dataType: "string", + sortable: true, + }, + { + id: nameof("first_brewed"), + label: "First Brewed", + dataType: "string", + sortable: true, + }, + { + id: nameof("description"), + label: "Description", + dataType: "string", + sortable: false, + }, + { + id: nameof("brewers_tips"), + label: "Brewer's Tips", + dataType: "string", + sortable: false, + }, + ]; + + public async getFilteredData( + filters: INovaFilters + ): Promise { + const start = filters.virtualScroll?.value?.start ?? 0; + const end = filters.virtualScroll?.value?.end ?? 0; + + // Resetting cache on first page request + if (start === 0) { + this.cache = []; + } + + // extract sorter settings to send to the backend + // filters.sorterValue.sortBy; filters.sorterValue.direction + return firstValueFrom( + from(this.fetch(start, end)).pipe( + tap((response: IBrewDatasourceResponse | undefined) => { + if (!response) { + return; + } + this.cache = this.sortData( + this.cache.concat(response.brewInfo), + filters + ); + this.dataSubject.next(this.cache); + }), + map(() => ({ + repeat: { itemsSource: this.cache }, + dataFields: this.dataFields, + })) + ) + ); + } + + public async fetch( + start: number, + end: number + ): Promise { + const delta: number = end - start; + const currentPage: number = end / delta || 0; + const response: object | Array = await ( + await fetch( + \\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\`\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\${BREW_API_URL}/?page=\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\${currentPage}&per_page=\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\${delta}\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\` + ) + ).json(); + + // Note: In case request fails we should not proceed with mapping + if (!Array.isArray(response)) { + return undefined; + } + + return { + brewInfo: response.map((result: IBrewInfo) => ({ + id: result.id, + name: result.name, + tagline: result.tagline, + first_brewed: result.first_brewed, + description: result.description, + brewers_tips: result.brewers_tips, + })), + total: response.length, + }; + } + + private sortData(data: IBrewInfo[], filters: INovaFilters): IBrewInfo[] { + return orderBy( + data, + filters.sorter?.value?.sortBy, + filters.sorter?.value?.direction as "desc" | "asc" + ); + } +} + +/** + * A component that instantiates the dashboard + */ +@Component({ + selector: "table-widget-example", + templateUrl: "./table-widget-example.component.html", + styleUrls: ["./table-widget-example.component.less"], + standalone: false, +}) +export class TableWidgetExampleComponent implements OnInit { + // This variable will hold all the data needed to define the layout and behavior of the widgets. + // Pass this to the dashboard component's dashboard input in the template. + public dashboard: IDashboard | undefined; + + // Angular gridster requires a configuration object even if it's empty. + // Pass this to the dashboard component's gridsterConfig input in the template. + public gridsterConfig: GridsterConfig = {}; + + // Boolean passed as an input to the dashboard. When true, widgets can be moved, resized, removed, or edited + public editMode: boolean = false; + + constructor( + // WidgetTypesService provides the widget's necessary structure information + private widgetTypesService: WidgetTypesService, + // In general, the ProviderRegistryService is used for making entities available for injection into dynamically loaded components. + private providerRegistry: ProviderRegistryService, + private changeDetectorRef: ChangeDetectorRef + ) {} + + public ngOnInit(): void { + // Grabbing the widget's default template which will be needed as a parameter for setNode + const widgetTemplate = this.widgetTypesService.getWidgetType( + "table", + 1 + ); + + // Registering our data sources as dropdown options in the widget editor/configurator + // Note: This could also be done in the parent module's constructor so that + // multiple dashboards could have access to the same widget template modification. + this.widgetTypesService.setNode( + // This is the template we grabbed above with getWidgetType + widgetTemplate, + // We are setting the editor/configurator part of the widget template + "configurator", + // This indicates which node you are changing and we want to change + // the data source providers available for selection in the editor. + WellKnownPathKey.DataSourceProviders, + // We are setting the data sources available for selection in the editor + [BeerDataSource.providerId] + ); + + // Registering the data source for injection into the widget. + this.providerRegistry.setProviders({ + [BeerDataSource.providerId]: { + provide: DATA_SOURCE, + useClass: BeerDataSource, + // Any dependencies that need to be injected into the provider must be listed here + deps: [], + }, + }); + + this.initializeDashboard(); + } + + /** Used for restoring widgets state */ + public reInitializeDashboard(): void { + // destroys the components and their providers so the dashboard can re init data + this.dashboard = undefined; + this.changeDetectorRef.detectChanges(); + + this.initializeDashboard(); + } + + public initializeDashboard(): void { + // We're using a static configuration object for this example, but this is where + // the widget's configuration could potentially be populated from a database + const tableWidget = widgetConfig; + const widgetIndex: IWidgets = { + // Enhance the widget with information coming from it's type definition + [tableWidget.id]: + this.widgetTypesService.mergeWithWidgetType(tableWidget), + }; + + // Setting the widget dimensions and position (this is for gridster) + const positions: Record = { + [tableWidget.id]: { + cols: 12, + rows: 6, + y: 0, + x: 0, + }, + }; + + // Finally, assigning the variables we created above to the dashboard + this.dashboard = { + positions, + widgets: widgetIndex, + }; + } +} + +const TABLE_COLUMNS: ITableWidgetColumnConfig[] = [ + { + id: "column1", + label: $localize\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\`Beer Name\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\`, + isActive: true, + width: 185, + formatter: { + componentType: "RawFormatterComponent", + properties: { + dataFieldIds: { + value: "name", + }, + }, + }, + }, + { + id: "column2", + label: $localize\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\`Tagline\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\`, + isActive: true, + width: 250, + formatter: { + componentType: "RawFormatterComponent", + properties: { + dataFieldIds: { + value: "tagline", + }, + }, + }, + }, + { + id: "column3", + label: $localize\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\`First Brewed\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\`, + isActive: true, + width: 100, + formatter: { + componentType: "RawFormatterComponent", + properties: { + dataFieldIds: { + value: "first_brewed", + }, + }, + }, + }, + { + id: "column4", + label: $localize\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\`Description\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\`, + isActive: true, + formatter: { + componentType: "RawFormatterComponent", + properties: { + dataFieldIds: { + value: "description", + }, + }, + }, + }, +]; + +export const widgetConfig: IWidget = { + id: "tableWidgetId", + type: "table", + pizzagna: { + configuration: { + header: { + properties: { + title: "Stupendous Suds", + subtitle: "Try These Brilliant Brews", + }, + }, + table: { + providers: { + [WellKnownProviders.DataSource]: { + providerId: BeerDataSource.providerId, + }, + }, + properties: { + configuration: { + columns: TABLE_COLUMNS, + sortable: true, + sorterConfiguration: { + descendantSorting: false, + sortBy: "", + }, + hasVirtualScroll: true, + }, + }, + }, + }, + }, +}; +\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\`, + "widget-types/table/table-widget-interactive/table-widget-interactive-example.component.ts": \\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\`// © 2022 SolarWinds Worldwide, LLC. All rights reserved. +// +// Permission is hereby granted, free of charge, to any person obtaining a copy +// of this software and associated documentation files (the "Software"), to +// deal in the Software without restriction, including without limitation the +// rights to use, copy, modify, merge, publish, distribute, sublicense, and/or +// sell copies of the Software, and to permit persons to whom the Software is +// furnished to do so, subject to the following conditions: +// +// The above copyright notice and this permission notice shall be included in +// all copies or substantial portions of the Software. +// +// THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR +// IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, +// FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE +// AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER +// LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, +// OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN +// THE SOFTWARE. + +import { ChangeDetectorRef, Component, OnInit } from "@angular/core"; +import { GridsterConfig, GridsterItem } from "angular-gridster2"; +import orderBy from "lodash/orderBy"; +import { BehaviorSubject, firstValueFrom, from } from "rxjs"; +import { map, tap } from "rxjs/operators"; + +import { + DataSourceService, + IDataField, + INovaFilteringOutputs, + INovaFilters, + nameof, +} from "@nova-ui/bits"; +import { + DATA_SOURCE, + DEFAULT_PIZZAGNA_ROOT, + IDashboard, + ITableWidgetColumnConfig, + IWidget, + IWidgets, + NOVA_URL_INTERACTION_HANDLER, + ProviderRegistryService, + WellKnownPathKey, + WellKnownProviders, + WidgetTypesService, +} from "@nova-ui/dashboards"; + +export const BREW_API_URL = "https://api.punkapi.com/v2/beers"; + +export interface IBrewInfo { + id: string; + name: string; + tagline: string; + first_brewed: string; + description: string; + brewers_tips: string; +} + +export interface IBrewDatasourceResponse { + brewInfo: IBrewInfo[]; + total: number; +} + +export class MockBeerDataSource extends DataSourceService { + public static providerId = "MockBeerDataSource"; + + private cache: IBrewInfo[] = []; + + public busy = new BehaviorSubject(false); + + public dataFields: Array = [ + { + id: nameof("id"), + label: "No", + dataType: "number", + sortable: true, + }, + // To indicate that a column should not be sortable, set the optional IDataField 'sortable' property to false + { + id: nameof("name"), + label: "Name", + dataType: "string", + sortable: true, + }, + { + id: nameof("tagline"), + label: "Tagline", + dataType: "string", + sortable: true, + }, + { + id: nameof("first_brewed"), + label: "First Brewed", + dataType: "string", + sortable: true, + }, + { + id: nameof("description"), + label: "Description", + dataType: "string", + sortable: false, + }, + { + id: nameof("brewers_tips"), + label: "Brewer's Tips", + dataType: "string", + sortable: false, + }, + ]; + + public async getFilteredData( + filters: INovaFilters + ): Promise { + const start = filters.virtualScroll?.value?.start ?? 0; + const end = filters.virtualScroll?.value?.end ?? 0; + + // Resetting cache on first page request + if (start === 0) { + this.cache = []; + } + + // extract sorter settings to send to the backend + // filters.sorterValue.sortBy; filters.sorterValue.direction + return firstValueFrom( + from(this.fetch(start, end)).pipe( + tap((response) => { + if (!response) { + return; + } + this.cache = this.sortData( + this.cache.concat(response.brewInfo), + filters + ); + this.dataSubject.next(this.cache); + }), + map(() => ({ + repeat: { itemsSource: this.cache }, + dataFields: this.dataFields, + })) + ) + ); + } + + public async fetch( + start: number, + end: number + ): Promise { + const delta: number = end - start; + const currentPage: number = end / delta || 0; + const response: object | Array = await ( + await fetch( + \\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\`\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\${BREW_API_URL}/?page=\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\${currentPage}&per_page=\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\${delta}\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\` + ) + ).json(); + console.log( + "📘 table-widget-interactive-example.component: 85# -> response:", + response + ); + + // Note: In case request fails we should not proceed with mapping + if (!Array.isArray(response)) { + return undefined; + } + + return { + brewInfo: response.map((result: IBrewInfo) => ({ + id: result.id, + name: result.name, + tagline: result.tagline, + first_brewed: result.first_brewed, + description: result.description, + brewers_tips: result.brewers_tips, + })), + total: response.length, + }; + } + + private sortData(data: IBrewInfo[], filters: INovaFilters): IBrewInfo[] { + return orderBy( + data, + filters.sorter?.value?.sortBy, + filters.sorter?.value?.direction as "desc" | "asc" + ); + } +} + +/** + * A component that instantiates the dashboard + */ +@Component({ + selector: "table-widget-interactive-example", + templateUrl: "./table-widget-interactive-example.component.html", + styleUrls: ["./table-widget-interactive-example.component.less"], + standalone: false, +}) +export class TableWidgetInteractiveExampleComponent implements OnInit { + // This variable will hold all the data needed to define the layout and behavior of the widgets. + // Pass this to the dashboard component's dashboard input in the template. + public dashboard: IDashboard | undefined; + + // Angular gridster requires a configuration object even if it's empty. + // Pass this to the dashboard component's gridsterConfig input in the template. + public gridsterConfig: GridsterConfig = {}; + + // Boolean passed as an input to the dashboard. When true, widgets can be moved, resized, removed, or edited + public editMode: boolean = false; + + constructor( + // WidgetTypesService provides the widget's necessary structure information + private widgetTypesService: WidgetTypesService, + // In general, the ProviderRegistryService is used for making entities available for injection into dynamically loaded components. + private providerRegistry: ProviderRegistryService, + private changeDetectorRef: ChangeDetectorRef + ) {} + + public ngOnInit(): void { + // Grabbing the widget's default template which will be needed as a parameter for setNode + const widgetTemplate = this.widgetTypesService.getWidgetType( + "table", + 1 + ); + + // Registering our data sources as dropdown options in the widget editor/configurator + // Note: This could also be done in the parent module's constructor so that + // multiple dashboards could have access to the same widget template modification. + this.widgetTypesService.setNode( + // This is the template we grabbed above with getWidgetType + widgetTemplate, + // We are setting the editor/configurator part of the widget template + "configurator", + // This indicates which node you are changing and we want to change + // the data source providers available for selection in the editor. + WellKnownPathKey.DataSourceProviders, + // We are setting the data sources available for selection in the editor + [MockBeerDataSource.providerId] + ); + + // Registering the data source for injection into the widget. + this.providerRegistry.setProviders({ + [MockBeerDataSource.providerId]: { + provide: DATA_SOURCE, + useClass: MockBeerDataSource, + // Any dependencies that need to be injected into the provider must be listed here + deps: [], + }, + }); + + this.initializeDashboard(); + } + + /** Used for restoring widgets state */ + public reInitializeDashboard(): void { + // destroys the components and their providers so the dashboard can re init data + this.dashboard = undefined; + this.changeDetectorRef.detectChanges(); + + this.initializeDashboard(); + } + + public initializeDashboard(): void { + // We're using a static configuration object for this example, but this is where + // the widget's configuration could potentially be populated from a database + const tableWidget = widgetConfig; + const widgetIndex: IWidgets = { + // Enhance the widget with information coming from it's type definition + [tableWidget.id]: + this.widgetTypesService.mergeWithWidgetType(tableWidget), + }; + + // Setting the widget dimensions and position (this is for gridster) + const positions: Record = { + [tableWidget.id]: { + cols: 12, + rows: 6, + y: 0, + x: 0, + }, + }; + + // Finally, assigning the variables we created above to the dashboard + this.dashboard = { + positions, + widgets: widgetIndex, + }; + } +} + +const TABLE_COLUMNS: ITableWidgetColumnConfig[] = [ + { + id: "column1", + label: $localize\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\`Beer Name\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\`, + isActive: true, + width: 185, + formatter: { + componentType: "RawFormatterComponent", + properties: { + dataFieldIds: { + value: "name", + }, + }, + }, + }, + { + id: "column2", + label: $localize\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\`Tagline\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\`, + isActive: true, + width: 250, + formatter: { + componentType: "RawFormatterComponent", + properties: { + dataFieldIds: { + value: "tagline", + }, + }, + }, + }, + { + id: "column3", + label: $localize\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\`First Brewed\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\`, + isActive: true, + width: 100, + formatter: { + componentType: "RawFormatterComponent", + properties: { + dataFieldIds: { + value: "first_brewed", + }, + }, + }, + }, + { + id: "column4", + label: $localize\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\`Description\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\`, + isActive: true, + formatter: { + componentType: "RawFormatterComponent", + properties: { + dataFieldIds: { + value: "description", + }, + }, + }, + }, +]; + +export const widgetConfig: IWidget = { + id: "tableWidgetId", + type: "table", + pizzagna: { + configuration: { + [DEFAULT_PIZZAGNA_ROOT]: { + providers: { + [WellKnownProviders.InteractionHandler]: { + // Configuring the UrlInteractionHandler to handle interactions + providerId: NOVA_URL_INTERACTION_HANDLER, + properties: { + // the 'url' property tells the handler what link to use when interaction occurs on the series + url: "\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\${'https://untappd.com/search?q='+data.name}", + // by default the link is opened in the current window, set 'newWindow' to true to open in a new tab instead + newWindow: true, + }, + }, + }, + }, + header: { + properties: { + title: "Stupendous Suds", + subtitle: "Try These Brilliant Brews", + }, + }, + table: { + providers: { + [WellKnownProviders.DataSource]: { + providerId: MockBeerDataSource.providerId, + }, + }, + properties: { + configuration: { + // set interactions to true on the table + interactive: true, + columns: TABLE_COLUMNS, + sortable: true, + sorterConfiguration: { + descendantSorting: false, + sortBy: "", + }, + hasVirtualScroll: true, + }, + }, + }, + }, + }, +}; +\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\`, + "widget-types/table/table-widget-paginator/table-widget-paginator-example.component.ts": \\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\`// © 2022 SolarWinds Worldwide, LLC. All rights reserved. +// +// Permission is hereby granted, free of charge, to any person obtaining a copy +// of this software and associated documentation files (the "Software"), to +// deal in the Software without restriction, including without limitation the +// rights to use, copy, modify, merge, publish, distribute, sublicense, and/or +// sell copies of the Software, and to permit persons to whom the Software is +// furnished to do so, subject to the following conditions: +// +// The above copyright notice and this permission notice shall be included in +// all copies or substantial portions of the Software. +// +// THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR +// IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, +// FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE +// AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER +// LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, +// OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN +// THE SOFTWARE. + +import { HttpClient } from "@angular/common/http"; +import { ChangeDetectorRef, Component, OnInit } from "@angular/core"; +import { GridsterConfig, GridsterItem } from "angular-gridster2"; + +import { LoggerService } from "@nova-ui/bits"; +import { + DATA_SOURCE, + DEFAULT_PIZZAGNA_ROOT, + IDashboard, + IProviderConfiguration, + ITableWidgetConfig, + IWidget, + IWidgets, + NOVA_URL_INTERACTION_HANDLER, + PizzagnaLayer, + ProviderRegistryService, + RawFormatterComponent, + ScrollType, + WellKnownPathKey, + WellKnownProviders, + WidgetTypesService, +} from "@nova-ui/dashboards"; + +import { AcmeTableMockDataSource } from "../../../../prototypes/data/table/acme-table-mock-data-source.service"; + +/** + * A component that instantiates the dashboard + */ +@Component({ + selector: "table-widget-paginator-example", + templateUrl: "./table-widget-paginator-example.component.html", + styleUrls: ["./table-widget-paginator-example.component.less"], + standalone: false, +}) +export class TableWidgetPaginatorExampleComponent implements OnInit { + public dashboard: IDashboard | undefined; + public gridsterConfig: GridsterConfig = {}; + public editMode: boolean = false; + + constructor( + private widgetTypesService: WidgetTypesService, + private providerRegistry: ProviderRegistryService, + private changeDetectorRef: ChangeDetectorRef + ) {} + + public ngOnInit(): void { + const widgetTemplate = this.widgetTypesService.getWidgetType( + "table", + 1 + ); + this.widgetTypesService.setNode( + widgetTemplate, + "configurator", + WellKnownPathKey.DataSourceProviders, + [AcmeTableMockDataSource.providerId] + ); + + this.providerRegistry.setProviders({ + [AcmeTableMockDataSource.providerId]: { + provide: DATA_SOURCE, + useClass: AcmeTableMockDataSource, + deps: [LoggerService, HttpClient], + }, + }); + + this.initializeDashboard(); + } + + /** Used for restoring widgets state */ + public reInitializeDashboard(): void { + // destroys the components and their providers so the dashboard can re init data + this.dashboard = undefined; + this.changeDetectorRef.detectChanges(); + + this.initializeDashboard(); + } + + public initializeDashboard(): void { + const tableWithPaginator = tableWidgetWithPaginator; + const tableWithVirtualScroll = tableWidgetWithVirtualScroll; + + const widgetIndex: IWidgets = { + [tableWithPaginator.id]: + this.widgetTypesService.mergeWithWidgetType(tableWithPaginator), + [tableWithVirtualScroll.id]: + this.widgetTypesService.mergeWithWidgetType( + tableWithVirtualScroll + ), + }; + + const positions: Record = { + [tableWithPaginator.id]: { + cols: 6, + rows: 6, + y: 0, + x: 0, + }, + [tableWithVirtualScroll.id]: { + cols: 6, + rows: 6, + y: 0, + x: 0, + }, + }; + + this.dashboard = { + positions, + widgets: widgetIndex, + }; + } +} + +export const tableWidgetWithPaginator: IWidget = { + id: "widget1", + type: "table", + pizzagna: { + [PizzagnaLayer.Configuration]: { + [DEFAULT_PIZZAGNA_ROOT]: { + providers: { + [WellKnownProviders.InteractionHandler]: { + providerId: NOVA_URL_INTERACTION_HANDLER, + }, + }, + }, + header: { + properties: { + title: "Table Widget with paginator!", + subtitle: "Basic table widget", + collapsible: true, + }, + }, + table: { + providers: { + [WellKnownProviders.DataSource]: { + providerId: AcmeTableMockDataSource.providerId, + } as IProviderConfiguration, + }, + properties: { + configuration: { + interactive: true, + columns: [ + { + id: "column1", + label: "No.", + isActive: true, + formatter: { + componentType: + RawFormatterComponent.lateLoadKey, + properties: { + dataFieldIds: { + value: "position", + }, + }, + }, + }, + { + id: "column2", + label: "Name", + isActive: true, + formatter: { + componentType: + RawFormatterComponent.lateLoadKey, + properties: { + dataFieldIds: { + value: "name", + }, + }, + }, + }, + { + id: "column3", + label: "Status", + isActive: true, + formatter: { + componentType: + RawFormatterComponent.lateLoadKey, + properties: { + dataFieldIds: { + value: "status", + }, + }, + }, + }, + ], + sorterConfiguration: { + descendantSorting: false, + sortBy: "column1", + }, + scrollType: ScrollType.paginator, + paginatorConfiguration: { + pageSize: 5, + pageSizeSet: [5, 10, 20, 30], + }, + hasVirtualScroll: false, + searchConfiguration: { + enabled: true, + }, + } as ITableWidgetConfig, + }, + }, + }, + }, +}; + +export const tableWidgetWithVirtualScroll: IWidget = { + id: "widget2", + type: "table", + pizzagna: { + [PizzagnaLayer.Configuration]: { + [DEFAULT_PIZZAGNA_ROOT]: { + providers: { + [WellKnownProviders.InteractionHandler]: { + providerId: NOVA_URL_INTERACTION_HANDLER, + }, + }, + }, + header: { + properties: { + title: "Table Widget with virtual scroll!", + subtitle: "Basic table widget", + collapsible: true, + }, + }, + table: { + providers: { + [WellKnownProviders.DataSource]: { + providerId: AcmeTableMockDataSource.providerId, + } as IProviderConfiguration, + }, + properties: { + configuration: { + interactive: true, + columns: [ + { + id: "column1", + label: "No.", + isActive: true, + formatter: { + componentType: + RawFormatterComponent.lateLoadKey, + properties: { + dataFieldIds: { + value: "position", + }, + }, + }, + }, + { + id: "column2", + label: "Name", + isActive: true, + formatter: { + componentType: + RawFormatterComponent.lateLoadKey, + properties: { + dataFieldIds: { + value: "name", + }, + }, + }, + }, + { + id: "column3", + label: "Status", + isActive: true, + formatter: { + componentType: + RawFormatterComponent.lateLoadKey, + properties: { + dataFieldIds: { + value: "status", + }, + }, + }, + }, + ], + sorterConfiguration: { + descendantSorting: false, + sortBy: "column1", + }, + hasVirtualScroll: true, + searchConfiguration: { + enabled: true, + }, + } as ITableWidgetConfig, + }, + }, + }, + }, +}; +\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\`, + "widget-types/table/table-widget-search/table-widget-search-example.component.ts": \\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\`// © 2022 SolarWinds Worldwide, LLC. All rights reserved. +// +// Permission is hereby granted, free of charge, to any person obtaining a copy +// of this software and associated documentation files (the "Software"), to +// deal in the Software without restriction, including without limitation the +// rights to use, copy, modify, merge, publish, distribute, sublicense, and/or +// sell copies of the Software, and to permit persons to whom the Software is +// furnished to do so, subject to the following conditions: +// +// The above copyright notice and this permission notice shall be included in +// all copies or substantial portions of the Software. +// +// THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR +// IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, +// FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE +// AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER +// LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, +// OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN +// THE SOFTWARE. + +import { HttpClient } from "@angular/common/http"; +import { + ChangeDetectorRef, + Component, + Injectable, + OnInit, +} from "@angular/core"; +import { GridsterConfig, GridsterItem } from "angular-gridster2"; +import isEqual from "lodash/isEqual"; +import isNil from "lodash/isNil"; +import { BehaviorSubject, firstValueFrom, Observable, of, Subject } from "rxjs"; +import { + catchError, + delay, + finalize, + map, + // eslint-disable-next-line import/no-deprecated + switchMap, + tap, +} from "rxjs/operators"; + +import { + DataSourceFeatures, + DataSourceService, + IDataField, + IDataSource, + IDataSourceFeatures, + IDataSourceFeaturesConfiguration, + IDataSourceOutput, + IFilter, + IFilters, + INovaFilteringOutputs, + INovaFilters, + LoggerService, +} from "@nova-ui/bits"; +import { + DATA_SOURCE, + IDashboard, + ITableWidgetConfig, + IWidget, + IWidgets, + ProviderRegistryService, + WellKnownPathKey, + WellKnownProviders, + WidgetTypesService, +} from "@nova-ui/dashboards"; + +import { GBOOKS_API_URL } from "../../../../prototypes/data/table/constants"; + +interface IGBooksApiResponse { + kind: string; + totalItems: number; + items: IGBooksItemModel[]; + [key: string]: any; +} + +interface IGBooksItemModel { + id: string; + volumeInfo: { + title: string; + subtitle: string; + authors: string[]; + [key: string]: any; + }; + accessInfo: { [key: string]: any }; + saleInfo: { [key: string]: any }; +} + +interface IGBooksData { + books: IGBooksVolume[]; + totalItems: number; +} + +interface IGBooksVolume { + title: string; + authors: string; +} + +type searchableColumnType = "title" | "authors"; + +@Injectable() +export class AcmeTableGBooksDataSource + extends DataSourceService + implements IDataSource +{ + public static providerId = "AcmeTableGBooksDataSource"; + public static mockError = false; + + public searchableColumn: searchableColumnType = "title"; + + public page: number = 1; + public busy = new BehaviorSubject(false); + public features: IDataSourceFeaturesConfiguration; + + private cache = Array.from({ length: 0 }); + private previousFilters: INovaFilters; + // DataSource Features declared + private supportedFeatures: IDataSourceFeatures = { + search: { enabled: true }, + pagination: { enabled: true }, + }; + private columnToQueryParamMap: { [k in searchableColumnType]: string } = { + title: "intitle", + authors: "inauthor", + }; + + private applyFilters$ = new Subject(); + + public dataFields: Array = [ + { + id: "title", + label: $localize\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\`Title\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\`, + dataType: "string", + sortable: false, + }, + { + id: "authors", + label: $localize\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\`Authors\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\`, + dataType: "string", + sortable: false, + }, + ]; + + constructor(private logger: LoggerService, private http: HttpClient) { + super(); + // Using Nova DataSourceFeatures implementation for the features + this.features = new DataSourceFeatures(this.supportedFeatures); + + this.applyFilters$ + // eslint-disable-next-line import/no-deprecated + .pipe(switchMap((filters) => this.getData(filters))) + .subscribe(async (res) => { + this.outputsSubject.next(await this.getFilteredData(res)); + }); + } + + public async getFilteredData( + booksData: IGBooksData + ): Promise> { + return firstValueFrom( + of(booksData).pipe( + tap((response) => { + this.cache = this.cache.concat(response.books); + }), + map((response) => ({ + result: { + repeat: { itemsSource: this.cache }, + paginator: { total: response.totalItems }, + dataFields: this.dataFields, + }, + })) + ) + ); + } + + private getData(filters: INovaFilters): Observable { + if ( + this.isNewSearchTerm(filters.search) && + filters.virtualScroll?.value.start === 0 + ) { + this.cache = []; + } + + return this.http + .get(this.getComposedUrl(filters)) + .pipe( + tap(() => this.busy.next(true)), + delay(300), // mock + map((response) => ({ + books: + response.items?.map((volume) => ({ + title: volume.volumeInfo.title, + authors: + volume.volumeInfo.authors?.join(", ") || "", + })) || [], + totalItems: response.totalItems, + })), + catchError((e) => { + this.logger.error(e); + return of({ + books: [], + totalItems: 0, + }); + }), + finalize(() => { + this.busy.next(false); + this.previousFilters = filters; + }) + ); + } + + private getComposedUrl(filters: INovaFilters) { + const initialUrl = \\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\`\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\${GBOOKS_API_URL}?q=\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\`; + const maxResults = \\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\`maxResults=\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\${ + (filters.virtualScroll?.value.end || 0) - + (filters.virtualScroll?.value.start || 0) + }\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\`; + + const virtualScrollPart = filters.virtualScroll + ? \\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\`startIndex=\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\${filters.virtualScroll.value.start}\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\` + : ""; + + const searchQueryParam = + this.columnToQueryParamMap[this.searchableColumn]; + const searchPart = filters.search + ? \\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\`\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\${searchQueryParam}:\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\${filters.search.value}\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\` + : "_"; // google books api requires some criteria to do the search + + return \\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\`\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\${initialUrl}\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\${searchPart}&\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\${maxResults}&\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\${virtualScrollPart}&filter=full\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\`; + } + + private isNewSearchTerm(search: IFilter | undefined) { + return ( + !isNil(search?.value) && + !isEqual(search?.value, this.previousFilters?.search?.value) + ); + } + + // redefine parent method + public async applyFilters(): Promise { + this.applyFilters$.next(this.getFilters()); + } +} + +/** + * A component that instantiates the dashboard + */ +@Component({ + selector: "table-widget-search-example", + templateUrl: "./table-widget-search-example.component.html", + styleUrls: ["./table-widget-search-example.component.less"], + standalone: false, +}) +export class TableWidgetSearchExampleComponent implements OnInit { + public dashboard: IDashboard | undefined; + public gridsterConfig: GridsterConfig = {}; + public editMode: boolean = false; + + constructor( + private widgetTypesService: WidgetTypesService, + private providerRegistry: ProviderRegistryService, + private changeDetectorRef: ChangeDetectorRef + ) {} + + public ngOnInit(): void { + const widgetTemplate = this.widgetTypesService.getWidgetType( + "table", + 1 + ); + this.widgetTypesService.setNode( + widgetTemplate, + "configurator", + WellKnownPathKey.DataSourceProviders, + [AcmeTableGBooksDataSource.providerId] + ); + + this.providerRegistry.setProviders({ + [AcmeTableGBooksDataSource.providerId]: { + provide: DATA_SOURCE, + useClass: AcmeTableGBooksDataSource, + deps: [LoggerService, HttpClient], + }, + }); + + this.initializeDashboard(); + } + + /** Used for restoring widgets state */ + public reInitializeDashboard(): void { + // destroys the components and their providers so the dashboard can re init data + this.dashboard = undefined; + this.changeDetectorRef.detectChanges(); + + this.initializeDashboard(); + } + + public initializeDashboard(): void { + const tableWidget = widgetConfig; + const widgetIndex: IWidgets = { + [tableWidget.id]: + this.widgetTypesService.mergeWithWidgetType(tableWidget), + }; + + const positions: Record = { + [tableWidget.id]: { + cols: 12, + rows: 6, + y: 0, + x: 0, + }, + }; + + this.dashboard = { + positions, + widgets: widgetIndex, + }; + } +} + +export const widgetConfig: IWidget = { + id: "tableWidgetId", + type: "table", + pizzagna: { + configuration: { + header: { + properties: { + title: "Google Books", + }, + }, + table: { + providers: { + [WellKnownProviders.DataSource]: { + providerId: AcmeTableGBooksDataSource.providerId, + }, + }, + properties: { + configuration: { + columns: [ + { + id: "column1", + label: $localize\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\`Title\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\`, + isActive: true, + formatter: { + componentType: "RawFormatterComponent", + properties: { + dataFieldIds: { + value: "title", + }, + }, + }, + }, + { + id: "column2", + label: $localize\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\`Author\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\`, + isActive: true, + formatter: { + componentType: "RawFormatterComponent", + properties: { + dataFieldIds: { + value: "authors", + }, + }, + }, + }, + ], + sortable: false, + // define search configuration here + searchConfiguration: { + enabled: true, + // following properties below can be configured as well + // searchTerm: "search criteria here", + // searchDebounce: 300, + }, + hasVirtualScroll: true, + } as ITableWidgetConfig, + }, + }, + }, + }, +}; +\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\`, + "widget-types/table/table-widget-search-docs.component.ts": \\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\`// © 2022 SolarWinds Worldwide, LLC. All rights reserved. +// +// Permission is hereby granted, free of charge, to any person obtaining a copy +// of this software and associated documentation files (the "Software"), to +// deal in the Software without restriction, including without limitation the +// rights to use, copy, modify, merge, publish, distribute, sublicense, and/or +// sell copies of the Software, and to permit persons to whom the Software is +// furnished to do so, subject to the following conditions: +// +// The above copyright notice and this permission notice shall be included in +// all copies or substantial portions of the Software. +// +// THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR +// IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, +// FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE +// AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER +// LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, +// OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN +// THE SOFTWARE. + +import { Component } from "@angular/core"; + +@Component({ + selector: "nui-table-search-docs", + templateUrl: "./table-widget-search-docs.component.html", + standalone: false, +}) +export class TableSearchDocsComponent { + public featuredDeclaredText = \\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\` + private supportedFeatures: IDataSourceFeatures = { + search: { enabled: true }, + pagination: { enabled: true }, + };\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\`; + public featuresUsedText = \\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\` + this.features = new DataSourceFeatures(this.supportedFeatures); + \\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\`; + public tableConfigurationText = \\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\` + "table": { + ... + properties: { + configuration: { + // define search configuration here + searchConfiguration: { + enabled: true, + // following optional properties below can be configured as well + // searchTerm: "search criteria here", + // searchDebounce: 300, + }, + } as ITableWidgetConfig, + }, + }, + \\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\`; +} +\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\`, + "widget-types/table/table-widget-selectable/table-widget-selectable-multi/table-widget-selectable-multi.example.component.ts": \\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\`// © 2022 SolarWinds Worldwide, LLC. All rights reserved. +// +// Permission is hereby granted, free of charge, to any person obtaining a copy +// of this software and associated documentation files (the "Software"), to +// deal in the Software without restriction, including without limitation the +// rights to use, copy, modify, merge, publish, distribute, sublicense, and/or +// sell copies of the Software, and to permit persons to whom the Software is +// furnished to do so, subject to the following conditions: +// +// The above copyright notice and this permission notice shall be included in +// all copies or substantial portions of the Software. +// +// THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR +// IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, +// FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE +// AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER +// LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, +// OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN +// THE SOFTWARE. + +import { Component } from "@angular/core"; + +import { TableSelectionMode } from "@nova-ui/bits"; +import { TableWidgetSelectionConfig } from "@nova-ui/dashboards"; + +/** + * A component that instantiates the dashboard + */ +@Component({ + selector: "table-widget-selectable-multi-example", + templateUrl: "./table-widget-selectable-multi.example.component.html", + styleUrls: ["./table-widget-selectable-multi.example.component.less"], + standalone: false, +}) +export class TableWidgetSelectableMultiExampleComponent { + public selectionConfiguration: TableWidgetSelectionConfig = { + enabled: true, + selectionMode: TableSelectionMode.Multi, + trackByProperty: "id", + clickableRow: true, + }; +} +\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\`, + "widget-types/table/table-widget-selectable/table-widget-selectable-radio/table-widget-selectable-radio.example.component.ts": \\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\`// © 2022 SolarWinds Worldwide, LLC. All rights reserved. +// +// Permission is hereby granted, free of charge, to any person obtaining a copy +// of this software and associated documentation files (the "Software"), to +// deal in the Software without restriction, including without limitation the +// rights to use, copy, modify, merge, publish, distribute, sublicense, and/or +// sell copies of the Software, and to permit persons to whom the Software is +// furnished to do so, subject to the following conditions: +// +// The above copyright notice and this permission notice shall be included in +// all copies or substantial portions of the Software. +// +// THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR +// IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, +// FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE +// AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER +// LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, +// OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN +// THE SOFTWARE. + +import { Component } from "@angular/core"; + +import { TableSelectionMode } from "@nova-ui/bits"; +import { TableWidgetSelectionConfig } from "@nova-ui/dashboards"; + +/** + * A component that instantiates the dashboard + */ +@Component({ + selector: "table-widget-selectable-radio-example", + templateUrl: "./table-widget-selectable-radio.example.component.html", + styleUrls: ["./table-widget-selectable-radio.example.component.less"], + standalone: false, +}) +export class TableWidgetSelectableRadioExampleComponent { + public selectionConfiguration: TableWidgetSelectionConfig = { + enabled: true, + selectionMode: TableSelectionMode.Radio, + trackByProperty: "id", + clickableRow: true, + }; +} +\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\`, + "widget-types/table/table-widget-selectable/table-widget-selectable-single/table-widget-selectable-single.example.component.ts": \\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\`// © 2022 SolarWinds Worldwide, LLC. All rights reserved. +// +// Permission is hereby granted, free of charge, to any person obtaining a copy +// of this software and associated documentation files (the "Software"), to +// deal in the Software without restriction, including without limitation the +// rights to use, copy, modify, merge, publish, distribute, sublicense, and/or +// sell copies of the Software, and to permit persons to whom the Software is +// furnished to do so, subject to the following conditions: +// +// The above copyright notice and this permission notice shall be included in +// all copies or substantial portions of the Software. +// +// THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR +// IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, +// FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE +// AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER +// LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, +// OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN +// THE SOFTWARE. + +import { Component } from "@angular/core"; + +import { TableSelectionMode } from "@nova-ui/bits"; +import { TableWidgetSelectionConfig } from "@nova-ui/dashboards"; + +/** + * A component that instantiates the dashboard + */ +@Component({ + selector: "table-widget-selectable-single-example", + templateUrl: "./table-widget-selectable-single.example.component.html", + styleUrls: ["./table-widget-selectable-single.example.component.less"], + standalone: false, +}) +export class TableWidgetSelectableSingleExampleComponent { + public selectionConfiguration: TableWidgetSelectionConfig = { + enabled: true, + selectionMode: TableSelectionMode.Single, + trackByProperty: "id", + }; +} +\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\`, + "widget-types/table/table-widget-selectable/table-widget-selectable.example.component.ts": \\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\`// © 2022 SolarWinds Worldwide, LLC. All rights reserved. +// +// Permission is hereby granted, free of charge, to any person obtaining a copy +// of this software and associated documentation files (the "Software"), to +// deal in the Software without restriction, including without limitation the +// rights to use, copy, modify, merge, publish, distribute, sublicense, and/or +// sell copies of the Software, and to permit persons to whom the Software is +// furnished to do so, subject to the following conditions: +// +// The above copyright notice and this permission notice shall be included in +// all copies or substantial portions of the Software. +// +// THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR +// IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, +// FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE +// AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER +// LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, +// OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN +// THE SOFTWARE. + +import { HttpClient } from "@angular/common/http"; +import { ChangeDetectorRef, Component, Input, OnInit } from "@angular/core"; +import { GridsterConfig, GridsterItem } from "angular-gridster2"; + +import { LoggerService, TableSelectionMode } from "@nova-ui/bits"; +import { + DATA_SOURCE, + DEFAULT_PIZZAGNA_ROOT, + IDashboard, + IProviderConfiguration, + ITableWidgetConfig, + IWidget, + IWidgets, + NOVA_URL_INTERACTION_HANDLER, + PizzagnaLayer, + ProviderRegistryService, + RawFormatterComponent, + TableWidgetSelectionConfig, + WellKnownPathKey, + WellKnownProviders, + WidgetTypesService, +} from "@nova-ui/dashboards"; + +import { AcmeTableMockDataSource } from "../../../../prototypes/data/table/acme-table-mock-data-source.service"; + +/** + * A component that instantiates the dashboard + */ +@Component({ + selector: "table-widget-selectable-example", + templateUrl: "./table-widget-selectable.example.component.html", + styleUrls: ["./table-widget-selectable.example.component.less"], + standalone: false, +}) +export class TableWidgetSelectableExampleComponent implements OnInit { + public dashboard: IDashboard | undefined; + public gridsterConfig: GridsterConfig = {}; + public editMode: boolean = false; + + @Input() public selectionConfiguration: TableWidgetSelectionConfig = { + enabled: false, + selectionMode: TableSelectionMode.None, + }; + + constructor( + private widgetTypesService: WidgetTypesService, + private providerRegistry: ProviderRegistryService, + private changeDetectorRef: ChangeDetectorRef + ) {} + + public ngOnInit(): void { + const widgetTemplate = this.widgetTypesService.getWidgetType( + "table", + 1 + ); + this.widgetTypesService.setNode( + widgetTemplate, + "configurator", + WellKnownPathKey.DataSourceProviders, + [AcmeTableMockDataSource.providerId] + ); + + this.providerRegistry.setProviders({ + [AcmeTableMockDataSource.providerId]: { + provide: DATA_SOURCE, + useClass: AcmeTableMockDataSource, + deps: [LoggerService, HttpClient], + }, + }); + + this.initializeDashboard(); + } + + /** Used for restoring widgets state */ + public reInitializeDashboard(): void { + // destroys the components and their providers so the dashboard can re init data + this.dashboard = undefined; + this.changeDetectorRef.detectChanges(); + + this.initializeDashboard(); + } + + public initializeDashboard(): void { + const tableWidget = this.widgetConfig; + const widgetIndex: IWidgets = { + [tableWidget.id]: + this.widgetTypesService.mergeWithWidgetType(tableWidget), + }; + + const positions: Record = { + [tableWidget.id]: { + cols: 12, + rows: 6, + y: 0, + x: 0, + }, + }; + + this.dashboard = { + positions, + widgets: widgetIndex, + }; + } + + private get widgetConfig(): IWidget { + return { + id: "widget1", + type: "table", + pizzagna: { + [PizzagnaLayer.Configuration]: { + [DEFAULT_PIZZAGNA_ROOT]: { + providers: { + [WellKnownProviders.InteractionHandler]: { + providerId: NOVA_URL_INTERACTION_HANDLER, + }, + }, + }, + header: { + properties: { + title: "Table Widget with Selection!", + subtitle: "Basic table widget", + collapsible: true, + }, + }, + table: { + providers: { + [WellKnownProviders.DataSource]: { + providerId: AcmeTableMockDataSource.providerId, + } as IProviderConfiguration, + }, + properties: { + configuration: { + // enabling selection here + selectionConfiguration: + this.selectionConfiguration, + columns: [ + { + id: "column1", + label: "No.", + isActive: true, + formatter: { + componentType: + RawFormatterComponent.lateLoadKey, + properties: { + dataFieldIds: { + value: "position", + }, + }, + }, + }, + { + id: "column2", + label: "Name", + isActive: true, + formatter: { + componentType: + RawFormatterComponent.lateLoadKey, + properties: { + dataFieldIds: { + value: "name", + }, + }, + }, + }, + { + id: "column3", + label: "Status", + isActive: true, + formatter: { + componentType: + RawFormatterComponent.lateLoadKey, + properties: { + dataFieldIds: { + value: "status", + }, + }, + }, + }, + ], + } as ITableWidgetConfig, + }, + }, + }, + }, + }; + } +} +\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\`, + "widget-types/timeseries/timeseries-docs.component.ts": \\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\`// © 2022 SolarWinds Worldwide, LLC. All rights reserved. +// +// Permission is hereby granted, free of charge, to any person obtaining a copy +// of this software and associated documentation files (the "Software"), to +// deal in the Software without restriction, including without limitation the +// rights to use, copy, modify, merge, publish, distribute, sublicense, and/or +// sell copies of the Software, and to permit persons to whom the Software is +// furnished to do so, subject to the following conditions: +// +// The above copyright notice and this permission notice shall be included in +// all copies or substantial portions of the Software. +// +// THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR +// IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, +// FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE +// AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER +// LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, +// OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN +// THE SOFTWARE. + +import { Component, OnInit } from "@angular/core"; + +import { mapContentFile } from "../../../../demo-files-factory"; + +@Component({ + selector: "nui-timeseries-docs", + templateUrl: "./timeseries-docs.component.html", + standalone: false, +}) +export class TimeseriesDocsComponent implements OnInit { + public timeseriesWidgetFileText = ""; + public timeseriesConfiguratorFileText = ""; + + async ngOnInit(): Promise { + this.timeseriesWidgetFileText = await import( + "./../../../../../../src/lib/widget-types/timeseries/timeseries-widget" + ).then(mapContentFile); + this.timeseriesConfiguratorFileText = await import( + "./../../../../../../src/lib/widget-types/timeseries/timeseries-configurator" + ).then(mapContentFile); + } +} +\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\`, + "widget-types/timeseries/timeseries-docs.module.ts": \\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\`// © 2022 SolarWinds Worldwide, LLC. All rights reserved. +// +// Permission is hereby granted, free of charge, to any person obtaining a copy +// of this software and associated documentation files (the "Software"), to +// deal in the Software without restriction, including without limitation the +// rights to use, copy, modify, merge, publish, distribute, sublicense, and/or +// sell copies of the Software, and to permit persons to whom the Software is +// furnished to do so, subject to the following conditions: +// +// The above copyright notice and this permission notice shall be included in +// all copies or substantial portions of the Software. +// +// THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR +// IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, +// FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE +// AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER +// LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, +// OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN +// THE SOFTWARE. + +import { NgModule } from "@angular/core"; +import { RouterModule, Routes } from "@angular/router"; + +import { DEMO_PATH_TOKEN } from "@nova-ui/bits"; +import { + NuiButtonModule, + NuiDocsModule, + NuiMessageModule, + NuiSwitchModule, +} from "@nova-ui/bits"; +import { NuiDashboardsModule } from "@nova-ui/dashboards"; + +import { TimeseriesDocsComponent } from "./timeseries-docs.component"; +import { TimeseriesWidgetExampleComponent } from "./timeseries-widget-example/timeseries-widget-example.component"; +import { TimeseriesWidgetInteractiveExampleComponent } from "./timeseries-widget-interactive-example/timeseries-widget-interactive-example.component"; +import { TimeseriesWidgetStatusBarExampleComponent } from "./timeseries-widget-status-bar-example/timeseries-widget-status-bar-example.component"; +import { getDemoFiles } from "../../../../demo-files-factory"; + +const routes: Routes = [ + { + path: "", + component: TimeseriesDocsComponent, + data: { + srlc: { + hideIndicator: true, + }, + showThemeSwitcher: true, + }, + }, + { + path: "example", + component: TimeseriesWidgetExampleComponent, + data: { + srlc: { + hideIndicator: true, + }, + }, + }, +]; + +@NgModule({ + imports: [ + RouterModule.forChild(routes), + NuiButtonModule, + NuiDocsModule, + NuiMessageModule, + NuiSwitchModule, + NuiDashboardsModule, + ], + declarations: [ + TimeseriesDocsComponent, + TimeseriesWidgetExampleComponent, + TimeseriesWidgetInteractiveExampleComponent, + TimeseriesWidgetStatusBarExampleComponent, + ], + providers: [ + { + provide: DEMO_PATH_TOKEN, + useValue: getDemoFiles("timeseries"), + }, + ], +}) +export default class TimeseriesDocsModule {} +\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\`, + "widget-types/timeseries/timeseries-widget-example/timeseries-widget-example.component.ts": \\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\`// © 2022 SolarWinds Worldwide, LLC. All rights reserved. +// +// Permission is hereby granted, free of charge, to any person obtaining a copy +// of this software and associated documentation files (the "Software"), to +// deal in the Software without restriction, including without limitation the +// rights to use, copy, modify, merge, publish, distribute, sublicense, and/or +// sell copies of the Software, and to permit persons to whom the Software is +// furnished to do so, subject to the following conditions: +// +// The above copyright notice and this permission notice shall be included in +// all copies or substantial portions of the Software. +// +// THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR +// IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, +// FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE +// AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER +// LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, +// OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN +// THE SOFTWARE. + +import { + ChangeDetectorRef, + Component, + Injectable, + OnInit, +} from "@angular/core"; +import { GridsterConfig, GridsterItem } from "angular-gridster2"; +import cloneDeep from "lodash/cloneDeep"; +import keyBy from "lodash/keyBy"; +import moment, { Moment } from "moment/moment"; +import { BehaviorSubject } from "rxjs"; + +import { + DataSourceService, + IDataSource, + INovaFilters, + ITimeframe, +} from "@nova-ui/bits"; +import { + DATA_SOURCE, + DEFAULT_PIZZAGNA_ROOT, + IDashboard, + IDataSourceOutput, + IProviderConfiguration, + ISerializableTimeframe, + ITimeseriesItemConfiguration, + ITimeseriesOutput, + ITimeseriesScaleConfig, + ITimeseriesWidgetConfig, + ITimeseriesWidgetData, + ITimeseriesWidgetSeriesData, + IWidget, + LegendPlacement, + PizzagnaLayer, + ProviderRegistryService, + TimeseriesChartPreset, + TimeseriesScaleType, + WellKnownPathKey, + WellKnownProviders, + WidgetTypesService, +} from "@nova-ui/dashboards"; + +/** + * A simple Timeseries data source implementation + */ +@Injectable() +export class BeerVsReadingMockDataSource + extends DataSourceService + implements IDataSource +{ + public static providerId = "BeerVsReadingMockDataSource"; + + public busy = new BehaviorSubject(false); + + public async getFilteredData( + filters: INovaFilters + ): Promise> { + // In this example we're using some static mock data located at the bottom of this file. In a real-world + // scenario, the data for the chart would likely be retrieved via an asynchronous backend call. + let filteredData = getData(); + + this.busy.next(true); + + // Filtering using the filter registered by the TimeFrameBar + const timeframeFilter = filters.timeframe?.value as ITimeframe; + if (timeframeFilter) { + filteredData = filteredData.map((item: ITimeseriesWidgetData) => ({ + id: item.id, + name: item.name, + description: item.description, + data: item.data.filter( + (seriesData: ITimeseriesWidgetSeriesData) => + filterDates( + seriesData.x, + timeframeFilter.startDatetime, + timeframeFilter.endDatetime + ) + ), + })); + } + + this.busy.next(false); + + return { result: { series: filteredData } }; + } +} + +function filterDates(dateToCheck: Date, startDate: Moment, endDate: Moment) { + const mom = moment(dateToCheck); + return ( + mom.isBetween(startDate, endDate) || + mom.isSame(startDate) || + mom.isSame(endDate) + ); +} + +/** + * A component that instantiates the dashboard + */ +@Component({ + selector: "timeseries-widget-example", + templateUrl: "./timeseries-widget-example.component.html", + styleUrls: ["./timeseries-widget-example.component.less"], + standalone: false, +}) +export class TimeseriesWidgetExampleComponent implements OnInit { + // This variable will hold all the data needed to define the layout and behavior of the widgets. + // Pass this to the dashboard component's dashboard input in the template. + public dashboard: IDashboard | undefined; + + // Angular gridster requires a configuration object even if it's empty. + // Pass this to the dashboard component's gridsterConfig input in the template. + public gridsterConfig: GridsterConfig = {}; + + // Boolean passed as an input to the dashboard. When true, widgets can be moved, resized, removed, or edited + public editMode: boolean = false; + + constructor( + // WidgetTypesService provides the widget's necessary structure information + private widgetTypesService: WidgetTypesService, + + // In general, the ProviderRegistryService is used for making entities available for injection into dynamically loaded components. + private providerRegistry: ProviderRegistryService, + + // Angular's ChangeDetectorRef + private changeDetectorRef: ChangeDetectorRef + ) {} + + public ngOnInit(): void { + // Grabbing the widget's default template which will be needed as a parameter for setNode + const widgetTemplate = this.widgetTypesService.getWidgetType( + "timeseries", + 1 + ); + // Registering our data sources as dropdown options in the widget editor/configurator + // Note: This could also be done in the parent module's constructor so that + // multiple dashboards could have access to the same widget template modification. + this.widgetTypesService.setNode( + // This is the template we grabbed above with getWidgetType + widgetTemplate, + // We are setting the editor/configurator part of the widget template + "configurator", + // This indicates which node you are changing and we want to change + // the data source providers available for selection in the editor. + WellKnownPathKey.DataSourceProviders, + // We are setting the data sources available for selection in the editor + [BeerVsReadingMockDataSource.providerId] + ); + + // Registering the data source for injection into the widget. + this.providerRegistry.setProviders({ + [BeerVsReadingMockDataSource.providerId]: { + provide: DATA_SOURCE, + useClass: BeerVsReadingMockDataSource, + deps: [], + }, + }); + + this.initializeDashboard(); + } + + public initializeDashboard(): void { + // We're using a static configuration object for this example, but this is where + // the widget's configuration could potentially be populated from a database + const widgetsWithStructure = widgetConfigs.map((w) => + this.widgetTypesService.mergeWithWidgetType(w) + ); + const widgetsIndex = keyBy(widgetsWithStructure, (w: IWidget) => w.id); + + // Finally, assigning the variables we created above to the dashboard + this.dashboard = { + positions: cloneDeep(positions), + widgets: widgetsIndex, + }; + } + + /** Used for restoring widgets state */ + public reInitializeDashboard(): void { + // destroys the components and their providers so the dashboard can re init data + this.dashboard = undefined; + this.changeDetectorRef.detectChanges(); + + this.initializeDashboard(); + } +} + +const widgetConfigs: IWidget[] = [ + { + id: "lineWidgetId", + type: "timeseries", + pizzagna: { + [PizzagnaLayer.Configuration]: { + [DEFAULT_PIZZAGNA_ROOT]: { + providers: { + [WellKnownProviders.DataSource]: { + // Setting the initially selected data source providerId + providerId: BeerVsReadingMockDataSource.providerId, + } as IProviderConfiguration, + }, + }, + header: { + properties: { + title: "Line Chart", + subtitle: "Survey of 1000 Solarians", + }, + }, + chart: { + providers: { + [WellKnownProviders.Adapter]: { + properties: { + // Setting the series and corresponding labels to initially display on the chart + series: [ + { + id: "series-1", + label: "Beer Tasting", + selectedSeriesId: "series-1", + }, + { + id: "series-2", + label: "Reading", + selectedSeriesId: "series-2", + }, + ] as ITimeseriesItemConfiguration[], + }, + } as Partial, + }, + properties: { + // Setting the general chart configuration + configuration: { + legendPlacement: LegendPlacement.Right, + enableZoom: true, + leftAxisLabel: "Solarians (%)", + // You can optionally define custom colors for the chart by setting the 'chartColors' configuration property + // "chartColors": [ + // "var(--nui-color-chart-eight)", + // "var(--nui-color-chart-nine)", + // "var(--nui-color-chart-ten)", + // ], + } as ITimeseriesWidgetConfig, + }, + }, + timeframeSelection: { + properties: { + // Setting the initial timeframe selected in the timeframe bar + timeframe: { + selectedPresetId: "last7Days", + } as ISerializableTimeframe, + minDate: moment().subtract(60, "days").format(), + maxDate: moment().format(), + }, + }, + }, + }, + }, + { + id: "stackedAreaWidgetId", + type: "timeseries", + pizzagna: { + [PizzagnaLayer.Configuration]: { + [DEFAULT_PIZZAGNA_ROOT]: { + providers: { + [WellKnownProviders.DataSource]: { + // Setting the initially selected data source providerId + providerId: BeerVsReadingMockDataSource.providerId, + } as IProviderConfiguration, + }, + }, + header: { + properties: { + title: "Stacked Area Chart", + subtitle: "Survey of 1000 Solarians", + }, + }, + chart: { + providers: { + [WellKnownProviders.Adapter]: { + properties: { + // Setting the series and corresponding labels to initially display on the chart + series: [ + { + id: "series-1", + label: "Beer Tasting", + selectedSeriesId: "series-1", + }, + { + id: "series-2", + label: "Reading", + selectedSeriesId: "series-2", + }, + ] as ITimeseriesItemConfiguration[], + }, + } as Partial, + }, + properties: { + // Setting the general chart configuration + configuration: { + legendPlacement: LegendPlacement.Right, + enableZoom: true, + // Setting the preset to stacked area + preset: TimeseriesChartPreset.StackedArea, + leftAxisLabel: "Solarians (%)", + // You can optionally define custom colors for the chart by setting the 'chartColors' configuration property + // "chartColors": [ + // "var(--nui-color-chart-eight)", + // "var(--nui-color-chart-nine)", + // "var(--nui-color-chart-ten)", + // ], + } as ITimeseriesWidgetConfig, + }, + }, + timeframeSelection: { + properties: { + // Setting the initial timeframe selected in the timeframe bar + timeframe: { + selectedPresetId: "last7Days", + } as ISerializableTimeframe, + minDate: moment().subtract(60, "days").format(), + maxDate: moment().format(), + }, + }, + }, + }, + }, + { + id: "stackedPercentageAreaWidgetId", + type: "timeseries", + pizzagna: { + [PizzagnaLayer.Configuration]: { + [DEFAULT_PIZZAGNA_ROOT]: { + providers: { + [WellKnownProviders.DataSource]: { + // Setting the initially selected data source providerId + providerId: BeerVsReadingMockDataSource.providerId, + } as IProviderConfiguration, + }, + }, + header: { + properties: { + title: "Stacked Percentage Area Chart", + subtitle: "Survey of 1000 Solarians", + }, + }, + chart: { + providers: { + [WellKnownProviders.Adapter]: { + properties: { + // Setting the series and corresponding labels to initially display on the chart + series: [ + { + id: "series-1", + label: "Beer Tasting", + selectedSeriesId: "series-1", + }, + { + id: "series-2", + label: "Reading", + selectedSeriesId: "series-2", + }, + ] as ITimeseriesItemConfiguration[], + }, + } as Partial, + }, + properties: { + // Setting the general chart configuration + configuration: { + legendPlacement: LegendPlacement.Right, + enableZoom: true, + // Setting the preset to stacked percentage area + preset: TimeseriesChartPreset.StackedPercentageArea, + leftAxisLabel: "Solarians (%)", + // You can optionally define custom colors for the chart by setting the 'chartColors' configuration property + // "chartColors": [ + // "var(--nui-color-chart-eight)", + // "var(--nui-color-chart-nine)", + // "var(--nui-color-chart-ten)", + // ], + } as ITimeseriesWidgetConfig, + }, + }, + timeframeSelection: { + properties: { + // Setting the initial timeframe selected in the timeframe bar + timeframe: { + selectedPresetId: "last7Days", + } as ISerializableTimeframe, + minDate: moment().subtract(60, "days").format(), + maxDate: moment().format(), + }, + }, + }, + }, + }, + { + id: "stackedBarWidgetId", + type: "timeseries", + pizzagna: { + [PizzagnaLayer.Configuration]: { + [DEFAULT_PIZZAGNA_ROOT]: { + providers: { + [WellKnownProviders.DataSource]: { + // Setting the initially selected data source providerId + providerId: BeerVsReadingMockDataSource.providerId, + } as IProviderConfiguration, + }, + }, + header: { + properties: { + title: "Stacked Bar Chart", + subtitle: "Survey of 1000 Solarians", + }, + }, + chart: { + providers: { + [WellKnownProviders.Adapter]: { + properties: { + // Setting the series and corresponding labels to initially display on the chart + series: [ + { + id: "series-1", + label: "Beer Tasting", + selectedSeriesId: "series-1", + }, + { + id: "series-2", + label: "Reading", + selectedSeriesId: "series-2", + }, + ] as ITimeseriesItemConfiguration[], + }, + } as Partial, + }, + properties: { + configuration: { + legendPlacement: LegendPlacement.Right, + enableZoom: true, + leftAxisLabel: "Solarians (%)", + // Setting the preset to stacked bar + preset: TimeseriesChartPreset.StackedBar, + scales: { + x: { + type: TimeseriesScaleType.TimeInterval, + properties: { + interval: 24 * 60 * 60, + }, + } as ITimeseriesScaleConfig, + }, + } as ITimeseriesWidgetConfig, + }, + }, + timeframeSelection: { + properties: { + // Setting the initial timeframe selected in the timeframe bar + timeframe: { + selectedPresetId: "last7Days", + } as ISerializableTimeframe, + minDate: moment().subtract(60, "days").format(), + maxDate: moment().format(), + }, + }, + }, + }, + }, +]; + +// using startOf("day") so that each band for the bar chart corresponds to a calendar day +const now = moment().startOf("day"); + +export const getData = (): ITimeseriesWidgetData[] => [ + { + id: "series-1", + name: "Beer Tasting", + description: "Havin' some suds", + data: [ + { x: now.clone().subtract(20, "day").toDate(), y: 30 }, + { x: now.clone().subtract(19, "day").toDate(), y: 35 }, + { x: now.clone().subtract(18, "day").toDate(), y: 33 }, + { x: now.clone().subtract(17, "day").toDate(), y: 40 }, + { x: now.clone().subtract(16, "day").toDate(), y: 35 }, + { x: now.clone().subtract(15, "day").toDate(), y: 30 }, + { x: now.clone().subtract(14, "day").toDate(), y: 35 }, + { x: now.clone().subtract(13, "day").toDate(), y: 15 }, + { x: now.clone().subtract(12, "day").toDate(), y: 30 }, + { x: now.clone().subtract(11, "day").toDate(), y: 45 }, + { x: now.clone().subtract(10, "day").toDate(), y: 60 }, + { x: now.clone().subtract(9, "day").toDate(), y: 54 }, + { x: now.clone().subtract(8, "day").toDate(), y: 42 }, + { x: now.clone().subtract(7, "day").toDate(), y: 44 }, + { x: now.clone().subtract(6, "day").toDate(), y: 54 }, + { x: now.clone().subtract(5, "day").toDate(), y: 43 }, + { x: now.clone().subtract(4, "day").toDate(), y: 76 }, + { x: now.clone().subtract(3, "day").toDate(), y: 54 }, + { x: now.clone().subtract(2, "day").toDate(), y: 42 }, + { x: now.clone().subtract(1, "day").toDate(), y: 34 }, + ], + }, + { + id: "series-2", + name: "Reading", + description: "Hittin' the books", + data: [ + { x: now.clone().subtract(20, "day").toDate(), y: 60 }, + { x: now.clone().subtract(19, "day").toDate(), y: 64 }, + { x: now.clone().subtract(18, "day").toDate(), y: 70 }, + { x: now.clone().subtract(17, "day").toDate(), y: 55 }, + { x: now.clone().subtract(16, "day").toDate(), y: 55 }, + { x: now.clone().subtract(15, "day").toDate(), y: 45 }, + { x: now.clone().subtract(14, "day").toDate(), y: 60 }, + { x: now.clone().subtract(13, "day").toDate(), y: 65 }, + { x: now.clone().subtract(12, "day").toDate(), y: 63 }, + { x: now.clone().subtract(11, "day").toDate(), y: 60 }, + { x: now.clone().subtract(10, "day").toDate(), y: 61 }, + { x: now.clone().subtract(9, "day").toDate(), y: 65 }, + { x: now.clone().subtract(8, "day").toDate(), y: 63 }, + { x: now.clone().subtract(7, "day").toDate(), y: 58 }, + { x: now.clone().subtract(6, "day").toDate(), y: 64 }, + { x: now.clone().subtract(5, "day").toDate(), y: 63 }, + { x: now.clone().subtract(4, "day").toDate(), y: 60 }, + { x: now.clone().subtract(3, "day").toDate(), y: 62 }, + { x: now.clone().subtract(2, "day").toDate(), y: 61 }, + { x: now.clone().subtract(1, "day").toDate(), y: 62 }, + ], + }, +]; + +// Setting the widget dimensions and position (this is for gridster) +const positions: Record = { + [widgetConfigs[0].id]: { + cols: 6, + rows: 6, + y: 0, + x: 0, + }, + [widgetConfigs[1].id]: { + cols: 6, + rows: 6, + y: 0, + x: 6, + }, + [widgetConfigs[3].id]: { + cols: 6, + rows: 6, + y: 6, + x: 0, + }, + [widgetConfigs[2].id]: { + cols: 6, + rows: 6, + y: 6, + x: 6, + }, +}; +\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\`, + "widget-types/timeseries/timeseries-widget-interactive-example/timeseries-widget-interactive-example.component.ts": \\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\`// © 2022 SolarWinds Worldwide, LLC. All rights reserved. +// +// Permission is hereby granted, free of charge, to any person obtaining a copy +// of this software and associated documentation files (the "Software"), to +// deal in the Software without restriction, including without limitation the +// rights to use, copy, modify, merge, publish, distribute, sublicense, and/or +// sell copies of the Software, and to permit persons to whom the Software is +// furnished to do so, subject to the following conditions: +// +// The above copyright notice and this permission notice shall be included in +// all copies or substantial portions of the Software. +// +// THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR +// IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, +// FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE +// AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER +// LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, +// OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN +// THE SOFTWARE. + +import { + ChangeDetectorRef, + Component, + Injectable, + OnInit, +} from "@angular/core"; +import { GridsterConfig, GridsterItem } from "angular-gridster2"; +import cloneDeep from "lodash/cloneDeep"; +import keyBy from "lodash/keyBy"; +import moment, { Moment } from "moment/moment"; +import { BehaviorSubject } from "rxjs"; + +import { + DataSourceService, + IDataSource, + INovaFilters, + ITimeframe, +} from "@nova-ui/bits"; +import { + DATA_SOURCE, + DEFAULT_PIZZAGNA_ROOT, + IDashboard, + IDataSourceOutput, + IProviderConfiguration, + ISerializableTimeframe, + ITimeseriesItemConfiguration, + ITimeseriesOutput, + ITimeseriesScaleConfig, + ITimeseriesWidgetConfig, + ITimeseriesWidgetData, + ITimeseriesWidgetSeriesData, + IWidget, + NOVA_URL_INTERACTION_HANDLER, + LegendPlacement, + PizzagnaLayer, + ProviderRegistryService, + TimeseriesChartPreset, + TimeseriesScaleType, + WellKnownPathKey, + WellKnownProviders, + WidgetTypesService, +} from "@nova-ui/dashboards"; + +/** + * A simple Timeseries data source implementation + */ +@Injectable() +export class TimeseriesMockDataSource + extends DataSourceService + implements IDataSource +{ + public static providerId = "TimeseriesMockDataSource"; + + public busy = new BehaviorSubject(false); + + public async getFilteredData( + filters: INovaFilters + ): Promise> { + // In this example we're using some static mock data located at the bottom of this file. In a real-world + // scenario, the data for the chart would likely be retrieved via an asynchronous backend call. + let filteredData = getData(); + + this.busy.next(true); + + // Filtering using the filter registered by the TimeFrameBar + const timeframeFilter = filters.timeframe?.value as ITimeframe; + if (timeframeFilter) { + filteredData = filteredData.map((item: ITimeseriesWidgetData) => ({ + id: item.id, + name: item.name, + description: item.description, + // the filtered data should return the provided links if they are set. + link: item?.link, + secondaryLink: item?.secondaryLink, + data: item.data.filter( + (seriesData: ITimeseriesWidgetSeriesData) => + filterDates( + seriesData.x, + timeframeFilter.startDatetime, + timeframeFilter.endDatetime + ) + ), + })); + } + + this.busy.next(false); + + return { result: { series: filteredData } }; + } +} + +function filterDates(dateToCheck: Date, startDate: Moment, endDate: Moment) { + const mom = moment(dateToCheck); + return ( + mom.isBetween(startDate, endDate) || + mom.isSame(startDate) || + mom.isSame(endDate) + ); +} + +/** + * A component that instantiates the dashboard + */ +@Component({ + selector: "timeseries-widget-interactive-example", + templateUrl: "./timeseries-widget-interactive-example.component.html", + styleUrls: ["./timeseries-widget-interactive-example.component.less"], + standalone: false, +}) +export class TimeseriesWidgetInteractiveExampleComponent implements OnInit { + // This variable will hold all the data needed to define the layout and behavior of the widgets. + // Pass this to the dashboard component's dashboard input in the template. + public dashboard: IDashboard | undefined; + + // Angular gridster requires a configuration object even if it's empty. + // Pass this to the dashboard component's gridsterConfig input in the template. + public gridsterConfig: GridsterConfig = {}; + + // Boolean passed as an input to the dashboard. When true, widgets can be moved, resized, removed, or edited + public editMode: boolean = false; + + constructor( + // WidgetTypesService provides the widget's necessary structure information + private widgetTypesService: WidgetTypesService, + + // In general, the ProviderRegistryService is used for making entities available for injection into dynamically loaded components. + private providerRegistry: ProviderRegistryService, + + // Angular's ChangeDetectorRef + private changeDetectorRef: ChangeDetectorRef + ) {} + + public ngOnInit(): void { + // Grabbing the widget's default template which will be needed as a parameter for setNode + const widgetTemplate = this.widgetTypesService.getWidgetType( + "timeseries", + 1 + ); + // Registering our data sources as dropdown options in the widget editor/configurator + // Note: This could also be done in the parent module's constructor so that + // multiple dashboards could have access to the same widget template modification. + this.widgetTypesService.setNode( + // This is the template we grabbed above with getWidgetType + widgetTemplate, + // We are setting the editor/configurator part of the widget template + "configurator", + // This indicates which node you are changing and we want to change + // the data source providers available for selection in the editor. + WellKnownPathKey.DataSourceProviders, + // We are setting the data sources available for selection in the editor + [TimeseriesMockDataSource.providerId] + ); + + // Registering the data source for injection into the widget. + this.providerRegistry.setProviders({ + [TimeseriesMockDataSource.providerId]: { + provide: DATA_SOURCE, + useClass: TimeseriesMockDataSource, + deps: [], + }, + }); + + this.initializeDashboard(); + } + + public initializeDashboard(): void { + // We're using a static configuration object for this example, but this is where + // the widget's configuration could potentially be populated from a database + const widgetsWithStructure = widgetConfigs.map((w) => + this.widgetTypesService.mergeWithWidgetType(w) + ); + const widgetsIndex = keyBy(widgetsWithStructure, (w: IWidget) => w.id); + + // Finally, assigning the variables we created above to the dashboard + this.dashboard = { + positions: cloneDeep(positions), + widgets: widgetsIndex, + }; + } + + /** Used for restoring widgets state */ + public reInitializeDashboard(): void { + // destroys the components and their providers so the dashboard can re init data + this.dashboard = undefined; + this.changeDetectorRef.detectChanges(); + + this.initializeDashboard(); + } +} + +const widgetConfigs: IWidget[] = [ + { + id: "lineWidgetId", + type: "timeseries", + pizzagna: { + [PizzagnaLayer.Configuration]: { + [DEFAULT_PIZZAGNA_ROOT]: { + providers: { + [WellKnownProviders.DataSource]: { + // Setting the initially selected data source providerId + providerId: TimeseriesMockDataSource.providerId, + } as IProviderConfiguration, + [WellKnownProviders.InteractionHandler]: { + // Setting the UrlInteractionHandler as an interactionHandler + providerId: NOVA_URL_INTERACTION_HANDLER, + properties: { + // the 'url' property tells the handler what link to use when interaction occurs on the series + url: "\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\${data.link || 'https://en.wikipedia.org/wiki/'+data.legendDescriptionPrimary}", + // by default the link is opened in the current window, set 'newWindow' to true to open in a new tab instead + // newWindow: true, + }, + }, + }, + }, + header: { + properties: { + title: "Line Chart", + subtitle: "Basic Timeseries with Interaction", + }, + }, + chart: { + providers: { + [WellKnownProviders.Adapter]: { + properties: { + // Setting the series and corresponding labels to initially display on the chart + series: [ + { + id: "series-1", + label: "Nur-Sultan", + selectedSeriesId: "series-1", + }, + { + id: "series-2", + label: "Brno", + selectedSeriesId: "series-2", + }, + { + id: "series-3", + label: "Lisbon", + selectedSeriesId: "series-3", + }, + { + id: "series-4", + label: "Austin", + selectedSeriesId: "series-4", + }, + ] as ITimeseriesItemConfiguration[], + }, + } as Partial, + }, + properties: { + // Setting the general chart configuration + configuration: { + // setting interaction to 'series' will make all series in the chart interactable + interaction: "series", + legendPlacement: LegendPlacement.Right, + enableZoom: true, + } as ITimeseriesWidgetConfig, + }, + }, + timeframeSelection: { + properties: { + timeframe: { + selectedPresetId: "last7Days", + } as ISerializableTimeframe, + minDate: moment().subtract(60, "days").format(), + maxDate: moment().format(), + }, + }, + }, + }, + }, + { + id: "stackedBarWidgetId", + type: "timeseries", + pizzagna: { + [PizzagnaLayer.Configuration]: { + [DEFAULT_PIZZAGNA_ROOT]: { + providers: { + [WellKnownProviders.DataSource]: { + // Setting the initially selected data source providerId + providerId: TimeseriesMockDataSource.providerId, + } as IProviderConfiguration, + }, + }, + header: { + properties: { + title: "Stacked Bar Chart", + subtitle: + "Basic Timeseries without Interaction Handler", + }, + }, + chart: { + providers: { + [WellKnownProviders.Adapter]: { + properties: { + // Setting the series and corresponding labels to initially display on the chart + series: [ + { + id: "series-1", + label: "Nur-Sultan", + selectedSeriesId: "series-1", + }, + { + id: "series-2", + label: "Brno", + selectedSeriesId: "series-2", + }, + { + id: "series-3", + label: "Lisbon", + selectedSeriesId: "series-3", + }, + { + id: "series-4", + label: "Austin", + selectedSeriesId: "series-4", + }, + ] as ITimeseriesItemConfiguration[], + }, + } as Partial, + }, + properties: { + // Setting the general chart configuration + configuration: { + legendPlacement: LegendPlacement.Right, + enableZoom: true, + // Setting the preset to stacked bar + preset: TimeseriesChartPreset.StackedBar, + scales: { + x: { + type: TimeseriesScaleType.TimeInterval, + properties: { + interval: 24 * 60 * 60, + }, + } as ITimeseriesScaleConfig, + }, + } as ITimeseriesWidgetConfig, + }, + }, + timeframeSelection: { + properties: { + // Setting the initial timeframe selected in the timeframe bar + timeframe: { + selectedPresetId: "last7Days", + } as ISerializableTimeframe, + minDate: moment().subtract(60, "days").format(), + maxDate: moment().format(), + }, + }, + }, + }, + }, +]; + +// using startOf("day") so that each band for the bar chart corresponds to a calendar day +const startOfToday = moment().startOf("day").toDate(); + +export const getData = (): ITimeseriesWidgetData[] => [ + { + id: "series-1", + name: "Nur-Sultan", + description: "'link' only", + link: "https://en.wikipedia.org/wiki/Nur-Sultan", + data: [ + { x: moment(startOfToday).subtract(59, "day").toDate(), y: 35 }, + { x: moment(startOfToday).subtract(58, "day").toDate(), y: 33 }, + { x: moment(startOfToday).subtract(57, "day").toDate(), y: 40 }, + { x: moment(startOfToday).subtract(56, "day").toDate(), y: 35 }, + { x: moment(startOfToday).subtract(55, "day").toDate(), y: 30 }, + { x: moment(startOfToday).subtract(54, "day").toDate(), y: 35 }, + { x: moment(startOfToday).subtract(53, "day").toDate(), y: 15 }, + { x: moment(startOfToday).subtract(52, "day").toDate(), y: 30 }, + { x: moment(startOfToday).subtract(51, "day").toDate(), y: 35 }, + { x: moment(startOfToday).subtract(50, "day").toDate(), y: 34 }, + { x: moment(startOfToday).subtract(49, "day").toDate(), y: 35 }, + { x: moment(startOfToday).subtract(48, "day").toDate(), y: 33 }, + { x: moment(startOfToday).subtract(47, "day").toDate(), y: 40 }, + { x: moment(startOfToday).subtract(46, "day").toDate(), y: 35 }, + { x: moment(startOfToday).subtract(45, "day").toDate(), y: 30 }, + { x: moment(startOfToday).subtract(44, "day").toDate(), y: 35 }, + { x: moment(startOfToday).subtract(43, "day").toDate(), y: 15 }, + { x: moment(startOfToday).subtract(42, "day").toDate(), y: 30 }, + { x: moment(startOfToday).subtract(41, "day").toDate(), y: 35 }, + { x: moment(startOfToday).subtract(40, "day").toDate(), y: 34 }, + { x: moment(startOfToday).subtract(39, "day").toDate(), y: 35 }, + { x: moment(startOfToday).subtract(38, "day").toDate(), y: 33 }, + { x: moment(startOfToday).subtract(37, "day").toDate(), y: 40 }, + { x: moment(startOfToday).subtract(36, "day").toDate(), y: 35 }, + { x: moment(startOfToday).subtract(35, "day").toDate(), y: 30 }, + { x: moment(startOfToday).subtract(34, "day").toDate(), y: 35 }, + { x: moment(startOfToday).subtract(33, "day").toDate(), y: 15 }, + { x: moment(startOfToday).subtract(32, "day").toDate(), y: 30 }, + { x: moment(startOfToday).subtract(31, "day").toDate(), y: 35 }, + { x: moment(startOfToday).subtract(30, "day").toDate(), y: 34 }, + { x: moment(startOfToday).subtract(29, "day").toDate(), y: 35 }, + { x: moment(startOfToday).subtract(28, "day").toDate(), y: 33 }, + { x: moment(startOfToday).subtract(27, "day").toDate(), y: 40 }, + { x: moment(startOfToday).subtract(26, "day").toDate(), y: 35 }, + { x: moment(startOfToday).subtract(25, "day").toDate(), y: 30 }, + { x: moment(startOfToday).subtract(24, "day").toDate(), y: 35 }, + { x: moment(startOfToday).subtract(23, "day").toDate(), y: 15 }, + { x: moment(startOfToday).subtract(22, "day").toDate(), y: 30 }, + { x: moment(startOfToday).subtract(21, "day").toDate(), y: 35 }, + { x: moment(startOfToday).subtract(20, "day").toDate(), y: 34 }, + { x: moment(startOfToday).subtract(19, "day").toDate(), y: 35 }, + { x: moment(startOfToday).subtract(18, "day").toDate(), y: 33 }, + { x: moment(startOfToday).subtract(17, "day").toDate(), y: 40 }, + { x: moment(startOfToday).subtract(16, "day").toDate(), y: 35 }, + { x: moment(startOfToday).subtract(15, "day").toDate(), y: 30 }, + { x: moment(startOfToday).subtract(14, "day").toDate(), y: 35 }, + { x: moment(startOfToday).subtract(13, "day").toDate(), y: 15 }, + { x: moment(startOfToday).subtract(12, "day").toDate(), y: 30 }, + { x: moment(startOfToday).subtract(11, "day").toDate(), y: 35 }, + { x: moment(startOfToday).subtract(10, "day").toDate(), y: 34 }, + { x: moment(startOfToday).subtract(9, "day").toDate(), y: 33 }, + { x: moment(startOfToday).subtract(8, "day").toDate(), y: 35 }, + { x: moment(startOfToday).subtract(7, "day").toDate(), y: 36 }, + { x: moment(startOfToday).subtract(6, "day").toDate(), y: 34 }, + { x: moment(startOfToday).subtract(5, "day").toDate(), y: 33 }, + { x: moment(startOfToday).subtract(4, "day").toDate(), y: 30 }, + { x: moment(startOfToday).subtract(3, "day").toDate(), y: 32 }, + { x: moment(startOfToday).subtract(2, "day").toDate(), y: 31 }, + { x: moment(startOfToday).subtract(1, "day").toDate(), y: 34 }, + { x: moment(startOfToday).toDate(), y: 25 }, + ], + }, + { + id: "series-2", + name: "Brno", + description: "'link' and 'secondaryLink'", + link: "https://en.wikipedia.org/wiki/Brno", + secondaryLink: "https://en.wikipedia.org/wiki/Europe", + data: [ + { x: moment(startOfToday).subtract(59, "day").toDate(), y: 35 }, + { x: moment(startOfToday).subtract(58, "day").toDate(), y: 33 }, + { x: moment(startOfToday).subtract(57, "day").toDate(), y: 40 }, + { x: moment(startOfToday).subtract(56, "day").toDate(), y: 35 }, + { x: moment(startOfToday).subtract(55, "day").toDate(), y: 30 }, + { x: moment(startOfToday).subtract(54, "day").toDate(), y: 35 }, + { x: moment(startOfToday).subtract(53, "day").toDate(), y: 15 }, + { x: moment(startOfToday).subtract(52, "day").toDate(), y: 30 }, + { x: moment(startOfToday).subtract(51, "day").toDate(), y: 35 }, + { x: moment(startOfToday).subtract(50, "day").toDate(), y: 34 }, + { x: moment(startOfToday).subtract(49, "day").toDate(), y: 35 }, + { x: moment(startOfToday).subtract(48, "day").toDate(), y: 33 }, + { x: moment(startOfToday).subtract(47, "day").toDate(), y: 40 }, + { x: moment(startOfToday).subtract(46, "day").toDate(), y: 35 }, + { x: moment(startOfToday).subtract(45, "day").toDate(), y: 30 }, + { x: moment(startOfToday).subtract(44, "day").toDate(), y: 35 }, + { x: moment(startOfToday).subtract(43, "day").toDate(), y: 15 }, + { x: moment(startOfToday).subtract(42, "day").toDate(), y: 30 }, + { x: moment(startOfToday).subtract(41, "day").toDate(), y: 35 }, + { x: moment(startOfToday).subtract(40, "day").toDate(), y: 34 }, + { x: moment(startOfToday).subtract(39, "day").toDate(), y: 35 }, + { x: moment(startOfToday).subtract(38, "day").toDate(), y: 33 }, + { x: moment(startOfToday).subtract(37, "day").toDate(), y: 40 }, + { x: moment(startOfToday).subtract(36, "day").toDate(), y: 35 }, + { x: moment(startOfToday).subtract(35, "day").toDate(), y: 30 }, + { x: moment(startOfToday).subtract(34, "day").toDate(), y: 35 }, + { x: moment(startOfToday).subtract(33, "day").toDate(), y: 15 }, + { x: moment(startOfToday).subtract(32, "day").toDate(), y: 30 }, + { x: moment(startOfToday).subtract(31, "day").toDate(), y: 35 }, + { x: moment(startOfToday).subtract(30, "day").toDate(), y: 34 }, + { x: moment(startOfToday).subtract(29, "day").toDate(), y: 35 }, + { x: moment(startOfToday).subtract(28, "day").toDate(), y: 33 }, + { x: moment(startOfToday).subtract(27, "day").toDate(), y: 40 }, + { x: moment(startOfToday).subtract(26, "day").toDate(), y: 35 }, + { x: moment(startOfToday).subtract(25, "day").toDate(), y: 30 }, + { x: moment(startOfToday).subtract(24, "day").toDate(), y: 35 }, + { x: moment(startOfToday).subtract(23, "day").toDate(), y: 15 }, + { x: moment(startOfToday).subtract(22, "day").toDate(), y: 30 }, + { x: moment(startOfToday).subtract(21, "day").toDate(), y: 35 }, + { x: moment(startOfToday).subtract(20, "day").toDate(), y: 34 }, + { x: moment(startOfToday).subtract(19, "day").toDate(), y: 64 }, + { x: moment(startOfToday).subtract(18, "day").toDate(), y: 70 }, + { x: moment(startOfToday).subtract(17, "day").toDate(), y: 55 }, + { x: moment(startOfToday).subtract(16, "day").toDate(), y: 55 }, + { x: moment(startOfToday).subtract(15, "day").toDate(), y: 45 }, + { x: moment(startOfToday).subtract(14, "day").toDate(), y: 10 }, + { x: moment(startOfToday).subtract(13, "day").toDate(), y: 65 }, + { x: moment(startOfToday).subtract(12, "day").toDate(), y: 35 }, + { x: moment(startOfToday).subtract(11, "day").toDate(), y: 60 }, + { x: moment(startOfToday).subtract(10, "day").toDate(), y: 61 }, + { x: moment(startOfToday).subtract(9, "day").toDate(), y: 65 }, + { x: moment(startOfToday).subtract(8, "day").toDate(), y: 63 }, + { x: moment(startOfToday).subtract(7, "day").toDate(), y: 58 }, + { x: moment(startOfToday).subtract(6, "day").toDate(), y: 64 }, + { x: moment(startOfToday).subtract(5, "day").toDate(), y: 63 }, + { x: moment(startOfToday).subtract(4, "day").toDate(), y: 60 }, + { x: moment(startOfToday).subtract(3, "day").toDate(), y: 62 }, + { x: moment(startOfToday).subtract(2, "day").toDate(), y: 61 }, + { x: moment(startOfToday).subtract(1, "day").toDate(), y: 62 }, + { x: moment(startOfToday).toDate(), y: 55 }, + ], + }, + { + id: "series-3", + name: "Lisbon", + description: "'secondaryLink' only", + secondaryLink: "https://en.wikipedia.org/wiki/Lisbon", + data: [ + { x: moment(startOfToday).subtract(59, "day").toDate(), y: 35 }, + { x: moment(startOfToday).subtract(58, "day").toDate(), y: 33 }, + { x: moment(startOfToday).subtract(57, "day").toDate(), y: 40 }, + { x: moment(startOfToday).subtract(56, "day").toDate(), y: 35 }, + { x: moment(startOfToday).subtract(55, "day").toDate(), y: 30 }, + { x: moment(startOfToday).subtract(54, "day").toDate(), y: 35 }, + { x: moment(startOfToday).subtract(53, "day").toDate(), y: 15 }, + { x: moment(startOfToday).subtract(52, "day").toDate(), y: 30 }, + { x: moment(startOfToday).subtract(51, "day").toDate(), y: 35 }, + { x: moment(startOfToday).subtract(50, "day").toDate(), y: 34 }, + { x: moment(startOfToday).subtract(49, "day").toDate(), y: 35 }, + { x: moment(startOfToday).subtract(48, "day").toDate(), y: 33 }, + { x: moment(startOfToday).subtract(47, "day").toDate(), y: 40 }, + { x: moment(startOfToday).subtract(46, "day").toDate(), y: 35 }, + { x: moment(startOfToday).subtract(45, "day").toDate(), y: 30 }, + { x: moment(startOfToday).subtract(44, "day").toDate(), y: 35 }, + { x: moment(startOfToday).subtract(43, "day").toDate(), y: 15 }, + { x: moment(startOfToday).subtract(42, "day").toDate(), y: 30 }, + { x: moment(startOfToday).subtract(41, "day").toDate(), y: 35 }, + { x: moment(startOfToday).subtract(40, "day").toDate(), y: 34 }, + { x: moment(startOfToday).subtract(39, "day").toDate(), y: 35 }, + { x: moment(startOfToday).subtract(38, "day").toDate(), y: 33 }, + { x: moment(startOfToday).subtract(37, "day").toDate(), y: 40 }, + { x: moment(startOfToday).subtract(36, "day").toDate(), y: 35 }, + { x: moment(startOfToday).subtract(35, "day").toDate(), y: 30 }, + { x: moment(startOfToday).subtract(34, "day").toDate(), y: 35 }, + { x: moment(startOfToday).subtract(33, "day").toDate(), y: 15 }, + { x: moment(startOfToday).subtract(32, "day").toDate(), y: 30 }, + { x: moment(startOfToday).subtract(31, "day").toDate(), y: 35 }, + { x: moment(startOfToday).subtract(30, "day").toDate(), y: 34 }, + { x: moment(startOfToday).subtract(29, "day").toDate(), y: 35 }, + { x: moment(startOfToday).subtract(28, "day").toDate(), y: 33 }, + { x: moment(startOfToday).subtract(27, "day").toDate(), y: 40 }, + { x: moment(startOfToday).subtract(26, "day").toDate(), y: 35 }, + { x: moment(startOfToday).subtract(25, "day").toDate(), y: 30 }, + { x: moment(startOfToday).subtract(24, "day").toDate(), y: 35 }, + { x: moment(startOfToday).subtract(23, "day").toDate(), y: 15 }, + { x: moment(startOfToday).subtract(22, "day").toDate(), y: 30 }, + { x: moment(startOfToday).subtract(21, "day").toDate(), y: 35 }, + { x: moment(startOfToday).subtract(20, "day").toDate(), y: 34 }, + { x: moment(startOfToday).subtract(19, "day").toDate(), y: 80 }, + { x: moment(startOfToday).subtract(18, "day").toDate(), y: 70 }, + { x: moment(startOfToday).subtract(17, "day").toDate(), y: 95 }, + { x: moment(startOfToday).subtract(16, "day").toDate(), y: 90 }, + { x: moment(startOfToday).subtract(15, "day").toDate(), y: 85 }, + { x: moment(startOfToday).subtract(14, "day").toDate(), y: 70 }, + { x: moment(startOfToday).subtract(13, "day").toDate(), y: 75 }, + { x: moment(startOfToday).subtract(12, "day").toDate(), y: 69 }, + { x: moment(startOfToday).subtract(11, "day").toDate(), y: 75 }, + { x: moment(startOfToday).subtract(10, "day").toDate(), y: 81 }, + { x: moment(startOfToday).subtract(9, "day").toDate(), y: 93 }, + { x: moment(startOfToday).subtract(8, "day").toDate(), y: 83 }, + { x: moment(startOfToday).subtract(7, "day").toDate(), y: 70 }, + { x: moment(startOfToday).subtract(6, "day").toDate(), y: 74 }, + { x: moment(startOfToday).subtract(5, "day").toDate(), y: 73 }, + { x: moment(startOfToday).subtract(4, "day").toDate(), y: 68 }, + { x: moment(startOfToday).subtract(3, "day").toDate(), y: 72 }, + { x: moment(startOfToday).subtract(2, "day").toDate(), y: 61 }, + { x: moment(startOfToday).subtract(1, "day").toDate(), y: 69 }, + { x: moment(startOfToday).toDate(), y: 60 }, + ], + }, + { + id: "series-4", + name: "Austin", + description: "No links", + data: [ + { x: moment(startOfToday).subtract(59, "day").toDate(), y: 25 }, + { x: moment(startOfToday).subtract(58, "day").toDate(), y: 43 }, + { x: moment(startOfToday).subtract(57, "day").toDate(), y: 40 }, + { x: moment(startOfToday).subtract(56, "day").toDate(), y: 65 }, + { x: moment(startOfToday).subtract(55, "day").toDate(), y: 30 }, + { x: moment(startOfToday).subtract(54, "day").toDate(), y: 25 }, + { x: moment(startOfToday).subtract(53, "day").toDate(), y: 45 }, + { x: moment(startOfToday).subtract(52, "day").toDate(), y: 30 }, + { x: moment(startOfToday).subtract(51, "day").toDate(), y: 85 }, + { x: moment(startOfToday).subtract(50, "day").toDate(), y: 74 }, + { x: moment(startOfToday).subtract(49, "day").toDate(), y: 55 }, + { x: moment(startOfToday).subtract(48, "day").toDate(), y: 23 }, + { x: moment(startOfToday).subtract(47, "day").toDate(), y: 40 }, + { x: moment(startOfToday).subtract(46, "day").toDate(), y: 15 }, + { x: moment(startOfToday).subtract(45, "day").toDate(), y: 20 }, + { x: moment(startOfToday).subtract(44, "day").toDate(), y: 65 }, + { x: moment(startOfToday).subtract(43, "day").toDate(), y: 25 }, + { x: moment(startOfToday).subtract(42, "day").toDate(), y: 40 }, + { x: moment(startOfToday).subtract(41, "day").toDate(), y: 25 }, + { x: moment(startOfToday).subtract(40, "day").toDate(), y: 54 }, + { x: moment(startOfToday).subtract(39, "day").toDate(), y: 65 }, + { x: moment(startOfToday).subtract(38, "day").toDate(), y: 33 }, + { x: moment(startOfToday).subtract(37, "day").toDate(), y: 50 }, + { x: moment(startOfToday).subtract(36, "day").toDate(), y: 45 }, + { x: moment(startOfToday).subtract(35, "day").toDate(), y: 20 }, + { x: moment(startOfToday).subtract(34, "day").toDate(), y: 25 }, + { x: moment(startOfToday).subtract(33, "day").toDate(), y: 35 }, + { x: moment(startOfToday).subtract(32, "day").toDate(), y: 20 }, + { x: moment(startOfToday).subtract(31, "day").toDate(), y: 35 }, + { x: moment(startOfToday).subtract(30, "day").toDate(), y: 14 }, + { x: moment(startOfToday).subtract(29, "day").toDate(), y: 55 }, + { x: moment(startOfToday).subtract(28, "day").toDate(), y: 23 }, + { x: moment(startOfToday).subtract(27, "day").toDate(), y: 10 }, + { x: moment(startOfToday).subtract(26, "day").toDate(), y: 5 }, + { x: moment(startOfToday).subtract(25, "day").toDate(), y: 20 }, + { x: moment(startOfToday).subtract(24, "day").toDate(), y: 35 }, + { x: moment(startOfToday).subtract(23, "day").toDate(), y: 15 }, + { x: moment(startOfToday).subtract(22, "day").toDate(), y: 30 }, + { x: moment(startOfToday).subtract(21, "day").toDate(), y: 35 }, + { x: moment(startOfToday).subtract(20, "day").toDate(), y: 34 }, + { x: moment(startOfToday).subtract(19, "day").toDate(), y: 50 }, + { x: moment(startOfToday).subtract(18, "day").toDate(), y: 60 }, + { x: moment(startOfToday).subtract(17, "day").toDate(), y: 95 }, + { x: moment(startOfToday).subtract(16, "day").toDate(), y: 80 }, + { x: moment(startOfToday).subtract(15, "day").toDate(), y: 65 }, + { x: moment(startOfToday).subtract(14, "day").toDate(), y: 80 }, + { x: moment(startOfToday).subtract(13, "day").toDate(), y: 85 }, + { x: moment(startOfToday).subtract(12, "day").toDate(), y: 69 }, + { x: moment(startOfToday).subtract(11, "day").toDate(), y: 65 }, + { x: moment(startOfToday).subtract(10, "day").toDate(), y: 71 }, + { x: moment(startOfToday).subtract(9, "day").toDate(), y: 73 }, + { x: moment(startOfToday).subtract(8, "day").toDate(), y: 43 }, + { x: moment(startOfToday).subtract(7, "day").toDate(), y: 70 }, + { x: moment(startOfToday).subtract(6, "day").toDate(), y: 84 }, + { x: moment(startOfToday).subtract(5, "day").toDate(), y: 73 }, + { x: moment(startOfToday).subtract(4, "day").toDate(), y: 38 }, + { x: moment(startOfToday).subtract(3, "day").toDate(), y: 72 }, + { x: moment(startOfToday).subtract(2, "day").toDate(), y: 81 }, + { x: moment(startOfToday).subtract(1, "day").toDate(), y: 59 }, + { x: moment(startOfToday).toDate(), y: 60 }, + ], + }, +]; +// Setting the widget dimensions and position (this is for gridster) +const positions: Record = { + [widgetConfigs[0].id]: { + cols: 6, + rows: 6, + y: 0, + x: 0, + }, + [widgetConfigs[1].id]: { + cols: 6, + rows: 6, + y: 0, + x: 6, + }, +}; +\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\`, + "widget-types/timeseries/timeseries-widget-status-bar-example/timeseries-widget-status-bar-example.component.ts": \\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\`// © 2022 SolarWinds Worldwide, LLC. All rights reserved. +// +// Permission is hereby granted, free of charge, to any person obtaining a copy +// of this software and associated documentation files (the "Software"), to +// deal in the Software without restriction, including without limitation the +// rights to use, copy, modify, merge, publish, distribute, sublicense, and/or +// sell copies of the Software, and to permit persons to whom the Software is +// furnished to do so, subject to the following conditions: +// +// The above copyright notice and this permission notice shall be included in +// all copies or substantial portions of the Software. +// +// THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR +// IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, +// FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE +// AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER +// LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, +// OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN +// THE SOFTWARE. + +import { + ChangeDetectorRef, + Component, + Injectable, + OnInit, +} from "@angular/core"; +import { GridsterConfig, GridsterItem } from "angular-gridster2"; +import keyBy from "lodash/keyBy"; +import moment, { Moment } from "moment/moment"; +import { BehaviorSubject } from "rxjs"; + +import { + DataSourceService, + IDataSource, + IDataSourceOutput, + INovaFilters, + ITimeframe, +} from "@nova-ui/bits"; +import { CHART_PALETTE_CS_S_EXTENDED } from "@nova-ui/charts"; +import { + applyStatusEndpoints, + DATA_SOURCE, + DEFAULT_PIZZAGNA_ROOT, + IDashboard, + IProviderConfiguration, + ISerializableTimeframe, + ITimeseriesItemConfiguration, + ITimeseriesOutput, + ITimeseriesScaleConfig, + ITimeseriesWidgetConfig, + ITimeseriesWidgetData, + ITimeseriesWidgetSeriesData, + ITimeseriesWidgetStatusData, + IWidget, + LegendPlacement, + PizzagnaLayer, + ProviderRegistryService, + TimeseriesChartPreset, + TimeseriesScaleType, + WellKnownPathKey, + WellKnownProviders, + WidgetTypesService, +} from "@nova-ui/dashboards"; + +/** + * A simple Timeseries data source implementation with continuous (non-interval-based) output + */ +@Injectable() +export class TimeseriesStatusContinuousDataSource + extends DataSourceService + implements IDataSource> +{ + public static providerId = "TimeseriesStatusContinuousDataSource"; + + public busy = new BehaviorSubject(false); + + public async getFilteredData( + filters: INovaFilters + ): Promise< + IDataSourceOutput> + > { + // In this example we're using some static mock data located at the bottom of this file. In a real-world + // scenario, the data for the chart would likely be retrieved via an asynchronous backend call. + const data = getContinuousData(); + let filteredData = data; + + this.busy.next(true); + + // Filtering using the filter registered by the TimeFrameBar + const timeframeFilter = filters.timeframe?.value as ITimeframe; + if (timeframeFilter) { + filteredData = filteredData.map((item: ITimeseriesWidgetData) => ({ + id: item.id, + name: item.name, + description: item.description, + data: item.data.filter( + (seriesData: ITimeseriesWidgetSeriesData) => + filterDates( + seriesData.x, + timeframeFilter.startDatetime, + timeframeFilter.endDatetime + ) + ), + })); + + // apply endpoints on the filtered status data so that when the status chart is zoomed (filtered), + // each status visualizations is ensured to have valid start and end values + filteredData = applyStatusEndpoints( + timeframeFilter, + filteredData, + data + ); + } + + this.busy.next(false); + return { result: { series: filteredData } }; + } +} + +/** + * A simple Timeseries data source implementation with interval-based output + */ +@Injectable() +export class TimeseriesStatusIntervalDataSource + extends DataSourceService + implements IDataSource> +{ + public static providerId = "TimeseriesStatusIntervalDataSource"; + + public busy = new BehaviorSubject(false); + + public async getFilteredData( + filters: INovaFilters + ): Promise< + IDataSourceOutput> + > { + // In this example we're using some static mock data located at the bottom of this file. In a real-world + // scenario, the data for the chart would likely be retrieved via an asynchronous backend call. + const data = getIntervalData(); + let filteredData = data; + + this.busy.next(true); + + // Filtering using the filter registered by the TimeFrameBar + const timeframeFilter = filters.timeframe?.value as ITimeframe; + if (timeframeFilter) { + filteredData = filteredData.map((item: ITimeseriesWidgetData) => ({ + id: item.id, + name: item.name, + description: item.description, + data: item.data.filter( + (seriesData: ITimeseriesWidgetSeriesData) => + filterDates( + seriesData.x, + timeframeFilter.startDatetime, + timeframeFilter.endDatetime + ) + ), + })); + + // Note: There's no need to apply filter endpoints to the status data in this case since we know it's visualized in regular intervals + } + + this.busy.next(false); + return { result: { series: filteredData } }; + } +} + +function filterDates(dateToCheck: Date, startDate: Moment, endDate: Moment) { + const mom = moment(dateToCheck); + return ( + mom.isBetween(startDate, endDate) || + mom.isSame(startDate) || + mom.isSame(endDate) + ); +} + +/** + * A component that instantiates the dashboard + */ +@Component({ + selector: "timeseries-widget-status-bar-example", + templateUrl: "./timeseries-widget-status-bar-example.component.html", + styleUrls: ["./timeseries-widget-status-bar-example.component.less"], + standalone: false, +}) +export class TimeseriesWidgetStatusBarExampleComponent implements OnInit { + // This variable will hold all the data needed to define the layout and behavior of the widgets. + // Pass this to the dashboard component's dashboard input in the template. + public dashboard: IDashboard | undefined; + + // Angular gridster requires a configuration object even if it's empty. + // Pass this to the dashboard component's gridsterConfig input in the template. + public gridsterConfig: GridsterConfig = {}; + + // Boolean passed as an input to the dashboard. When true, widgets can be moved, resized, removed, or edited + public editMode: boolean = false; + + constructor( + // WidgetTypesService provides the widget's necessary structure information + private widgetTypesService: WidgetTypesService, + + // In general, the ProviderRegistryService is used for making entities available for injection into dynamically loaded components. + private providerRegistry: ProviderRegistryService, + private changeDetectorRef: ChangeDetectorRef + ) {} + + public ngOnInit(): void { + // Grabbing the widget's default template which will be needed as a parameter for setNode + const widgetTemplate = this.widgetTypesService.getWidgetType( + "timeseries", + 1 + ); + // Registering our data sources as dropdown options in the widget editor/configurator + // Note: This could also be done in the parent module's constructor so that + // multiple dashboards could have access to the same widget template modification. + this.widgetTypesService.setNode( + // This is the template we grabbed above with getWidgetType + widgetTemplate, + // We are setting the editor/configurator part of the widget template + "configurator", + // This indicates which node you are changing and we want to change + // the data source providers available for selection in the editor. + WellKnownPathKey.DataSourceProviders, + // We are setting the data sources available for selection in the editor + [ + TimeseriesStatusContinuousDataSource.providerId, + TimeseriesStatusIntervalDataSource.providerId, + ] + ); + + // Registering the data source for injection into the widget. + this.providerRegistry.setProviders({ + [TimeseriesStatusContinuousDataSource.providerId]: { + provide: DATA_SOURCE, + useClass: TimeseriesStatusContinuousDataSource, + deps: [], + }, + [TimeseriesStatusIntervalDataSource.providerId]: { + provide: DATA_SOURCE, + useClass: TimeseriesStatusIntervalDataSource, + deps: [], + }, + }); + + this.initializeDashboard(); + } + + /** Used for restoring widgets state */ + public reInitializeDashboard(): void { + // destroys the components and their providers so the dashboard can re init data + this.dashboard = undefined; + this.changeDetectorRef.detectChanges(); + + this.initializeDashboard(); + } + + public initializeDashboard(): void { + // We're using a static configuration object for this example, but this is where + // the widget's configuration could potentially be populated from a database + const widgetsWithStructure = widgetConfigs.map((w) => + this.widgetTypesService.mergeWithWidgetType(w) + ); + const widgetsIndex = keyBy(widgetsWithStructure, (w: IWidget) => w.id); + + // Finally, assigning the variables we created above to the dashboard + this.dashboard = { + positions, + widgets: widgetsIndex, + }; + } +} + +const widgetConfigs: IWidget[] = [ + { + id: "statusChartWidgetId", + type: "timeseries", + pizzagna: { + [PizzagnaLayer.Configuration]: { + [DEFAULT_PIZZAGNA_ROOT]: { + providers: { + [WellKnownProviders.DataSource]: { + // Setting the initially selected data source providerId + providerId: + TimeseriesStatusContinuousDataSource.providerId, + } as IProviderConfiguration, + }, + }, + header: { + properties: { + title: "Status Bar Chart with Continuous (Non-Interval) Scale", + subtitle: "Basic Timeseries Widget", + }, + }, + chart: { + providers: { + [WellKnownProviders.Adapter]: { + properties: { + // Setting the series and corresponding labels to initially display on the chart + series: [ + { + id: "series-1", + label: "Node Status", + selectedSeriesId: "series-1", + }, + { + id: "series-2", + label: "Node Status", + selectedSeriesId: "series-2", + }, + ] as ITimeseriesItemConfiguration[], + }, + } as Partial, + }, + properties: { + configuration: { + legendPlacement: LegendPlacement.Right, + enableZoom: true, + // Setting the preset to status bar + preset: TimeseriesChartPreset.StatusBar, + } as ITimeseriesWidgetConfig, + }, + }, + timeframeSelection: { + properties: { + // Setting the initial timeframe selected in the timeframe bar + timeframe: { + selectedPresetId: "last7Days", + } as ISerializableTimeframe, + maxDate: moment().format(), + }, + }, + }, + }, + }, + { + id: "statusIntervalChartWidgetId", + type: "timeseries", + pizzagna: { + [PizzagnaLayer.Configuration]: { + [DEFAULT_PIZZAGNA_ROOT]: { + providers: { + [WellKnownProviders.DataSource]: { + // Setting the initially selected data source providerId + providerId: + TimeseriesStatusIntervalDataSource.providerId, + } as IProviderConfiguration, + }, + }, + header: { + properties: { + title: "Status Bar Chart with Interval Scale", + subtitle: "Basic Timeseries Widget", + }, + }, + chart: { + providers: { + [WellKnownProviders.Adapter]: { + properties: { + // Setting the series and corresponding labels to initially display on the chart + series: [ + { + id: "series-1", + label: "Node Status", + selectedSeriesId: "series-1", + }, + { + id: "series-2", + label: "Node Status", + selectedSeriesId: "series-2", + }, + ] as ITimeseriesItemConfiguration[], + }, + } as Partial, + }, + properties: { + configuration: { + legendPlacement: LegendPlacement.Right, + enableZoom: true, + // Setting the preset to status bar + preset: TimeseriesChartPreset.StatusBar, + scales: { + x: { + type: TimeseriesScaleType.TimeInterval, + properties: { + // one-day interval in seconds + interval: 24 * 60 * 60, + }, + } as ITimeseriesScaleConfig, + }, + } as ITimeseriesWidgetConfig, + }, + }, + timeframeSelection: { + properties: { + // Setting the initial timeframe selected in the timeframe bar + timeframe: { + selectedPresetId: "last7Days", + } as ISerializableTimeframe, + maxDate: moment().format(), + }, + }, + }, + }, + }, +]; + +export const startOfToday = (): Moment => moment().startOf("day"); + +export const getContinuousData = + (): ITimeseriesWidgetData[] => { + const series: ITimeseriesWidgetData[] = [ + { + id: "series-1", + name: "Node Status", + description: "lastchance.demo.lab", + data: [ + // the 'x' value is set to the time and 'y' to the status at that given time + { + x: startOfToday().subtract(20, "day").toDate(), + y: Status.Warning, + }, + { + x: startOfToday().subtract(19, "day").toDate(), + y: Status.Down, + }, + { + x: startOfToday().subtract(17, "day").toDate(), + y: Status.Critical, + }, + { + x: startOfToday().subtract(16, "day").toDate(), + y: Status.Warning, + }, + { + x: startOfToday().subtract(15, "day").toDate(), + y: Status.Down, + }, + { + x: startOfToday().subtract(14, "day").toDate(), + y: Status.Critical, + }, + { + x: startOfToday().subtract(12, "day").toDate(), + y: Status.Unknown, + }, + { + x: startOfToday().subtract(10, "day").toDate(), + y: Status.Up, + }, + { + x: startOfToday().subtract(9, "day").toDate(), + y: Status.Critical, + }, + { + x: startOfToday().subtract(6, "day").toDate(), + y: Status.Up, + }, + { + x: startOfToday().subtract(3, "day").toDate(), + y: Status.Warning, + }, + { + x: startOfToday().subtract(2, "day").toDate(), + y: Status.Critical, + }, + { + x: startOfToday().subtract(1, "day").toDate(), + y: Status.Up, + }, + // This data point will be ignored and is only here to provide an endpoint for the previous status. + { x: moment().toDate(), y: Status.Up }, + ], + }, + { + id: "series-2", + name: "Node Status", + description: "newhope.demo.lab", + data: [ + { + x: startOfToday().subtract(19, "day").toDate(), + y: Status.Critical, + }, + { + x: startOfToday().subtract(18, "day").toDate(), + y: Status.Unknown, + }, + { + x: startOfToday().subtract(17, "day").toDate(), + y: Status.Warning, + }, + { + x: startOfToday().subtract(15, "day").toDate(), + y: Status.Down, + }, + { + x: startOfToday().subtract(8, "day").toDate(), + y: Status.Critical, + }, + { + x: startOfToday().subtract(7, "day").toDate(), + y: Status.Down, + }, + { + x: startOfToday().subtract(6, "day").toDate(), + y: Status.Up, + }, + { + x: startOfToday().subtract(5, "day").toDate(), + y: Status.Critical, + }, + { + x: startOfToday().subtract(4, "day").toDate(), + y: Status.Up, + }, + { + x: startOfToday().subtract(3, "day").toDate(), + y: Status.Warning, + }, + { + x: startOfToday().subtract(2, "day").toDate(), + y: Status.Up, + }, + { + x: startOfToday().subtract(1, "day").toDate(), + y: Status.Down, + }, + // This data point will be ignored and is only here to provide an endpoint for the previous status. + { x: moment().toDate(), y: Status.Down }, + ], + }, + ]; + + for (const s of series) { + // here are we setting the color and icon associated to the status for each data point + s.data = s.data.map((d: any, i: number) => ({ + ...d, + color: statusColors[d.y as Status], + // The thickness of the line is dependant on the status. If the status equals 'Up' then 'thick' is set to false. + thick: d.y !== Status.Up, + icon: "status_" + d.y, + })); + } + + return series; + }; + +// Note that the output of this function is spaced evenly at one-day intervals +export const getIntervalData = + (): ITimeseriesWidgetData[] => { + const series: ITimeseriesWidgetData[] = [ + { + id: "series-1", + name: "Node Status", + description: "lastchance.demo.lab", + data: [ + // the 'x' value is set to the time and 'y' to the status at that given time + { + x: startOfToday().subtract(20, "day").toDate(), + y: Status.Up, + }, + { + x: startOfToday().subtract(19, "day").toDate(), + y: Status.Critical, + }, + { + x: startOfToday().subtract(18, "day").toDate(), + y: Status.Warning, + }, + { + x: startOfToday().subtract(17, "day").toDate(), + y: Status.Down, + }, + { + x: startOfToday().subtract(16, "day").toDate(), + y: Status.Critical, + }, + { + x: startOfToday().subtract(15, "day").toDate(), + y: Status.Down, + }, + { + x: startOfToday().subtract(14, "day").toDate(), + y: Status.Up, + }, + { + x: startOfToday().subtract(13, "day").toDate(), + y: Status.Warning, + }, + { + x: startOfToday().subtract(12, "day").toDate(), + y: Status.Up, + }, + { + x: startOfToday().subtract(11, "day").toDate(), + y: Status.Critical, + }, + { + x: startOfToday().subtract(10, "day").toDate(), + y: Status.Up, + }, + { + x: startOfToday().subtract(9, "day").toDate(), + y: Status.Down, + }, + { + x: startOfToday().subtract(8, "day").toDate(), + y: Status.Critical, + }, + { + x: startOfToday().subtract(7, "day").toDate(), + y: Status.Warning, + }, + { + x: startOfToday().subtract(6, "day").toDate(), + y: Status.Down, + }, + { + x: startOfToday().subtract(5, "day").toDate(), + y: Status.Critical, + }, + { + x: startOfToday().subtract(4, "day").toDate(), + y: Status.Down, + }, + { + x: startOfToday().subtract(3, "day").toDate(), + y: Status.Up, + }, + { + x: startOfToday().subtract(2, "day").toDate(), + y: Status.Warning, + }, + { + x: startOfToday().subtract(1, "day").toDate(), + y: Status.Critical, + }, + { x: startOfToday().toDate(), y: Status.Up }, + ], + }, + { + id: "series-2", + name: "Node Status", + description: "newhope.demo.lab", + data: [ + { + x: startOfToday().subtract(20, "day").toDate(), + y: Status.Up, + }, + { + x: startOfToday().subtract(19, "day").toDate(), + y: Status.Up, + }, + { + x: startOfToday().subtract(18, "day").toDate(), + y: Status.Down, + }, + { + x: startOfToday().subtract(17, "day").toDate(), + y: Status.Critical, + }, + { + x: startOfToday().subtract(16, "day").toDate(), + y: Status.Down, + }, + { + x: startOfToday().subtract(15, "day").toDate(), + y: Status.Up, + }, + { + x: startOfToday().subtract(14, "day").toDate(), + y: Status.Critical, + }, + { + x: startOfToday().subtract(13, "day").toDate(), + y: Status.Up, + }, + { + x: startOfToday().subtract(12, "day").toDate(), + y: Status.Critical, + }, + { + x: startOfToday().subtract(11, "day").toDate(), + y: Status.Warning, + }, + { + x: startOfToday().subtract(10, "day").toDate(), + y: Status.Up, + }, + { + x: startOfToday().subtract(9, "day").toDate(), + y: Status.Down, + }, + { + x: startOfToday().subtract(8, "day").toDate(), + y: Status.Up, + }, + { + x: startOfToday().subtract(7, "day").toDate(), + y: Status.Down, + }, + { + x: startOfToday().subtract(6, "day").toDate(), + y: Status.Critical, + }, + { + x: startOfToday().subtract(5, "day").toDate(), + y: Status.Down, + }, + { + x: startOfToday().subtract(4, "day").toDate(), + y: Status.Up, + }, + { + x: startOfToday().subtract(3, "day").toDate(), + y: Status.Critical, + }, + { + x: startOfToday().subtract(2, "day").toDate(), + y: Status.Up, + }, + { + x: startOfToday().subtract(1, "day").toDate(), + y: Status.Warning, + }, + { x: startOfToday().toDate(), y: Status.Critical }, + ], + }, + ]; + + for (const s of series) { + // here are we setting the color and icon associated to the status for each data point + s.data = s.data.map((d: any, i: number) => ({ + ...d, + color: statusColors[d.y as Status], + // The thickness of the line is dependant on the status. If the status equals 'Up' then 'thick' is set to false. + thick: d.y !== Status.Up, + icon: "status_" + d.y, + })); + } + + return series; + }; + +// An enumeration of statuses +enum Status { + Unknown = "unknown", + Up = "up", + Warning = "warning", + Down = "down", + Critical = "critical", +} + +// This is the map used for setting the color of each status bar +const statusColors: Record = { + [Status.Unknown]: CHART_PALETTE_CS_S_EXTENDED[6], + [Status.Up]: CHART_PALETTE_CS_S_EXTENDED[8], + [Status.Warning]: CHART_PALETTE_CS_S_EXTENDED[4], + [Status.Down]: CHART_PALETTE_CS_S_EXTENDED[0], + [Status.Critical]: CHART_PALETTE_CS_S_EXTENDED[2], +}; + +// Setting the widget dimensions and position (this is for gridster) +const positions: Record = { + [widgetConfigs[0].id]: { + cols: 12, + rows: 4, + y: 0, + x: 0, + }, + [widgetConfigs[1].id]: { + cols: 12, + rows: 4, + y: 4, + x: 0, + }, +}; +\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\`, + "widget-types/view-components/kpi-tile-view-basic/kpi-tile-view-basic-example.component.ts": \\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\`// © 2022 SolarWinds Worldwide, LLC. All rights reserved. +// +// Permission is hereby granted, free of charge, to any person obtaining a copy +// of this software and associated documentation files (the "Software"), to +// deal in the Software without restriction, including without limitation the +// rights to use, copy, modify, merge, publish, distribute, sublicense, and/or +// sell copies of the Software, and to permit persons to whom the Software is +// furnished to do so, subject to the following conditions: +// +// The above copyright notice and this permission notice shall be included in +// all copies or substantial portions of the Software. +// +// THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR +// IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, +// FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE +// AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER +// LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, +// OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN +// THE SOFTWARE. + +import { Component } from "@angular/core"; + +/** + * Basic KPI Tile View example demonstrating standalone usage + * without any Pizzagna framework dependencies. + */ +@Component({ + selector: "kpi-tile-view-basic-example", + templateUrl: "./kpi-tile-view-basic-example.component.html", + standalone: false, +}) +export class KpiTileViewBasicExampleComponent {} +\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\`, + "widget-types/view-components/kpi-tile-view-interactive/kpi-tile-view-interactive-example.component.ts": \\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\`// © 2022 SolarWinds Worldwide, LLC. All rights reserved. +// +// Permission is hereby granted, free of charge, to any person obtaining a copy +// of this software and associated documentation files (the "Software"), to +// deal in the Software without restriction, including without limitation the +// rights to use, copy, modify, merge, publish, distribute, sublicense, and/or +// sell copies of the Software, and to permit persons to whom the Software is +// furnished to do so, subject to the following conditions: +// +// The above copyright notice and this permission notice shall be included in +// all copies or substantial portions of the Software. +// +// THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR +// IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, +// FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE +// AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER +// LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, +// OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN +// THE SOFTWARE. + +import { Component, TemplateRef, ViewChild } from "@angular/core"; + +/** + * Interactive KPI Tile View example with custom value formatting + * and click event handling. + */ +@Component({ + selector: "kpi-tile-view-interactive-example", + templateUrl: "./kpi-tile-view-interactive-example.component.html", + standalone: false, +}) +export class KpiTileViewInteractiveExampleComponent { + public currentValue = 1_247; + public lastClickedTile = ""; + + @ViewChild("customValueTpl", { static: true }) + public customValueTpl: TemplateRef; + + public onTileClick(): void { + this.lastClickedTile = "Active Sessions"; + } + + public onUptimeClick(): void { + this.lastClickedTile = "Uptime"; + } +} +\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\`, + "widget-types/view-components/proportional-chart-view-bar/proportional-chart-view-bar-example.component.ts": \\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\`// © 2022 SolarWinds Worldwide, LLC. All rights reserved. +// +// Permission is hereby granted, free of charge, to any person obtaining a copy +// of this software and associated documentation files (the "Software"), to +// deal in the Software without restriction, including without limitation the +// rights to use, copy, modify, merge, publish, distribute, sublicense, and/or +// sell copies of the Software, and to permit persons to whom the Software is +// furnished to do so, subject to the following conditions: +// +// The above copyright notice and this permission notice shall be included in +// all copies or substantial portions of the Software. +// +// THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR +// IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, +// FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE +// AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER +// LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, +// OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN +// THE SOFTWARE. + +import { Component } from "@angular/core"; + +import { IProportionalDataItem } from "@nova-ui/dashboards"; + +/** + * Proportional Chart View - Bar chart with interaction. + * Demonstrates horizontal bar chart variant with click handling. + */ +@Component({ + selector: "proportional-chart-view-bar-example", + templateUrl: "./proportional-chart-view-bar-example.component.html", + standalone: false, +}) +export class ProportionalChartViewBarExampleComponent { + public lastClicked: IProportionalDataItem | null = null; + + public colors: Array = [ + "#0058e9", + "#2cc079", + "#f3a002", + "#dc3545", + "#8a2be2", + ]; + + public chartData: Array = [ + { id: "chrome", name: "Chrome", value: 64 }, + { id: "firefox", name: "Firefox", value: 18 }, + { id: "safari", name: "Safari", value: 10 }, + { id: "edge", name: "Edge", value: 5 }, + { id: "other", name: "Other", value: 3 }, + ]; + + public onItemClick(item: IProportionalDataItem): void { + this.lastClicked = item; + } +} +\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\`, + "widget-types/view-components/proportional-chart-view-donut/proportional-chart-view-donut-example.component.ts": \\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\`// © 2022 SolarWinds Worldwide, LLC. All rights reserved. +// +// Permission is hereby granted, free of charge, to any person obtaining a copy +// of this software and associated documentation files (the "Software"), to +// deal in the Software without restriction, including without limitation the +// rights to use, copy, modify, merge, publish, distribute, sublicense, and/or +// sell copies of the Software, and to permit persons to whom the Software is +// furnished to do so, subject to the following conditions: +// +// The above copyright notice and this permission notice shall be included in +// all copies or substantial portions of the Software. +// +// THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR +// IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, +// FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE +// AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER +// LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, +// OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN +// THE SOFTWARE. + +import { Component } from "@angular/core"; + +import { IProportionalDataItem } from "@nova-ui/dashboards"; + +/** + * Proportional Chart View - Donut example. + * Demonstrates standalone usage of the proportional chart view + * without any Pizzagna framework dependencies. + */ +@Component({ + selector: "proportional-chart-view-donut-example", + templateUrl: "./proportional-chart-view-donut-example.component.html", + standalone: false, +}) +export class ProportionalChartViewDonutExampleComponent { + public colors: Record = { + down: "#dc3545", + up: "#2cc079", + warning: "#f3a002", + unknown: "#707070", + }; + + public chartData: Array = [ + { id: "up", name: "Up", value: 78 }, + { id: "down", name: "Down", value: 8 }, + { id: "warning", name: "Warning", value: 12 }, + { id: "unknown", name: "Unknown", value: 2 }, + ]; +} +\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\`, + "widget-types/view-components/view-components-docs.component.ts": \\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\`// © 2022 SolarWinds Worldwide, LLC. All rights reserved. +// +// Permission is hereby granted, free of charge, to any person obtaining a copy +// of this software and associated documentation files (the "Software"), to +// deal in the Software without restriction, including without limitation the +// rights to use, copy, modify, merge, publish, distribute, sublicense, and/or +// sell copies of the Software, and to permit persons to whom the Software is +// furnished to do so, subject to the following conditions: +// +// The above copyright notice and this permission notice shall be included in +// all copies or substantial portions of the Software. +// +// THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR +// IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, +// FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE +// AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER +// LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, +// OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN +// THE SOFTWARE. + +import { Component } from "@angular/core"; + +@Component({ + selector: "nui-view-components-docs", + templateUrl: "./view-components-docs.component.html", + standalone: false, +}) +export class ViewComponentsDocsComponent {} +\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\`, + "widget-types/view-components/view-components-docs.module.ts": \\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\`// © 2022 SolarWinds Worldwide, LLC. All rights reserved. +// +// Permission is hereby granted, free of charge, to any person obtaining a copy +// of this software and associated documentation files (the "Software"), to +// deal in the Software without restriction, including without limitation the +// rights to use, copy, modify, merge, publish, distribute, sublicense, and/or +// sell copies of the Software, and to permit persons to whom the Software is +// furnished to do so, subject to the following conditions: +// +// The above copyright notice and this permission notice shall be included in +// all copies or substantial portions of the Software. +// +// THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR +// IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, +// FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE +// AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER +// LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, +// OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN +// THE SOFTWARE. + +import { CommonModule } from "@angular/common"; +import { NgModule } from "@angular/core"; +import { RouterModule, Routes } from "@angular/router"; + +import { + NuiDocsModule, + NuiIconModule, + NuiMessageModule, + DEMO_PATH_TOKEN, +} from "@nova-ui/bits"; +import { NuiDashboardViewsModule } from "@nova-ui/dashboards"; + +import { getDemoFiles } from "../../../../demo-files-factory"; +import { KpiTileViewBasicExampleComponent } from "./kpi-tile-view-basic/kpi-tile-view-basic-example.component"; +import { KpiTileViewInteractiveExampleComponent } from "./kpi-tile-view-interactive/kpi-tile-view-interactive-example.component"; +import { ProportionalChartViewBarExampleComponent } from "./proportional-chart-view-bar/proportional-chart-view-bar-example.component"; +import { ProportionalChartViewDonutExampleComponent } from "./proportional-chart-view-donut/proportional-chart-view-donut-example.component"; +import { ViewComponentsDocsComponent } from "./view-components-docs.component"; + +const routes: Routes = [ + { + path: "", + component: ViewComponentsDocsComponent, + data: { + srlc: { + hideIndicator: true, + }, + showThemeSwitcher: true, + }, + }, + { + path: "kpi-tile-view-basic", + component: KpiTileViewBasicExampleComponent, + data: { + srlc: { + hideIndicator: true, + }, + }, + }, + { + path: "proportional-chart-view-donut", + component: ProportionalChartViewDonutExampleComponent, + data: { + srlc: { + hideIndicator: true, + }, + }, + }, + { + path: "proportional-chart-view-bar", + component: ProportionalChartViewBarExampleComponent, + data: { + srlc: { + hideIndicator: true, + }, + }, + }, +]; + +@NgModule({ + imports: [ + CommonModule, + RouterModule.forChild(routes), + NuiDocsModule, + NuiMessageModule, + NuiIconModule, + NuiDashboardViewsModule, + ], + declarations: [ + ViewComponentsDocsComponent, + KpiTileViewBasicExampleComponent, + KpiTileViewInteractiveExampleComponent, + ProportionalChartViewDonutExampleComponent, + ProportionalChartViewBarExampleComponent, + ], + providers: [ + { + provide: DEMO_PATH_TOKEN, + useValue: getDemoFiles("view-components"), + }, + ], +}) +export default class ViewComponentsDocsModule {} +\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\`, + "widget-types/widget-types.module.ts": \\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\`// © 2022 SolarWinds Worldwide, LLC. All rights reserved. +// +// Permission is hereby granted, free of charge, to any person obtaining a copy +// of this software and associated documentation files (the "Software"), to +// deal in the Software without restriction, including without limitation the +// rights to use, copy, modify, merge, publish, distribute, sublicense, and/or +// sell copies of the Software, and to permit persons to whom the Software is +// furnished to do so, subject to the following conditions: +// +// The above copyright notice and this permission notice shall be included in +// all copies or substantial portions of the Software. +// +// THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR +// IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, +// FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE +// AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER +// LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, +// OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN +// THE SOFTWARE. + +import { NgModule, Type } from "@angular/core"; +import { RouterModule, Routes } from "@angular/router"; + +import { NuiDocsModule } from "@nova-ui/bits"; +import { + ConfiguratorHeadingService, + NuiDashboardsModule, +} from "@nova-ui/dashboards"; + +export enum WidgetTypesRoute { + kpi = "kpi", + riskScore = "risk-score", + timeseries = "timeseries", + table = "table", + proportional = "proportional", + embedded = "embedded", + drilldown = "drilldown", + viewComponents = "view-components", +} + +const routes: Routes = [ + { + path: WidgetTypesRoute.kpi, + loadChildren: async () => + import("./kpi/kpi-docs.module") as object as Promise>, + data: { + srlc: { + hideIndicator: true, + }, + }, + }, + { + path: WidgetTypesRoute.riskScore, + loadChildren: async () => + import("./risk-score/risk-score-docs.module") as object as Promise< + Type + >, + data: { + srlc: { + hideIndicator: true, + }, + }, + }, + { + path: WidgetTypesRoute.timeseries, + loadChildren: async () => + import("./timeseries/timeseries-docs.module") as object as Promise< + Type + >, + data: { + srlc: { + hideIndicator: true, + }, + }, + }, + { + path: WidgetTypesRoute.table, + loadChildren: async () => + import("./table/table-docs.module") as object as Promise>, + data: { + srlc: { + hideIndicator: true, + }, + }, + }, + { + path: WidgetTypesRoute.proportional, + loadChildren: async () => + import( + "./proportional/proportional-docs.module" + ) as object as Promise>, + data: { + srlc: { + hideIndicator: true, + }, + }, + }, + { + path: WidgetTypesRoute.embedded, + loadChildren: async () => + import( + "./embedded-content/embedded-content-docs.module" + ) as object as Promise>, + data: { + srlc: { + hideIndicator: true, + }, + }, + }, + { + path: WidgetTypesRoute.drilldown, + loadChildren: async () => + import( + "./drilldown/drilldown-widget-docs.module" + ) as object as Promise>, + data: { + srlc: { + hideIndicator: true, + }, + }, + }, + { + path: WidgetTypesRoute.viewComponents, + loadChildren: async () => + import( + "./view-components/view-components-docs.module" + ) as object as Promise>, + data: { + srlc: { + hideIndicator: true, + }, + }, + }, +]; + +@NgModule({ + imports: [ + RouterModule.forChild(routes), + NuiDocsModule, + NuiDashboardsModule, + ], + providers: [ConfiguratorHeadingService], +}) +export default class WidgetTypesModule {} +\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\`, +}; +\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\`, + "overview/hero/dashboard/hero-dashboard.component.ts": \\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\`// © 2022 SolarWinds Worldwide, LLC. All rights reserved. +// +// Permission is hereby granted, free of charge, to any person obtaining a copy +// of this software and associated documentation files (the "Software"), to +// deal in the Software without restriction, including without limitation the +// rights to use, copy, modify, merge, publish, distribute, sublicense, and/or +// sell copies of the Software, and to permit persons to whom the Software is +// furnished to do so, subject to the following conditions: +// +// The above copyright notice and this permission notice shall be included in +// all copies or substantial portions of the Software. +// +// THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR +// IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, +// FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE +// AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER +// LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, +// OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN +// THE SOFTWARE. + +import { HttpClient } from "@angular/common/http"; +import { + ChangeDetectionStrategy, + Component, + OnInit, + ViewEncapsulation, +} from "@angular/core"; +import keyBy from "lodash/keyBy"; + +import { LoggerService, ThemeSwitchService } from "@nova-ui/bits"; +import { + DATA_SOURCE, + IDashboard, + IWidget, + ProviderRegistryService, + WidgetTypesService, +} from "@nova-ui/dashboards"; + +import { positions, widgets } from "./widget-configs"; +import { + HarryPotterAverageRatingDataSource, + HarryPotterRatingsCountDataSource, +} from "../data/kpi-datasources"; +import { + BeerReviewCountsByCityMockDataSource, + BeerReviewCountsByCityMockDataSource2, +} from "../data/proportional-datasources"; +import { BeerDataSource } from "../data/table/beer-data-source"; +import { RandomUserDataSource } from "../data/table/random-user-data-source"; +import { + BeerVsReadingMockDataSource, + LoungingVsFrisbeeGolfMockDataSource, +} from "../data/timeseries-data-sources"; + +/** + * A component that instantiates the dashboard + */ +@Component({ + selector: "hero-dashboard", + templateUrl: "./hero-dashboard.component.html", + styleUrls: ["./hero-dashboard.component.less"], + encapsulation: ViewEncapsulation.Emulated, + changeDetection: ChangeDetectionStrategy.Default, + standalone: false, +}) +export class HeroDashboardComponent implements OnInit { + public dashboard: IDashboard = { + positions: {}, + widgets: {}, + }; + + public gridsterConfig = {}; + public editMode = false; + + constructor( + private providerRegistry: ProviderRegistryService, + public themeSwitcherService: ThemeSwitchService, + private widgetTypesService: WidgetTypesService + ) { + this.providerRegistry.setProviders({ + [HarryPotterAverageRatingDataSource.providerId]: { + provide: DATA_SOURCE, + useClass: HarryPotterAverageRatingDataSource, + deps: [HttpClient], + }, + [HarryPotterRatingsCountDataSource.providerId]: { + provide: DATA_SOURCE, + useClass: HarryPotterRatingsCountDataSource, + deps: [HttpClient], + }, + [BeerReviewCountsByCityMockDataSource.providerId]: { + provide: DATA_SOURCE, + useClass: BeerReviewCountsByCityMockDataSource, + deps: [], + }, + [BeerReviewCountsByCityMockDataSource2.providerId]: { + provide: DATA_SOURCE, + useClass: BeerReviewCountsByCityMockDataSource2, + deps: [], + }, + [RandomUserDataSource.providerId]: { + provide: DATA_SOURCE, + useClass: RandomUserDataSource, + deps: [LoggerService], + }, + [BeerDataSource.providerId]: { + provide: DATA_SOURCE, + useClass: BeerDataSource, + deps: [LoggerService], + }, + [BeerVsReadingMockDataSource.providerId]: { + provide: DATA_SOURCE, + useClass: BeerVsReadingMockDataSource, + deps: [], + }, + [LoungingVsFrisbeeGolfMockDataSource.providerId]: { + provide: DATA_SOURCE, + useClass: LoungingVsFrisbeeGolfMockDataSource, + deps: [], + }, + }); + } + + public ngOnInit(): void { + const widgetsWithStructure = widgets.map((w) => ({ + ...w, + pizzagna: { + ...this.widgetTypesService.getWidgetType(w.type, w.version) + .widget, + ...w.pizzagna, + }, + })); + const widgetsIndex = keyBy(widgetsWithStructure, (w: IWidget) => w.id); + + this.dashboard = { + positions: positions, + widgets: widgetsIndex, + }; + } +} +\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\`, + "overview/hero/dashboard/widget-configs.ts": \\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\`// © 2022 SolarWinds Worldwide, LLC. All rights reserved. +// +// Permission is hereby granted, free of charge, to any person obtaining a copy +// of this software and associated documentation files (the "Software"), to +// deal in the Software without restriction, including without limitation the +// rights to use, copy, modify, merge, publish, distribute, sublicense, and/or +// sell copies of the Software, and to permit persons to whom the Software is +// furnished to do so, subject to the following conditions: +// +// The above copyright notice and this permission notice shall be included in +// all copies or substantial portions of the Software. +// +// THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR +// IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, +// FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE +// AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER +// LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, +// OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN +// THE SOFTWARE. + +import { GridsterItem } from "angular-gridster2"; + +import { IWidget } from "@nova-ui/dashboards"; + +import { kpiConfig } from "../widget-configs/kpi"; +import { proportionalConfig } from "../widget-configs/proportional"; +import { tableConfig } from "../widget-configs/table"; +import { timeseriesConfig } from "../widget-configs/timeseries"; + +export const positions: Record = { + [tableConfig.id]: { + cols: 7, + rows: 7, + y: 0, + x: 0, + }, + [proportionalConfig.id]: { + cols: 5, + rows: 7, + y: 0, + x: 7, + }, + [kpiConfig.id]: { + cols: 6, + rows: 7, + y: 7, + x: 0, + }, + [timeseriesConfig.id]: { + cols: 6, + rows: 7, + y: 7, + x: 6, + }, +}; + +export const widgets: IWidget[] = [ + { + ...tableConfig, + }, + { + ...proportionalConfig, + }, + { + ...kpiConfig, + }, + { + ...timeseriesConfig, + }, +]; +\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\`, + "overview/hero/data/kpi-datasources.ts": \\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\`// © 2022 SolarWinds Worldwide, LLC. All rights reserved. +// +// Permission is hereby granted, free of charge, to any person obtaining a copy +// of this software and associated documentation files (the "Software"), to +// deal in the Software without restriction, including without limitation the +// rights to use, copy, modify, merge, publish, distribute, sublicense, and/or +// sell copies of the Software, and to permit persons to whom the Software is +// furnished to do so, subject to the following conditions: +// +// The above copyright notice and this permission notice shall be included in +// all copies or substantial portions of the Software. +// +// THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR +// IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, +// FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE +// AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER +// LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, +// OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN +// THE SOFTWARE. + +import { HttpClient, HttpErrorResponse } from "@angular/common/http"; +import { Injectable, OnDestroy } from "@angular/core"; +import { BehaviorSubject } from "rxjs"; +import { finalize } from "rxjs/operators"; + +import { DataSourceService, IFilteringOutputs } from "@nova-ui/bits"; +import { IKpiData } from "@nova-ui/dashboards"; + +import { GOOGLE_BOOKS_URL } from "./table/constants"; + +@Injectable() +export class HarryPotterAverageRatingDataSource + extends DataSourceService + implements OnDestroy +{ + public static providerId = "HarryPotterAverageRatingDataSource"; + + public busy = new BehaviorSubject(false); + + constructor(private http: HttpClient) { + super(); + } + + public async getFilteredData(): Promise { + this.busy.next(true); + return new Promise((resolve) => { + // *** Make a resource request to an external API (if needed) + this.http + .get(\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\`\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\${GOOGLE_BOOKS_URL}/5MQFrgEACAAJ\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\`) + .pipe(finalize(() => this.busy.next(false))) + .subscribe({ + next: (data: any) => { + resolve({ + result: { + value: data.volumeInfo.averageRating, + }, + }); + }, + error: (error: HttpErrorResponse) => { + resolve({ + result: null, + error: { + type: error.status, + }, + }); + }, + }); + }); + } + + public ngOnDestroy(): void { + this.outputsSubject.complete(); + } +} + +@Injectable() +export class HarryPotterRatingsCountDataSource + extends DataSourceService + implements OnDestroy +{ + public static providerId = "HarryPotterRatingsCountDataSource"; + + public busy = new BehaviorSubject(false); + + constructor(private http: HttpClient) { + super(); + } + + public async getFilteredData(): Promise { + this.busy.next(true); + return new Promise((resolve) => { + this.http + .get(\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\`\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\${GOOGLE_BOOKS_URL}/5MQFrgEACAAJ\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\`) + .pipe(finalize(() => this.busy.next(false))) + .subscribe({ + next: (data: any) => { + resolve({ + result: { + value: data.volumeInfo.ratingsCount, + }, + }); + }, + error: (error: HttpErrorResponse) => { + resolve({ + result: null, + error: { + type: error.status, + }, + }); + }, + }); + }); + } + + public ngOnDestroy(): void { + this.outputsSubject.complete(); + } +} +\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\`, + "overview/hero/data/proportional-datasources.ts": \\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\`// © 2022 SolarWinds Worldwide, LLC. All rights reserved. +// +// Permission is hereby granted, free of charge, to any person obtaining a copy +// of this software and associated documentation files (the "Software"), to +// deal in the Software without restriction, including without limitation the +// rights to use, copy, modify, merge, publish, distribute, sublicense, and/or +// sell copies of the Software, and to permit persons to whom the Software is +// furnished to do so, subject to the following conditions: +// +// The above copyright notice and this permission notice shall be included in +// all copies or substantial portions of the Software. +// +// THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR +// IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, +// FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE +// AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER +// LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, +// OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN +// THE SOFTWARE. + +import { Injectable, OnDestroy } from "@angular/core"; +import { BehaviorSubject } from "rxjs"; + +import { + DataSourceService, + IDataSource, + IFilteringOutputs, +} from "@nova-ui/bits"; + +import { + getMockBeerReviewCountsByCity, + getMockBeerReviewCountsByCity2, + IProportionalWidgetData, +} from "./widget-data"; + +@Injectable() +export class BeerReviewCountsByCityMockDataSource + extends DataSourceService + implements IDataSource, OnDestroy +{ + // This is the ID we'll use to identify the provider + public static providerId = "BeerReviewCountsByCityMockDataSource"; + public busy = new BehaviorSubject(false); + + public async getFilteredData(): Promise { + this.busy.next(true); + return new Promise((resolve) => { + setTimeout(() => { + this.outputsSubject.next({ + result: getMockBeerReviewCountsByCity(), + }); + this.busy.next(false); + }, 300); + }); + } + + public ngOnDestroy(): void { + this.outputsSubject.complete(); + } +} + +@Injectable() +export class BeerReviewCountsByCityMockDataSource2 + extends DataSourceService + implements IDataSource, OnDestroy +{ + // This is the ID we'll use to identify the provider + public static providerId = "BeerReviewCountsByCityMockDataSource2"; + public busy = new BehaviorSubject(false); + + public async getFilteredData(): Promise { + this.busy.next(true); + return new Promise((resolve) => { + setTimeout(() => { + this.outputsSubject.next({ + result: getMockBeerReviewCountsByCity2(), + }); + this.busy.next(false); + }, 1500); + }); + } + + public ngOnDestroy(): void { + this.outputsSubject.complete(); + } +} +\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\`, + "overview/hero/data/table/beer-data-source.ts": \\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\`// © 2022 SolarWinds Worldwide, LLC. All rights reserved. +// +// Permission is hereby granted, free of charge, to any person obtaining a copy +// of this software and associated documentation files (the "Software"), to +// deal in the Software without restriction, including without limitation the +// rights to use, copy, modify, merge, publish, distribute, sublicense, and/or +// sell copies of the Software, and to permit persons to whom the Software is +// furnished to do so, subject to the following conditions: +// +// The above copyright notice and this permission notice shall be included in +// all copies or substantial portions of the Software. +// +// THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR +// IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, +// FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE +// AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER +// LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, +// OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN +// THE SOFTWARE. + +import { ListRange } from "@angular/cdk/collections"; +import { Injectable } from "@angular/core"; +import isEqual from "lodash/isEqual"; +import orderBy from "lodash/orderBy"; +import { BehaviorSubject } from "rxjs"; + +import { + DataSourceService, + IDataField, + INovaFilteringOutputs, + INovaFilters, + ISorterFilter, + LoggerService, +} from "@nova-ui/bits"; + +import { IBrewDatasourceResponse, IBrewInfo } from "../types"; +import { BREW_API_URL } from "./constants"; + +@Injectable() +export class BeerDataSource extends DataSourceService { + public static providerId = "BeerDataSource"; + + private cache = Array.from({ length: 0 }); + private lastSortValue?: ISorterFilter; + private lastVirtualScroll?: ListRange; + private totalItems: number = 325; + + public page: number = 1; + public busy = new BehaviorSubject(false); + + public dataFields: Array = [ + { id: "id", label: "No", dataType: "number" }, + { id: "name", label: "Name", dataType: "string" }, + { id: "tagline", label: "Tagline", dataType: "string" }, + { id: "first_brewed", label: "First Brewed", dataType: "string" }, + { id: "description", label: "Description", dataType: "string" }, + { id: "brewers_tips", label: "Brewer's Tips", dataType: "string" }, + ]; + + constructor(private logger: LoggerService) { + super(); + } + + public async getFilteredData( + filters: INovaFilters + ): Promise { + const start = filters.virtualScroll?.value?.start ?? 0; + const end = filters.virtualScroll?.value?.end ?? 0; + const delta = end - start; + + // This condition handles sorting. We want to sort columns without fetching another chunk of data. + // Since the data is being fetched when scrolled, we compare virtual scroll indexes here in the condition as well. + if (filters.sorter?.value) { + if ( + !isEqual(this.lastSortValue, filters.sorter.value) && + isEqual(this.lastVirtualScroll, filters.virtualScroll?.value) + ) { + const totalPages = Math.ceil( + delta ? this.totalItems / delta : 1 + ); + const itemsPerPage: number = Math.max( + delta < 80 ? delta : 80, + 1 + ); + let response: Array | null = null; + let map: IBrewDatasourceResponse; + + if (filters.sorter?.value?.direction === "desc") { + this.cache = []; + for (let i = 0; i < this.page; ++i) { + response = await ( + await fetch( + \\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\`\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\${BREW_API_URL}/?page=\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\${ + totalPages - i || 1 + }&per_page=\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\${itemsPerPage}\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\` + ) + ).json(); + + // since the last page contains only 5 items we need to fetch another page to give virtual scroll enough space to work + if (response && response.length < itemsPerPage) { + this.page++; + } + map = { + brewInfo: response?.map((result: IBrewInfo) => ({ + id: result.id, + name: result.name, + tagline: result.tagline, + first_brewed: result.first_brewed, + description: result.description, + brewers_tips: result.brewers_tips, + })), + total: response?.length, + } as IBrewDatasourceResponse; + this.cache = + totalPages - i !== 0 + ? this.cache.concat(map.brewInfo) + : this.cache; + } + } + + if (filters.sorter?.value?.direction === "asc") { + this.cache = []; + for (let i = 0; i < this.page; i++) { + response = await ( + await fetch( + \\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\`\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\${BREW_API_URL}/?page=\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\${ + i + 1 + }&per_page=\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\${itemsPerPage}\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\` + ) + ).json(); + map = { + brewInfo: response?.map((result: IBrewInfo) => ({ + id: result.id, + name: result.name, + tagline: result.tagline, + first_brewed: result.first_brewed, + description: result.description, + brewers_tips: result.brewers_tips, + })), + total: response?.length, + } as IBrewDatasourceResponse; + this.cache = this.cache.concat(map.brewInfo); + } + } + + this.lastSortValue = filters.sorter?.value; + this.lastVirtualScroll = filters.virtualScroll?.value; + + return { + repeat: { itemsSource: this.sortData(this.cache, filters) }, + paginator: { total: this.totalItems }, + dataFields: this.dataFields, + }; + } + } + + this.busy.next(true); + return new Promise((resolve) => { + setTimeout(() => { + this.getData(start, end, filters).then( + (response: INovaFilteringOutputs) => { + if (!response) { + return; + } + + this.cache = this.cache.concat(response.brewInfo); + + this.dataSubject.next(this.cache); + resolve({ + repeat: { + itemsSource: this.sortData(this.cache, filters), + }, + paginator: { total: this.totalItems }, + dataFields: this.dataFields, + }); + + this.lastSortValue = filters.sorter?.value; + this.lastVirtualScroll = filters.virtualScroll?.value; + this.busy.next(false); + } + ); + }, 500); + }); + } + + public async getData( + start: number = 0, + end: number = 20, + filters: INovaFilters + ): Promise { + const delta = end - start; + const totalPages = Math.ceil(delta ? this.totalItems / delta : 1); + let response: Array | null = null; + // The api.punk.com is able to return only 80 items per page + const itemsPerPage: number = Math.max(delta < 80 ? delta : 80, 1); + + if (filters.sorter?.value?.direction === "asc") { + response = await ( + await fetch( + \\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\`\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\${BREW_API_URL}/?page=\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\${this.page}&per_page=\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\${itemsPerPage}\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\` + ) + ).json(); + } + + if (filters.sorter?.value?.direction === "desc") { + response = await ( + await fetch( + \\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\`\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\${BREW_API_URL}/?page=\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\${ + totalPages - this.page + }&per_page=\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\${itemsPerPage}\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\` + ) + ).json(); + } + + if (!filters.sorter) { + response = await ( + await fetch( + \\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\`\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\${BREW_API_URL}/?page=\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\${this.page}&per_page=\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\${itemsPerPage}\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\` + ) + ).json(); + } + return { + brewInfo: response?.map((result: IBrewInfo, i: number) => ({ + id: result.id, + name: result.name, + tagline: result.tagline, + first_brewed: result.first_brewed, + description: result.description, + brewers_tips: result.brewers_tips, + })), + total: response?.length, + } as IBrewDatasourceResponse; + } + + private sortData(data: IBrewInfo[], filters: INovaFilters) { + return orderBy( + data, + filters.sorter?.value?.sortBy, + filters.sorter?.value?.direction as "desc" | "asc" + ); + } +} +\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\`, + "overview/hero/data/table/constants.ts": \\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\`// © 2022 SolarWinds Worldwide, LLC. All rights reserved. +// +// Permission is hereby granted, free of charge, to any person obtaining a copy +// of this software and associated documentation files (the "Software"), to +// deal in the Software without restriction, including without limitation the +// rights to use, copy, modify, merge, publish, distribute, sublicense, and/or +// sell copies of the Software, and to permit persons to whom the Software is +// furnished to do so, subject to the following conditions: +// +// The above copyright notice and this permission notice shall be included in +// all copies or substantial portions of the Software. +// +// THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR +// IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, +// FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE +// AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER +// LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, +// OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN +// THE SOFTWARE. + +export const corsProxy = "https://cors-anywhere.herokuapp.com"; +export const RANDOMUSER_API_URL = "https://randomuser.me"; +export const BREW_API_URL = "https://api.punkapi.com/v2/beers"; +export const GOOGLE_BOOKS_URL = "https://www.googleapis.com/books/v1/volumes"; +export const apiRoute = "api/1.3"; +export const responseError = \\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\`Error responding from server. Please visit \\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\${RANDOMUSER_API_URL} and \\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\${corsProxy} to see if they're available\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\`; +\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\`, + "overview/hero/data/table/random-user-data-source.ts": \\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\`// © 2022 SolarWinds Worldwide, LLC. All rights reserved. +// +// Permission is hereby granted, free of charge, to any person obtaining a copy +// of this software and associated documentation files (the "Software"), to +// deal in the Software without restriction, including without limitation the +// rights to use, copy, modify, merge, publish, distribute, sublicense, and/or +// sell copies of the Software, and to permit persons to whom the Software is +// furnished to do so, subject to the following conditions: +// +// The above copyright notice and this permission notice shall be included in +// all copies or substantial portions of the Software. +// +// THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR +// IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, +// FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE +// AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER +// LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, +// OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN +// THE SOFTWARE. + +import { ListRange } from "@angular/cdk/collections"; +import { Injectable } from "@angular/core"; +import isEqual from "lodash/isEqual"; +import orderBy from "lodash/orderBy"; +import { BehaviorSubject } from "rxjs"; + +import { + DataSourceService, + IDataField, + INovaFilteringOutputs, + INovaFilters, + ISorterFilter, + LoggerService, +} from "@nova-ui/bits"; + +import { + IRandomUserResponse, + IRandomUserResults, + IRandomUserTableModel, + UsersQueryResponse, +} from "../types"; +import { + apiRoute, + corsProxy, + RANDOMUSER_API_URL, + responseError, +} from "./constants"; + +@Injectable() +export class RandomUserDataSource extends DataSourceService { + public static providerId = "RandomUserDataSource"; + + private readonly seed = "sw"; + + private cache = Array.from({ length: 0 }); + private lastSortValue?: ISorterFilter; + private lastVirtualScroll?: ListRange; + + public page: number = 1; + public busy = new BehaviorSubject(false); + + public dataFields: Array = [ + { id: "no", label: $localize\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\`No\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\`, dataType: "number" }, + { id: "nameTitle", label: $localize\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\`Title\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\`, dataType: "string" }, + { id: "nameFirst", label: $localize\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\`First\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\`, dataType: "string" }, + { id: "nameLast", label: $localize\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\`Last\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\`, dataType: "string" }, + { id: "gender", label: $localize\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\`Gender\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\`, dataType: "string" }, + { id: "country", label: $localize\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\`Country\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\`, dataType: "string" }, + { id: "city", label: $localize\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\`City\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\`, dataType: "string" }, + { id: "postcode", label: $localize\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\`Postcode\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\`, dataType: "number" }, + { id: "email", label: $localize\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\`E-Mail\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\`, dataType: "string" }, + { id: "cell", label: $localize\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\`Cell\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\`, dataType: "string" }, + ]; + + constructor(private logger: LoggerService) { + super(); + } + + public async getFilteredData( + filters: INovaFilters + ): Promise { + // This condition handles sorting. We want to sort columns without fetching another chunk of data. + // Since the data is being fetched when scrolled, we compare virtual scroll indexes here in the condition as well. + if (filters.sorter?.value) { + if ( + !isEqual(this.lastSortValue, filters.sorter.value) && + isEqual(this.lastVirtualScroll, filters.virtualScroll?.value) + ) { + this.lastSortValue = filters.sorter?.value; + this.lastVirtualScroll = filters.virtualScroll?.value; + + return { + repeat: { itemsSource: this.sortData(this.cache, filters) }, + paginator: { total: 200 }, + dataFields: this.dataFields, + }; + } + } + this.busy.next(true); + + const virtualScrollFilter = + filters.virtualScroll && filters.virtualScroll.value; + const start = virtualScrollFilter + ? filters.virtualScroll?.value.start + : 0; + const end = virtualScrollFilter ? filters.virtualScroll?.value.end : 0; + + // We're returning Promise with setTimeout here to make the response from the server longer, as the API being used sends responses + // almost immediately. We need it longer to be able the show the spinner component on data load + return new Promise((resolve) => { + setTimeout(() => { + this.getData(start, end).then( + (response: INovaFilteringOutputs | undefined) => { + if (!response) { + return; + } + + this.cache = this.cache.concat(response.users); + + this.dataSubject.next(this.cache); + resolve({ + repeat: { + itemsSource: this.sortData(this.cache, filters), + }, + // This API can return thousands of results, however doesn't return the max number of results, + // so we set the max number of result manually here. + paginator: { total: 200 }, + dataFields: this.dataFields, + }); + + this.lastSortValue = filters.sorter?.value; + this.lastVirtualScroll = filters.virtualScroll?.value; + this.busy.next(false); + } + ); + }, 300); + }); + } + + public async getData( + start: number = 0, + end: number = 20 + ): Promise { + let response: IRandomUserResponse | null = null; + try { + response = await ( + await fetch( + \\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\`\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\${corsProxy}/\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\${RANDOMUSER_API_URL}/\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\${apiRoute}/?page=\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\${ + this.page + }&results=\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\${end - start}&seed=\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\${this.seed}\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\` + ) + ).json(); + return { + users: response?.results.map( + (result: IRandomUserResults, i: number) => ({ + no: this.cache.length + i + 1, + nameTitle: result.name.title, + nameFirst: result.name.first, + nameLast: result.name.last, + gender: result.gender, + country: result.location.country, + city: result.location.city, + postcode: result.location.postcode, + email: result.email, + cell: result.cell, + }) + ), + total: response?.results.length, + start: start, + } as UsersQueryResponse; + } catch (e) { + this.logger.error(responseError); + } + } + + private sortData(data: IRandomUserTableModel[], filters: INovaFilters) { + return orderBy( + data, + filters.sorter?.value?.sortBy, + filters.sorter?.value?.direction as "desc" | "asc" + ); + } +} +\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\`, + "overview/hero/data/table/types.ts": \\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\`// © 2022 SolarWinds Worldwide, LLC. All rights reserved. +// +// Permission is hereby granted, free of charge, to any person obtaining a copy +// of this software and associated documentation files (the "Software"), to +// deal in the Software without restriction, including without limitation the +// rights to use, copy, modify, merge, publish, distribute, sublicense, and/or +// sell copies of the Software, and to permit persons to whom the Software is +// furnished to do so, subject to the following conditions: +// +// The above copyright notice and this permission notice shall be included in +// all copies or substantial portions of the Software. +// +// THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR +// IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, +// FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE +// AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER +// LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, +// OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN +// THE SOFTWARE. + +import { IDataField, INovaFilteringOutputs } from "@nova-ui/bits"; +export interface BasicTableModel { + position: number; + name: string; + features: any; + status: string; + checks: any; + "cpu-load": number; + firstUrl: string; + firstUrlLabel: string; + secondUrl: string; + secondUrlLabel: string; +} + +export interface ITableDataSourceOutput extends INovaFilteringOutputs { + dataFields: IDataField[]; +} +\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\`, + "overview/hero/data/timeseries-data-sources.ts": \\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\`// © 2022 SolarWinds Worldwide, LLC. All rights reserved. +// +// Permission is hereby granted, free of charge, to any person obtaining a copy +// of this software and associated documentation files (the "Software"), to +// deal in the Software without restriction, including without limitation the +// rights to use, copy, modify, merge, publish, distribute, sublicense, and/or +// sell copies of the Software, and to permit persons to whom the Software is +// furnished to do so, subject to the following conditions: +// +// The above copyright notice and this permission notice shall be included in +// all copies or substantial portions of the Software. +// +// THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR +// IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, +// FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE +// AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER +// LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, +// OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN +// THE SOFTWARE. + +import { Injectable } from "@angular/core"; +import { Moment } from "moment/moment"; +import { BehaviorSubject } from "rxjs"; + +import { DataSourceService, IDataSource, INovaFilters } from "@nova-ui/bits"; +import { + ITimeseriesOutput, + ITimeseriesWidgetData, + ITimeseriesWidgetSeriesData, +} from "@nova-ui/dashboards"; + +import { + BEER_VS_READING_DATA, + LOUNGING_VS_ULTIMATE_FRISBEE_DATA, +} from "./widget-data"; + +@Injectable() +export class BeerVsReadingMockDataSource + extends DataSourceService + implements IDataSource +{ + public static providerId = "BeerVsReadingMockDataSource"; + + public busy = new BehaviorSubject(false); + + constructor() { + super(); + } + + public async getFilteredData( + filters: INovaFilters + ): Promise { + this.busy.next(true); + const result = await delay( + { series: getData(filters, BEER_VS_READING_DATA) }, + 1000 + ); + this.busy.next(false); + return result; + } +} + +@Injectable() +export class LoungingVsFrisbeeGolfMockDataSource + extends DataSourceService + implements IDataSource +{ + public static providerId = "LoungingVsFrisbeeGolfMockDataSource"; + + public busy = new BehaviorSubject(false); + + constructor() { + super(); + } + + public async getFilteredData( + filters: INovaFilters + ): Promise { + this.busy.next(true); + const result = await delay( + { series: getData(filters, LOUNGING_VS_ULTIMATE_FRISBEE_DATA) }, + 1000 + ); + this.busy.next(false); + return result; + } +} + +function getData( + filters: INovaFilters, + data: ITimeseriesWidgetData[] +): ITimeseriesWidgetData[] { + const timeframeFilter = filters.timeframe; + let filteredData = data; + // TIME FRAME PICKER FILTERING + if (timeframeFilter) { + filteredData = filteredData.map((item: ITimeseriesWidgetData) => ({ + id: item.id, + name: item.name, + description: item.description, + data: item.data.filter((seriesData: ITimeseriesWidgetSeriesData) => + filterDates( + seriesData.x, + timeframeFilter.value.startDatetime, + timeframeFilter.value.endDatetime + ) + ), + })); + } + + return filteredData; +} + +function filterDates(dateToCheck: Moment, startDate: Moment, endDate: Moment) { + return ( + dateToCheck.isBetween(startDate, endDate) || + dateToCheck.isSame(startDate) || + dateToCheck.isSame(endDate) + ); +} + +async function delay( + value: ITimeseriesOutput, + ms: number +): Promise { + return new Promise((resolve) => setTimeout(() => resolve(value), ms)); +} +\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\`, + "overview/hero/data/types.ts": \\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\`// © 2022 SolarWinds Worldwide, LLC. All rights reserved. +// +// Permission is hereby granted, free of charge, to any person obtaining a copy +// of this software and associated documentation files (the "Software"), to +// deal in the Software without restriction, including without limitation the +// rights to use, copy, modify, merge, publish, distribute, sublicense, and/or +// sell copies of the Software, and to permit persons to whom the Software is +// furnished to do so, subject to the following conditions: +// +// The above copyright notice and this permission notice shall be included in +// all copies or substantial portions of the Software. +// +// THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR +// IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, +// FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE +// AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER +// LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, +// OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN +// THE SOFTWARE. + +export interface UsersQueryResponse { + users: IRandomUserTableModel[]; + total: number; + start: number; +} + +export interface IRandomUserResponse { + info: Array; + results: Array; +} + +export interface IRandomUserInfo { + page: number; + results: number; + seed: string; + version: string; +} + +export interface IRandomUserResults { + cell: string; + dob: { + age: number; + date: string; + }; + email: string; + gender: string; + id: any; + location: IRandomUserLocation; + login: { + md5: string; + password: string; + salt: string; + sha1: string; + sha256: string; + username: string; + uuid: string; + }; + name: { + title: string; + first: string; + last: string; + }; + nat: string; + phone: string; + picture: { + large: string; + medium: string; + thumbnail: string; + }; + registered: { + date: string; + age: number; + }; +} + +export interface IRandomUserTableModel { + no: number; + nameTitle: string; + nameFirst: string; + nameLast: string; + gender: string; + country: string; + city: string; + postcode: number; + email: string; + cell: string; +} + +export interface IRandomUserLocation { + city: string; + coordinates: { latitude: string; longitude: string }; + country: string; + postcode: number; + state: string; + street: { number: number; name: string }; + timezone: any; +} + +export interface IBrewInfo { + id: number; + name: string; + tagline: string; + first_brewed: string; + description: string; + brewers_tips: string; +} + +export interface IBrewDatasourceResponse { + brewInfo: IBrewInfo[]; + total: number; +} +\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\`, + "overview/hero/data/widget-data.ts": \\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\`// © 2022 SolarWinds Worldwide, LLC. All rights reserved. +// +// Permission is hereby granted, free of charge, to any person obtaining a copy +// of this software and associated documentation files (the "Software"), to +// deal in the Software without restriction, including without limitation the +// rights to use, copy, modify, merge, publish, distribute, sublicense, and/or +// sell copies of the Software, and to permit persons to whom the Software is +// furnished to do so, subject to the following conditions: +// +// The above copyright notice and this permission notice shall be included in +// all copies or substantial portions of the Software. +// +// THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR +// IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, +// FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE +// AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER +// LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, +// OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN +// THE SOFTWARE. + +import moment from "moment/moment"; + +import { ITimeseriesWidgetData } from "@nova-ui/dashboards"; + +import { BasicTableModel } from "./table/types"; + +export interface IProportionalWidgetData { + id: string; + name: string; + data: number[]; + link: string; + value: string; +} + +export function getMockBeerReviewCountsByCity(): IProportionalWidgetData[] { + return [ + { + id: "Brno", + name: "Brno", + data: [Math.round(Math.random() * 100000)], + link: "https://en.wikipedia.org/wiki/Brno", + value: "Brno", + }, + { + id: "kyiv", + name: "Kyiv", + data: [Math.round(Math.random() * 100000)], + link: "https://en.wikipedia.org/wiki/Kyiv", + value: "Kyiv", + }, + { + id: "austin", + name: "Austin", + data: [Math.round(Math.random() * 100000)], + link: "https://en.wikipedia.org/wiki/Austin", + value: "Austin", + }, + { + id: "lisbon", + name: "Lisbon", + data: [Math.round(Math.random() * 100000)], + link: "https://en.wikipedia.org/wiki/Lisbon", + value: "Lisbon", + }, + { + id: "sydney", + name: "Sydney", + data: [Math.round(Math.random() * 100000)], + link: "https://en.wikipedia.org/wiki/Sydney", + value: "Sydney", + }, + { + id: "nur-sultan", + name: "Nur-Sultan", + data: [Math.round(Math.random() * 100000)], + link: "https://en.wikipedia.org/wiki/Nur-Sultan", + value: "Nur-Sultan", + }, + ].sort((a, b) => a.data[0] - b.data[0]); +} + +export function getMockBeerReviewCountsByCity2(): IProportionalWidgetData[] { + return [ + { + id: "london", + name: "London", + data: [Math.round(Math.random() * 100000)], + link: "https://en.wikipedia.org/wiki/London", + value: "London", + }, + { + id: "paris", + name: "Paris", + data: [Math.round(Math.random() * 100000)], + link: "https://en.wikipedia.org/wiki/Paris", + value: "Paris", + }, + { + id: "rio", + name: "Rio", + data: [Math.round(Math.random() * 100000)], + link: "https://en.wikipedia.org/wiki/Rio_de_Janeiro", + value: "Rio", + }, + ].sort((a, b) => a.data[0] - b.data[0]); +} + +export const BEER_VS_READING_DATA: ITimeseriesWidgetData[] = [ + { + id: "series-1", + name: "Beer Tasting", + description: "Havin' some suds", + data: [ + { x: moment().subtract(10, "day"), y: 30 }, + { x: moment().subtract(9, "day"), y: 35 }, + { x: moment().subtract(8, "day"), y: 33 }, + { x: moment().subtract(7, "day"), y: 40 }, + { x: moment().subtract(6, "day"), y: 35 }, + { x: moment().subtract(5, "day"), y: 30 }, + { x: moment().subtract(4, "day"), y: 35 }, + { x: moment().subtract(3, "day"), y: 15 }, + { x: moment().subtract(2, "day"), y: 30 }, + { x: moment().subtract(1, "day"), y: 35 }, + { x: moment().subtract(24, "hour"), y: 34 }, + { x: moment().subtract(15, "hour"), y: 33 }, + { x: moment().subtract(10, "hour"), y: 35 }, + { x: moment().subtract(5, "hour"), y: 36 }, + { x: moment().subtract(1, "hour"), y: 34 }, + { x: moment().subtract(50, "minute"), y: 33 }, + { x: moment().subtract(40, "minute"), y: 30 }, + { x: moment().subtract(30, "minute"), y: 32 }, + { x: moment().subtract(20, "minute"), y: 31 }, + { x: moment().subtract(10, "minute"), y: 34 }, + ], + }, + { + id: "series-2", + name: "Reading", + description: "Hittin' the books", + data: [ + { x: moment().subtract(10, "day"), y: 60 }, + { x: moment().subtract(9, "day"), y: 64 }, + { x: moment().subtract(8, "day"), y: 70 }, + { x: moment().subtract(7, "day"), y: 55 }, + { x: moment().subtract(6, "day"), y: 55 }, + { x: moment().subtract(5, "day"), y: 45 }, + { x: moment().subtract(4, "day"), y: 10 }, + { x: moment().subtract(3, "day"), y: 65 }, + { x: moment().subtract(2, "day"), y: 35 }, + { x: moment().subtract(1, "day"), y: 60 }, + { x: moment().subtract(24, "hour"), y: 61 }, + { x: moment().subtract(15, "hour"), y: 65 }, + { x: moment().subtract(10, "hour"), y: 63 }, + { x: moment().subtract(5, "hour"), y: 58 }, + { x: moment().subtract(1, "hour"), y: 64 }, + { x: moment().subtract(50, "minute"), y: 63 }, + { x: moment().subtract(40, "minute"), y: 60 }, + { x: moment().subtract(30, "minute"), y: 62 }, + { x: moment().subtract(20, "minute"), y: 61 }, + { x: moment().subtract(10, "minute"), y: 62 }, + ], + }, +]; + +export const LOUNGING_VS_ULTIMATE_FRISBEE_DATA: ITimeseriesWidgetData[] = [ + { + id: "series-a", + name: "Lounging", + description: "Shootin' the Breeze", + data: [ + { x: moment().subtract(10, "day"), y: 10 }, + { x: moment().subtract(9, "day"), y: 15 }, + { x: moment().subtract(8, "day"), y: 13 }, + { x: moment().subtract(7, "day"), y: 20 }, + { x: moment().subtract(6, "day"), y: 15 }, + { x: moment().subtract(5, "day"), y: 10 }, + { x: moment().subtract(4, "day"), y: 15 }, + { x: moment().subtract(3, "day"), y: 5 }, + { x: moment().subtract(2, "day"), y: 10 }, + { x: moment().subtract(1, "day"), y: 15 }, + { x: moment().subtract(24, "hour"), y: 14 }, + { x: moment().subtract(15, "hour"), y: 13 }, + { x: moment().subtract(10, "hour"), y: 15 }, + { x: moment().subtract(5, "hour"), y: 16 }, + { x: moment().subtract(1, "hour"), y: 14 }, + { x: moment().subtract(50, "minute"), y: 13 }, + { x: moment().subtract(40, "minute"), y: 10 }, + { x: moment().subtract(30, "minute"), y: 12 }, + { x: moment().subtract(20, "minute"), y: 11 }, + { x: moment().subtract(10, "minute"), y: 14 }, + ], + }, + { + id: "series-b", + name: "Frisbee Golfing", + description: "Golfin' with a disc", + data: [ + { x: moment().subtract(10, "day"), y: 80 }, + { x: moment().subtract(9, "day"), y: 84 }, + { x: moment().subtract(8, "day"), y: 80 }, + { x: moment().subtract(7, "day"), y: 75 }, + { x: moment().subtract(6, "day"), y: 95 }, + { x: moment().subtract(5, "day"), y: 85 }, + { x: moment().subtract(4, "day"), y: 80 }, + { x: moment().subtract(3, "day"), y: 85 }, + { x: moment().subtract(2, "day"), y: 85 }, + { x: moment().subtract(1, "day"), y: 80 }, + { x: moment().subtract(24, "hour"), y: 81 }, + { x: moment().subtract(15, "hour"), y: 85 }, + { x: moment().subtract(10, "hour"), y: 83 }, + { x: moment().subtract(5, "hour"), y: 88 }, + { x: moment().subtract(1, "hour"), y: 84 }, + { x: moment().subtract(50, "minute"), y: 83 }, + { x: moment().subtract(40, "minute"), y: 80 }, + { x: moment().subtract(30, "minute"), y: 82 }, + { x: moment().subtract(20, "minute"), y: 81 }, + { x: moment().subtract(10, "minute"), y: 82 }, + ], + }, +]; + +export const TABLE_DATA: BasicTableModel[] = [ + { + position: 1, + name: "FOCUS-SVR-02258", + features: ["remote-access-vpn-tunnel", "patch-manager01"], + status: "Active", + checks: { + icon: "status_up", + num: 25, + }, + "cpu-load": 86, + firstUrl: "https://en.wikipedia.org/wiki/Brno", + firstUrlLabel: "Brno", + secondUrl: "https://en.wikipedia.org/wiki/VMware_Workstation", + secondUrlLabel: "Workstation", + }, + { + position: 2, + name: "FOCUS-SVR-03312", + features: ["tools", "database", "orion-ape-backup"], + status: "Active", + checks: { + icon: "status_critical", + num: 25, + }, + "cpu-load": 47, + firstUrl: "https://en.wikipedia.org/wiki/Brno", + firstUrlLabel: "Brno", + secondUrl: "https://en.wikipedia.org/wiki/VMware_Workstation", + secondUrlLabel: "Workstation", + }, + { + position: 3, + name: "FOCUS-SVR-02258", + features: [ + "remote-access-vpn-tunnel", + "database", + "orion-ape-backup", + "patch-manager01", + ], + status: "Active", + checks: { + icon: "status_down", + num: 25, + }, + "cpu-load": 53, + firstUrl: "https://en.wikipedia.org/wiki/Kyiv", + firstUrlLabel: "Kyiv", + secondUrl: "https://en.wikipedia.org/wiki/VMware_Workstation", + secondUrlLabel: "Workstation", + }, + { + position: 4, + name: "Man-LT-JYJ4AD5", + features: [ + "remote-access-vpn-tunnel", + "tools", + "database", + "orion-ape-backup", + ], + status: "Active", + checks: { + icon: "status_up", + num: 25, + }, + "cpu-load": 32, + firstUrl: "https://en.wikipedia.org/wiki/Kyiv", + firstUrlLabel: "Kyiv", + secondUrl: "https://en.wikipedia.org/wiki/VirtualBox", + secondUrlLabel: "VirtualBox", + }, + { + position: 5, + name: "Man-LT-JYJ425", + features: [ + "remote-access-vpn-tunnel", + "tools", + "database", + "orion-ape-backup", + ], + status: "Active", + checks: { + icon: "status_up", + num: 25, + }, + "cpu-load": 22, + firstUrl: "https://en.wikipedia.org/wiki/Kyiv", + firstUrlLabel: "Kyiv", + secondUrl: "https://en.wikipedia.org/wiki/VirtualBox", + secondUrlLabel: "VirtualBox", + }, + { + position: 6, + name: "Man-LT-JYJ4333", + features: [ + "remote-access-vpn-tunnel", + "tools", + "database", + "orion-ape-backup", + ], + status: "Active", + checks: { + icon: "status_up", + num: 25, + }, + "cpu-load": 12, + firstUrl: "https://en.wikipedia.org/wiki/Kyiv", + firstUrlLabel: "Kyiv", + secondUrl: "https://en.wikipedia.org/wiki/VirtualBox", + secondUrlLabel: "VirtualBox", + }, + { + position: 7, + name: "FOCUS-SVR-02258", + features: ["remote-access-vpn-tunnel", "patch-manager01"], + status: "Active", + checks: { + icon: "status_up", + num: 25, + }, + "cpu-load": 86, + firstUrl: "https://en.wikipedia.org/wiki/Austin", + firstUrlLabel: "Austin", + secondUrl: "https://en.wikipedia.org/wiki/VMware_Workstation", + secondUrlLabel: "Workstation", + }, + { + position: 8, + name: "Man-LT-JYJ4AD5", + features: [ + "remote-access-vpn-tunnel", + "tools", + "database", + "orion-ape-backup", + "patch-manager01", + ], + status: "Active", + checks: { + icon: "status_inactive", + num: 25, + }, + "cpu-load": 35, + firstUrl: "https://en.wikipedia.org/wiki/Austin", + firstUrlLabel: "Austin", + secondUrl: "https://en.wikipedia.org/wiki/VMware_Workstation", + secondUrlLabel: "Workstation", + }, + { + position: 9, + name: "Man-LT-JYJ4AD5", + features: [ + "remote-access-vpn-tunnel", + "tools", + "database", + "patch-manager01", + ], + status: "Active", + checks: { + icon: "status_up", + num: 25, + }, + "cpu-load": 32, + firstUrl: "https://en.wikipedia.org/wiki/Brno", + firstUrlLabel: "Brno", + secondUrl: "https://en.wikipedia.org/wiki/VMware_Workstation", + secondUrlLabel: "Workstation", + }, + { + position: 10, + name: "Man-LT-JYJ4AD5", + features: [ + "remote-access-vpn-tunnel", + "database", + "orion-ape-backup", + "patch-manager01", + ], + status: "Active", + checks: { + icon: "status_up", + num: 25, + }, + "cpu-load": 64, + firstUrl: "https://en.wikipedia.org/wiki/Kyiv", + firstUrlLabel: "Kyiv", + secondUrl: "https://en.wikipedia.org/wiki/VMware_Workstation", + secondUrlLabel: "Workstation", + }, + { + position: 11, + name: "Man-LT-111", + features: [], + status: "Active", + checks: { + icon: "status_external", + num: 25, + }, + "cpu-load": 55, + firstUrl: "https://en.wikipedia.org/wiki/Brno", + firstUrlLabel: "Brno", + secondUrl: "https://en.wikipedia.org/wiki/VMware_Workstation", + secondUrlLabel: "Workstation", + }, + { + position: 12, + name: "Man-LT-2222", + features: [ + "remote-access-vpn-tunnel", + "tools", + "database", + "orion-ape-backup", + "patch-manager01", + ], + status: "Active", + checks: { + icon: "status_inactive", + num: 25, + }, + "cpu-load": 34, + firstUrl: "https://en.wikipedia.org/wiki/Brno", + firstUrlLabel: "Brno", + secondUrl: "https://en.wikipedia.org/wiki/VMware_Workstation", + secondUrlLabel: "Workstation", + }, + { + position: 13, + name: "Man-LT-333333", + features: [ + "remote-access-vpn-tunnel", + "tools", + "database", + "patch-manager01", + ], + status: "Active", + checks: { + icon: "status_up", + num: 25, + }, + "cpu-load": 56, + firstUrl: "https://en.wikipedia.org/wiki/Kyiv", + firstUrlLabel: "Kyiv", + secondUrl: "https://en.wikipedia.org/wiki/VMware_Workstation", + secondUrlLabel: "Workstation", + }, + { + position: 14, + name: "Man-LT-444444", + features: [ + "remote-access-vpn-tunnel", + "database", + "orion-ape-backup", + "patch-manager01", + ], + status: "Active", + checks: { + icon: "status_up", + num: 25, + }, + "cpu-load": 26, + firstUrl: "https://en.wikipedia.org/wiki/Kyiv", + firstUrlLabel: "Kyiv", + secondUrl: "https://en.wikipedia.org/wiki/VMware_Workstation", + secondUrlLabel: "Workstation", + }, + { + position: 15, + name: "Man-LT-555555", + features: [ + "remote-access-vpn-tunnel", + "database", + "orion-ape-backup", + "patch-manager01", + ], + status: "Active", + checks: { + icon: "status_up", + num: 25, + }, + "cpu-load": 76, + firstUrl: "https://en.wikipedia.org/wiki/Austin", + firstUrlLabel: "Austin", + secondUrl: "https://en.wikipedia.org/wiki/VMware_Workstation", + secondUrlLabel: "Workstation", + }, + { + position: 16, + name: "FOCUS-SVR-02258", + features: ["remote-access-vpn-tunnel", "patch-manager01"], + status: "Active", + checks: { + icon: "status_up", + num: 25, + }, + "cpu-load": 86, + firstUrl: "https://en.wikipedia.org/wiki/Brno", + firstUrlLabel: "Brno", + secondUrl: "https://en.wikipedia.org/wiki/VMware_Workstation", + secondUrlLabel: "Workstation", + }, + { + position: 17, + name: "FOCUS-SVR-03312", + features: ["tools", "database", "orion-ape-backup"], + status: "Active", + checks: { + icon: "status_critical", + num: 25, + }, + "cpu-load": 47, + firstUrl: "https://en.wikipedia.org/wiki/Brno", + firstUrlLabel: "Brno", + secondUrl: "https://en.wikipedia.org/wiki/VMware_Workstation", + secondUrlLabel: "Workstation", + }, + { + position: 18, + name: "FOCUS-SVR-02258", + features: [ + "remote-access-vpn-tunnel", + "database", + "orion-ape-backup", + "patch-manager01", + ], + status: "Active", + checks: { + icon: "status_down", + num: 25, + }, + "cpu-load": 53, + firstUrl: "https://en.wikipedia.org/wiki/Kyiv", + firstUrlLabel: "Kyiv", + secondUrl: "https://en.wikipedia.org/wiki/VMware_Workstation", + secondUrlLabel: "Workstation", + }, + { + position: 19, + name: "Man-LT-JYJ4AD5", + features: [ + "remote-access-vpn-tunnel", + "tools", + "database", + "orion-ape-backup", + ], + status: "Active", + checks: { + icon: "status_up", + num: 25, + }, + "cpu-load": 32, + firstUrl: "https://en.wikipedia.org/wiki/Kyiv", + firstUrlLabel: "Kyiv", + secondUrl: "https://en.wikipedia.org/wiki/VirtualBox", + secondUrlLabel: "VirtualBox", + }, + { + position: 20, + name: "Man-LT-JYJ425", + features: [ + "remote-access-vpn-tunnel", + "tools", + "database", + "orion-ape-backup", + ], + status: "Active", + checks: { + icon: "status_up", + num: 25, + }, + "cpu-load": 22, + firstUrl: "https://en.wikipedia.org/wiki/Kyiv", + firstUrlLabel: "Kyiv", + secondUrl: "https://en.wikipedia.org/wiki/VirtualBox", + secondUrlLabel: "VirtualBox", + }, + { + position: 21, + name: "Man-LT-JYJ4333", + features: [ + "remote-access-vpn-tunnel", + "tools", + "database", + "orion-ape-backup", + ], + status: "Active", + checks: { + icon: "status_up", + num: 25, + }, + "cpu-load": 12, + firstUrl: "https://en.wikipedia.org/wiki/Kyiv", + firstUrlLabel: "Kyiv", + secondUrl: "https://en.wikipedia.org/wiki/VirtualBox", + secondUrlLabel: "VirtualBox", + }, + { + position: 22, + name: "FOCUS-SVR-02258", + features: ["remote-access-vpn-tunnel", "patch-manager01"], + status: "Active", + checks: { + icon: "status_up", + num: 25, + }, + "cpu-load": 86, + firstUrl: "https://en.wikipedia.org/wiki/Austin", + firstUrlLabel: "Austin", + secondUrl: "https://en.wikipedia.org/wiki/VMware_Workstation", + secondUrlLabel: "Workstation", + }, + { + position: 23, + name: "Man-LT-JYJ4AD5", + features: [ + "remote-access-vpn-tunnel", + "tools", + "database", + "orion-ape-backup", + "patch-manager01", + ], + status: "Active", + checks: { + icon: "status_inactive", + num: 25, + }, + "cpu-load": 35, + firstUrl: "https://en.wikipedia.org/wiki/Austin", + firstUrlLabel: "Austin", + secondUrl: "https://en.wikipedia.org/wiki/VMware_Workstation", + secondUrlLabel: "Workstation", + }, + { + position: 24, + name: "Man-LT-JYJ4AD5", + features: [ + "remote-access-vpn-tunnel", + "tools", + "database", + "patch-manager01", + ], + status: "Active", + checks: { + icon: "status_up", + num: 25, + }, + "cpu-load": 32, + firstUrl: "https://en.wikipedia.org/wiki/Brno", + firstUrlLabel: "Brno", + secondUrl: "https://en.wikipedia.org/wiki/VMware_Workstation", + secondUrlLabel: "Workstation", + }, + { + position: 25, + name: "Man-LT-JYJ4AD5", + features: [ + "remote-access-vpn-tunnel", + "database", + "orion-ape-backup", + "patch-manager01", + ], + status: "Active", + checks: { + icon: "status_up", + num: 25, + }, + "cpu-load": 64, + firstUrl: "https://en.wikipedia.org/wiki/Kyiv", + firstUrlLabel: "Kyiv", + secondUrl: "https://en.wikipedia.org/wiki/VMware_Workstation", + secondUrlLabel: "Workstation", + }, + { + position: 26, + name: "Man-LT-111", + features: [], + status: "Active", + checks: { + icon: "status_external", + num: 25, + }, + "cpu-load": 55, + firstUrl: "https://en.wikipedia.org/wiki/Brno", + firstUrlLabel: "Brno", + secondUrl: "https://en.wikipedia.org/wiki/VMware_Workstation", + secondUrlLabel: "Workstation", + }, + { + position: 27, + name: "Man-LT-2222", + features: [ + "remote-access-vpn-tunnel", + "tools", + "database", + "orion-ape-backup", + "patch-manager01", + ], + status: "Active", + checks: { + icon: "status_inactive", + num: 25, + }, + "cpu-load": 34, + firstUrl: "https://en.wikipedia.org/wiki/Brno", + firstUrlLabel: "Brno", + secondUrl: "https://en.wikipedia.org/wiki/VMware_Workstation", + secondUrlLabel: "Workstation", + }, + { + position: 28, + name: "Man-LT-333333", + features: [ + "remote-access-vpn-tunnel", + "tools", + "database", + "patch-manager01", + ], + status: "Active", + checks: { + icon: "status_up", + num: 25, + }, + "cpu-load": 56, + firstUrl: "https://en.wikipedia.org/wiki/Kyiv", + firstUrlLabel: "Kyiv", + secondUrl: "https://en.wikipedia.org/wiki/VMware_Workstation", + secondUrlLabel: "Workstation", + }, + { + position: 29, + name: "Man-LT-444444", + features: [ + "remote-access-vpn-tunnel", + "database", + "orion-ape-backup", + "patch-manager01", + ], + status: "Active", + checks: { + icon: "status_up", + num: 25, + }, + "cpu-load": 26, + firstUrl: "https://en.wikipedia.org/wiki/Kyiv", + firstUrlLabel: "Kyiv", + secondUrl: "https://en.wikipedia.org/wiki/VMware_Workstation", + secondUrlLabel: "Workstation", + }, + { + position: 30, + name: "Man-LT-555555", + features: [ + "remote-access-vpn-tunnel", + "database", + "orion-ape-backup", + "patch-manager01", + ], + status: "Active", + checks: { + icon: "status_up", + num: 25, + }, + "cpu-load": 76, + firstUrl: "https://en.wikipedia.org/wiki/Austin", + firstUrlLabel: "Austin", + secondUrl: "https://en.wikipedia.org/wiki/VMware_Workstation", + secondUrlLabel: "Workstation", + }, + { + position: 31, + name: "FOCUS-SVR-02258", + features: ["remote-access-vpn-tunnel", "patch-manager01"], + status: "Active", + checks: { + icon: "status_up", + num: 25, + }, + "cpu-load": 86, + firstUrl: "https://en.wikipedia.org/wiki/Brno", + firstUrlLabel: "Brno", + secondUrl: "https://en.wikipedia.org/wiki/VMware_Workstation", + secondUrlLabel: "Workstation", + }, + { + position: 32, + name: "FOCUS-SVR-03312", + features: ["tools", "database", "orion-ape-backup"], + status: "Active", + checks: { + icon: "status_critical", + num: 25, + }, + "cpu-load": 47, + firstUrl: "https://en.wikipedia.org/wiki/Brno", + firstUrlLabel: "Brno", + secondUrl: "https://en.wikipedia.org/wiki/VMware_Workstation", + secondUrlLabel: "Workstation", + }, + { + position: 33, + name: "FOCUS-SVR-02258", + features: [ + "remote-access-vpn-tunnel", + "database", + "orion-ape-backup", + "patch-manager01", + ], + status: "Active", + checks: { + icon: "status_down", + num: 25, + }, + "cpu-load": 53, + firstUrl: "https://en.wikipedia.org/wiki/Kyiv", + firstUrlLabel: "Kyiv", + secondUrl: "https://en.wikipedia.org/wiki/VMware_Workstation", + secondUrlLabel: "Workstation", + }, + { + position: 34, + name: "Man-LT-JYJ4AD5", + features: [ + "remote-access-vpn-tunnel", + "tools", + "database", + "orion-ape-backup", + ], + status: "Active", + checks: { + icon: "status_up", + num: 25, + }, + "cpu-load": 32, + firstUrl: "https://en.wikipedia.org/wiki/Kyiv", + firstUrlLabel: "Kyiv", + secondUrl: "https://en.wikipedia.org/wiki/VirtualBox", + secondUrlLabel: "VirtualBox", + }, + { + position: 35, + name: "Man-LT-JYJ425", + features: [ + "remote-access-vpn-tunnel", + "tools", + "database", + "orion-ape-backup", + ], + status: "Active", + checks: { + icon: "status_up", + num: 25, + }, + "cpu-load": 22, + firstUrl: "https://en.wikipedia.org/wiki/Kyiv", + firstUrlLabel: "Kyiv", + secondUrl: "https://en.wikipedia.org/wiki/VirtualBox", + secondUrlLabel: "VirtualBox", + }, + { + position: 36, + name: "Man-LT-JYJ4333", + features: [ + "remote-access-vpn-tunnel", + "tools", + "database", + "orion-ape-backup", + ], + status: "Active", + checks: { + icon: "status_up", + num: 25, + }, + "cpu-load": 12, + firstUrl: "https://en.wikipedia.org/wiki/Kyiv", + firstUrlLabel: "Kyiv", + secondUrl: "https://en.wikipedia.org/wiki/VirtualBox", + secondUrlLabel: "VirtualBox", + }, + { + position: 37, + name: "FOCUS-SVR-02258", + features: ["remote-access-vpn-tunnel", "patch-manager01"], + status: "Active", + checks: { + icon: "status_up", + num: 25, + }, + "cpu-load": 86, + firstUrl: "https://en.wikipedia.org/wiki/Austin", + firstUrlLabel: "Austin", + secondUrl: "https://en.wikipedia.org/wiki/VMware_Workstation", + secondUrlLabel: "Workstation", + }, + { + position: 38, + name: "Man-LT-JYJ4AD5", + features: [ + "remote-access-vpn-tunnel", + "tools", + "database", + "orion-ape-backup", + "patch-manager01", + ], + status: "Active", + checks: { + icon: "status_inactive", + num: 25, + }, + "cpu-load": 35, + firstUrl: "https://en.wikipedia.org/wiki/Austin", + firstUrlLabel: "Austin", + secondUrl: "https://en.wikipedia.org/wiki/VMware_Workstation", + secondUrlLabel: "Workstation", + }, + { + position: 39, + name: "Man-LT-JYJ4AD5", + features: [ + "remote-access-vpn-tunnel", + "tools", + "database", + "patch-manager01", + ], + status: "Active", + checks: { + icon: "status_up", + num: 25, + }, + "cpu-load": 32, + firstUrl: "https://en.wikipedia.org/wiki/Brno", + firstUrlLabel: "Brno", + secondUrl: "https://en.wikipedia.org/wiki/VMware_Workstation", + secondUrlLabel: "Workstation", + }, + { + position: 40, + name: "Man-LT-JYJ4AD5", + features: [ + "remote-access-vpn-tunnel", + "database", + "orion-ape-backup", + "patch-manager01", + ], + status: "Active", + checks: { + icon: "status_up", + num: 25, + }, + "cpu-load": 64, + firstUrl: "https://en.wikipedia.org/wiki/Kyiv", + firstUrlLabel: "Kyiv", + secondUrl: "https://en.wikipedia.org/wiki/VMware_Workstation", + secondUrlLabel: "Workstation", + }, + { + position: 41, + name: "Man-LT-111", + features: [], + status: "Active", + checks: { + icon: "status_external", + num: 25, + }, + "cpu-load": 55, + firstUrl: "https://en.wikipedia.org/wiki/Brno", + firstUrlLabel: "Brno", + secondUrl: "https://en.wikipedia.org/wiki/VMware_Workstation", + secondUrlLabel: "Workstation", + }, + { + position: 42, + name: "Man-LT-2222", + features: [ + "remote-access-vpn-tunnel", + "tools", + "database", + "orion-ape-backup", + "patch-manager01", + ], + status: "Active", + checks: { + icon: "status_inactive", + num: 25, + }, + "cpu-load": 34, + firstUrl: "https://en.wikipedia.org/wiki/Brno", + firstUrlLabel: "Brno", + secondUrl: "https://en.wikipedia.org/wiki/VMware_Workstation", + secondUrlLabel: "Workstation", + }, + { + position: 43, + name: "Man-LT-333333", + features: [ + "remote-access-vpn-tunnel", + "tools", + "database", + "patch-manager01", + ], + status: "Active", + checks: { + icon: "status_up", + num: 25, + }, + "cpu-load": 56, + firstUrl: "https://en.wikipedia.org/wiki/Kyiv", + firstUrlLabel: "Kyiv", + secondUrl: "https://en.wikipedia.org/wiki/VMware_Workstation", + secondUrlLabel: "Workstation", + }, + { + position: 44, + name: "Man-LT-444444", + features: [ + "remote-access-vpn-tunnel", + "database", + "orion-ape-backup", + "patch-manager01", + ], + status: "Active", + checks: { + icon: "status_up", + num: 25, + }, + "cpu-load": 26, + firstUrl: "https://en.wikipedia.org/wiki/Kyiv", + firstUrlLabel: "Kyiv", + secondUrl: "https://en.wikipedia.org/wiki/VMware_Workstation", + secondUrlLabel: "Workstation", + }, + { + position: 45, + name: "Man-LT-555555", + features: [ + "remote-access-vpn-tunnel", + "database", + "orion-ape-backup", + "patch-manager01", + ], + status: "Active", + checks: { + icon: "status_up", + num: 25, + }, + "cpu-load": 76, + firstUrl: "https://en.wikipedia.org/wiki/Austin", + firstUrlLabel: "Austin", + secondUrl: "https://en.wikipedia.org/wiki/VMware_Workstation", + secondUrlLabel: "Workstation", + }, +]; +\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\`, + "overview/hero/widget-configs/kpi.ts": \\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\`// © 2022 SolarWinds Worldwide, LLC. All rights reserved. +// +// Permission is hereby granted, free of charge, to any person obtaining a copy +// of this software and associated documentation files (the "Software"), to +// deal in the Software without restriction, including without limitation the +// rights to use, copy, modify, merge, publish, distribute, sublicense, and/or +// sell copies of the Software, and to permit persons to whom the Software is +// furnished to do so, subject to the following conditions: +// +// The above copyright notice and this permission notice shall be included in +// all copies or substantial portions of the Software. +// +// THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR +// IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, +// FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE +// AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER +// LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, +// OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN +// THE SOFTWARE. + +import { + DEFAULT_PIZZAGNA_ROOT, + IProviderConfiguration, + IRefresherProperties, + IWidget, + KpiComponent, + NOVA_KPI_DATASOURCE_ADAPTER, + PizzagnaLayer, + WellKnownProviders, +} from "@nova-ui/dashboards"; + +import { + HarryPotterAverageRatingDataSource, + HarryPotterRatingsCountDataSource, +} from "../data/kpi-datasources"; + +export const kpiConfig: IWidget = { + id: "kpiWidgetId", + type: "kpi", + pizzagna: { + [PizzagnaLayer.Configuration]: { + [DEFAULT_PIZZAGNA_ROOT]: { + providers: { + [WellKnownProviders.Refresher]: { + properties: { + // Configuring the refresher interval so that our data source is invoked every ten minutes + interval: 60 * 10, + enabled: true, + } as IRefresherProperties, + } as Partial, + }, + }, + header: { + properties: { + title: "Harry Potter and the Sorcerer's Stone", + subtitle: "By J. K. Rowling", + }, + }, + tiles: { + properties: { + nodes: ["kpi1", "kpi2"], + }, + }, + kpi1: { + id: "kpi1", + componentType: KpiComponent.lateLoadKey, + properties: { + widgetData: { + label: "Average Rating", + backgroundColor: "var(--nui-color-chart-three)", + units: "out of 5 Stars", + }, + }, + providers: { + [WellKnownProviders.DataSource]: { + // Setting the data source providerId for the tile with id "kpi1" + providerId: + HarryPotterAverageRatingDataSource.providerId, + } as IProviderConfiguration, + [WellKnownProviders.Adapter]: { + providerId: NOVA_KPI_DATASOURCE_ADAPTER, + properties: { + componentId: "kpi1", + propertyPath: "widgetData", + }, + } as IProviderConfiguration, + }, + }, + kpi2: { + id: "kpi2", + componentType: KpiComponent.lateLoadKey, + properties: { + widgetData: { + label: "Reader Feedback", + units: "Ratings", + }, + }, + providers: { + [WellKnownProviders.DataSource]: { + // Setting the data source providerId for the tile with id "kpi" + providerId: + HarryPotterRatingsCountDataSource.providerId, + } as IProviderConfiguration, + [WellKnownProviders.Adapter]: { + providerId: NOVA_KPI_DATASOURCE_ADAPTER, + properties: { + componentId: "kpi2", + propertyPath: "widgetData", + }, + } as IProviderConfiguration, + }, + }, + }, + }, +}; +\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\`, + "overview/hero/widget-configs/proportional.ts": \\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\`// © 2022 SolarWinds Worldwide, LLC. All rights reserved. +// +// Permission is hereby granted, free of charge, to any person obtaining a copy +// of this software and associated documentation files (the "Software"), to +// deal in the Software without restriction, including without limitation the +// rights to use, copy, modify, merge, publish, distribute, sublicense, and/or +// sell copies of the Software, and to permit persons to whom the Software is +// furnished to do so, subject to the following conditions: +// +// The above copyright notice and this permission notice shall be included in +// all copies or substantial portions of the Software. +// +// THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR +// IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, +// FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE +// AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER +// LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, +// OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN +// THE SOFTWARE. + +import { + DEFAULT_PIZZAGNA_ROOT, + IProportionalWidgetChartOptions, + IProviderConfiguration, + IWidget, + LegendPlacement, + PizzagnaLayer, + ProportionalWidgetChartTypes, + WellKnownProviders, +} from "@nova-ui/dashboards"; + +import { BeerReviewCountsByCityMockDataSource } from "../data/proportional-datasources"; + +export const proportionalConfig: IWidget = { + id: "proportionalWidgetId", + type: "proportional", + pizzagna: { + [PizzagnaLayer.Configuration]: { + [DEFAULT_PIZZAGNA_ROOT]: { + providers: { + [WellKnownProviders.Refresher]: { + properties: { + interval: 0, + }, + }, + }, + }, + header: { + properties: { + title: "Beer Review Tally by City", + subtitle: "These People Love Beer", + }, + }, + chart: { + providers: { + [WellKnownProviders.DataSource]: { + providerId: + BeerReviewCountsByCityMockDataSource.providerId, + } as IProviderConfiguration, + }, + properties: { + configuration: { + chartOptions: { + type: ProportionalWidgetChartTypes.DonutChart, + legendPlacement: LegendPlacement.Right, + } as IProportionalWidgetChartOptions, + }, + }, + }, + }, + }, +}; +\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\`, + "overview/hero/widget-configs/risk-score.ts": \\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\`// © 2023 SolarWinds Worldwide, LLC. All rights reserved. +// +// Permission is hereby granted, free of charge, to any person obtaining a copy +// of this software and associated documentation files (the "Software"), to +// deal in the Software without restriction, including without limitation the +// rights to use, copy, modify, merge, publish, distribute, sublicense, and/or +// sell copies of the Software, and to permit persons to whom the Software is +// furnished to do so, subject to the following conditions: +// +// The above copyright notice and this permission notice shall be included in +// all copies or substantial portions of the Software. +// +// THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR +// IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, +// FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE +// AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER +// LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, +// OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN +// THE SOFTWARE. + +import { + DEFAULT_PIZZAGNA_ROOT, + IProviderConfiguration, + IRefresherProperties, + IWidget, + RiskScoreTileComponent, + NOVA_KPI_DATASOURCE_ADAPTER, + PizzagnaLayer, + WellKnownProviders, +} from "@nova-ui/dashboards"; + +import { HarryPotterAverageRatingDataSource } from "../data/kpi-datasources"; + +export const riskScoreConfig: IWidget = { + id: "riskScoreWidgetId", + type: "risk-score", + pizzagna: { + [PizzagnaLayer.Configuration]: { + [DEFAULT_PIZZAGNA_ROOT]: { + providers: { + [WellKnownProviders.Refresher]: { + properties: { + // Configuring the refresher interval so that our data source is invoked every ten minutes + interval: 60 * 10, + enabled: true, + } as IRefresherProperties, + } as Partial, + }, + }, + header: { + properties: { + title: "Harry Potter and the Sorcerer's Stone", + subtitle: "By J. K. Rowling", + }, + }, + tiles: { + properties: { + nodes: ["riskScore1"], + }, + }, + riskScore1: { + id: "riskScore1", + componentType: RiskScoreTileComponent.lateLoadKey, + properties: { + widgetData: { + minValue: 0, + maxValue: 5, + useStaticLabel: false, + staticLabel: undefined, + label: \\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\`Average Rating\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\`, + description: \\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\`Harry Potter and the Sorcerer's Stone By J. K. Rowling Average Rating Risk Score\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\`, + }, + }, + providers: { + [WellKnownProviders.DataSource]: { + // Setting the data source providerId for the tile with id "riskScore1" + providerId: + HarryPotterAverageRatingDataSource.providerId, + } as IProviderConfiguration, + [WellKnownProviders.Adapter]: { + providerId: NOVA_KPI_DATASOURCE_ADAPTER, + properties: { + componentId: "riskScore1", + propertyPath: "widgetData", + }, + } as IProviderConfiguration, + }, + }, + }, + }, +}; +\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\`, + "overview/hero/widget-configs/table.ts": \\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\`// © 2022 SolarWinds Worldwide, LLC. All rights reserved. +// +// Permission is hereby granted, free of charge, to any person obtaining a copy +// of this software and associated documentation files (the "Software"), to +// deal in the Software without restriction, including without limitation the +// rights to use, copy, modify, merge, publish, distribute, sublicense, and/or +// sell copies of the Software, and to permit persons to whom the Software is +// furnished to do so, subject to the following conditions: +// +// The above copyright notice and this permission notice shall be included in +// all copies or substantial portions of the Software. +// +// THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR +// IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, +// FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE +// AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER +// LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, +// OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN +// THE SOFTWARE. + +import { + ITableWidgetColumnConfig, + ITableWidgetSorterConfig, + IWidget, + PizzagnaLayer, + RawFormatterComponent, + WellKnownProviders, +} from "@nova-ui/dashboards"; + +import { BeerDataSource } from "../data/table/beer-data-source"; + +export const tableConfig: IWidget = { + id: "tableWidgetId", + type: "table", + pizzagna: { + [PizzagnaLayer.Configuration]: { + header: { + properties: { + title: "Stupendous Suds", + subtitle: "Try These Brilliant Brews", + }, + }, + table: { + providers: { + [WellKnownProviders.DataSource]: { + providerId: BeerDataSource.providerId, + }, + }, + properties: { + configuration: { + columns: [ + { + id: "column1", + label: "Beer Name", + isActive: true, + width: 185, + formatter: { + componentType: + RawFormatterComponent.lateLoadKey, + properties: { + dataFieldIds: { + value: "name", + }, + }, + }, + }, + { + id: "column2", + label: "Tagline", + isActive: true, + width: 250, + formatter: { + componentType: + RawFormatterComponent.lateLoadKey, + properties: { + dataFieldIds: { + value: "tagline", + }, + }, + }, + }, + { + id: "column3", + label: "First Brewed", + isActive: true, + formatter: { + componentType: + RawFormatterComponent.lateLoadKey, + properties: { + dataFieldIds: { + value: "first_brewed", + }, + }, + }, + }, + { + id: "column4", + label: "Description", + isActive: true, + width: 275, + formatter: { + componentType: + RawFormatterComponent.lateLoadKey, + properties: { + dataFieldIds: { + value: "description", + }, + }, + }, + }, + ] as ITableWidgetColumnConfig[], + sorterConfiguration: { + descendantSorting: false, + sortBy: "", + } as ITableWidgetSorterConfig, + hasVirtualScroll: true, + }, + }, + }, + }, + }, +}; +\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\`, + "overview/hero/widget-configs/timeseries.ts": \\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\`// © 2022 SolarWinds Worldwide, LLC. All rights reserved. +// +// Permission is hereby granted, free of charge, to any person obtaining a copy +// of this software and associated documentation files (the "Software"), to +// deal in the Software without restriction, including without limitation the +// rights to use, copy, modify, merge, publish, distribute, sublicense, and/or +// sell copies of the Software, and to permit persons to whom the Software is +// furnished to do so, subject to the following conditions: +// +// The above copyright notice and this permission notice shall be included in +// all copies or substantial portions of the Software. +// +// THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR +// IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, +// FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE +// AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER +// LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, +// OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN +// THE SOFTWARE. + +import moment from "moment/moment"; + +import { + DEFAULT_PIZZAGNA_ROOT, + IProviderConfiguration, + ISerializableTimeframe, + ITimeseriesItemConfiguration, + IWidget, + LegendPlacement, + WellKnownProviders, +} from "@nova-ui/dashboards"; + +import { BeerVsReadingMockDataSource } from "../data/timeseries-data-sources"; + +export const timeseriesConfig: IWidget = { + id: "timeseriesWidgetId", + type: "timeseries", + pizzagna: { + configuration: { + [DEFAULT_PIZZAGNA_ROOT]: { + providers: { + [WellKnownProviders.DataSource]: { + providerId: BeerVsReadingMockDataSource.providerId, + } as IProviderConfiguration, + }, + }, + header: { + properties: { + title: "Primary Leisure Activity Over Time", + subtitle: "Survey of 1000 Solarians", + }, + }, + chart: { + providers: { + [WellKnownProviders.Adapter]: { + properties: { + series: [ + { + id: "series-1", + label: "Beer Tasting", + selectedSeriesId: "series-1", + }, + { + id: "series-2", + label: "Reading", + selectedSeriesId: "series-2", + }, + ] as ITimeseriesItemConfiguration[], + }, + } as Partial, + }, + properties: { + configuration: { + legendPlacement: LegendPlacement.Right, + enableZoom: true, + leftAxisLabel: "Solarians (%)", + }, + }, + }, + timeframeSelection: { + properties: { + timeframe: { + selectedPresetId: "last7Days", + } as ISerializableTimeframe, + minDate: moment().subtract(10, "days").format(), + maxDate: moment().format(), + }, + }, + }, + }, +}; +\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\`, + "overview/overview-docs.component.ts": \\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\`// © 2022 SolarWinds Worldwide, LLC. All rights reserved. +// +// Permission is hereby granted, free of charge, to any person obtaining a copy +// of this software and associated documentation files (the "Software"), to +// deal in the Software without restriction, including without limitation the +// rights to use, copy, modify, merge, publish, distribute, sublicense, and/or +// sell copies of the Software, and to permit persons to whom the Software is +// furnished to do so, subject to the following conditions: +// +// The above copyright notice and this permission notice shall be included in +// all copies or substantial portions of the Software. +// +// THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR +// IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, +// FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE +// AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER +// LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, +// OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN +// THE SOFTWARE. + +import { Component } from "@angular/core"; + +@Component({ + selector: "nui-dashboard-overview-docs", + templateUrl: "./overview-docs.component.html", + standalone: false, +}) +export class OverviewDocsComponent {} +\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\`, + "overview/overview.module.ts": \\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\`// © 2022 SolarWinds Worldwide, LLC. All rights reserved. +// +// Permission is hereby granted, free of charge, to any person obtaining a copy +// of this software and associated documentation files (the "Software"), to +// deal in the Software without restriction, including without limitation the +// rights to use, copy, modify, merge, publish, distribute, sublicense, and/or +// sell copies of the Software, and to permit persons to whom the Software is +// furnished to do so, subject to the following conditions: +// +// The above copyright notice and this permission notice shall be included in +// all copies or substantial portions of the Software. +// +// THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR +// IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, +// FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE +// AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER +// LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, +// OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN +// THE SOFTWARE. + +import { CommonModule } from "@angular/common"; +import { NgModule } from "@angular/core"; +import { RouterModule } from "@angular/router"; + +import { + NuiBusyModule, + NuiButtonModule, + NuiDocsModule, + NuiIconModule, + NuiMessageModule, + NuiSwitchModule, +} from "@nova-ui/bits"; +import { + ConfiguratorHeadingService, + IFormatterDefinition, + LinkFormatterComponent, + NuiDashboardsModule, + WellKnownPathKey, + WidgetTypesService, +} from "@nova-ui/dashboards"; + +import { HeroDashboardComponent } from "./hero/dashboard/hero-dashboard.component"; +import { + HarryPotterAverageRatingDataSource, + HarryPotterRatingsCountDataSource, +} from "./hero/data/kpi-datasources"; +import { + BeerReviewCountsByCityMockDataSource, + BeerReviewCountsByCityMockDataSource2, +} from "./hero/data/proportional-datasources"; +import { BeerDataSource } from "./hero/data/table/beer-data-source"; +import { RandomUserDataSource } from "./hero/data/table/random-user-data-source"; +import { + BeerVsReadingMockDataSource, + LoungingVsFrisbeeGolfMockDataSource, +} from "./hero/data/timeseries-data-sources"; +import { OverviewDocsComponent } from "./overview-docs.component"; + +const routes = [ + { + path: "", + component: OverviewDocsComponent, + data: { + srlc: { + hideIndicator: true, + }, + showThemeSwitcher: true, + }, + }, + { + path: "hero", + component: HeroDashboardComponent, + data: { + srlc: { + hideIndicator: true, + }, + }, + }, +]; + +@NgModule({ + imports: [ + CommonModule, + NuiDashboardsModule, + NuiBusyModule, + NuiButtonModule, + NuiDocsModule, + NuiMessageModule, + NuiSwitchModule, + NuiIconModule, + RouterModule.forChild(routes), + ], + declarations: [OverviewDocsComponent, HeroDashboardComponent], + providers: [ConfiguratorHeadingService], +}) +export default class OverviewModule { + constructor(private widgetTypesService: WidgetTypesService) { + this.setupDataSourceProviders(); + this.setupProportionalLegendFormatters(); + } + + private setupDataSourceProviders() { + this.setDataSourceProviders("table", [ + RandomUserDataSource.providerId, + BeerDataSource.providerId, + ]); + this.setDataSourceProviders("kpi", [ + HarryPotterAverageRatingDataSource.providerId, + HarryPotterRatingsCountDataSource.providerId, + ]); + this.setDataSourceProviders("risk-score", [ + HarryPotterAverageRatingDataSource.providerId, + HarryPotterRatingsCountDataSource.providerId, + ]); + this.setDataSourceProviders("proportional", [ + BeerReviewCountsByCityMockDataSource.providerId, + BeerReviewCountsByCityMockDataSource2.providerId, + ]); + this.setDataSourceProviders("timeseries", [ + BeerVsReadingMockDataSource.providerId, + LoungingVsFrisbeeGolfMockDataSource.providerId, + ]); + } + + private setDataSourceProviders(type: string, providers: string[]) { + const widgetTemplate = this.widgetTypesService.getWidgetType(type, 1); + this.widgetTypesService.setNode( + widgetTemplate, + "configurator", + WellKnownPathKey.DataSourceProviders, + providers + ); + } + + private setupProportionalLegendFormatters() { + const formatters: IFormatterDefinition[] = [ + { + componentType: LinkFormatterComponent.lateLoadKey, + label: $localize\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\`Link\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\`, + dataTypes: { + value: "label", + link: "link", + }, + }, + ]; + + const widgetTemplate = this.widgetTypesService.getWidgetType( + "proportional", + 1 + ); + this.widgetTypesService.setNode( + widgetTemplate, + "configurator", + WellKnownPathKey.Formatters, + formatters + ); + } +} +\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\`, + "tutorials/customization/configurator-section/custom-configurator-section/custom-configurator-section.example.component.ts": \\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\`// © 2022 SolarWinds Worldwide, LLC. All rights reserved. +// +// Permission is hereby granted, free of charge, to any person obtaining a copy +// of this software and associated documentation files (the "Software"), to +// deal in the Software without restriction, including without limitation the +// rights to use, copy, modify, merge, publish, distribute, sublicense, and/or +// sell copies of the Software, and to permit persons to whom the Software is +// furnished to do so, subject to the following conditions: +// +// The above copyright notice and this permission notice shall be included in +// all copies or substantial portions of the Software. +// +// THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR +// IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, +// FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE +// AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER +// LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, +// OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN +// THE SOFTWARE. + +import { HttpClient, HttpErrorResponse } from "@angular/common/http"; +import { + ChangeDetectionStrategy, + ChangeDetectorRef, + Component, + EventEmitter, + Injectable, + Input, + OnChanges, + OnDestroy, + OnInit, + Output, + SimpleChanges, +} from "@angular/core"; +import { FormBuilder, FormGroup, Validators } from "@angular/forms"; +import { GridsterConfig, GridsterItem } from "angular-gridster2"; +// eslint-disable-next-line import/no-deprecated +import { BehaviorSubject, combineLatest, Observable } from "rxjs"; +// eslint-disable-next-line import/no-deprecated +import { finalize, map, startWith } from "rxjs/operators"; + +import { DataSourceService, IFilteringOutputs } from "@nova-ui/bits"; +import { + ComponentRegistryService, + DATA_SOURCE, + DEFAULT_PIZZAGNA_ROOT, + IDashboard, + IHasChangeDetector, + IHasForm, + IKpiData, + IProviderConfiguration, + IRefresherProperties, + IWidget, + IWidgets, + KpiComponent, + NOVA_KPI_DATASOURCE_ADAPTER, + PizzagnaLayer, + ProviderRegistryService, + WellKnownPathKey, + WellKnownProviders, + WidgetTypesService, +} from "@nova-ui/dashboards"; + +/** + * A custom version of the KpiDescriptionConfigurationComponent provided by the dashboards framework. + * --- + * For this example, the existing background color selection functionality has been replaced by custom + * template content. + */ +@Component({ + selector: "custom-kpi-description-configuration", + template: \\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\` + + +
+
+ + + +
+ + +
+
+ Custom Content +
+
+ The default version of this configurator section + displays a background color selector here. +
+
+ + +
+ + + +
+
+
+ \\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\`, + styleUrls: ["./custom-configurator-section.example.component.less"], + changeDetection: ChangeDetectionStrategy.OnPush, + standalone: false, +}) +// Remember to declare this class in the parent module +export class CustomKpiDescriptionConfigurationComponent + implements OnInit, OnChanges, IHasChangeDetector, IHasForm +{ + // Ensure that the lateLoadKey value matches class name + public static lateLoadKey = "CustomKpiDescriptionConfigurationComponent"; + + @Input() componentId: string; + @Input() configurableUnits: boolean; + + @Input() label: string = ""; + @Input() units: string = ""; + + @Output() formReady = new EventEmitter(); + + public form: FormGroup; + public subtitle$: Observable; + + constructor( + public changeDetector: ChangeDetectorRef, + private formBuilder: FormBuilder + ) {} + + public ngOnInit(): void { + this.form = this.formBuilder.group({ + label: [this.label, [Validators.required]], + }); + + if (this.configurableUnits) { + this.form.addControl("units", this.formBuilder.control(this.units)); + } + + const label = this.form.get("label"); + // eslint-disable-next-line import/no-deprecated + const labelValue = label?.valueChanges.pipe(startWith(label?.value)); + + // eslint-disable-next-line import/no-deprecated + this.subtitle$ = combineLatest([ + labelValue?.pipe(map((t) => t || $localize\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\`no label\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\`)), + ]).pipe(map((labels) => labels.join(", "))); + + this.formReady.emit(this.form); + } + + public ngOnChanges(changes: SimpleChanges): void { + if (changes.label) { + this.form.patchValue({ label: changes.label.currentValue }); + } + if (changes.units) { + this.form.patchValue({ units: changes.units.currentValue }); + } + } +} + +/** + * A simple KPI data source to retrieve the average rating of Harry Potter and the Sorcerer's Stone (book) via googleapis + */ +@Injectable() +export class AverageRatingKpiDataSource + extends DataSourceService + implements OnDestroy +{ + // This is the ID we'll use to identify the provider + public static providerId = "AverageRatingKpiDataSource"; + + // Use this subject to communicate the data source's busy state + public busy = new BehaviorSubject(false); + + constructor(private http: HttpClient) { + super(); + } + + // In this example, getFilteredData is invoked every 10 minutes (Take a look at the refresher + // provider definition in the widget configuration below to see how the interval is set) + public async getFilteredData(): Promise { + this.busy.next(true); + return new Promise((resolve) => { + // *** Make a resource request to an external API (if needed) + this.http + .get("https://www.googleapis.com/books/v1/volumes/5MQFrgEACAAJ") + .pipe(finalize(() => this.busy.next(false))) + .subscribe({ + next: (data: any) => { + resolve({ + result: { + value: data.volumeInfo.averageRating, + }, + }); + }, + error: (error: HttpErrorResponse) => { + resolve({ + result: null, + error: { + type: error.status, + }, + }); + }, + }); + }); + } + + public ngOnDestroy(): void { + this.outputsSubject.complete(); + } +} + +/** + * A simple KPI data source to retrieve the ratings count of Harry Potter and the Sorcerer's Stone (book) via googleapis + */ +@Injectable() +export class RatingsCountKpiDataSource + extends DataSourceService + implements OnDestroy +{ + public static providerId = "RatingsCountKpiDataSource"; + + // Use this subject to communicate the data source's busy state + public busy = new BehaviorSubject(false); + + constructor(private http: HttpClient) { + super(); + } + + public async getFilteredData(): Promise { + this.busy.next(true); + return new Promise((resolve) => { + this.http + .get("https://www.googleapis.com/books/v1/volumes/5MQFrgEACAAJ") + .pipe(finalize(() => this.busy.next(false))) + .subscribe({ + next: (data: any) => { + resolve({ + result: { + value: data.volumeInfo.ratingsCount, + }, + }); + }, + error: (error: HttpErrorResponse) => { + resolve({ + result: null, + error: { + type: error.status, + }, + }); + }, + }); + }); + } + + public ngOnDestroy(): void { + this.outputsSubject.complete(); + } +} + +/** + * A component that instantiates the dashboard + */ +@Component({ + selector: "custom-configurator-section-example", + templateUrl: "./custom-configurator-section.example.component.html", + styleUrls: ["./custom-configurator-section.example.component.less"], + standalone: false, +}) +export class CustomConfiguratorSectionExampleComponent implements OnInit { + // This variable will hold all the data needed to define the layout and behavior of the widgets. + // Pass this to the dashboard component's dashboard input in the template. + public dashboard: IDashboard | undefined; + + // Angular gridster requires a configuration object even if it's empty. + // Pass this to the dashboard component's gridsterConfig input in the template. + public gridsterConfig: GridsterConfig = {}; + + // Boolean which dashboard takes in as an input if its true it allows you to move widgets around. + public editMode: boolean = false; + + constructor( + // WidgetTypesService provides the widget's necessary structure information + private widgetTypesService: WidgetTypesService, + + // In general, the ProviderRegistryService is used for making entities available for injection into dynamically loaded components. + private providerRegistry: ProviderRegistryService, + + // Inject the ComponentRegistryService to make our custom component available for late loading by the dashboards framework + private componentRegistry: ComponentRegistryService, + + private changeDetectorRef: ChangeDetectorRef + ) {} + + public ngOnInit(): void { + // Grab the widget's default template which will be needed as a parameter for setNode. + const widgetTemplate = this.widgetTypesService.getWidgetType("kpi", 1); + + // Replace the default KPI description configuration component with our custom one. + // Note: This could also be done in the parent module's constructor to give + // multiple dashboards access to the same custom configurator section. + this.widgetTypesService.setNode( + widgetTemplate, + "configurator", + WellKnownPathKey.TileDescriptionConfigComponentType, + CustomKpiDescriptionConfigurationComponent.lateLoadKey + ); + + // Register the custom configurator section with the component registry to make it available + // for late loading by the dashboards framework. + this.componentRegistry.registerByLateLoadKey( + CustomKpiDescriptionConfigurationComponent + ); + + // Register our data sources as dropdown options in the widget editor/configurator + // Note: This could also be done in the parent module's constructor so that + // multiple dashboards could have access to the same widget template modification. + this.widgetTypesService.setNode( + // This is the template we grabbed above with getWidgetType + widgetTemplate, + // We are setting the editor/configurator part of the widget template + "configurator", + // This indicates which node you are changing and we want to change + // the data source providers available for selection in the editor. + WellKnownPathKey.DataSourceProviders, + // We are setting the data sources available for selection in the editor + [ + AverageRatingKpiDataSource.providerId, + RatingsCountKpiDataSource.providerId, + ] + ); + + // Register the data sources available for injection into the KPI tiles. + // Note: Each tile of a KPI widget is assigned its own instance of a data source + this.providerRegistry.setProviders({ + [AverageRatingKpiDataSource.providerId]: { + provide: DATA_SOURCE, + useClass: AverageRatingKpiDataSource, + // Any dependencies that need to be injected into the provider must be listed here + deps: [HttpClient], + }, + [RatingsCountKpiDataSource.providerId]: { + provide: DATA_SOURCE, + useClass: RatingsCountKpiDataSource, + deps: [HttpClient], + }, + }); + + this.initializeDashboard(); + } + + public initializeDashboard(): void { + // We're using a static configuration object for this example (see widgetConfig at the bottom of the file), + // but this is where the widget's configuration could potentially be populated from a database + const kpiWidget = widgetConfig; + const widgetIndex: IWidgets = { + // Complete the KPI widget with information coming from its type definition + [kpiWidget.id]: + this.widgetTypesService.mergeWithWidgetType(kpiWidget), + }; + + // Setting the widget dimensions and position (this is for gridster) + const positions: Record = { + [kpiWidget.id]: { + cols: 4, + rows: 6, + y: 0, + x: 0, + }, + }; + + // Finally, assigning the variables we created above to the dashboard + this.dashboard = { + positions, + widgets: widgetIndex, + }; + } + + /** Used for restoring widgets state */ + public reInitializeDashboard(): void { + // destroys the components and their providers so the dashboard can re init data + this.dashboard = undefined; + this.changeDetectorRef.detectChanges(); + + this.initializeDashboard(); + } +} + +const widgetConfig: IWidget = { + id: "widget1", + type: "kpi", + pizzagna: { + [PizzagnaLayer.Configuration]: { + [DEFAULT_PIZZAGNA_ROOT]: { + providers: { + [WellKnownProviders.Refresher]: { + properties: { + // Configuring the refresher interval so that our data source is invoked every ten minutes + interval: 60 * 10, + enabled: true, + } as IRefresherProperties, + } as Partial, + }, + }, + header: { + properties: { + title: "Harry Potter and the Sorcerer's Stone", + subtitle: "By J. K. Rowling", + }, + }, + tiles: { + properties: { + nodes: ["kpi1"], + }, + }, + kpi1: { + id: "kpi1", + componentType: KpiComponent.lateLoadKey, + properties: { + widgetData: { + units: "out of 5 Stars", + label: "Average Rating", + }, + }, + providers: { + [WellKnownProviders.DataSource]: { + // Setting the data source providerId for the tile with id "kpi1" + providerId: AverageRatingKpiDataSource.providerId, + } as IProviderConfiguration, + [WellKnownProviders.Adapter]: { + providerId: NOVA_KPI_DATASOURCE_ADAPTER, + properties: { + componentId: "kpi1", + propertyPath: "widgetData", + }, + } as IProviderConfiguration, + }, + }, + }, + }, +}; +\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\`, + "tutorials/customization/configurator-section/custom-configurator-section-docs.component.ts": \\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\`// © 2022 SolarWinds Worldwide, LLC. All rights reserved. +// +// Permission is hereby granted, free of charge, to any person obtaining a copy +// of this software and associated documentation files (the "Software"), to +// deal in the Software without restriction, including without limitation the +// rights to use, copy, modify, merge, publish, distribute, sublicense, and/or +// sell copies of the Software, and to permit persons to whom the Software is +// furnished to do so, subject to the following conditions: +// +// The above copyright notice and this permission notice shall be included in +// all copies or substantial portions of the Software. +// +// THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR +// IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, +// FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE +// AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER +// LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, +// OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN +// THE SOFTWARE. + +import { Component } from "@angular/core"; + +@Component({ + selector: "custom-configurator-section-docs", + templateUrl: "./custom-configurator-section-docs.component.html", + standalone: false, +}) +export class CustomConfiguratorSectionDocsComponent {} +\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\`, + "tutorials/customization/configurator-section/custom-configurator-section.module.ts": \\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\`// © 2022 SolarWinds Worldwide, LLC. All rights reserved. +// +// Permission is hereby granted, free of charge, to any person obtaining a copy +// of this software and associated documentation files (the "Software"), to +// deal in the Software without restriction, including without limitation the +// rights to use, copy, modify, merge, publish, distribute, sublicense, and/or +// sell copies of the Software, and to permit persons to whom the Software is +// furnished to do so, subject to the following conditions: +// +// The above copyright notice and this permission notice shall be included in +// all copies or substantial portions of the Software. +// +// THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR +// IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, +// FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE +// AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER +// LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, +// OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN +// THE SOFTWARE. + +import { CommonModule } from "@angular/common"; +import { HttpClientModule } from "@angular/common/http"; +import { NgModule } from "@angular/core"; +import { ReactiveFormsModule } from "@angular/forms"; +import { RouterModule } from "@angular/router"; + +import { + NuiButtonModule, + NuiDocsModule, + NuiFormFieldModule, + NuiIconModule, + NuiMessageModule, + NuiSwitchModule, + NuiTextboxModule, + DEMO_PATH_TOKEN, +} from "@nova-ui/bits"; +import { + NuiDashboardConfiguratorModule, + NuiDashboardsModule, +} from "@nova-ui/dashboards"; + +import { + CustomConfiguratorSectionExampleComponent, + CustomKpiDescriptionConfigurationComponent, +} from "./custom-configurator-section/custom-configurator-section.example.component"; +import { CustomConfiguratorSectionDocsComponent } from "./custom-configurator-section-docs.component"; +import { getDemoFiles } from "../../../../../demo-files-factory"; + +const routes = [ + { + path: "", + component: CustomConfiguratorSectionDocsComponent, + data: { + srlc: { + hideIndicator: true, + }, + showThemeSwitcher: true, + }, + }, + { + path: "example", + component: CustomConfiguratorSectionExampleComponent, + data: { + srlc: { + hideIndicator: true, + }, + }, + }, +]; + +@NgModule({ + imports: [ + CommonModule, + ReactiveFormsModule, + HttpClientModule, + NuiDashboardsModule, + NuiDashboardConfiguratorModule, + NuiDocsModule, + NuiFormFieldModule, + NuiIconModule, + NuiMessageModule, + NuiSwitchModule, + NuiTextboxModule, + NuiButtonModule, + RouterModule.forChild(routes), + ], + declarations: [ + CustomConfiguratorSectionDocsComponent, + CustomKpiDescriptionConfigurationComponent, + CustomConfiguratorSectionExampleComponent, + ], + providers: [ + { + provide: DEMO_PATH_TOKEN, + useValue: getDemoFiles("configurator-section"), + }, + ], +}) +export default class CustomConfiguratorSectionModule {} +\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\`, + "tutorials/customization/customization.module.ts": \\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\`// © 2022 SolarWinds Worldwide, LLC. All rights reserved. +// +// Permission is hereby granted, free of charge, to any person obtaining a copy +// of this software and associated documentation files (the "Software"), to +// deal in the Software without restriction, including without limitation the +// rights to use, copy, modify, merge, publish, distribute, sublicense, and/or +// sell copies of the Software, and to permit persons to whom the Software is +// furnished to do so, subject to the following conditions: +// +// The above copyright notice and this permission notice shall be included in +// all copies or substantial portions of the Software. +// +// THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR +// IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, +// FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE +// AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER +// LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, +// OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN +// THE SOFTWARE. + +import { NgModule, Type } from "@angular/core"; +import { RouterModule, Routes } from "@angular/router"; + +import { ConfiguratorHeadingService } from "@nova-ui/dashboards"; + +enum CustomizationModuleRoute { + ConfiguratorSection = "configurator-section", + Widget = "widget", + Formatter = "formatter", + DataSourceConfigurator = "data-source-configurator", +} + +const routes: Routes = [ + { + path: CustomizationModuleRoute.ConfiguratorSection, + loadChildren: async () => + import( + "./configurator-section/custom-configurator-section.module" + ) as object as Promise>, + }, + { + path: CustomizationModuleRoute.Widget, + loadChildren: async () => + import("./widget/custom-widget.module") as object as Promise< + Type + >, + }, + { + path: CustomizationModuleRoute.Formatter, + loadChildren: async () => + import("./formatter/custom-formatter.module") as object as Promise< + Type + >, + }, + { + path: CustomizationModuleRoute.DataSourceConfigurator, + loadChildren: async () => + import( + "./data-source-configurator/custom-data-source-configurator.module" + ) as object as Promise>, + }, +]; + +@NgModule({ + imports: [RouterModule.forChild(routes)], + providers: [ConfiguratorHeadingService], +}) +export default class CustomizationModule {} +\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\`, + "tutorials/customization/data-source-configurator/custom-data-source-configurator-docs.component.ts": \\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\`// © 2022 SolarWinds Worldwide, LLC. All rights reserved. +// +// Permission is hereby granted, free of charge, to any person obtaining a copy +// of this software and associated documentation files (the "Software"), to +// deal in the Software without restriction, including without limitation the +// rights to use, copy, modify, merge, publish, distribute, sublicense, and/or +// sell copies of the Software, and to permit persons to whom the Software is +// furnished to do so, subject to the following conditions: +// +// The above copyright notice and this permission notice shall be included in +// all copies or substantial portions of the Software. +// +// THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR +// IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, +// FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE +// AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER +// LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, +// OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN +// THE SOFTWARE. + +import { Component } from "@angular/core"; + +@Component({ + selector: "nui-custom-data-source-configurator-docs", + templateUrl: "./custom-data-source-configurator-docs.component.html", + standalone: false, +}) +export class CustomDataSourceConfiguratorDocComponent {} +\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\`, + "tutorials/customization/data-source-configurator/custom-data-source-configurator.module.ts": \\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\`// © 2022 SolarWinds Worldwide, LLC. All rights reserved. +// +// Permission is hereby granted, free of charge, to any person obtaining a copy +// of this software and associated documentation files (the "Software"), to +// deal in the Software without restriction, including without limitation the +// rights to use, copy, modify, merge, publish, distribute, sublicense, and/or +// sell copies of the Software, and to permit persons to whom the Software is +// furnished to do so, subject to the following conditions: +// +// The above copyright notice and this permission notice shall be included in +// all copies or substantial portions of the Software. +// +// THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR +// IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, +// FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE +// AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER +// LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, +// OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN +// THE SOFTWARE. + +import { NgModule } from "@angular/core"; +import { ReactiveFormsModule } from "@angular/forms"; +import { RouterModule, Routes } from "@angular/router"; + +// eslint-disable-next-line max-len +import { + NuiButtonModule, + NuiDocsModule, + NuiFormFieldModule, + NuiIconModule, + NuiMessageModule, + NuiSelectV2Module, + NuiSwitchModule, + NuiTextboxModule, + NuiValidationMessageModule, + DEMO_PATH_TOKEN, +} from "@nova-ui/bits"; +import { + NuiDashboardConfiguratorModule, + NuiDashboardsModule, +} from "@nova-ui/dashboards"; + +import { CustomDataSourceConfiguratorDocComponent } from "./custom-data-source-configurator-docs.component"; +import { + CustomDataSourceConfiguratorExampleComponent, + HarryPotterDataSourceConfiguratorComponent, +} from "./example/custom-data-source-configurator-example.component"; +import { getDemoFiles } from "../../../../../demo-files-factory"; + +const routes: Routes = [ + { + path: "", + component: CustomDataSourceConfiguratorDocComponent, + data: { + srlc: { + hideIndicator: true, + }, + showThemeSwitcher: true, + }, + }, +]; + +@NgModule({ + imports: [ + RouterModule.forChild(routes), + NuiDocsModule, + NuiButtonModule, + NuiMessageModule, + NuiDashboardConfiguratorModule, + NuiDashboardsModule, + NuiFormFieldModule, + NuiTextboxModule, + NuiSwitchModule, + NuiSelectV2Module, + NuiValidationMessageModule, + NuiIconModule, + ReactiveFormsModule, + ], + declarations: [ + CustomDataSourceConfiguratorDocComponent, + CustomDataSourceConfiguratorExampleComponent, + HarryPotterDataSourceConfiguratorComponent, + ], + providers: [ + { + provide: DEMO_PATH_TOKEN, + useValue: getDemoFiles("data-source-configurator"), + }, + ], +}) +export default class CustomDataSourceConfiguratorModuleRoute {} +\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\`, + "tutorials/customization/data-source-configurator/example/custom-data-source-configurator-example.component.ts": \\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\`// © 2022 SolarWinds Worldwide, LLC. All rights reserved. +// +// Permission is hereby granted, free of charge, to any person obtaining a copy +// of this software and associated documentation files (the "Software"), to +// deal in the Software without restriction, including without limitation the +// rights to use, copy, modify, merge, publish, distribute, sublicense, and/or +// sell copies of the Software, and to permit persons to whom the Software is +// furnished to do so, subject to the following conditions: +// +// The above copyright notice and this permission notice shall be included in +// all copies or substantial portions of the Software. +// +// THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR +// IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, +// FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE +// AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER +// LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, +// OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN +// THE SOFTWARE. + +import { HttpClient, HttpErrorResponse } from "@angular/common/http"; +import { + ChangeDetectorRef, + Component, + Inject, + Injectable, + Injector, + OnDestroy, + OnInit, +} from "@angular/core"; +import { FormBuilder, Validators } from "@angular/forms"; +import { GridsterConfig, GridsterItem } from "angular-gridster2"; +import { BehaviorSubject } from "rxjs"; +import { finalize } from "rxjs/operators"; + +import { + DataSourceService, + EventBus, + IEvent, + IFilteringOutputs, + LoggerService, +} from "@nova-ui/bits"; +import { + ComponentRegistryService, + ConfiguratorHeadingService, + DataSourceConfigurationV2Component, + DATA_SOURCE, + DEFAULT_PIZZAGNA_ROOT, + IConfigurable, + IDashboard, + IKpiData, + IProperties, + IProviderConfiguration, + IRefresherProperties, + IWidget, + IWidgets, + KpiComponent, + NOVA_KPI_DATASOURCE_ADAPTER, + PizzagnaLayer, + PIZZAGNA_EVENT_BUS, + ProviderRegistryService, + WellKnownPathKey, + WellKnownProviders, + WidgetTypesService, +} from "@nova-ui/dashboards"; + +/** + * This component will serve as the data source accordion in the configurator. + */ +@Component({ + selector: "harry-potter-data-source-configurator", + styleUrls: ["./custom-data-source-configurator-example.component.less"], + template: \\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\` + +
+ +
+ Data Source +
+ Harry Potter Books +
+
+
+
+ + + + {{ book.title }} + + + +
+
+ + + + {{ metric.label }} + + + +
+
+ \\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\`, + standalone: false, +}) +@Injectable() +export class HarryPotterDataSourceConfiguratorComponent + extends DataSourceConfigurationV2Component + implements OnInit +{ + // This lateLoadKey allows the component to be able to be registered by the componentRegistry + public static lateLoadKey = "HarryPotterDataSourceConfiguratorComponent"; + + // Array of books that will populate the book select + public books = [ + { + id: "5MQFrgEACAAJ", + title: $localize\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\`Harry Potter and the Sorcerer's Stone\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\`, + }, + { + id: "5iTebBW-w7QC", + title: $localize\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\`Harry Potter and the Chamber of Secrets\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\`, + }, + ]; + + // Array of metrics that will populate the metric select + public metrics = [ + { + id: "averageRating", + label: $localize\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\`Average Rating\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\`, + }, + { + id: "ratingsCount", + label: $localize\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\`Ratings Count\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\`, + }, + ]; + + // These need to be injected because DataSourceConfigurationV2Component uses them + constructor( + changeDetector: ChangeDetectorRef, + configuratorHeading: ConfiguratorHeadingService, + formBuilder: FormBuilder, + providerRegistryService: ProviderRegistryService, + @Inject(PIZZAGNA_EVENT_BUS) eventBus: EventBus, + injector: Injector, + logger: LoggerService + ) { + super( + changeDetector, + configuratorHeading, + formBuilder, + providerRegistryService, + eventBus, + injector, + logger + ); + } + + // Overriding 'ngOnInit' to add custom controls to the 'properties' form group + public ngOnInit(): void { + super.ngOnInit(); + + // Overriding the 'properties' control on the form to create a form group that accommodates our custom properties + this.form.setControl( + "properties", + this.formBuilder.group({ + bookId: [this.properties?.bookId ?? "", Validators.required], + metric: [this.properties?.metric ?? "", Validators.required], + }) + ); + // The default data source control has a required validator we're removing that validator here since we aren't using it. + this.form.setControl("dataSource", this.formBuilder.control(null)); + // Here we set the providerId to our only data source so when a new tile gets created it will default to it. + this.form.get("providerId")?.setValue(AcmeKpiDataSource.providerId); + // Here we subscribe to the form and if there are any changes we invoke the data source + this.form.valueChanges.subscribe((value) => { + if (!value.providerId) { + return; + } + this.invokeDataSource(value); + }); + } +} + +/** + * A simple KPI data source to retrieve the average rating of Harry Potter and the Sorcerer's Stone (book) via googleapis + */ +@Injectable() +export class AcmeKpiDataSource + extends DataSourceService + implements OnDestroy, IConfigurable +{ + // This is the ID we'll use to identify the provider + public static providerId = "AcmeKpiDataSource"; + + // Use this subject to communicate the data source's busy state + public busy = new BehaviorSubject(false); + + public properties: IProperties; + + constructor(private http: HttpClient) { + super(); + } + + // This function MUST be implemented in order to receive property updates from our configurator + public updateConfiguration(properties: IProperties): void { + // Saving the properties because we will need it for this data source. + this.properties = properties; + } + + // In this example, getFilteredData is invoked every 10 minutes (Take a look at the refresher + // provider definition in the widget configuration below to see how the interval is set) + public async getFilteredData(): Promise { + // For loading indicator to show + this.busy.next(true); + return new Promise((resolve) => { + // *** Make a resource request to an external API (if needed) + this.http + .get( + \\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\`https://www.googleapis.com/books/v1/volumes/\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\${this.properties?.bookId}\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\` + ) + // For loading indicator to be hidden + .pipe(finalize(() => this.busy.next(false))) + .subscribe({ + next: (data: any) => { + resolve({ + result: { + value: data.volumeInfo[this.properties?.metric], + }, + }); + }, + error: (error: HttpErrorResponse) => { + resolve({ + result: null, + error: { + type: error.status, + }, + }); + }, + }); + }); + } + + public ngOnDestroy(): void { + this.outputsSubject.complete(); + } +} + +/** + * A component that instantiates the dashboard + */ +@Component({ + selector: "custom-data-source-configurator-example", + templateUrl: "./custom-data-source-configurator-example.component.html", + styleUrls: ["./custom-data-source-configurator-example.component.less"], + standalone: false, +}) +export class CustomDataSourceConfiguratorExampleComponent implements OnInit { + // This variable will hold all the data needed to define the layout and behavior of the widgets. + // Pass this to the dashboard component's dashboard input in the template. + public dashboard: IDashboard; + + // Angular gridster requires a configuration object even if it's empty. + // Pass this to the dashboard component's gridsterConfig input in the template. + public gridsterConfig: GridsterConfig = {}; + + // Boolean which dashboard takes in as an input if its true it allows you to move widgets around. + public editMode: boolean = false; + + constructor( + // WidgetTypesService provides the widget's necessary structure information + private widgetTypesService: WidgetTypesService, + + // In general, the ProviderRegistryService is used for making entities available for injection into dynamically loaded components. + private providerRegistry: ProviderRegistryService, + + // Inject the ComponentRegistryService to make our custom component available for late loading by the dashboards framework + private componentRegistry: ComponentRegistryService + ) {} + + public ngOnInit(): void { + // Registering the new data source configurator so it can be used. + this.componentRegistry.registerByLateLoadKey( + HarryPotterDataSourceConfiguratorComponent + ); + // Registering the data source for injection into the KPI tile. + // Note: Each tile of a KPI widget is assigned its own instance of the data source + this.providerRegistry.setProviders({ + [AcmeKpiDataSource.providerId]: { + provide: DATA_SOURCE, + useClass: AcmeKpiDataSource, + // Any dependencies that need to be injected into the provider must be listed here + deps: [HttpClient], + }, + }); + + const kpiWidgetTemplate = this.widgetTypesService.getWidgetType( + "kpi", + 1 + ); + + this.widgetTypesService.setNode( + // This is the template we grabbed above with getWidgetType + kpiWidgetTemplate, + // We are setting the editor/configurator part of the widget template + "configurator", + // This is the path to go to the data source config component type. + WellKnownPathKey.DataSourceConfigComponentType, + // We are changing it to use the component we just created above instead of the default. + HarryPotterDataSourceConfiguratorComponent.lateLoadKey + ); + + // We're using a static configuration object for this example, but this is where + // the widget's configuration could potentially be populated from a database + const kpiWidget = widgetConfig; + const widgetIndex: IWidgets = { + // Complete the KPI widget with information coming from its type definition + [kpiWidget.id]: + this.widgetTypesService.mergeWithWidgetType(kpiWidget), + }; + + // Setting the widget dimensions and position (this is for gridster) + const positions: Record = { + [kpiWidget.id]: { + cols: 4, + rows: 6, + y: 0, + x: 0, + }, + }; + + // Finally, assigning the variables we created above to the dashboard + this.dashboard = { + positions, + widgets: widgetIndex, + }; + } +} + +const widgetConfig: IWidget = { + id: "widget1", + type: "kpi", + pizzagna: { + [PizzagnaLayer.Configuration]: { + [DEFAULT_PIZZAGNA_ROOT]: { + providers: { + [WellKnownProviders.Refresher]: { + properties: { + // Configuring the refresher interval so that our data source is invoked every ten minutes + interval: 60 * 10, + enabled: true, + } as IRefresherProperties, + } as Partial, + }, + }, + header: { + properties: { + title: "Harry Potter and the Sorcerer's Stone", + subtitle: "By J. K. Rowling", + }, + }, + tiles: { + properties: { + nodes: ["kpi1"], + }, + }, + kpi1: { + id: "kpi1", + componentType: KpiComponent.lateLoadKey, + properties: { + widgetData: { + units: "out of 5 Stars", + label: "Average Rating", + }, + }, + providers: { + [WellKnownProviders.DataSource]: { + // Setting the data source providerId for the tile with id "kpi1" + providerId: AcmeKpiDataSource.providerId, + properties: { + bookId: "5MQFrgEACAAJ", + metric: "averageRating", + }, + } as IProviderConfiguration, + [WellKnownProviders.Adapter]: { + providerId: NOVA_KPI_DATASOURCE_ADAPTER, + properties: { + componentId: "kpi1", + propertyPath: "widgetData", + }, + } as IProviderConfiguration, + }, + }, + }, + }, +}; +\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\`, + "tutorials/customization/formatter/custom-formatter.module.ts": \\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\`// © 2022 SolarWinds Worldwide, LLC. All rights reserved. +// +// Permission is hereby granted, free of charge, to any person obtaining a copy +// of this software and associated documentation files (the "Software"), to +// deal in the Software without restriction, including without limitation the +// rights to use, copy, modify, merge, publish, distribute, sublicense, and/or +// sell copies of the Software, and to permit persons to whom the Software is +// furnished to do so, subject to the following conditions: +// +// The above copyright notice and this permission notice shall be included in +// all copies or substantial portions of the Software. +// +// THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR +// IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, +// FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE +// AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER +// LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, +// OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN +// THE SOFTWARE. + +import { NgModule } from "@angular/core"; +import { ReactiveFormsModule } from "@angular/forms"; +import { RouterModule, Routes } from "@angular/router"; + +// eslint-disable-next-line max-len +import { + NuiButtonModule, + NuiDocsModule, + NuiFormFieldModule, + NuiIconModule, + NuiMessageModule, + NuiSelectV2Module, + NuiSwitchModule, + NuiTextboxModule, + NuiValidationMessageModule, + DEMO_PATH_TOKEN, +} from "@nova-ui/bits"; +import { NuiDashboardsModule } from "@nova-ui/dashboards"; + +import { CustomDonutContentFormatterDocComponent } from "./donut-content-formatter-example/custom-donut-content-formatter-docs.component"; +import { + CustomDonutContentFormatterComponent, + CustomDonutContentFormatterConfiguratorComponent, + CustomDonutContentFormatterExampleComponent, +} from "./donut-content-formatter-example/custom-donut-content-formatter-example.component"; +import { CustomFormatterDocComponent } from "./formatter-example/custom-formatter-docs.component"; +import { + CustomFormatterComponent, + CustomFormatterConfiguratorComponent, + CustomFormatterExampleComponent, +} from "./formatter-example/custom-formatter-example.component"; +import { getDemoFiles } from "../../../../../demo-files-factory"; + +const routes: Routes = [ + { + path: "table-formatter", + component: CustomFormatterDocComponent, + data: { + srlc: { + hideIndicator: true, + }, + showThemeSwitcher: true, + }, + }, + { + path: "donut-content-formatter", + component: CustomDonutContentFormatterDocComponent, + data: { + srlc: { + hideIndicator: true, + }, + showThemeSwitcher: true, + }, + }, +]; + +@NgModule({ + imports: [ + RouterModule.forChild(routes), + NuiDocsModule, + NuiButtonModule, + NuiMessageModule, + NuiDashboardsModule, + NuiFormFieldModule, + NuiTextboxModule, + NuiSwitchModule, + NuiSelectV2Module, + NuiValidationMessageModule, + NuiIconModule, + ReactiveFormsModule, + ], + declarations: [ + CustomDonutContentFormatterComponent, + CustomDonutContentFormatterExampleComponent, + CustomDonutContentFormatterConfiguratorComponent, + CustomDonutContentFormatterDocComponent, + CustomFormatterDocComponent, + CustomFormatterExampleComponent, + CustomFormatterConfiguratorComponent, + CustomFormatterComponent, + ], + providers: [ + { + provide: DEMO_PATH_TOKEN, + useValue: getDemoFiles("formatter"), + }, + ], +}) +export default class CustomFormatterModuleRoute {} +\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\`, + "tutorials/customization/formatter/donut-content-formatter-example/custom-donut-content-formatter-docs.component.ts": \\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\`// © 2022 SolarWinds Worldwide, LLC. All rights reserved. +// +// Permission is hereby granted, free of charge, to any person obtaining a copy +// of this software and associated documentation files (the "Software"), to +// deal in the Software without restriction, including without limitation the +// rights to use, copy, modify, merge, publish, distribute, sublicense, and/or +// sell copies of the Software, and to permit persons to whom the Software is +// furnished to do so, subject to the following conditions: +// +// The above copyright notice and this permission notice shall be included in +// all copies or substantial portions of the Software. +// +// THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR +// IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, +// FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE +// AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER +// LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, +// OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN +// THE SOFTWARE. + +import { Component } from "@angular/core"; + +@Component({ + selector: "nui-custom-donut-content-formatter-docs", + templateUrl: "./custom-donut-content-formatter-docs.component.html", + standalone: false, +}) +export class CustomDonutContentFormatterDocComponent {} +\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\`, + "tutorials/customization/formatter/donut-content-formatter-example/custom-donut-content-formatter-example.component.ts": \\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\`// © 2022 SolarWinds Worldwide, LLC. All rights reserved. +// +// Permission is hereby granted, free of charge, to any person obtaining a copy +// of this software and associated documentation files (the "Software"), to +// deal in the Software without restriction, including without limitation the +// rights to use, copy, modify, merge, publish, distribute, sublicense, and/or +// sell copies of the Software, and to permit persons to whom the Software is +// furnished to do so, subject to the following conditions: +// +// The above copyright notice and this permission notice shall be included in +// all copies or substantial portions of the Software. +// +// THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR +// IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, +// FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE +// AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER +// LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, +// OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN +// THE SOFTWARE. + +import { HttpClient } from "@angular/common/http"; +import { + ChangeDetectorRef, + Component, + Injectable, + Input, + OnChanges, + OnDestroy, + OnInit, + SimpleChanges, +} from "@angular/core"; +import { FormBuilder, FormGroup, Validators } from "@angular/forms"; +import { GridsterConfig, GridsterItem } from "angular-gridster2"; +import { Subject } from "rxjs"; +import { takeUntil, tap } from "rxjs/operators"; + +import { + DataSourceService, + IconService, + IDataSource, + IFilteringOutputs, + LoggerService, +} from "@nova-ui/bits"; +import { + ChartAssist, + IAccessors, + IChartAssistEvent, + IChartAssistSeries, +} from "@nova-ui/charts"; +import { + ComponentRegistryService, + ConfiguratorHeadingService, + DATA_SOURCE, + DonutChartFormatterConfiguratorComponent, + DonutContentPercentageConfigurationComponent, + DonutContentPercentageFormatterComponent, + DonutContentSumFormatterComponent, + IDashboard, + IFormatterDefinition, + IHasChangeDetector, + IProperties, + IProportionalWidgetChartOptions, + IProportionalWidgetConfig, + IProviderConfiguration, + IWidget, + IWidgets, + LegendPlacement, + PizzagnaLayer, + ProportionalWidgetChartTypes, + ProviderRegistryService, + WellKnownPathKey, + WellKnownProviders, + WidgetTypesService, +} from "@nova-ui/dashboards"; + +export enum Units { + Days = "Day(s)", + Weeks = "Week(s)", + Hours = "Hour(s)", +} + +@Component({ + selector: "custom-donut-content-formatter", + host: { class: "d-flex flex-column align-items-center" }, + template: \\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\` +
+ + {{ chartMetric || properties?.currentMetric || data[0].id }} +
+
+ {{ chartContent }} +
+
+ {{ units }} +
+
\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\`, + styleUrls: ["./custom-donut-content-formatter-example.component.less"], + standalone: false, +}) +export class CustomDonutContentFormatterComponent + implements IHasChangeDetector, OnInit, OnChanges +{ + public static lateLoadKey = "CustomDonutContentFormatterComponent"; + + // Used to emphasize the chart series when user interacts either with the chart legend, or chart segments. + public emphasizedSeriesData: IChartAssistSeries | undefined; + + // Current raw value of the metric to display + public currentMetricData: number; + + // Metric value rendered inside the template, when user selects a metric, and gets automatically recalculated depending on selected units + public chartContent: number; + + // Metric value rendered inside the template, when user interacts with either chart legend, or chart segments + public chartMetric: number; + + // Units which user can select from the configuration + public units: Units = Units.Days; + + private readonly destroy$ = new Subject(); + + constructor(public changeDetector: ChangeDetectorRef) {} + + // The data we receive from the chart, including metrics names and their values + @Input() data: IChartAssistSeries[]; + + // We use this chart assist instance to subscribe to the events triggered when an interaction with the chart occurs + @Input() chartAssist: ChartAssist; + + // These are the current properties from pizzagna. Used to use data set at the configuration layer + @Input() properties: IProperties; + + public ngOnChanges(changes: SimpleChanges): void { + if (changes.properties || !this.properties) { + // If current metric is not in the list of metrics any more we fall back to the very first one from the list we get from the datasource + this.currentMetricData = + this.data.find( + (item) => item.id === this.properties?.currentMetric + )?.data[0] || this.data[0].data[0]; + + // We either take the selected value, or fall back to the preselected default one + this.units = this.properties?.units || this.units; + } + + this.setContentValue(); + } + + public ngOnInit(): void { + // Here 'chartAssistSubject' is the entity that emits events every time user interacts with either chart legend, or chart segments. + // Subscribing to properly react on these kind of events + this.chartAssist.chartAssistSubject + .pipe( + tap( + (data: IChartAssistEvent) => + (this.emphasizedSeriesData = this.data.find( + (item) => item.id === data.payload.seriesId + )) + ), + tap(() => this.setContentValue()), + tap(() => this.setMetricValue()), + takeUntil(this.destroy$) + ) + .subscribe(); + } + + public getConvertedData(emphData: number): number { + // Recalculating data depending on the units user selected from the configuration view + switch (this.units) { + case Units.Weeks: + return this.emphasizedSeriesData + ? this.convertToWeeks(emphData) + : this.convertToWeeks(this.currentMetricData); + + case Units.Hours: + return this.emphasizedSeriesData + ? this.convertToHours(emphData) + : this.convertToHours(this.currentMetricData); + + default: + return this.emphasizedSeriesData + ? emphData + : this.currentMetricData; + } + } + + public setContentValue(): void { + this.chartContent = this.getConvertedData( + this.emphasizedSeriesData?.data[0] + ); + } + + public setMetricValue(): void { + this.chartMetric = this.emphasizedSeriesData + ? this.data.find( + (item) => + this.getConvertedData(item.data[0]) === + this.getConvertedData(this.emphasizedSeriesData?.data[0]) + )?.id + : // if metric was not initially selected we fall back to the very first one + this.properties?.currentMetric || this.data[0].id; + } + + private convertToWeeks(days: number | undefined): number { + return days ? Number((days / 7).toFixed(2)) : 0; + } + + private convertToHours(days: number | undefined): number { + return days ? Number((days * 24).toFixed(2)) : 0; + } +} + +@Component({ + selector: "custom-donut-content-formatter-configurator", + styleUrls: ["./custom-donut-content-formatter-example.component.less"], + template: \\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\` +
+
+ + + + {{ itemValue?.name }} + + + + This field is required + + +
+
+ + + + {{ itemValue }} + + + + This field is required + + +
+
+ \\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\`, + standalone: false, +}) +export class CustomDonutContentFormatterConfiguratorComponent + extends DonutChartFormatterConfiguratorComponent + implements OnChanges, OnInit, IHasChangeDetector +{ + public static lateLoadKey = "CustomFormatterConfiguratorComponent"; + + constructor( + changeDetector: ChangeDetectorRef, + formBuilder: FormBuilder, + logger: LoggerService, + public iconService: IconService, + public configuratorHeading: ConfiguratorHeadingService + ) { + super(changeDetector, formBuilder, logger); + } + + public availableUnits: Units[] = [Units.Days, Units.Hours, Units.Weeks]; + + protected addCustomFormControls(form: FormGroup): void { + form.addControl( + "units", + this.formBuilder.control(Units.Days, Validators.required) + ); + } +} + +/** + * A component that instantiates the dashboard + */ +@Component({ + selector: "custom-donut-content-formatter-example", + templateUrl: "./custom-donut-content-formatter-example.component.html", + styleUrls: ["./custom-donut-content-formatter-example.component.less"], + standalone: false, +}) +export class CustomDonutContentFormatterExampleComponent implements OnInit { + public editMode: boolean = false; + // This variable will hold all the data needed to define the layout and behavior of the widgets. + // Pass this to the dashboard component's dashboard input in the template. + public dashboard: IDashboard | undefined; + + // Angular gridster requires a configuration object even if it's empty. + // Pass this to the dashboard component's gridsterConfig input in the template. + public gridsterConfig: GridsterConfig = {}; + + constructor( + // WidgetTypesService provides the widget's necessary structure information + private widgetTypesService: WidgetTypesService, + // In general, the ProviderRegistryService is used for making entities available for injection into dynamically loaded components. + private providerRegistry: ProviderRegistryService, + // Inject the ComponentRegistryService to make our custom component available for late loading by the dashboards framework + private componentRegistry: ComponentRegistryService, + private changeDetectorRef: ChangeDetectorRef + ) { + // Register the custom configurator component with the component registry to make it available + // for late loading by the dashboard framework. + this.componentRegistry.registerByLateLoadKey( + CustomDonutContentFormatterConfiguratorComponent + ); + // Register the custom formatter component with the component registry to make it available + // for late loading by the dashboard framework. + this.componentRegistry.registerByLateLoadKey( + CustomDonutContentFormatterComponent + ); + + // Grab the widget's default template which will be needed as a parameter for setNode below. + const proportional = this.widgetTypesService.getWidgetType( + "proportional", + 1 + ); + + const donutFormatters: IFormatterDefinition[] = [ + { + componentType: DonutContentSumFormatterComponent.lateLoadKey, + label: $localize\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\`Sum\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\`, + } as IFormatterDefinition, + { + componentType: + DonutContentPercentageFormatterComponent.lateLoadKey, + label: $localize\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\`Percentage\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\`, + configurationComponent: + DonutContentPercentageConfigurationComponent.lateLoadKey, + } as IFormatterDefinition, + { + componentType: CustomDonutContentFormatterComponent.lateLoadKey, + label: $localize\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\`Custom\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\`, + // This is a custom configurator that will pop up below the formatter once it gets selected + configurationComponent: + CustomDonutContentFormatterConfiguratorComponent.lateLoadKey, + } as IFormatterDefinition, + ]; + + this.widgetTypesService.setNode( + // This is the template we grabbed above with getWidgetType + proportional, + // We are setting the editor/configurator part of the widget template + "configurator", + // This indicates which node you are changing and we want to change the formatters available for selection in the editor. + WellKnownPathKey.Formatters, + // We are setting the available formatters with the array we created above. + donutFormatters + ); + + // This sets the donut chart's datasource to have the StatusesExampleDatasource so the drop down is filled similar to the line above. + this.widgetTypesService.setNode( + proportional, + "configurator", + WellKnownPathKey.DataSourceProviders, + [StatusesExampleDatasource.providerId] + ); + + // Registering the data source for injection into the widget. + this.providerRegistry.setProviders({ + [StatusesExampleDatasource.providerId]: { + provide: DATA_SOURCE, + useClass: StatusesExampleDatasource, + // Any dependencies that need to be injected into the provider must be listed here + deps: [], + }, + }); + } + + public ngOnInit(): void { + this.initializeDashboard(); + } + + /** Used for restoring widgets state */ + public reInitializeDashboard(): void { + // destroys the components and their providers so the dashboard can re init data + this.dashboard = undefined; + this.changeDetectorRef.detectChanges(); + + this.initializeDashboard(); + } + + public initializeDashboard(): void { + // We're using a static configuration object for this example, but this is where + // the widget's configuration could potentially be populated from a database + const proportionalWidget = widgetConfig; + const widgetIndex: IWidgets = { + // Enhance the widget with information coming from it's type definition + [proportionalWidget.id]: + this.widgetTypesService.mergeWithWidgetType(proportionalWidget), + }; + + // Setting the widget dimensions and position (this is for gridster) + const positions: Record = { + [proportionalWidget.id]: { + cols: 12, + rows: 6, + y: 0, + x: 0, + }, + }; + + // Finally, assigning the variables we created above to the dashboard + this.dashboard = { + positions, + widgets: widgetIndex, + }; + } +} + +export interface IStatusesWidgetData { + id: string; + name: string; + data: number[]; +} + +export const randomStatusesWidgetData: IStatusesWidgetData[] = [ + { + id: "Down", + name: "Down", + data: [Math.round(Math.random() * 100)], + }, + { + id: "Critical", + name: "Critical", + data: [Math.round(Math.random() * 100)], + }, + { + id: "Warning", + name: "Warning", + data: [Math.round(Math.random() * 100)], + }, + { + id: "Unknown", + name: "Unknown", + data: [Math.round(Math.random() * 100)], + }, + { + id: "Up", + name: "Up", + data: [Math.round(Math.random() * 100)], + }, + { + id: "Unmanaged", + name: "Unmanaged", + data: [Math.round(Math.random() * 100)], + }, +]; + +@Injectable() +export class StatusesExampleDatasource + extends DataSourceService + implements IDataSource, OnDestroy +{ + public static providerId = "StatusesExampleDatasource"; + + public busy = new Subject(); + + constructor(private http: HttpClient) { + super(); + } + + public async getFilteredData(): Promise { + this.busy.next(true); + + return new Promise((resolve) => { + setTimeout(() => { + resolve({ + result: randomStatusesWidgetData, + }); + this.busy.next(false); + }, 1000); + }); + } + + public ngOnDestroy(): void { + this.outputsSubject.complete(); + } +} + +export const widgetConfig: IWidget = { + id: "proportionalWidgetId", + type: "proportional", + pizzagna: { + [PizzagnaLayer.Configuration]: { + header: { + properties: { + title: "Proportional Widget!", + subtitle: "Proportional widget with legend formatters", + }, + }, + chart: { + providers: { + [WellKnownProviders.DataSource]: { + providerId: StatusesExampleDatasource.providerId, + } as IProviderConfiguration, + }, + properties: { + configuration: { + interactive: true, + chartOptions: { + type: ProportionalWidgetChartTypes.DonutChart, + legendPlacement: LegendPlacement.Right, + contentFormatter: { + componentType: + CustomDonutContentFormatterComponent.lateLoadKey, + properties: { + // here you can set the default value for the metric you receive. If not set the first one from the list will be taken + currentMetric: "Down", + // here you set the default value for your custom controls. If not set the first one from the list will be taken + units: Units.Weeks, + }, + }, + } as IProportionalWidgetChartOptions, + chartColors: [ + "var(--nui-color-chart-eight)", + "var(--nui-color-chart-nine)", + "var(--nui-color-chart-ten)", + ], + } as IProportionalWidgetConfig, + }, + }, + }, + }, +}; +\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\`, + "tutorials/customization/formatter/formatter-example/custom-formatter-docs.component.ts": \\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\`// © 2022 SolarWinds Worldwide, LLC. All rights reserved. +// +// Permission is hereby granted, free of charge, to any person obtaining a copy +// of this software and associated documentation files (the "Software"), to +// deal in the Software without restriction, including without limitation the +// rights to use, copy, modify, merge, publish, distribute, sublicense, and/or +// sell copies of the Software, and to permit persons to whom the Software is +// furnished to do so, subject to the following conditions: +// +// The above copyright notice and this permission notice shall be included in +// all copies or substantial portions of the Software. +// +// THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR +// IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, +// FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE +// AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER +// LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, +// OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN +// THE SOFTWARE. + +import { Component } from "@angular/core"; + +@Component({ + selector: "nui-custom-formatter-docs", + templateUrl: "./custom-formatter-docs.component.html", + standalone: false, +}) +export class CustomFormatterDocComponent {} +\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\`, + "tutorials/customization/formatter/formatter-example/custom-formatter-example.component.ts": \\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\`// © 2022 SolarWinds Worldwide, LLC. All rights reserved. +// +// Permission is hereby granted, free of charge, to any person obtaining a copy +// of this software and associated documentation files (the "Software"), to +// deal in the Software without restriction, including without limitation the +// rights to use, copy, modify, merge, publish, distribute, sublicense, and/or +// sell copies of the Software, and to permit persons to whom the Software is +// furnished to do so, subject to the following conditions: +// +// The above copyright notice and this permission notice shall be included in +// all copies or substantial portions of the Software. +// +// THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR +// IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, +// FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE +// AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER +// LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, +// OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN +// THE SOFTWARE. + +import { ListRange } from "@angular/cdk/collections"; +import { ChangeDetectorRef, Component, Input, OnInit } from "@angular/core"; +import { FormBuilder, FormGroup, Validators } from "@angular/forms"; +import { GridsterConfig, GridsterItem } from "angular-gridster2"; +import isEqual from "lodash/isEqual"; +import orderBy from "lodash/orderBy"; +import { BehaviorSubject } from "rxjs"; + +import { + DataSourceService, + IconService, + IDataField, + INovaFilteringOutputs, + INovaFilters, + ISorterFilter, + LoggerService, +} from "@nova-ui/bits"; +import { + ComponentRegistryService, + ConfiguratorHeadingService, + DATA_SOURCE, + FormatterConfiguratorComponent, + IDashboard, + IDataSourceOutput, + IFormatterDefinition, + IHasChangeDetector, + ITableWidgetColumnConfig, + ITableWidgetSorterConfig, + IWidget, + IWidgets, + PizzagnaLayer, + ProviderRegistryService, + RawFormatterComponent, + TableFormatterRegistryService, + WellKnownPathKey, + WellKnownProviders, + WidgetTypesService, +} from "@nova-ui/dashboards"; + +export const BREW_API_URL = "https://api.punkapi.com/v2/beers"; + +@Component({ + selector: "custom-formatter", + host: { class: "d-flex" }, + template: \\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\` +
+
+ +
+
+ {{ data.value }} +
+
+ \\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\`, + styleUrls: ["./custom-formatter-example.component.less"], + standalone: false, +}) +export class CustomFormatterComponent implements IHasChangeDetector { + public static lateLoadKey = "CustomFormatterComponent"; + + constructor(public changeDetector: ChangeDetectorRef) {} + + @Input() public data: any; + @Input() public icon: string; + @Input() public threshold: string; + + public isAboveThreshold(): boolean { + return parseFloat(this.threshold) <= this.data.value; + } +} + +@Component({ + selector: "custom-formatter-configurator", + styleUrls: ["./custom-formatter-example.component.less"], + template: \\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\` +
+
+ + + + {{ item.label }} + + + + This field is required + + +
+
+ + + + + + + + This field is required + + +
+
+ + + + + This field is required + + +
+
+ +
+
+ +
+ + +
+ + + Select Item + +
+ \\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\`, + standalone: false, +}) +export class CustomFormatterConfiguratorComponent + extends FormatterConfiguratorComponent + implements OnInit, IHasChangeDetector +{ + public static lateLoadKey = "CustomFormatterConfiguratorComponent"; + + constructor( + changeDetector: ChangeDetectorRef, + configuratorHeading: ConfiguratorHeadingService, + formBuilder: FormBuilder, + logger: LoggerService, + public iconService: IconService + ) { + super(changeDetector, configuratorHeading, formBuilder, logger); + } + + public formatterFormGroup: FormGroup; + // This array is where the icon names will be stored + public options: string[] = []; + + public ngOnInit(): void { + for (const icon of this.iconService.icons) { + if (icon.category === "severity") { + this.options.push(icon.name); + } + } + } + + protected addCustomFormControls(form: FormGroup): void { + form.addControl( + "icon", + this.formBuilder.control("", Validators.required) + ); + form.addControl( + "threshold", + this.formBuilder.control(null, Validators.required) + ); + } +} + +/** + * A component that instantiates the dashboard + */ +@Component({ + selector: "custom-formatter-example", + templateUrl: "./custom-formatter-example.component.html", + styleUrls: ["./custom-formatter-example.component.less"], + standalone: false, +}) +export class CustomFormatterExampleComponent implements OnInit { + public editMode: boolean = false; + // This variable will hold all the data needed to define the layout and behavior of the widgets. + // Pass this to the dashboard component's dashboard input in the template. + public dashboard: IDashboard | undefined; + + // Angular gridster requires a configuration object even if it's empty. + // Pass this to the dashboard component's gridsterConfig input in the template. + public gridsterConfig: GridsterConfig = {}; + + constructor( + // WidgetTypesService provides the widget's necessary structure information + private widgetTypesService: WidgetTypesService, + // In general, the ProviderRegistryService is used for making entities available for injection into dynamically loaded components. + private providerRegistry: ProviderRegistryService, + // Inject the ComponentRegistryService to make our custom component available for late loading by the dashboards framework + private componentRegistry: ComponentRegistryService, + private tableFormatterRegistryService: TableFormatterRegistryService, + private changeDetectorRef: ChangeDetectorRef + ) { + // Register the custom configurator component with the component registry to make it available + // for late loading by the dashboard framework. + this.componentRegistry.registerByLateLoadKey( + CustomFormatterConfiguratorComponent + ); + // Register the custom formatter component with the component registry to make it available + // for late loading by the dashboard framework. + this.componentRegistry.registerByLateLoadKey(CustomFormatterComponent); + + // Grab the widget's default template which will be needed as a parameter for setNode below. + const table = this.widgetTypesService.getWidgetType("table", 1); + + const tableFormatters: IFormatterDefinition[] = [ + { + // This will be the component that will format the data + componentType: RawFormatterComponent.lateLoadKey, + // This is the label for what the formatter is selected in the drop down + label: $localize\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\`:table formatter|:No formatter\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\`, + // This says what datatype the formatter supports. If the value node is null, it accepts any data type. + dataTypes: { + // @ts-ignore: Ignoring compiler error to keep the same flow + value: null, + }, + }, + { + componentType: CustomFormatterComponent.lateLoadKey, + label: $localize\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\`:table formatter|:Custom formatter\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\`, + // This is a custom configurator that will pop up below the formatter once it gets selected + configurationComponent: + CustomFormatterConfiguratorComponent.lateLoadKey, + // This says what data types the formatter supports. + // In this case, it supports abv values only. + // If you look below in the table data source you'll see where we define our column's data types. + dataTypes: { + value: ["abv"], + }, + }, + ]; + + // Registering the formatters + this.tableFormatterRegistryService.addItems(tableFormatters); + + // This sets the table's datasource to have the BeerDataSource so the drop down is filled similar to the line above. + this.widgetTypesService.setNode( + table, + "configurator", + WellKnownPathKey.DataSourceProviders, + [BeerDataSource.providerId] + ); + + // Registering the data source for injection into the widget. + this.providerRegistry.setProviders({ + [BeerDataSource.providerId]: { + provide: DATA_SOURCE, + useClass: BeerDataSource, + // Any dependencies that need to be injected into the provider must be listed here + deps: [], + }, + }); + } + + public ngOnInit(): void { + this.initializeDashboard(); + } + + /** Used for restoring widgets state */ + public reInitializeDashboard(): void { + // destroys the components and their providers so the dashboard can re init data + this.dashboard = undefined; + this.changeDetectorRef.detectChanges(); + + this.initializeDashboard(); + } + + public initializeDashboard(): void { + // We're using a static configuration object for this example, but this is where + // the widget's configuration could potentially be populated from a database + const tableWidget = widgetConfig; + const widgetIndex: IWidgets = { + // Enhance the widget with information coming from it's type definition + [tableWidget.id]: + this.widgetTypesService.mergeWithWidgetType(tableWidget), + }; + + // Setting the widget dimensions and position (this is for gridster) + const positions: Record = { + [tableWidget.id]: { + cols: 12, + rows: 6, + y: 0, + x: 0, + }, + }; + + // Finally, assigning the variables we created above to the dashboard + this.dashboard = { + positions, + widgets: widgetIndex, + }; + } +} + +export interface IBrewInfo { + id: number; + name: string; + tagline: string; + first_brewed: string; + description: string; + brewers_tips: string; + abv: number; +} + +export interface IBrewDatasourceResponse { + brewInfo: IBrewInfo[]; + total: number; +} + +export class BeerDataSource extends DataSourceService { + public static providerId = "BeerDataSource"; + + private cache = Array.from({ length: 0 }); + private lastSortValue?: ISorterFilter; + private lastVirtualScroll?: ListRange; + // For simplicity, the totalItems value is hard-coded here, but in a real-world scenario the value would likely be retrieved via an async backend call + private totalItems: number = 325; + + public page: number = 1; + public busy = new BehaviorSubject(false); + + public dataFields: Array = [ + { id: "id", label: "No", dataType: "number" }, + { id: "name", label: "Name", dataType: "string" }, + { id: "tagline", label: "Tagline", dataType: "string" }, + { id: "first_brewed", label: "First Brewed", dataType: "string" }, + { id: "description", label: "Description", dataType: "string" }, + { id: "brewers_tips", label: "Brewer's Tips", dataType: "string" }, + // We are giving this field a custom data type of 'abv' so the dropdown in the custom formatter configurator can use it to filter out other data types + { id: "abv", label: "Alcohol By Volume", dataType: "abv" }, + ]; + + constructor(private logger: LoggerService) { + super(); + } + + public async getFilteredData( + filters: INovaFilters + ): Promise> { + const start = filters.virtualScroll?.value?.start ?? 0; + const end = filters.virtualScroll?.value?.end ?? 0; + const delta = end - start; + + // Note: We should start with a clean cache every time first page is requested + if (start === 0) { + this.cache = []; + } + + // This condition handles sorting. We want to sort columns without fetching another chunk of data. + // Since the data is being fetched when scrolled, we compare virtual scroll indexes here in the condition as well. + if (filters.sorter?.value) { + if ( + !isEqual(this.lastSortValue, filters.sorter.value) && + filters.virtualScroll?.value.start === 0 && + !!this.lastVirtualScroll + ) { + const totalPages = Math.ceil( + delta ? this.totalItems / delta : 1 + ); + const itemsPerPage: number = Math.max( + delta < 80 ? delta : 80, + 1 + ); + let response: Array | null = null; + let map: IBrewDatasourceResponse; + + if (filters.sorter?.value?.direction === "desc") { + this.cache = []; + for (let i = 0; i < this.page; ++i) { + response = await ( + await fetch( + \\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\`\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\${BREW_API_URL}/?page=\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\${ + totalPages - i || 1 + }&per_page=\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\${itemsPerPage}\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\` + ) + ).json(); + + // since the last page contains only 5 items we need to fetch another page to give virtual scroll enough space to work + if (response && response.length < itemsPerPage) { + this.page++; + } + map = { + brewInfo: response?.map((result: IBrewInfo) => ({ + id: result.id, + name: result.name, + tagline: result.tagline, + first_brewed: result.first_brewed, + description: result.description, + brewers_tips: result.brewers_tips, + })), + total: response?.length, + } as IBrewDatasourceResponse; + this.cache = + totalPages - i !== 0 + ? this.cache.concat(map.brewInfo) + : this.cache; + } + } + + if (filters.sorter?.value?.direction === "asc") { + this.cache = []; + for (let i = 0; i < this.page; i++) { + response = await ( + await fetch( + \\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\`\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\${BREW_API_URL}/?page=\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\${ + i + 1 + }&per_page=\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\${itemsPerPage}\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\` + ) + ).json(); + map = { + brewInfo: response?.map((result: IBrewInfo) => ({ + id: result.id, + name: result.name, + tagline: result.tagline, + first_brewed: result.first_brewed, + description: result.description, + brewers_tips: result.brewers_tips, + })), + total: response?.length, + } as IBrewDatasourceResponse; + this.cache = this.cache.concat(map.brewInfo); + } + } + + this.lastSortValue = filters.sorter?.value; + this.lastVirtualScroll = filters.virtualScroll?.value; + + return { + result: { + repeat: { + itemsSource: this.sortData(this.cache, filters), + }, + paginator: { total: this.totalItems }, + dataFields: this.dataFields, + }, + }; + } + } + + this.busy.next(true); + return new Promise((resolve) => { + setTimeout(() => { + this.getData(start, end, filters).then( + (response: INovaFilteringOutputs) => { + if (!response) { + return; + } + + this.cache = this.cache.concat(response.brewInfo); + + this.dataSubject.next(this.cache); + resolve({ + result: { + repeat: { + itemsSource: this.sortData( + this.cache, + filters + ), + }, + paginator: { total: this.totalItems }, + dataFields: this.dataFields, + }, + }); + + this.lastSortValue = filters.sorter?.value; + this.lastVirtualScroll = filters.virtualScroll?.value; + this.busy.next(false); + } + ); + }, 500); + }); + } + + public async getData( + start: number = 0, + end: number = 20, + filters: INovaFilters + ): Promise { + const delta = end - start; + const totalPages = Math.ceil(delta ? this.totalItems / delta : 1); + let response: Array | null = null; + // The api.punk.com is able to return only 80 items per page + const itemsPerPage: number = Math.max(delta < 80 ? delta : 80, 1); + + if (filters.sorter?.value?.direction === "asc") { + response = await ( + await fetch( + \\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\`\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\${BREW_API_URL}/?page=\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\${this.page}&per_page=\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\${itemsPerPage}\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\` + ) + ).json(); + } + + if (filters.sorter?.value?.direction === "desc") { + response = await ( + await fetch( + \\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\`\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\${BREW_API_URL}/?page=\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\${ + totalPages - this.page + }&per_page=\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\${itemsPerPage}\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\` + ) + ).json(); + } + + if (!filters.sorter) { + response = await ( + await fetch( + \\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\`\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\${BREW_API_URL}/?page=\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\${this.page}&per_page=\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\${itemsPerPage}\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\` + ) + ).json(); + } + return { + brewInfo: response?.map((result: IBrewInfo, i: number) => ({ + id: result.id, + abv: result.abv, + name: result.name, + tagline: result.tagline, + first_brewed: result.first_brewed, + description: result.description, + brewers_tips: result.brewers_tips, + })), + total: response?.length, + } as IBrewDatasourceResponse; + } + + private sortData(data: IBrewInfo[], filters: INovaFilters) { + return orderBy( + data, + filters.sorter?.value?.sortBy, + filters.sorter?.value?.direction as "desc" | "asc" + ); + } +} + +export const widgetConfig: IWidget = { + id: "tableWidgetId", + type: "table", + pizzagna: { + [PizzagnaLayer.Configuration]: { + header: { + properties: { + title: "Stupendous Suds", + subtitle: "Try These Brilliant Brews", + }, + }, + table: { + providers: { + [WellKnownProviders.DataSource]: { + providerId: BeerDataSource.providerId, + }, + }, + properties: { + configuration: { + columns: [ + { + id: "column1", + label: "Beer Name", + isActive: true, + width: 185, + formatter: { + componentType: + RawFormatterComponent.lateLoadKey, + properties: { + dataFieldIds: { + value: "name", + }, + }, + }, + }, + { + id: "column2", + label: "Tagline", + isActive: true, + width: 250, + formatter: { + componentType: + RawFormatterComponent.lateLoadKey, + properties: { + dataFieldIds: { + value: "tagline", + }, + }, + }, + }, + { + id: "column3", + label: "Alcohol By Volume", + isActive: true, + width: 150, + formatter: { + componentType: + CustomFormatterComponent.lateLoadKey, + properties: { + dataFieldIds: { + value: "abv", + }, + icon: "severity_error", + threshold: "5", + }, + }, + }, + { + id: "column4", + label: "Description", + isActive: true, + formatter: { + componentType: + RawFormatterComponent.lateLoadKey, + properties: { + dataFieldIds: { + value: "description", + }, + }, + }, + }, + ] as ITableWidgetColumnConfig[], + sorterConfiguration: { + descendantSorting: false, + sortBy: "", + } as ITableWidgetSorterConfig, + hasVirtualScroll: true, + }, + }, + }, + }, + }, +}; +\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\`, + "tutorials/customization/widget/custom-widget-docs.component.ts": \\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\`// © 2022 SolarWinds Worldwide, LLC. All rights reserved. +// +// Permission is hereby granted, free of charge, to any person obtaining a copy +// of this software and associated documentation files (the "Software"), to +// deal in the Software without restriction, including without limitation the +// rights to use, copy, modify, merge, publish, distribute, sublicense, and/or +// sell copies of the Software, and to permit persons to whom the Software is +// furnished to do so, subject to the following conditions: +// +// The above copyright notice and this permission notice shall be included in +// all copies or substantial portions of the Software. +// +// THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR +// IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, +// FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE +// AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER +// LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, +// OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN +// THE SOFTWARE. + +import { Component } from "@angular/core"; + +@Component({ + selector: "custom-widget-docs", + templateUrl: "./custom-widget-docs.component.html", + standalone: false, +}) +export class CustomWidgetDocsComponent {} +\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\`, + "tutorials/customization/widget/custom-widget.component.ts": \\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\`// © 2022 SolarWinds Worldwide, LLC. All rights reserved. +// +// Permission is hereby granted, free of charge, to any person obtaining a copy +// of this software and associated documentation files (the "Software"), to +// deal in the Software without restriction, including without limitation the +// rights to use, copy, modify, merge, publish, distribute, sublicense, and/or +// sell copies of the Software, and to permit persons to whom the Software is +// furnished to do so, subject to the following conditions: +// +// The above copyright notice and this permission notice shall be included in +// all copies or substantial portions of the Software. +// +// THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR +// IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, +// FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE +// AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER +// LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, +// OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN +// THE SOFTWARE. + +import { + ChangeDetectionStrategy, + ChangeDetectorRef, + Component, + EventEmitter, + HostBinding, + Input, + OnChanges, + OnInit, + Output, + SimpleChanges, +} from "@angular/core"; +import { FormBuilder, FormGroup, Validators } from "@angular/forms"; +import { GridsterConfig, GridsterItem } from "angular-gridster2"; + +import { IMenuItem } from "@nova-ui/bits"; +import { + ComponentRegistryService, + ConfiguratorHeadingService, + DEFAULT_PIZZAGNA_ROOT, + EVENT_PROXY, + FormStackComponent, + IConverterFormPartsProperties, + IDashboard, + IHasChangeDetector, + IHasForm, + IProviderConfiguration, + IWidget, + IWidgets, + IWidgetTypeDefinition, + NOVA_GENERIC_CONVERTER, + NOVA_TITLE_AND_DESCRIPTION_CONVERTER, + PizzagnaLayer, + refresher, + StackComponent, + TitleAndDescriptionConfigurationComponent, + WellKnownPathKey, + WellKnownProviders, + widgetBodyContentNodes, + WidgetConfiguratorSectionComponent, + WidgetTypesService, + WIDGET_BODY, + WIDGET_HEADER, + WIDGET_LOADING, +} from "@nova-ui/dashboards"; + +// The custom widget type name we'll use +const CUSTOM_WIDGET_TYPENAME = "example-custom-widget"; +// The path key we'll use for image selection in the configurator definition +const IMAGE_SELECTION_CONFIGURATOR_PATH_KEY = "imageSelection"; + +@Component({ + selector: "custom-widget-body", + // A simple template for our custom widget + template: \\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\`\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\`, + styleUrls: ["./custom-widget.component.less"], + changeDetection: ChangeDetectionStrategy.OnPush, + standalone: false, +}) +// Remember to declare this class in the parent module +export class CustomWidgetBodyContentComponent implements IHasChangeDetector { + // Ensure that the lateLoadKey value matches class name + public static lateLoadKey = "CustomWidgetBodyContentComponent"; + + // Optionally, providing an input for styling of the host element + @Input() @HostBinding("class") public elementClass = ""; + + // We'll map this input with the configurator form using the NOVA_GENERIC_CONVERTER. + // See the customWidget definition at the bottom of the file. + @Input() public imageSource: string; + + // Injecting the ChangeDetectorRef to implement IHasChangeDetector. + // This allows the dashboard framework to reliably propagate component property changes to the DOM. + constructor(public changeDetector: ChangeDetectorRef) {} +} + +/** + * A custom configurator section component for selecting the image source for the custom widget + */ +@Component({ + selector: "custom-configurator-section", + template: \\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\` + + + + +
+ + +
+ Image Selection +
+ {{ imageDisplayValue }} +
+
+
+
+ + + + + {{ item.title }} + + + +
+
+ \\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\`, + styleUrls: ["./custom-widget.component.less"], + changeDetection: ChangeDetectionStrategy.OnPush, + standalone: false, +}) +// Remember to declare this class in the parent module +export class CustomConfiguratorSectionComponent + implements OnInit, OnChanges, IHasChangeDetector, IHasForm +{ + // Ensure that the lateLoadKey value matches the class name + public static lateLoadKey = "CustomConfiguratorSectionComponent"; + + /** + * This input serves as the itemsSource a user can select an image from. + */ + @Input() imageItems: IMenuItem[] = []; + /** + * This property holds the currently selected image source string. + */ + @Input() imageSource: string; + + /** + * An output for emitting formReady to allow the immediate parent formGroup component to register us as a form control + * in the larger form. In this case, the immediate parent would be the WidgetConfiguratorSectionComponent as specified + * in the customWidget configurator definition at the bottom of this file. + */ + @Output() formReady = new EventEmitter(); + + public form: FormGroup; + public imageDisplayValue: string; + + constructor( + public changeDetector: ChangeDetectorRef, + private formBuilder: FormBuilder, + public configuratorHeading: ConfiguratorHeadingService + ) {} + + public ngOnInit(): void { + // Initializing the form + this.form = this.formBuilder.group({ + // Note: When using the NOVA_GENERIC_CONVERTER, the form control name, in this case 'imageSource', must match the input name on + // this component as well as that of the corresponding property on the custom widget body component. + imageSource: [{}, [Validators.required]], + }); + + // Emitting the formReady as described above. + this.formReady.emit(this.form); + } + + public ngOnChanges(changes: SimpleChanges): void { + if (changes.imageSource && !changes.imageSource.isFirstChange()) { + const previousValue: string = changes.imageSource.previousValue; + if (previousValue !== this.imageSource) { + // Setting the display value according to the current imageSource value + this.imageDisplayValue = this.imageItems.find( + (item: IMenuItem) => item.url === this.imageSource + )?.title; + + // Updating the form when the imageSource input gets updated + this.form.get("imageSource")?.setValue(this.imageSource); + } + } + } + + public onChanged(newValue: string): void { + // Keeping the display value updated as the user changes the dropdown selection + this.imageDisplayValue = this.imageItems.find( + (item: IMenuItem) => item.url === newValue + )?.title; + } +} + +/** + * The component that instantiates the dashboard + */ +@Component({ + selector: "custom-widget", + templateUrl: "./custom-widget.component.html", + styleUrls: ["./custom-widget.component.less"], + standalone: false, +}) +export class CustomWidgetComponent implements OnInit { + // This variable will hold all the data needed to define the layout and behavior of the widgets. + // Pass this to the dashboard component's dashboard input in the template. + public dashboard: IDashboard | undefined; + + // Angular gridster requires a configuration object even if it's empty. + // Pass this to the dashboard component's gridsterConfig input in the template. + public gridsterConfig: GridsterConfig = {}; + + // Boolean which dashboard takes in as an input if its true it allows you to move widgets around. + public editMode: boolean = false; + + constructor( + // WidgetTypesService provides the widget's necessary structure information + private widgetTypesService: WidgetTypesService, + + // Inject the ComponentRegistryService to make our custom component available for late loading by the dashboards framework + private componentRegistry: ComponentRegistryService, + private changeDetectorRef: ChangeDetectorRef + ) {} + + public ngOnInit(): void { + // Register the custom widget type and custom components + // Note: This could also be done in the parent module's constructor so that + // multiple dashboards could have access to the same registrations. + this.prepareNovaDashboards(); + + // Register some image items as dropdown options in the widget editor/configurator + // Note: This could also be done in the parent module's constructor so that + // multiple dashboards could have access to the same dropdown options. + this.registerImageOptions(); + + // Initialize our current instance of a dashboard with an instance of our custom widget + this.initializeDashboard(); + } + + /** Used for restoring widgets state */ + public reInitializeDashboard(): void { + // destroys the components and their providers so the dashboard can re init data + this.dashboard = undefined; + this.changeDetectorRef.detectChanges(); + + this.initializeDashboard(); + } + + public initializeDashboard(): void { + // We're using a static configuration object for this example (see widgetConfig at the bottom of the file), + // but this is where the widget's configuration could potentially be populated from a database + const widget = widgetConfig; + + // Create an index of widgets complete with structure and configuration to assign to the dashboard + const widgets: IWidgets = { + // Complete the custom widget with structure information coming from its type definition + [widget.id]: this.widgetTypesService.mergeWithWidgetType(widget), + }; + + // Setting the widget dimensions and position (this is for gridster) + const positions: Record = { + [widget.id]: { + cols: 4, + rows: 11, + y: 0, + x: 0, + }, + }; + + // Finally, assigning the variables we created above to the dashboard + this.dashboard = { positions, widgets }; + } + + private prepareNovaDashboards() { + // Register the custom widget type + this.widgetTypesService.registerWidgetType( + CUSTOM_WIDGET_TYPENAME, + 1, + customWidget + ); + + // Register the custom widget body component with the component registry to make it available + // for late loading by the dashboard framework. + this.componentRegistry.registerByLateLoadKey( + CustomWidgetBodyContentComponent + ); + + // Register the custom configurator section with the component registry to make it available + // for late loading by the dashboard framework. + this.componentRegistry.registerByLateLoadKey( + CustomConfiguratorSectionComponent + ); + } + + private registerImageOptions() { + // Grab the widget's default template which will be needed as a parameter for setNode below. + const widgetTemplate = this.widgetTypesService.getWidgetType( + CUSTOM_WIDGET_TYPENAME, + 1 + ); + + // Register some image items as dropdown options in the widget editor/configurator + this.widgetTypesService.setNode( + // This is the template we grabbed above with getWidgetType + widgetTemplate, + // We are setting the editor/configurator part of the widget template + "configurator", + // This indicates which node you are changing and we want to change the image items available for selection in the editor. + // For reference, see the 'paths' property of the custom widget's IWidgetTypeDefinition at the bottom of this file. + IMAGE_SELECTION_CONFIGURATOR_PATH_KEY, + // We are setting the image items available for selection in the editor. 'imageItems' is defined + // at the bottom of this file. + imageItems + ); + } +} + +/*************************************************************************************************** + * This is the type definition of our custom widget + ***************************************************************************************************/ +const customWidget: IWidgetTypeDefinition = { + /*************************************************************************************************** + * Paths to important settings in this type definition + ***************************************************************************************************/ + paths: { + widget: { + [WellKnownPathKey.Root]: DEFAULT_PIZZAGNA_ROOT, + }, + configurator: { + [WellKnownPathKey.Root]: DEFAULT_PIZZAGNA_ROOT, + // for the custom configuration component, this is the path for the list of image items available for selection + [IMAGE_SELECTION_CONFIGURATOR_PATH_KEY]: + "imageSelection.properties.imageItems", + }, + }, + /*************************************************************************************************** + * Widget section describes the structural part of the custom widget + ***************************************************************************************************/ + widget: { + [PizzagnaLayer.Structure]: { + [DEFAULT_PIZZAGNA_ROOT]: { + id: DEFAULT_PIZZAGNA_ROOT, + // base layout of the widget - all components referenced herein will be stacked in a column + componentType: StackComponent.lateLoadKey, + providers: { + // When enabled, this provider emits the REFRESH event on the pizzagna event bus every X seconds + [WellKnownProviders.Refresher]: refresher(), + // event proxy manages the transmission of events between widget and dashboard such as the WIDGET_EDIT and WIDGET_REMOVE events + [WellKnownProviders.EventProxy]: EVENT_PROXY, + }, + properties: { + // these values reference child components in the widget structure defined below + nodes: ["header", "loading", "body"], + }, + }, + // standard widget header + header: WIDGET_HEADER, + // this is the loading bar below the header + loading: WIDGET_LOADING, + // the body node + body: WIDGET_BODY, + + // retrieving the definitions for the body content nodes. the argument corresponds to the main content node key + ...widgetBodyContentNodes("mainContent"), + + // the component that supplies the content of our custom widget + mainContent: { + id: "mainContent", + componentType: CustomWidgetBodyContentComponent.lateLoadKey, + properties: { + elementClass: "d-flex w-100 justify-content-center", + }, + }, + }, + [PizzagnaLayer.Configuration]: { + [DEFAULT_PIZZAGNA_ROOT]: { + id: DEFAULT_PIZZAGNA_ROOT, + providers: { + // default refresher configuration + [WellKnownProviders.Refresher]: refresher(false, 60), + }, + }, + // default header configuration + header: { + properties: { + title: $localize\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\`Empty Custom Widget\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\`, + }, + }, + }, + }, + /*************************************************************************************************** + * Configurator section describes the form that's used to configure the widget + ***************************************************************************************************/ + configurator: { + [PizzagnaLayer.Structure]: { + [DEFAULT_PIZZAGNA_ROOT]: { + id: DEFAULT_PIZZAGNA_ROOT, + // base layout of the configurator - all form components referenced herein will be stacked in a column + componentType: FormStackComponent.lateLoadKey, + properties: { + elementClass: + "flex-grow-1 overflow-auto nui-scroll-shadows", + // these values reference child components laid out in this form (defined below) + nodes: ["presentation", "customConfig"], + }, + }, + // /presentation + presentation: { + id: "presentation", + componentType: WidgetConfiguratorSectionComponent.lateLoadKey, + properties: { + headerText: $localize\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\`Presentation\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\`, + nodes: ["titleAndDescription"], + }, + }, + // /presentation/titleAndDescription + titleAndDescription: { + id: "titleAndDescription", + componentType: + TitleAndDescriptionConfigurationComponent.lateLoadKey, + providers: { + converter: { + providerId: NOVA_TITLE_AND_DESCRIPTION_CONVERTER, + } as IProviderConfiguration, + }, + }, + // /customConfig + customConfig: { + id: "customConfig", + componentType: WidgetConfiguratorSectionComponent.lateLoadKey, + properties: { + headerText: $localize\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\`Custom Widget Configuration\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\`, + nodes: ["imageSelection"], + }, + }, + // /customConfig/imageSelection + imageSelection: { + id: "imageSelection", + // Here's where we set the configurator to use our custom configurator section + componentType: CustomConfiguratorSectionComponent.lateLoadKey, + properties: { + // This corresponds to the 'imageItems' input on the custom configurator section component + // which defines the list of image items to pick from. The empty value shown here is overridden + // in the 'registerImageOptions' method above. + imageItems: [] as IMenuItem[], + }, + providers: { + // Using the generic converter to map the selected image source between the widget and the form + [WellKnownProviders.Converter]: { + providerId: NOVA_GENERIC_CONVERTER, + properties: { + formParts: [ + { + // Setting up the generic converter to update the 'imageSource' property of the custom widget 'mainContent' component + previewPath: "mainContent.properties", + // Note: To use the NOVA_GENERIC_CONVERTER, the linked properties must have the same name between the configurator + // section component and the widget 'mainContent' component. Additionally, the property name must match the formControl + // name used in the configurator section component. In this case, the common name among all three is 'imageSource'. + keys: ["imageSource"], + }, + ] as IConverterFormPartsProperties[], + }, + } as IProviderConfiguration, + }, + }, + }, + }, +}; + +// For this example, we're using static items for the image selection dropdown. In a more realistic scenario, +// the items available for selection might come from a backend database. +const imageItems = [ + { + title: "Harry Potter Book Cover", + url: "https://imgc.allpostersimages.com/img/print/u-g-F8PQ9I0.jpg?w=550&h=550&p=0", + }, + { + title: "Harry Potter Movie Poster", + url: "https://images-na.ssl-images-amazon.com/images/I/81gpmMdKOHL._AC_SY741_.jpg", + }, +] as IMenuItem[]; + +// We're using a static configuration object for this example. In a more realistic scenario, +// a widget's configuration would likely be stored in a database. +const widgetConfig: IWidget = { + id: "widget1", + // This custom type is registered in the 'prepareNovaDashboards' method above. + type: CUSTOM_WIDGET_TYPENAME, + pizzagna: { + [PizzagnaLayer.Configuration]: { + header: { + // Setting the initial property values for the WidgetHeaderComponent + properties: { + title: "Harry Potter and the Sorcerer's Stone", + subtitle: "By J. K. Rowling", + }, + }, + mainContent: { + properties: { + // Setting the initial value for the 'imageSource' property on our custom widget body + imageSource: imageItems[0].url, + }, + }, + }, + }, +}; +\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\`, + "tutorials/customization/widget/custom-widget.module.ts": \\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\`// © 2022 SolarWinds Worldwide, LLC. All rights reserved. +// +// Permission is hereby granted, free of charge, to any person obtaining a copy +// of this software and associated documentation files (the "Software"), to +// deal in the Software without restriction, including without limitation the +// rights to use, copy, modify, merge, publish, distribute, sublicense, and/or +// sell copies of the Software, and to permit persons to whom the Software is +// furnished to do so, subject to the following conditions: +// +// The above copyright notice and this permission notice shall be included in +// all copies or substantial portions of the Software. +// +// THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR +// IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, +// FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE +// AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER +// LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, +// OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN +// THE SOFTWARE. + +import { CommonModule } from "@angular/common"; +import { HttpClientModule } from "@angular/common/http"; +import { NgModule } from "@angular/core"; +import { ReactiveFormsModule } from "@angular/forms"; +import { RouterModule } from "@angular/router"; + +import { + NuiButtonModule, + NuiDocsModule, + NuiFormFieldModule, + NuiIconModule, + NuiImageModule, + NuiMessageModule, + NuiSelectV2Module, + NuiSwitchModule, + DEMO_PATH_TOKEN, +} from "@nova-ui/bits"; +import { + NuiDashboardConfiguratorModule, + NuiDashboardsModule, +} from "@nova-ui/dashboards"; + +import { CustomWidgetDocsComponent } from "./custom-widget-docs.component"; +import { + CustomConfiguratorSectionComponent, + CustomWidgetBodyContentComponent, + CustomWidgetComponent, +} from "./custom-widget.component"; +import { getDemoFiles } from "../../../../../demo-files-factory"; + +const routes = [ + { + path: "", + component: CustomWidgetDocsComponent, + data: { + srlc: { + hideIndicator: true, + }, + showThemeSwitcher: true, + }, + }, + { + path: "example", + component: CustomWidgetComponent, + data: { + srlc: { + hideIndicator: true, + }, + }, + }, +]; + +@NgModule({ + imports: [ + CommonModule, + ReactiveFormsModule, + HttpClientModule, + NuiDashboardsModule, + NuiDashboardConfiguratorModule, + NuiDocsModule, + NuiFormFieldModule, + NuiIconModule, + NuiImageModule, + NuiMessageModule, + NuiSelectV2Module, + NuiSwitchModule, + NuiButtonModule, + RouterModule.forChild(routes), + ], + declarations: [ + CustomWidgetDocsComponent, + CustomConfiguratorSectionComponent, + CustomWidgetBodyContentComponent, + CustomWidgetComponent, + ], + providers: [ + { + provide: DEMO_PATH_TOKEN, + useValue: getDemoFiles("widget"), + }, + ], +}) +export default class CustomWidgetModule {} +\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\`, + "tutorials/data-source-setup/data-source-setup-docs.component.ts": \\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\`// © 2022 SolarWinds Worldwide, LLC. All rights reserved. +// +// Permission is hereby granted, free of charge, to any person obtaining a copy +// of this software and associated documentation files (the "Software"), to +// deal in the Software without restriction, including without limitation the +// rights to use, copy, modify, merge, publish, distribute, sublicense, and/or +// sell copies of the Software, and to permit persons to whom the Software is +// furnished to do so, subject to the following conditions: +// +// The above copyright notice and this permission notice shall be included in +// all copies or substantial portions of the Software. +// +// THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR +// IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, +// FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE +// AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER +// LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, +// OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN +// THE SOFTWARE. + +import { Component } from "@angular/core"; + +@Component({ + selector: "nui-dashboard-data-source-docs", + templateUrl: "./data-source-setup-docs.component.html", + standalone: false, +}) +export class DataSourceDocsComponent {} +\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\`, + "tutorials/data-source-setup/data-source-setup.component.ts": \\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\`// © 2022 SolarWinds Worldwide, LLC. All rights reserved. +// +// Permission is hereby granted, free of charge, to any person obtaining a copy +// of this software and associated documentation files (the "Software"), to +// deal in the Software without restriction, including without limitation the +// rights to use, copy, modify, merge, publish, distribute, sublicense, and/or +// sell copies of the Software, and to permit persons to whom the Software is +// furnished to do so, subject to the following conditions: +// +// The above copyright notice and this permission notice shall be included in +// all copies or substantial portions of the Software. +// +// THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR +// IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, +// FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE +// AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER +// LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, +// OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN +// THE SOFTWARE. + +import { HttpClient, HttpErrorResponse } from "@angular/common/http"; +import { Component, Injectable, OnDestroy, OnInit } from "@angular/core"; +import { GridsterConfig, GridsterItem } from "angular-gridster2"; +import { BehaviorSubject } from "rxjs"; +import { finalize } from "rxjs/operators"; + +import { DataSourceService, IFilteringOutputs } from "@nova-ui/bits"; +import { + DATA_SOURCE, + DEFAULT_PIZZAGNA_ROOT, + IDashboard, + IKpiData, + IProviderConfiguration, + IRefresherProperties, + IWidget, + IWidgets, + KpiComponent, + NOVA_KPI_DATASOURCE_ADAPTER, + PizzagnaLayer, + ProviderRegistryService, + WellKnownProviders, + WidgetTypesService, +} from "@nova-ui/dashboards"; + +/** + * A simple KPI data source to retrieve the average rating of Harry Potter and the Sorcerer's Stone (book) via googleapis + */ +@Injectable() +export class AverageRatingKpiDataSource + extends DataSourceService + implements OnDestroy +{ + // This is the ID we'll use to identify the provider + public static providerId = "AverageRatingKpiDataSource"; + + // Use this subject to communicate the data source's busy state + public busy = new BehaviorSubject(false); + + constructor(private http: HttpClient) { + super(); + } + + // In this example, getFilteredData is invoked every 10 minutes (Take a look at the refresher + // provider definition in the widget configuration below to see how the interval is set) + public async getFilteredData(): Promise { + this.busy.next(true); + return new Promise((resolve) => { + // *** Make a resource request to an external API (if needed) + this.http + .get("https://www.googleapis.com/books/v1/volumes/5MQFrgEACAAJ") + .pipe(finalize(() => this.busy.next(false))) + .subscribe({ + next: (data: any) => { + resolve({ + result: { + value: data.volumeInfo.averageRating, + }, + }); + }, + error: (error: HttpErrorResponse) => { + resolve({ + result: null, + error: { + type: error.status, + }, + }); + }, + }); + }); + } + + public ngOnDestroy(): void { + this.outputsSubject.complete(); + } +} + +/** + * A component that instantiates the dashboard + */ +@Component({ + selector: "data-source-setup", + templateUrl: "./data-source-setup.component.html", + styleUrls: ["./data-source-setup.component.less"], + standalone: false, +}) +export class DataSourceSetupComponent implements OnInit { + // This variable will hold all the data needed to define the layout and behavior of the widgets. + // Pass this to the dashboard component's dashboard input in the template. + public dashboard: IDashboard; + + // Angular gridster requires a configuration object even if it's empty. + // Pass this to the dashboard component's gridsterConfig input in the template. + public gridsterConfig: GridsterConfig = {}; + + constructor( + // WidgetTypesService provides the widget's necessary structure information + private widgetTypesService: WidgetTypesService, + + // In general, the ProviderRegistryService is used for making entities available for injection into dynamically loaded components. + private providerRegistry: ProviderRegistryService + ) {} + + public ngOnInit(): void { + // Registering the data source for injection into the KPI tile. + // Note: Each tile of a KPI widget is assigned its own instance of the data source + this.providerRegistry.setProviders({ + [AverageRatingKpiDataSource.providerId]: { + provide: DATA_SOURCE, + useClass: AverageRatingKpiDataSource, + // Any dependencies that need to be injected into the provider must be listed here + deps: [HttpClient], + }, + }); + // We're using a static configuration object for this example, but this is where + // the widget's configuration could potentially be populated from a database + const kpiWidget = widgetConfig; + const widgetIndex: IWidgets = { + // Complete the KPI widget with information coming from its type definition + [kpiWidget.id]: + this.widgetTypesService.mergeWithWidgetType(kpiWidget), + }; + + // Setting the widget dimensions and position (this is for gridster) + const positions: Record = { + [kpiWidget.id]: { + cols: 4, + rows: 6, + y: 0, + x: 0, + }, + }; + + // Finally, assigning the variables we created above to the dashboard + this.dashboard = { + positions, + widgets: widgetIndex, + }; + } +} + +const widgetConfig: IWidget = { + id: "widget1", + type: "kpi", + pizzagna: { + [PizzagnaLayer.Configuration]: { + [DEFAULT_PIZZAGNA_ROOT]: { + providers: { + [WellKnownProviders.Refresher]: { + properties: { + // Configuring the refresher interval so that our data source is invoked every ten minutes + interval: 60 * 10, + enabled: true, + } as IRefresherProperties, + } as Partial, + }, + }, + header: { + properties: { + title: "Harry Potter and the Sorcerer's Stone", + subtitle: "By J. K. Rowling", + }, + }, + tiles: { + properties: { + nodes: ["kpi1"], + }, + }, + kpi1: { + id: "kpi1", + componentType: KpiComponent.lateLoadKey, + properties: { + widgetData: { + units: "out of 5 Stars", + label: "Average Rating", + }, + }, + providers: { + [WellKnownProviders.DataSource]: { + // Setting the data source providerId for the tile with id "kpi1" + providerId: AverageRatingKpiDataSource.providerId, + } as IProviderConfiguration, + [WellKnownProviders.Adapter]: { + providerId: NOVA_KPI_DATASOURCE_ADAPTER, + properties: { + componentId: "kpi1", + propertyPath: "widgetData", + }, + } as IProviderConfiguration, + }, + }, + }, + }, +}; +\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\`, + "tutorials/data-source-setup/data-source-setup.module.ts": \\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\`// © 2022 SolarWinds Worldwide, LLC. All rights reserved. +// +// Permission is hereby granted, free of charge, to any person obtaining a copy +// of this software and associated documentation files (the "Software"), to +// deal in the Software without restriction, including without limitation the +// rights to use, copy, modify, merge, publish, distribute, sublicense, and/or +// sell copies of the Software, and to permit persons to whom the Software is +// furnished to do so, subject to the following conditions: +// +// The above copyright notice and this permission notice shall be included in +// all copies or substantial portions of the Software. +// +// THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR +// IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, +// FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE +// AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER +// LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, +// OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN +// THE SOFTWARE. + +import { CommonModule } from "@angular/common"; +import { HttpClientModule } from "@angular/common/http"; +import { NgModule } from "@angular/core"; +import { RouterModule } from "@angular/router"; + +import { + NuiDocsModule, + NuiMessageModule, + DEMO_PATH_TOKEN, +} from "@nova-ui/bits"; +import { NuiDashboardsModule } from "@nova-ui/dashboards"; + +import { DataSourceDocsComponent } from "./data-source-setup-docs.component"; +import { DataSourceSetupComponent } from "./data-source-setup.component"; +import { getDemoFiles } from "../../../../demo-files-factory"; + +const routes = [ + { + path: "", + component: DataSourceDocsComponent, + data: { + srlc: { + hideIndicator: true, + }, + showThemeSwitcher: true, + }, + }, + { + path: "example", + component: DataSourceSetupComponent, + data: { + srlc: { + hideIndicator: true, + }, + }, + }, +]; + +@NgModule({ + imports: [ + CommonModule, + HttpClientModule, + NuiDashboardsModule, + NuiDocsModule, + NuiMessageModule, + RouterModule.forChild(routes), + ], + declarations: [DataSourceDocsComponent, DataSourceSetupComponent], + providers: [ + { + provide: DEMO_PATH_TOKEN, + useValue: getDemoFiles("data-source-setup"), + }, + ], +}) +export default class DataSourceSetupModule {} +\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\`, + "tutorials/dynamic-header-links/dynamic-header-links-docs.component.ts": \\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\`// © 2022 SolarWinds Worldwide, LLC. All rights reserved. +// +// Permission is hereby granted, free of charge, to any person obtaining a copy +// of this software and associated documentation files (the "Software"), to +// deal in the Software without restriction, including without limitation the +// rights to use, copy, modify, merge, publish, distribute, sublicense, and/or +// sell copies of the Software, and to permit persons to whom the Software is +// furnished to do so, subject to the following conditions: +// +// The above copyright notice and this permission notice shall be included in +// all copies or substantial portions of the Software. +// +// THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR +// IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, +// FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE +// AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER +// LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, +// OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN +// THE SOFTWARE. + +import { Component } from "@angular/core"; + +@Component({ + selector: "nui-dynamic-header-links-docs", + templateUrl: "./dynamic-header-links-docs.component.html", + standalone: false, +}) +export class DynamicHeaderLinksDocsComponent {} +\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\`, + "tutorials/dynamic-header-links/dynamic-header-links-docs.module.ts": \\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\`// © 2022 SolarWinds Worldwide, LLC. All rights reserved. +// +// Permission is hereby granted, free of charge, to any person obtaining a copy +// of this software and associated documentation files (the "Software"), to +// deal in the Software without restriction, including without limitation the +// rights to use, copy, modify, merge, publish, distribute, sublicense, and/or +// sell copies of the Software, and to permit persons to whom the Software is +// furnished to do so, subject to the following conditions: +// +// The above copyright notice and this permission notice shall be included in +// all copies or substantial portions of the Software. +// +// THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR +// IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, +// FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE +// AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER +// LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, +// OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN +// THE SOFTWARE. + +import { NgModule } from "@angular/core"; +import { RouterModule } from "@angular/router"; + +import { DynamicHeaderLinksDocsComponent } from "./dynamic-header-links-docs.component"; + +const routes = [ + { + path: "", + component: DynamicHeaderLinksDocsComponent, + data: { + srlc: { + hideIndicator: true, + }, + showThemeSwitcher: true, + }, + }, +]; + +@NgModule({ + imports: [RouterModule.forChild(routes)], + declarations: [DynamicHeaderLinksDocsComponent], +}) +export default class DynamicHeaderLinksDocsModule {} +\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\`, + "tutorials/hello-dashboards/hello-dashboards-docs.component.ts": \\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\`// © 2022 SolarWinds Worldwide, LLC. All rights reserved. +// +// Permission is hereby granted, free of charge, to any person obtaining a copy +// of this software and associated documentation files (the "Software"), to +// deal in the Software without restriction, including without limitation the +// rights to use, copy, modify, merge, publish, distribute, sublicense, and/or +// sell copies of the Software, and to permit persons to whom the Software is +// furnished to do so, subject to the following conditions: +// +// The above copyright notice and this permission notice shall be included in +// all copies or substantial portions of the Software. +// +// THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR +// IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, +// FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE +// AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER +// LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, +// OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN +// THE SOFTWARE. + +import { Component } from "@angular/core"; + +@Component({ + selector: "nui-dashboard-hello-dashboards-docs", + templateUrl: "./hello-dashboards-docs.component.html", + standalone: false, +}) +export class HelloDashboardsDocsComponent {} +\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\`, + "tutorials/hello-dashboards/hello-dashboards-example/hello-dashboards-example.component.ts": \\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\`// © 2022 SolarWinds Worldwide, LLC. All rights reserved. +// +// Permission is hereby granted, free of charge, to any person obtaining a copy +// of this software and associated documentation files (the "Software"), to +// deal in the Software without restriction, including without limitation the +// rights to use, copy, modify, merge, publish, distribute, sublicense, and/or +// sell copies of the Software, and to permit persons to whom the Software is +// furnished to do so, subject to the following conditions: +// +// The above copyright notice and this permission notice shall be included in +// all copies or substantial portions of the Software. +// +// THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR +// IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, +// FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE +// AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER +// LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, +// OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN +// THE SOFTWARE. + +import { Component, OnInit } from "@angular/core"; +import { GridsterConfig, GridsterItem } from "angular-gridster2"; + +import { + IDashboard, + IWidget, + IWidgets, + KpiComponent, + PizzagnaLayer, + WidgetTypesService, +} from "@nova-ui/dashboards"; + +/** + * A component that instantiates the dashboard + */ +@Component({ + selector: "hello-dashboards-example", + templateUrl: "./hello-dashboards-example.component.html", + styleUrls: ["./hello-dashboards-example.component.less"], + standalone: false, +}) +export class HelloDashboardsExampleComponent implements OnInit { + // This variable will have all the data needed to render the widgets widgets. + // Pass this to the dashboard component's dashboard input. + public dashboard: IDashboard; + // Angular gridster requires a configuration object even if its empty. + // Pass this to the dashboard component's gridsterConfig input. + public gridsterConfig: GridsterConfig = {}; + + // WidgetTypesService provides the widget's necessary structure information + constructor(private widgetTypesService: WidgetTypesService) {} + + public ngOnInit(): void { + // Here we are hard-coding the widget config for this example, but this is where you + // could potentially populate the widget's configuration from a database + const kpiWidget = widgetConfig; + const widgetIndex: IWidgets = { + // Complete the KPI widget with information coming from its type definition + [kpiWidget.id]: + this.widgetTypesService.mergeWithWidgetType(kpiWidget), + }; + // Setting widget position and dimensions (this is for gridster) + const positions: Record = { + [kpiWidget.id]: { + cols: 4, + rows: 6, + y: 0, + x: 0, + }, + }; + // Finally, assigning the variables we created above to the dashboard + this.dashboard = { + positions, + widgets: widgetIndex, + }; + } +} + +// In a real-world scenario, this configuration would typically be fetched from a database or at least live in another file +const widgetConfig: IWidget = { + id: "widget1", + type: "kpi", + pizzagna: { + [PizzagnaLayer.Configuration]: { + header: { + properties: { + title: "Hello, KPI Widget!", + subtitle: "A Venue for Meaningful Values", + }, + }, + tiles: { + properties: { + nodes: ["kpi1"], + }, + }, + kpi1: { + id: "kpi1", + componentType: KpiComponent.lateLoadKey, + properties: { + widgetData: { + id: "totalStorage", + value: 1, + label: "Total storage", + units: "TB", + }, + }, + }, + }, + }, +}; +\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\`, + "tutorials/hello-dashboards/hello-dashboards.module.ts": \\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\`// © 2022 SolarWinds Worldwide, LLC. All rights reserved. +// +// Permission is hereby granted, free of charge, to any person obtaining a copy +// of this software and associated documentation files (the "Software"), to +// deal in the Software without restriction, including without limitation the +// rights to use, copy, modify, merge, publish, distribute, sublicense, and/or +// sell copies of the Software, and to permit persons to whom the Software is +// furnished to do so, subject to the following conditions: +// +// The above copyright notice and this permission notice shall be included in +// all copies or substantial portions of the Software. +// +// THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR +// IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, +// FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE +// AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER +// LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, +// OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN +// THE SOFTWARE. + +import { CommonModule } from "@angular/common"; +import { NgModule } from "@angular/core"; +import { RouterModule } from "@angular/router"; + +import { + NuiDocsModule, + NuiMessageModule, + DEMO_PATH_TOKEN, +} from "@nova-ui/bits"; +import { NuiDashboardsModule } from "@nova-ui/dashboards"; + +import { HelloDashboardsDocsComponent } from "./hello-dashboards-docs.component"; +import { HelloDashboardsExampleComponent } from "./hello-dashboards-example/hello-dashboards-example.component"; +import { getDemoFiles } from "../../../../demo-files-factory"; + +const routes = [ + { + path: "", + component: HelloDashboardsDocsComponent, + data: { + srlc: { + hideIndicator: true, + }, + showThemeSwitcher: true, + }, + }, + { + path: "example", + component: HelloDashboardsExampleComponent, + data: { + srlc: { + hideIndicator: true, + }, + }, + }, +]; + +@NgModule({ + imports: [ + CommonModule, + NuiDashboardsModule, + NuiDocsModule, + NuiMessageModule, + RouterModule.forChild(routes), + ], + declarations: [ + HelloDashboardsDocsComponent, + HelloDashboardsExampleComponent, + ], + providers: [ + { + provide: DEMO_PATH_TOKEN, + useValue: getDemoFiles("hello-dashboards"), + }, + ], +}) +export default class HelloDashboardsModule {} +\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\`, + "tutorials/persistence-handler-setup/persistence-handler-setup-docs.component.ts": \\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\`// © 2022 SolarWinds Worldwide, LLC. All rights reserved. +// +// Permission is hereby granted, free of charge, to any person obtaining a copy +// of this software and associated documentation files (the "Software"), to +// deal in the Software without restriction, including without limitation the +// rights to use, copy, modify, merge, publish, distribute, sublicense, and/or +// sell copies of the Software, and to permit persons to whom the Software is +// furnished to do so, subject to the following conditions: +// +// The above copyright notice and this permission notice shall be included in +// all copies or substantial portions of the Software. +// +// THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR +// IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, +// FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE +// AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER +// LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, +// OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN +// THE SOFTWARE. + +import { Component } from "@angular/core"; + +@Component({ + selector: "nui-dashboard-persistence-handler-setup-docs", + templateUrl: "./persistence-handler-setup-docs.component.html", + standalone: false, +}) +export class PersistenceHandlerSetupDocsComponent {} +\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\`, + "tutorials/persistence-handler-setup/persistence-handler-setup.component.ts": \\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\`// © 2022 SolarWinds Worldwide, LLC. All rights reserved. +// +// Permission is hereby granted, free of charge, to any person obtaining a copy +// of this software and associated documentation files (the "Software"), to +// deal in the Software without restriction, including without limitation the +// rights to use, copy, modify, merge, publish, distribute, sublicense, and/or +// sell copies of the Software, and to permit persons to whom the Software is +// furnished to do so, subject to the following conditions: +// +// The above copyright notice and this permission notice shall be included in +// all copies or substantial portions of the Software. +// +// THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR +// IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, +// FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE +// AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER +// LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, +// OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN +// THE SOFTWARE. + +import { HttpClient, HttpErrorResponse } from "@angular/common/http"; +import { + ChangeDetectorRef, + Component, + Injectable, + OnDestroy, + OnInit, +} from "@angular/core"; +import { GridsterConfig, GridsterItem } from "angular-gridster2"; +import { BehaviorSubject, Observable, Subject } from "rxjs"; +import { finalize } from "rxjs/operators"; + +import { + DataSourceService, + IFilteringOutputs, + ToastService, + uuid, +} from "@nova-ui/bits"; +import { + DATA_SOURCE, + DEFAULT_PIZZAGNA_ROOT, + IDashboard, + IDashboardPersistenceHandler, + IKpiData, + IProviderConfiguration, + IRefresherProperties, + IWidget, + IWidgets, + KpiComponent, + NOVA_KPI_DATASOURCE_ADAPTER, + PizzagnaLayer, + ProviderRegistryService, + WellKnownPathKey, + WellKnownProviders, + WidgetTypesService, +} from "@nova-ui/dashboards"; + +/** + * A simple persistence handler that is tied to the widget editor directive + */ +@Injectable() +// The realizer of IDashboardPersistenceHandler may implement a trySubmit and/or a tryRemove method. +export class PersistenceHandler implements IDashboardPersistenceHandler { + // This variable is just to show how to handle error handling. + private persistenceSucceeded: boolean = true; + + // The example uses the toast service to demonstrate the + // invocation of each of the persistence handler callbacks + constructor(private toastService: ToastService) { + // toastService options to let it sit on the page for 2 seconds. + this.toastService.setConfig({ + timeOut: 2000, + }); + } + + // This method will be invoked anytime the widget editor form gets submitted. + public trySubmit = (widget: IWidget): Observable => { + // Since we are working asynchronously, we'll return a subject. So, after the submit attempt + // succeeds or fails, we can let the subscriber know the result. + const subject = new Subject(); + + if (!widget.id) { + // Creates an id if the widget has no id. + // (This step will make more sense in the context of the widget cloning tutorial + // in which we handle the persistence of a newly created widget.) + widget.id = uuid(); + } + + // For this example, we're using a setTimeout to mock an asynchronous persistence request to a backend + setTimeout(() => { + if (this.persistenceSucceeded) { + // Passes along the new widget after one second. + subject.next(widget); + // Toast on the page on success. + this.toastService.success({ + title: $localize\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\`Submit succeeded.\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\`, + }); + } else { + const errorText = $localize\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\`Submit failed.\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\`; + // Toast on the page on failure. + this.toastService.error({ title: errorText }); + // Makes the subject say there is an error. + subject.error(errorText); + } + // Completes the subject so whoever subscribes to it knows its finished. + subject.complete(); + }, 1000); + + // Returns the subject as an observable. + return subject.asObservable(); + }; + + // This method will be invoked anytime there's a widget removal attempt. + public tryRemove = (widgetId: string): Observable => { + const subject = new Subject(); + + setTimeout(() => { + if (this.persistenceSucceeded) { + // Pass through the id of the widget that was removed. + subject.next(widgetId); + this.toastService.success({ + title: $localize\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\`Removal succeeded.\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\`, + }); + } else { + const errorText = $localize\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\`Removal failed.\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\`; + this.toastService.error({ title: errorText }); + subject.error(errorText); + } + subject.complete(); + }, 1000); + + return subject.asObservable(); + }; +} + +/** + * A component that instantiates the dashboard + */ +@Component({ + selector: "persistence-handler-setup", + templateUrl: "./persistence-handler-setup.component.html", + styleUrls: ["./persistence-handler-setup.component.less"], + // Here we provide our persistence handler at the component level; this can also be done in the module. + providers: [PersistenceHandler], + standalone: false, +}) +export class PersistenceHandlerSetupComponent implements OnInit { + // This variable will hold all the data needed to define the layout and behavior of the widgets. + // Pass this to the dashboard component's dashboard input in the template. + public dashboard: IDashboard | undefined; + + // Angular gridster requires a configuration object even if it's empty. + // Pass this to the dashboard component's gridsterConfig input in the template. + public gridsterConfig: GridsterConfig = {}; + + // Boolean which dashboard takes in as an input if its true it allows you to move widgets around. + public editMode: boolean = false; + + constructor( + // WidgetTypesService provides the widget's necessary structure information + private widgetTypesService: WidgetTypesService, + + // In general, the ProviderRegistryService is used for making entities available for injection into dynamically loaded components. + private providerRegistry: ProviderRegistryService, + + // We are injecting the PersistenceHandler we created and assigning it to a property we use in the template. + public persistenceHandler: PersistenceHandler, + private changeDetectorRef: ChangeDetectorRef + ) {} + + public ngOnInit(): void { + // Grabbing the widget's default template which will be needed as a parameter for setNode + const widgetTemplate = this.widgetTypesService.getWidgetType("kpi", 1); + // Registering our data sources as dropdown options in the widget editor/configurator + // Note: This could also be done in the parent module's constructor so that + // multiple dashboards could have access to the same widget template modification. + this.widgetTypesService.setNode( + // This is the template we grabbed above with getWidgetType + widgetTemplate, + // We are setting the editor/configurator part of the widget template + "configurator", + // This indicates which node you are changing and we want to change + // the data source providers available for selection in the editor. + WellKnownPathKey.DataSourceProviders, + // We are setting the data sources available for selection in the editor + [ + AverageRatingKpiDataSource.providerId, + RatingsCountKpiDataSource.providerId, + ] + ); + + // Registering the data sources available for injection into the KPI tiles. + // Note: Each tile of a KPI widget is assigned its own instance of a data source + this.providerRegistry.setProviders({ + [AverageRatingKpiDataSource.providerId]: { + provide: DATA_SOURCE, + useClass: AverageRatingKpiDataSource, + // Any dependencies that need to be injected into the provider must be listed here + deps: [HttpClient], + }, + [RatingsCountKpiDataSource.providerId]: { + provide: DATA_SOURCE, + useClass: RatingsCountKpiDataSource, + deps: [HttpClient], + }, + }); + + this.initializeDashboard(); + } + + /** Used for restoring widgets state */ + public reInitializeDashboard(): void { + // destroys the components and their providers so the dashboard can re init data + this.dashboard = undefined; + this.changeDetectorRef.detectChanges(); + + this.initializeDashboard(); + } + + public initializeDashboard(): void { + // We're using a static configuration object for this example (see widgetConfig at the bottom of the file), + // but this is where the widget's configuration could potentially be populated from a database + const kpiWidget = widgetConfig; + const widgetIndex: IWidgets = { + // Complete the KPI widget with information coming from its type definition + [kpiWidget.id]: + this.widgetTypesService.mergeWithWidgetType(kpiWidget), + }; + + // Setting the widget dimensions and position (this is for gridster) + const positions: Record = { + [kpiWidget.id]: { + cols: 4, + rows: 6, + y: 0, + x: 0, + }, + }; + + // Finally, assigning the variables we created above to the dashboard + this.dashboard = { + positions, + widgets: widgetIndex, + }; + } +} + +/** + * A simple KPI data source to retrieve the average rating of Harry Potter and the Sorcerer's Stone (book) via googleapis + */ +@Injectable() +export class AverageRatingKpiDataSource + extends DataSourceService + implements OnDestroy +{ + // This is the ID we'll use to identify the provider + public static providerId = "AverageRatingKpiDataSource"; + + // Use this subject to communicate the data source's busy state + public busy = new BehaviorSubject(false); + + constructor(private http: HttpClient) { + super(); + } + + // In this example, getFilteredData is invoked every 10 minutes (Take a look at the refresher + // provider definition in the widget configuration below to see how the interval is set) + public async getFilteredData(): Promise { + this.busy.next(true); + return new Promise((resolve) => { + // *** Make a resource request to an external API (if needed) + this.http + .get("https://www.googleapis.com/books/v1/volumes/5MQFrgEACAAJ") + .pipe(finalize(() => this.busy.next(false))) + .subscribe({ + next: (data: any) => { + resolve({ + result: { + value: data.volumeInfo.averageRating, + }, + }); + }, + error: (error: HttpErrorResponse) => { + resolve({ + result: null, + error: { + type: error.status, + }, + }); + }, + }); + }); + } + + public ngOnDestroy(): void { + this.outputsSubject.complete(); + } +} + +/** + * A simple KPI data source to retrieve the ratings count of Harry Potter and the Sorcerer's Stone (book) via googleapis + */ +@Injectable() +export class RatingsCountKpiDataSource + extends DataSourceService + implements OnDestroy +{ + public static providerId = "RatingsCountKpiDataSource"; + + // Use this subject to communicate the data source's busy state + public busy = new BehaviorSubject(false); + + constructor(private http: HttpClient) { + super(); + } + + public async getFilteredData(): Promise { + this.busy.next(true); + return new Promise((resolve) => { + this.http + .get("https://www.googleapis.com/books/v1/volumes/5MQFrgEACAAJ") + .pipe(finalize(() => this.busy.next(false))) + .subscribe({ + next: (data: any) => { + resolve({ + result: { + value: data.volumeInfo.ratingsCount, + }, + }); + }, + error: (error: HttpErrorResponse) => { + resolve({ + result: null, + error: { + type: error.status, + }, + }); + }, + }); + }); + } + + public ngOnDestroy(): void { + this.outputsSubject.complete(); + } +} + +const widgetConfig: IWidget = { + id: "widget1", + type: "kpi", + pizzagna: { + [PizzagnaLayer.Configuration]: { + [DEFAULT_PIZZAGNA_ROOT]: { + providers: { + [WellKnownProviders.Refresher]: { + properties: { + // Configuring the refresher interval so that our data source is invoked every ten minutes + interval: 60 * 10, + enabled: true, + } as IRefresherProperties, + } as Partial, + }, + }, + header: { + properties: { + title: "Harry Potter and the Sorcerer's Stone", + subtitle: "By J. K. Rowling", + }, + }, + tiles: { + properties: { + nodes: ["kpi1"], + }, + }, + kpi1: { + id: "kpi1", + componentType: KpiComponent.lateLoadKey, + properties: { + widgetData: { + units: "out of 5 Stars", + label: "Average Rating", + }, + }, + providers: { + [WellKnownProviders.DataSource]: { + // Setting the data source providerId for the tile with id "kpi1" + providerId: AverageRatingKpiDataSource.providerId, + } as IProviderConfiguration, + [WellKnownProviders.Adapter]: { + providerId: NOVA_KPI_DATASOURCE_ADAPTER, + properties: { + componentId: "kpi1", + propertyPath: "widgetData", + }, + } as IProviderConfiguration, + }, + }, + }, + }, +}; +\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\`, + "tutorials/persistence-handler-setup/persistence-handler-setup.module.ts": \\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\`// © 2022 SolarWinds Worldwide, LLC. All rights reserved. +// +// Permission is hereby granted, free of charge, to any person obtaining a copy +// of this software and associated documentation files (the "Software"), to +// deal in the Software without restriction, including without limitation the +// rights to use, copy, modify, merge, publish, distribute, sublicense, and/or +// sell copies of the Software, and to permit persons to whom the Software is +// furnished to do so, subject to the following conditions: +// +// The above copyright notice and this permission notice shall be included in +// all copies or substantial portions of the Software. +// +// THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR +// IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, +// FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE +// AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER +// LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, +// OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN +// THE SOFTWARE. + +import { CommonModule } from "@angular/common"; +import { HttpClientModule } from "@angular/common/http"; +import { NgModule } from "@angular/core"; +import { RouterModule } from "@angular/router"; + +import { + NuiButtonModule, + NuiDocsModule, + NuiMessageModule, + NuiSwitchModule, + NuiToastModule, + DEMO_PATH_TOKEN, +} from "@nova-ui/bits"; +import { NuiDashboardsModule } from "@nova-ui/dashboards"; + +import { PersistenceHandlerSetupDocsComponent } from "./persistence-handler-setup-docs.component"; +import { PersistenceHandlerSetupComponent } from "./persistence-handler-setup.component"; +import { getDemoFiles } from "../../../../demo-files-factory"; + +const routes = [ + { + path: "", + component: PersistenceHandlerSetupDocsComponent, + data: { + srlc: { + hideIndicator: true, + }, + showThemeSwitcher: true, + }, + }, + { + path: "example", + component: PersistenceHandlerSetupComponent, + data: { + srlc: { + hideIndicator: true, + }, + }, + }, +]; + +@NgModule({ + imports: [ + CommonModule, + HttpClientModule, + NuiDashboardsModule, + NuiDocsModule, + NuiMessageModule, + NuiSwitchModule, + NuiToastModule, + NuiButtonModule, + RouterModule.forChild(routes), + ], + declarations: [ + PersistenceHandlerSetupDocsComponent, + PersistenceHandlerSetupComponent, + ], + providers: [ + { + provide: DEMO_PATH_TOKEN, + useValue: getDemoFiles("persistence-handler-setup"), + }, + ], +}) +export default class PersistenceHandlerSetupModule {} +\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\`, + "tutorials/tutorials.module.ts": \\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\`// © 2022 SolarWinds Worldwide, LLC. All rights reserved. +// +// Permission is hereby granted, free of charge, to any person obtaining a copy +// of this software and associated documentation files (the "Software"), to +// deal in the Software without restriction, including without limitation the +// rights to use, copy, modify, merge, publish, distribute, sublicense, and/or +// sell copies of the Software, and to permit persons to whom the Software is +// furnished to do so, subject to the following conditions: +// +// The above copyright notice and this permission notice shall be included in +// all copies or substantial portions of the Software. +// +// THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR +// IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, +// FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE +// AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER +// LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, +// OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN +// THE SOFTWARE. + +import { NgModule, Type } from "@angular/core"; +import { RouterModule, Routes } from "@angular/router"; + +import { ConfiguratorHeadingService } from "@nova-ui/dashboards"; + +export enum TutorialsModuleRoute { + HelloDashboards = "hello-dashboards", + DataSource = "data-source-setup", + WidgetEditor = "widget-editor-setup", + SubmitHandler = "persistence-handler-setup", + WidgetCreation = "widget-creation", + Customization = "customization", + WidgetErrorHandling = "widget-error-handling", + DynamicHeaderLinks = "dynamic-header-links", +} + +const routes: Routes = [ + { + path: TutorialsModuleRoute.HelloDashboards, + loadChildren: async () => + import( + "./hello-dashboards/hello-dashboards.module" + ) as object as Promise>, + }, + { + path: TutorialsModuleRoute.DataSource, + loadChildren: async () => + import( + "./data-source-setup/data-source-setup.module" + ) as object as Promise>, + }, + { + path: TutorialsModuleRoute.WidgetEditor, + loadChildren: async () => + import( + "./widget-editor-setup/widget-editor-setup.module" + ) as object as Promise>, + }, + { + path: TutorialsModuleRoute.SubmitHandler, + loadChildren: async () => + import( + "./persistence-handler-setup/persistence-handler-setup.module" + ) as object as Promise>, + }, + { + path: TutorialsModuleRoute.WidgetCreation, + loadChildren: async () => + import( + "./widget-creation/widget-creation.module" + ) as object as Promise>, + }, + { + path: TutorialsModuleRoute.Customization, + loadChildren: async () => + import("./customization/customization.module") as object as Promise< + Type + >, + }, + { + path: TutorialsModuleRoute.WidgetErrorHandling, + loadChildren: async () => + import( + "./widget-error-handling/widget-error-handling.module" + ) as object as Promise>, + }, + { + path: TutorialsModuleRoute.DynamicHeaderLinks, + loadChildren: async () => + import( + "./dynamic-header-links/dynamic-header-links-docs.module" + ) as object as Promise>, + }, +]; + +@NgModule({ + imports: [RouterModule.forChild(routes)], + providers: [ConfiguratorHeadingService], +}) +export default class TutorialsModule {} +\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\`, + "tutorials/widget-creation/widget-creation-docs.component.ts": \\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\`// © 2022 SolarWinds Worldwide, LLC. All rights reserved. +// +// Permission is hereby granted, free of charge, to any person obtaining a copy +// of this software and associated documentation files (the "Software"), to +// deal in the Software without restriction, including without limitation the +// rights to use, copy, modify, merge, publish, distribute, sublicense, and/or +// sell copies of the Software, and to permit persons to whom the Software is +// furnished to do so, subject to the following conditions: +// +// The above copyright notice and this permission notice shall be included in +// all copies or substantial portions of the Software. +// +// THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR +// IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, +// FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE +// AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER +// LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, +// OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN +// THE SOFTWARE. + +import { Component } from "@angular/core"; + +@Component({ + selector: "nui-dashboard-widget-creation-docs", + templateUrl: "./widget-creation-docs.component.html", + standalone: false, +}) +export class WidgetCreationDocsComponent {} +\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\`, + "tutorials/widget-creation/widget-creation.component.ts": \\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\`// © 2022 SolarWinds Worldwide, LLC. All rights reserved. +// +// Permission is hereby granted, free of charge, to any person obtaining a copy +// of this software and associated documentation files (the "Software"), to +// deal in the Software without restriction, including without limitation the +// rights to use, copy, modify, merge, publish, distribute, sublicense, and/or +// sell copies of the Software, and to permit persons to whom the Software is +// furnished to do so, subject to the following conditions: +// +// The above copyright notice and this permission notice shall be included in +// all copies or substantial portions of the Software. +// +// THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR +// IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, +// FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE +// AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER +// LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, +// OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN +// THE SOFTWARE. + +import { HttpClient, HttpErrorResponse } from "@angular/common/http"; +import { + Component, + EventEmitter, + Injectable, + OnDestroy, + OnInit, + Output, + ViewChild, +} from "@angular/core"; +import { GridsterConfig, GridsterItem } from "angular-gridster2"; +import { BehaviorSubject, Observable, Subject } from "rxjs"; +import { finalize, take, takeUntil } from "rxjs/operators"; + +import { + DataSourceService, + IFilteringOutputs, + ToastService, + uuid, +} from "@nova-ui/bits"; +import { + DashboardComponent, + DATA_SOURCE, + DEFAULT_PIZZAGNA_ROOT, + IDashboard, + IDashboardPersistenceHandler, + IDataSourceOutput, + IKpiData, + IProviderConfiguration, + IRefresherProperties, + IWidget, + IWidgets, + IWidgetSelector, + IWidgetTemplateSelector, + KpiComponent, + NOVA_KPI_DATASOURCE_ADAPTER, + PizzagnaLayer, + ProviderRegistryService, + WellKnownPathKey, + WellKnownProviders, + WidgetClonerService, + WidgetTypesService, +} from "@nova-ui/dashboards"; + +// Interface of a widget item +interface IWidgetItem { + name: string; + widget: IWidget; +} + +// This component acts as the first step, or page, in the wizard where the user selects a wizard type to create. +// It's recommended to have this component in a different file. For this tutorial, it's included in the same +// file for simplicity. +@Component({ + selector: "widget-template-selection", + styleUrls: ["./widget-creation.component.less"], + template: \\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\` +
+ + +
+ + +
+
{{ item.name }}
+
+
+ \\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\`, + standalone: false, +}) +export class WidgetTemplateSelectionComponent + implements IWidgetTemplateSelector, OnInit +{ + // This output will notify the wizard that a widget has been selected. + @Output() public widgetSelected = new EventEmitter(); + + public widgetItems: IWidgetItem[] = []; + public widgetSelection: IWidgetItem[]; + + constructor(private widgetTypesService: WidgetTypesService) {} + + public ngOnInit(): void { + // Here we combine the widget structure from the WidgetTypesService with the corresponding widget + // configuration to create an array of widget objects for the itemSource on the repeat component. + this.widgetItems = [ + { + name: "Fully Configured KPI Widget", + widget: this.widgetTypesService.mergeWithWidgetType( + fullKpiWidgetConfig + ), + }, + { + name: "Unconfigured Proportional Widget", + // Note that 'partialPropWidgetConfig' sets 'metadata.needsConfiguration' to true. + // When this widget is selected in the wizard, the 'Create Widget' button will be hidden + // to guide the user to the second step where they can complete the configuration. + widget: this.widgetTypesService.mergeWithWidgetType( + partialPropWidgetConfig + ), + }, + ]; + + // You can optionally auto-select a widget by doing the following + // this.onSelect([this.widgetItems[0]]); + } + + public onSelect(selectedItems: any[]): void { + // We emit the selected widget to communicate the selection to the configurator + this.widgetSelected.emit(selectedItems[0].widget); + this.widgetSelection = selectedItems; + } +} + +/** + * A simple persistence handler that is tied to the widget editor directive + */ +@Injectable() +// The realizer of IDashboardPersistenceHandler may implement a trySubmit and/or a tryRemove method. +export class PersistenceHandler implements IDashboardPersistenceHandler { + // This variable is just to show how to handle error handling. + private persistenceSucceeded: boolean = true; + + // The example uses the toast service to demonstrate the + // invocation of each of the persistence handler callbacks + constructor(private toastService: ToastService) { + // toastService options to let it sit on the page for 2 seconds. + this.toastService.setConfig({ + timeOut: 2000, + }); + } + + // This method will be invoked anytime the widget editor form gets submitted. + public trySubmit = (widget: IWidget): Observable => { + // Since we are working asynchronously, we'll return a subject. So, after the submit attempt + // succeeds or fails, we can let the subscriber know the result. + const subject = new Subject(); + + if (!widget.id) { + // Creates an id if the widget has no id. + // (This step will make more sense in the context of the widget cloning tutorial + // in which we handle the persistence of a newly created widget.) + widget.id = uuid(); + } + + // For this example, we're using a setTimeout to mock an asynchronous persistence request to a backend + setTimeout(() => { + if (this.persistenceSucceeded) { + // Passes along the new widget after one second. + subject.next(widget); + // Toast on the page on success. + this.toastService.success({ + title: $localize\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\`Submit succeeded.\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\`, + }); + } else { + const errorText = $localize\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\`Submit failed.\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\`; + // Toast on the page on failure. + this.toastService.error({ title: errorText }); + // Makes the subject say there is an error. + subject.error(errorText); + } + // Completes the subject so whoever subscribes to it knows its finished. + subject.complete(); + }, 1000); + + // Returns the subject as an observable. + return subject.asObservable(); + }; + + // This method will be invoked anytime there's a widget removal attempt. + public tryRemove = (widgetId: string): Observable => { + const subject = new Subject(); + + setTimeout(() => { + if (this.persistenceSucceeded) { + // Pass through the id of the widget that was removed. + subject.next(widgetId); + this.toastService.success({ + title: $localize\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\`Removal success\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\`, + }); + } else { + const errorText = $localize\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\`Removal failed.\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\`; + this.toastService.error({ title: errorText }); + subject.error(errorText); + } + subject.complete(); + }, 1000); + + return subject.asObservable(); + }; +} + +/** + * A component that instantiates the dashboard + */ +@Component({ + selector: "widget-creation", + templateUrl: "./widget-creation.component.html", + styleUrls: ["./widget-creation.component.less"], + // Here we provide our persistence handler at the component level; this can also be done in the module. + providers: [PersistenceHandler], + standalone: false, +}) +export class WidgetCreationComponent implements OnInit { + // The WidgetClonerService will need this for updating the dashboard + @ViewChild(DashboardComponent, { static: true }) + dashboardComponent: DashboardComponent; + // This variable will hold all the data needed to define the layout and behavior of the widgets. + // Pass this to the dashboard component's dashboard input in the template. + public dashboard: IDashboard; + + // Angular gridster requires a configuration object even if it's empty. + // Pass this to the dashboard component's gridsterConfig input in the template. + public gridsterConfig: GridsterConfig = { + // These values will be used to set the initial widget dimensions on creation. + // If not set, they each default to 6. + defaultItemCols: 3, + defaultItemRows: 5, + }; + + // Boolean the dashboard takes in as an input; if it's set to true + // the dashboard allows you to resize widgets and move them around. + public editMode: boolean = false; + + // Subject used for auto-unsubscribing from subscriptions on component destruction + private readonly destroy$ = new Subject(); + + constructor( + // WidgetTypesService provides the widget's necessary structure information + private widgetTypesService: WidgetTypesService, + + // In general, the ProviderRegistryService is used for making entities available for injection into dynamically loaded components. + private providerRegistry: ProviderRegistryService, + + // Injecting the PersistenceHandler we created and assigning it to a property we use in the template. + public persistenceHandler: PersistenceHandler, + + // Injecting the cloner service which is needed for opening up the cloner wizard. + private widgetClonerService: WidgetClonerService + ) {} + + public ngOnInit(): void { + // Grabbing the widget's default template which will be needed as a parameter for setNode + const kpiTemplate = this.widgetTypesService.getWidgetType("kpi", 1); + const proportionalTemplate = this.widgetTypesService.getWidgetType( + "proportional", + 1 + ); + + // Registering our data sources as dropdown options in the widget editor/configurator + // Note: This could also be done in the parent module's constructor so that + // multiple dashboards could have access to the same widget template modification. + this.widgetTypesService.setNode( + // This is the template we grabbed above with getWidgetType + proportionalTemplate, + // Setting the editor/configurator part of the widget template + "configurator", + // This indicates which node you are changing and we want to change + // the data source providers available for selection in the editor. + WellKnownPathKey.DataSourceProviders, + // Setting the data sources available for selection in the editor + [RandomCitiesProportionalDataSource.providerId] + ); + + // Same as above, but for the KPI data sources + this.widgetTypesService.setNode( + kpiTemplate, + "configurator", + WellKnownPathKey.DataSourceProviders, + [ + AverageRatingKpiDataSource.providerId, + RatingsCountKpiDataSource.providerId, + ] + ); + + // Registering the data sources available for injection into the KPI tiles and proportional widget. + // Note: Each tile of a KPI widget is assigned its own instance of a data source. + this.providerRegistry.setProviders({ + [AverageRatingKpiDataSource.providerId]: { + provide: DATA_SOURCE, + useClass: AverageRatingKpiDataSource, + // Any dependencies that need to be injected into the provider must be listed here + deps: [HttpClient], + }, + [RatingsCountKpiDataSource.providerId]: { + provide: DATA_SOURCE, + useClass: RatingsCountKpiDataSource, + deps: [HttpClient], + }, + [RandomCitiesProportionalDataSource.providerId]: { + provide: DATA_SOURCE, + useClass: RandomCitiesProportionalDataSource, + deps: [], + }, + }); + + this.initializeDashboard(); + } + + public onCreateWidget(): void { + const widgetSelector: IWidgetSelector = { + // Template ref of the dashboard component. + dashboardComponent: this.dashboardComponent, + // A trySubmit function; in this case, we use the trySubmit from the PersistenceHandler created in the previous tutorial. + trySubmit: this.persistenceHandler.trySubmit, + // WidgetTemplateSelectionComponent will act as step one of the wizard to allow the user to select which widget will be cloned. + widgetSelectionComponentType: WidgetTemplateSelectionComponent, + }; + this.widgetClonerService + .open(widgetSelector) + .pipe( + // Auto-unsubscribe after one emission or on component destruction + take(1), + takeUntil(this.destroy$) + ) + .subscribe(); + } + + public initializeDashboard(): void { + // We're using a static configuration object for this example (see widgetConfig at the bottom of the file), + // but this is where the widget's configuration could potentially be populated from a database + const kpiWidget = fullKpiWidgetConfig; + const widgetIndex: IWidgets = { + // Complete the KPI widget with information coming from its type definition + [kpiWidget.id]: + this.widgetTypesService.mergeWithWidgetType(kpiWidget), + }; + + // Setting the widget dimensions and position (this is for gridster) + // Note: If no position is given for a widget the 'defaultItemCols' and 'defaultItemRows' properties + // from the gridsterConfig will be used for the dimensions + const positions: Record = { + [kpiWidget.id]: { + cols: 3, + rows: 5, + y: 0, + x: 0, + }, + }; + + // Finally, assigning the variables we created above to the dashboard + this.dashboard = { + positions, + widgets: widgetIndex, + }; + } +} + +// Interface for each data point in a proportional widget. +interface IProportionalWidgetData { + id: string; + name: string; + data: number[]; + icon: string; + link: string; + value: string; +} + +@Injectable() +export class RandomCitiesProportionalDataSource implements OnDestroy { + public static providerId = "RandomCitiesProportionalDataSource"; + + public outputsSubject = new Subject< + IDataSourceOutput + >(); + + // Every time applyFilters gets ran we are changing the data source. + public applyFilters(): void { + setTimeout(() => { + this.outputsSubject.next({ + result: this.getRandomProportionalWidgetData(), + }); + }, 1000); + } + + public ngOnDestroy(): void { + this.outputsSubject.complete(); + } + + private getRandomProportionalWidgetData(): IProportionalWidgetData[] { + return [ + { + id: "Down", + name: "Down", + data: [Math.round(Math.random() * 100)], + icon: "status_down", + link: "https://en.wikipedia.org/wiki/Brno", + value: "Brno", + }, + { + id: "Critical", + name: "Critical", + data: [Math.round(Math.random() * 100)], + icon: "status_critical", + link: "https://en.wikipedia.org/wiki/Kyiv", + value: "Kyiv", + }, + { + id: "Warning", + name: "Warning", + data: [Math.round(Math.random() * 100)], + icon: "status_warning", + link: "https://en.wikipedia.org/wiki/Austin", + value: "Austin", + }, + { + id: "Unknown", + name: "Unknown", + data: [Math.round(Math.random() * 100)], + icon: "status_unknown", + link: "https://en.wikipedia.org/wiki/Lisbon", + value: "Lisbon", + }, + { + id: "Up", + name: "Up", + data: [Math.round(Math.random() * 100)], + icon: "status_up", + link: "https://en.wikipedia.org/wiki/Sydney", + value: "Sydney", + }, + { + id: "Unmanaged", + name: "Unmanaged", + data: [Math.round(Math.random() * 100)], + icon: "status_unmanaged", + link: "https://en.wikipedia.org/wiki/Nur-Sultan", + value: "Nur-Sultan", + }, + ]; + } +} + +/** + * A simple KPI data source to retrieve the average rating of Harry Potter and the Sorcerer's Stone (book) via googleapis + */ +@Injectable() +export class AverageRatingKpiDataSource + extends DataSourceService + implements OnDestroy +{ + // This is the ID we'll use to identify the provider + public static providerId = "AverageRatingKpiDataSource"; + + // Use this subject to communicate the data source's busy state + public busy = new BehaviorSubject(false); + + constructor(private http: HttpClient) { + super(); + } + + // In this example, getFilteredData is invoked every 10 minutes (Take a look at the refresher + // provider definition in the widget configuration below to see how the interval is set) + public async getFilteredData(): Promise { + this.busy.next(true); + return new Promise((resolve) => { + // *** Make a resource request to an external API (if needed) + this.http + .get("https://www.googleapis.com/books/v1/volumes/5MQFrgEACAAJ") + .pipe(finalize(() => this.busy.next(false))) + .subscribe({ + next: (data: any) => { + resolve({ + result: { + value: data.volumeInfo.averageRating, + }, + }); + }, + error: (error: HttpErrorResponse) => { + resolve({ + result: null, + error: { + type: error.status, + }, + }); + }, + }); + }); + } + + public ngOnDestroy(): void { + this.outputsSubject.complete(); + } +} + +/** + * A simple KPI data source to retrieve the ratings count of Harry Potter and the Sorcerer's Stone (book) via googleapis + */ +@Injectable() +export class RatingsCountKpiDataSource + extends DataSourceService + implements OnDestroy +{ + public static providerId = "RatingsCountKpiDataSource"; + + // Use this subject to communicate the data source's busy state + public busy = new BehaviorSubject(false); + + constructor(private http: HttpClient) { + super(); + } + + public async getFilteredData(): Promise { + this.busy.next(true); + return new Promise((resolve) => { + this.http + .get("https://www.googleapis.com/books/v1/volumes/5MQFrgEACAAJ") + .pipe(finalize(() => this.busy.next(false))) + .subscribe({ + next: (data: any) => { + resolve({ + result: { + value: data.volumeInfo.ratingsCount, + }, + }); + }, + error: (error: HttpErrorResponse) => { + resolve({ + result: null, + error: { + type: error.status, + }, + }); + }, + }); + }); + } + + public ngOnDestroy(): void { + this.outputsSubject.complete(); + } +} + +const fullKpiWidgetConfig: IWidget = { + id: "widget1", + type: "kpi", + pizzagna: { + [PizzagnaLayer.Configuration]: { + [DEFAULT_PIZZAGNA_ROOT]: { + providers: { + [WellKnownProviders.Refresher]: { + properties: { + // Configuring the refresher interval so that our data source is invoked every ten minutes + interval: 60 * 10, + enabled: true, + } as IRefresherProperties, + } as Partial, + }, + }, + header: { + properties: { + title: "Harry Potter and the Sorcerer's Stone", + subtitle: "By J. K. Rowling", + }, + }, + tiles: { + properties: { + nodes: ["kpi1"], + }, + }, + kpi1: { + id: "kpi1", + componentType: KpiComponent.lateLoadKey, + properties: { + widgetData: { + units: \\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\`out of 5 Stars\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\`, + label: \\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\`Average Rating\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\`, + }, + }, + providers: { + [WellKnownProviders.DataSource]: { + // Setting the data source providerId for the tile with id "kpi1" + providerId: AverageRatingKpiDataSource.providerId, + } as IProviderConfiguration, + [WellKnownProviders.Adapter]: { + providerId: NOVA_KPI_DATASOURCE_ADAPTER, + properties: { + componentId: "kpi1", + propertyPath: "widgetData", + }, + } as IProviderConfiguration, + }, + }, + }, + }, +}; + +const partialPropWidgetConfig: IWidget = { + id: "widget2", + type: "proportional", + metadata: { + // Set 'needsConfiguration' to true if the widget needs further configuration before it can be + // placed on the dashboard. The "Create Widget" button will be hidden in the wizard when this + // widget is selected. + needsConfiguration: true, + }, + pizzagna: { + [PizzagnaLayer.Configuration]: { + header: { + properties: { + title: "*New Proportional Widget*", + }, + }, + }, + }, +}; +\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\`, + "tutorials/widget-creation/widget-creation.module.ts": \\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\`// © 2022 SolarWinds Worldwide, LLC. All rights reserved. +// +// Permission is hereby granted, free of charge, to any person obtaining a copy +// of this software and associated documentation files (the "Software"), to +// deal in the Software without restriction, including without limitation the +// rights to use, copy, modify, merge, publish, distribute, sublicense, and/or +// sell copies of the Software, and to permit persons to whom the Software is +// furnished to do so, subject to the following conditions: +// +// The above copyright notice and this permission notice shall be included in +// all copies or substantial portions of the Software. +// +// THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR +// IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, +// FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE +// AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER +// LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, +// OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN +// THE SOFTWARE. + +import { CommonModule } from "@angular/common"; +import { HttpClientModule } from "@angular/common/http"; +import { NgModule } from "@angular/core"; +import { RouterModule } from "@angular/router"; + +import { + NuiButtonModule, + NuiDocsModule, + NuiImageModule, + NuiMessageModule, + NuiRepeatModule, + NuiSwitchModule, + NuiToastModule, + DEMO_PATH_TOKEN, +} from "@nova-ui/bits"; +import { NuiDashboardsModule } from "@nova-ui/dashboards"; + +import { WidgetCreationDocsComponent } from "./widget-creation-docs.component"; +import { + WidgetCreationComponent, + WidgetTemplateSelectionComponent, +} from "./widget-creation.component"; +import { getDemoFiles } from "../../../../demo-files-factory"; + +const routes = [ + { + path: "", + component: WidgetCreationDocsComponent, + data: { + srlc: { + hideIndicator: true, + }, + showThemeSwitcher: true, + }, + }, + { + path: "example", + component: WidgetCreationComponent, + data: { + srlc: { + hideIndicator: true, + }, + }, + }, +]; + +@NgModule({ + imports: [ + CommonModule, + HttpClientModule, + NuiDashboardsModule, + NuiDocsModule, + NuiMessageModule, + NuiSwitchModule, + NuiToastModule, + NuiButtonModule, + NuiRepeatModule, + NuiImageModule, + RouterModule.forChild(routes), + ], + declarations: [ + WidgetCreationDocsComponent, + WidgetCreationComponent, + WidgetTemplateSelectionComponent, + ], + providers: [ + { + provide: DEMO_PATH_TOKEN, + useValue: getDemoFiles("widget-creation"), + }, + ], +}) +export default class WidgetCreationModule {} +\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\`, + "tutorials/widget-editor-setup/widget-editor-setup-docs.component.ts": \\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\`// © 2022 SolarWinds Worldwide, LLC. All rights reserved. +// +// Permission is hereby granted, free of charge, to any person obtaining a copy +// of this software and associated documentation files (the "Software"), to +// deal in the Software without restriction, including without limitation the +// rights to use, copy, modify, merge, publish, distribute, sublicense, and/or +// sell copies of the Software, and to permit persons to whom the Software is +// furnished to do so, subject to the following conditions: +// +// The above copyright notice and this permission notice shall be included in +// all copies or substantial portions of the Software. +// +// THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR +// IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, +// FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE +// AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER +// LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, +// OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN +// THE SOFTWARE. + +import { Component } from "@angular/core"; + +@Component({ + selector: "nui-dashboard-widget-editor-docs", + templateUrl: "./widget-editor-setup-docs.component.html", + standalone: false, +}) +export class WidgetEditorDocsComponent {} +\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\`, + "tutorials/widget-editor-setup/widget-editor-setup.component.ts": \\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\`// © 2022 SolarWinds Worldwide, LLC. All rights reserved. +// +// Permission is hereby granted, free of charge, to any person obtaining a copy +// of this software and associated documentation files (the "Software"), to +// deal in the Software without restriction, including without limitation the +// rights to use, copy, modify, merge, publish, distribute, sublicense, and/or +// sell copies of the Software, and to permit persons to whom the Software is +// furnished to do so, subject to the following conditions: +// +// The above copyright notice and this permission notice shall be included in +// all copies or substantial portions of the Software. +// +// THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR +// IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, +// FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE +// AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER +// LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, +// OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN +// THE SOFTWARE. + +import { HttpClient, HttpErrorResponse } from "@angular/common/http"; +import { + ChangeDetectorRef, + Component, + Injectable, + OnDestroy, + OnInit, +} from "@angular/core"; +import { GridsterConfig, GridsterItem } from "angular-gridster2"; +import { BehaviorSubject } from "rxjs"; +import { finalize } from "rxjs/operators"; + +import { DataSourceService, IFilteringOutputs } from "@nova-ui/bits"; +import { + DATA_SOURCE, + DEFAULT_PIZZAGNA_ROOT, + IDashboard, + IKpiData, + IProviderConfiguration, + IRefresherProperties, + IWidget, + IWidgets, + KpiComponent, + NOVA_KPI_DATASOURCE_ADAPTER, + PizzagnaLayer, + ProviderRegistryService, + WellKnownPathKey, + WellKnownProviders, + WidgetTypesService, +} from "@nova-ui/dashboards"; + +/** + * A simple KPI data source to retrieve the average rating of Harry Potter and the Sorcerer's Stone (book) via googleapis + */ +@Injectable() +export class AverageRatingKpiDataSource + extends DataSourceService + implements OnDestroy +{ + // This is the ID we'll use to identify the provider + public static providerId = "AverageRatingKpiDataSource"; + + // Use this subject to communicate the data source's busy state + public busy = new BehaviorSubject(false); + + constructor(private http: HttpClient) { + super(); + } + + // In this example, getFilteredData is invoked every 10 minutes (Take a look at the refresher + // provider definition in the widget configuration below to see how the interval is set) + public async getFilteredData(): Promise { + this.busy.next(true); + return new Promise((resolve) => { + // *** Make a resource request to an external API (if needed) + this.http + .get("https://www.googleapis.com/books/v1/volumes/5MQFrgEACAAJ") + .pipe(finalize(() => this.busy.next(false))) + .subscribe({ + next: (data: any) => { + resolve({ + result: { + value: data.volumeInfo.averageRating, + }, + }); + }, + error: (error: HttpErrorResponse) => { + resolve({ + result: null, + error: { + type: error.status, + }, + }); + }, + }); + }); + } + + public ngOnDestroy(): void { + this.outputsSubject.complete(); + } +} + +/** + * A simple KPI data source to retrieve the ratings count of Harry Potter and the Sorcerer's Stone (book) via googleapis + */ +@Injectable() +export class RatingsCountKpiDataSource + extends DataSourceService + implements OnDestroy +{ + public static providerId = "RatingsCountKpiDataSource"; + + // Use this subject to communicate the data source's busy state + public busy = new BehaviorSubject(false); + + constructor(private http: HttpClient) { + super(); + } + + public async getFilteredData(): Promise { + this.busy.next(true); + return new Promise((resolve) => { + this.http + .get("https://www.googleapis.com/books/v1/volumes/5MQFrgEACAAJ") + .pipe(finalize(() => this.busy.next(false))) + .subscribe({ + next: (data: any) => { + resolve({ + result: { + value: data.volumeInfo.ratingsCount, + }, + }); + }, + error: (error: HttpErrorResponse) => { + resolve({ + result: null, + error: { + type: error.status, + }, + }); + }, + }); + }); + } + + public ngOnDestroy(): void { + this.outputsSubject.complete(); + } +} + +/** + * A component that instantiates the dashboard + */ +@Component({ + selector: "widget-editor-setup", + templateUrl: "./widget-editor-setup.component.html", + styleUrls: ["./widget-editor-setup.component.less"], + standalone: false, +}) +export class WidgetEditorSetupComponent implements OnInit { + // This variable will hold all the data needed to define the layout and behavior of the widgets. + // Pass this to the dashboard component's dashboard input in the template. + public dashboard: IDashboard | undefined; + + // Angular gridster requires a configuration object even if it's empty. + // Pass this to the dashboard component's gridsterConfig input in the template. + public gridsterConfig: GridsterConfig = {}; + + // Boolean which dashboard takes in as an input if its true it allows you to move widgets around. + public editMode: boolean = false; + + constructor( + // WidgetTypesService provides the widget's necessary structure information + private widgetTypesService: WidgetTypesService, + + // In general, the ProviderRegistryService is used for making entities available for injection into dynamically loaded components. + private providerRegistry: ProviderRegistryService, + private changeDetectorRef: ChangeDetectorRef + ) {} + + public ngOnInit(): void { + // Grabbing the widget's default template which will be needed as a parameter for setNode + const widgetTemplate = this.widgetTypesService.getWidgetType("kpi", 1); + // Registering our data sources as dropdown options in the widget editor/configurator + // Note: This could also be done in the parent module's constructor so that + // multiple dashboards could have access to the same widget template modification. + this.widgetTypesService.setNode( + // This is the template we grabbed above with getWidgetType + widgetTemplate, + // We are setting the editor/configurator part of the widget template + "configurator", + // This indicates which node you are changing and we want to change + // the data source providers available for selection in the editor. + WellKnownPathKey.DataSourceProviders, + // We are setting the data sources available for selection in the editor + [ + AverageRatingKpiDataSource.providerId, + RatingsCountKpiDataSource.providerId, + ] + ); + + // Registering the data sources available for injection into the KPI tiles. + // Note: Each tile of a KPI widget is assigned its own instance of a data source + this.providerRegistry.setProviders({ + [AverageRatingKpiDataSource.providerId]: { + provide: DATA_SOURCE, + useClass: AverageRatingKpiDataSource, + // Any dependencies that need to be injected into the provider must be listed here + deps: [HttpClient], + }, + [RatingsCountKpiDataSource.providerId]: { + provide: DATA_SOURCE, + useClass: RatingsCountKpiDataSource, + deps: [HttpClient], + }, + }); + + this.initializeDashboard(); + } + + /** Used for restoring widgets state */ + public reInitializeDashboard(): void { + // destroys the components and their providers so the dashboard can re init data + this.dashboard = undefined; + this.changeDetectorRef.detectChanges(); + + this.initializeDashboard(); + } + + public initializeDashboard(): void { + // We're using a static configuration object for this example (see widgetConfig at the bottom of the file), + // but this is where the widget's configuration could potentially be populated from a database + const kpiWidget = widgetConfig; + const widgetIndex: IWidgets = { + // Complete the KPI widget with information coming from its type definition + [kpiWidget.id]: + this.widgetTypesService.mergeWithWidgetType(kpiWidget), + }; + + // Setting the widget dimensions and position (this is for gridster) + const positions: Record = { + [kpiWidget.id]: { + cols: 4, + rows: 6, + y: 0, + x: 0, + }, + }; + + // Finally, assigning the variables we created above to the dashboard + this.dashboard = { + positions, + widgets: widgetIndex, + }; + } +} + +const widgetConfig: IWidget = { + id: "widget1", + type: "kpi", + pizzagna: { + [PizzagnaLayer.Configuration]: { + [DEFAULT_PIZZAGNA_ROOT]: { + providers: { + [WellKnownProviders.Refresher]: { + properties: { + // Configuring the refresher interval so that our data source is invoked every ten minutes + interval: 60 * 10, + enabled: true, + } as IRefresherProperties, + } as Partial, + }, + }, + header: { + properties: { + title: "Harry Potter and the Sorcerer's Stone", + subtitle: "By J. K. Rowling", + }, + }, + tiles: { + properties: { + nodes: ["kpi1"], + }, + }, + kpi1: { + id: "kpi1", + componentType: KpiComponent.lateLoadKey, + properties: { + widgetData: { + units: "out of 5 Stars", + label: "Average Rating", + }, + }, + providers: { + [WellKnownProviders.DataSource]: { + // Setting the data source providerId for the tile with id "kpi1" + providerId: AverageRatingKpiDataSource.providerId, + } as IProviderConfiguration, + [WellKnownProviders.Adapter]: { + providerId: NOVA_KPI_DATASOURCE_ADAPTER, + properties: { + componentId: "kpi1", + propertyPath: "widgetData", + }, + } as IProviderConfiguration, + }, + }, + }, + }, +}; +\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\`, + "tutorials/widget-editor-setup/widget-editor-setup.module.ts": \\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\`// © 2022 SolarWinds Worldwide, LLC. All rights reserved. +// +// Permission is hereby granted, free of charge, to any person obtaining a copy +// of this software and associated documentation files (the "Software"), to +// deal in the Software without restriction, including without limitation the +// rights to use, copy, modify, merge, publish, distribute, sublicense, and/or +// sell copies of the Software, and to permit persons to whom the Software is +// furnished to do so, subject to the following conditions: +// +// The above copyright notice and this permission notice shall be included in +// all copies or substantial portions of the Software. +// +// THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR +// IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, +// FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE +// AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER +// LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, +// OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN +// THE SOFTWARE. + +import { CommonModule } from "@angular/common"; +import { HttpClientModule } from "@angular/common/http"; +import { NgModule } from "@angular/core"; +import { RouterModule } from "@angular/router"; + +import { + NuiButtonModule, + NuiDocsModule, + NuiMessageModule, + NuiSwitchModule, + DEMO_PATH_TOKEN, +} from "@nova-ui/bits"; +import { NuiDashboardsModule } from "@nova-ui/dashboards"; + +import { WidgetEditorDocsComponent } from "./widget-editor-setup-docs.component"; +import { WidgetEditorSetupComponent } from "./widget-editor-setup.component"; +import { getDemoFiles } from "../../../../demo-files-factory"; + +const routes = [ + { + path: "", + component: WidgetEditorDocsComponent, + data: { + srlc: { + hideIndicator: true, + }, + showThemeSwitcher: true, + }, + }, + { + path: "example", + component: WidgetEditorSetupComponent, + data: { + srlc: { + hideIndicator: true, + }, + }, + }, +]; + +@NgModule({ + imports: [ + CommonModule, + HttpClientModule, + NuiDashboardsModule, + NuiDocsModule, + NuiMessageModule, + NuiSwitchModule, + NuiButtonModule, + RouterModule.forChild(routes), + ], + declarations: [WidgetEditorDocsComponent, WidgetEditorSetupComponent], + providers: [ + { + provide: DEMO_PATH_TOKEN, + useValue: getDemoFiles("widget-editor-setup"), + }, + ], +}) +export default class WidgetEditorSetupModule {} +\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\`, + "tutorials/widget-error-handling/widget-error-handling-docs.component.ts": \\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\`// © 2022 SolarWinds Worldwide, LLC. All rights reserved. +// +// Permission is hereby granted, free of charge, to any person obtaining a copy +// of this software and associated documentation files (the "Software"), to +// deal in the Software without restriction, including without limitation the +// rights to use, copy, modify, merge, publish, distribute, sublicense, and/or +// sell copies of the Software, and to permit persons to whom the Software is +// furnished to do so, subject to the following conditions: +// +// The above copyright notice and this permission notice shall be included in +// all copies or substantial portions of the Software. +// +// THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR +// IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, +// FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE +// AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER +// LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, +// OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN +// THE SOFTWARE. + +import { Component } from "@angular/core"; + +@Component({ + selector: "nui-widget-error-handling-docs", + templateUrl: "./widget-error-handling-docs.component.html", + standalone: false, +}) +export class WidgetErrorHandlingDocsComponent { + public fallbackAdapter = \\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\` +@Injectable() +export class StatusContentFallbackAdapter implements OnDestroy, IHasComponent { + + protected readonly destroy$ = new Subject(); + protected componentId: string; + + constructor(@Inject(PIZZAGNA_EVENT_BUS) protected eventBus: EventBus, + protected pizzagnaService: PizzagnaService) { + this.eventBus.getStream(DATA_SOURCE_OUTPUT) + .pipe(takeUntil(this.destroy$)).subscribe((event: IEvent>) => { + this.handleDataSourceOutput(event); + }); + } + + public ngOnDestroy(): void { + this.destroy$.next(); + this.destroy$.complete(); + } + + public setComponent(component: any, componentId: string) { + this.componentId = componentId; + } + + protected handleDataSourceOutput(event: IEvent>) { + this.pizzagnaService.setProperty({ + componentId: this.componentId, + propertyPath: ["fallbackKey"], + pizzagnaKey: PizzagnaLayer.Data, + }, typeof event.payload?.error?.type !== "undefined" ? event.payload?.error?.type.toString() : undefined); + } +}\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\`; + public errorsMap = \\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\` +export const ERROR_FALLBACK_MAP: Record = { + [HttpStatusCode.Unknown]: ErrorNodeKey.ErrorUnknown, + [HttpStatusCode.Forbidden]: ErrorNodeKey.ErrorForbidden, + [HttpStatusCode.NotFound]: ErrorNodeKey.ErrorNotFound, +}; +\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\`; + public errorNodes = \\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\` +export const ERROR_NODES: Record = { + [ErrorNodeKey.ErrorUnknown]: { + id: ErrorNodeKey.ErrorUnknown, + componentType: WidgetErrorComponent.lateLoadKey, + properties: { + image: "no-data-to-show", + title: $localize\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\`Whoops, something went wrong\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\`, + description: $localize\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\`There was an unexpected error.\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\`, + } as IWidgetErrorDisplayProperties, + }, + [ErrorNodeKey.ErrorForbidden]: { + id: ErrorNodeKey.ErrorForbidden, + componentType: WidgetErrorComponent.lateLoadKey, + properties: { + image: "no-data-to-show", + title: $localize\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\`403 - Forbidden\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\`, + description: $localize\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\`The requested action was forbidden.\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\`, + } as IWidgetErrorDisplayProperties, + }, + [ErrorNodeKey.ErrorNotFound]: { + id: ErrorNodeKey.ErrorNotFound, + componentType: WidgetErrorComponent.lateLoadKey, + properties: { + image: "no-data-to-show", + title: $localize\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\`404 - Not Found\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\`, + description: $localize\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\`The requested resource could not be found.\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\`, + } as IWidgetErrorDisplayProperties, + }, +};\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\`; + public widgetBodyContentNodesSignature = \\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\` +/** + * Retrieves an index of the basic widget body content nodes including fallback nodes + * + * @param mainContentNodeKey The key corresponding to the main body content node + * @param fallbackAdapterId The id for the adapter responsible for activating fallback content in case of an error + * @param fallbackMap A map of node keys to fallback content definitions + * @param fallbackNodes An index of fallback content definitions + * + * @returns An index of component configurations + */ +export function widgetBodyContentNodes( + mainContentNodeKey: string, + fallbackAdapterId = NOVA_STATUS_CONTENT_FALLBACK_ADAPTER, + fallbackMap: Record = ERROR_FALLBACK_MAP, + fallbackNodes: Record = ERROR_NODES +): Record { ... } +\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\`; +} +\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\`, + "tutorials/widget-error-handling/widget-error-handling.component.ts": \\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\`// © 2022 SolarWinds Worldwide, LLC. All rights reserved. +// +// Permission is hereby granted, free of charge, to any person obtaining a copy +// of this software and associated documentation files (the "Software"), to +// deal in the Software without restriction, including without limitation the +// rights to use, copy, modify, merge, publish, distribute, sublicense, and/or +// sell copies of the Software, and to permit persons to whom the Software is +// furnished to do so, subject to the following conditions: +// +// The above copyright notice and this permission notice shall be included in +// all copies or substantial portions of the Software. +// +// THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR +// IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, +// FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE +// AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER +// LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, +// OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN +// THE SOFTWARE. + +import { HttpClient, HttpErrorResponse } from "@angular/common/http"; +import { + ChangeDetectorRef, + Component, + Injectable, + OnDestroy, + OnInit, +} from "@angular/core"; +import { GridsterConfig, GridsterItem } from "angular-gridster2"; +import { BehaviorSubject } from "rxjs"; +import { finalize } from "rxjs/operators"; + +import { DataSourceService, IFilteringOutputs } from "@nova-ui/bits"; +import { + DATA_SOURCE, + DEFAULT_PIZZAGNA_ROOT, + HttpStatusCode, + IDashboard, + IKpiData, + IProviderConfiguration, + IRefresherProperties, + IWidget, + IWidgets, + KpiComponent, + NOVA_KPI_DATASOURCE_ADAPTER, + PizzagnaLayer, + ProviderRegistryService, + WellKnownPathKey, + WellKnownProviders, + WidgetTypesService, +} from "@nova-ui/dashboards"; + +/** + * A simple KPI data source to retrieve the average rating of Harry Potter and the Sorcerer's Stone (book) via googleapis + */ +@Injectable() +export class AverageRatingKpiDataSource + extends DataSourceService + implements OnDestroy +{ + // This is the ID we'll use to identify the provider + public static providerId = "AverageRatingKpiDataSource"; + + // Use this subject to communicate the data source's busy state + public busy = new BehaviorSubject(false); + + constructor(private http: HttpClient) { + super(); + } + + // In this example, getFilteredData is invoked every 10 minutes (Take a look at the refresher + // provider definition in the widget configuration below to see how the interval is set) + public async getFilteredData(): Promise { + this.busy.next(true); + return new Promise((resolve) => { + // *** Make a resource request to an external API (if needed) + this.http + .get("https://www.googleapis.com/books/v1/volumes/5MQFrgEACAAJ") + .pipe(finalize(() => this.busy.next(false))) + .subscribe({ + next: (data: any) => { + resolve({ + result: { + value: data.volumeInfo.averageRating, + }, + }); + }, + error: (error: HttpErrorResponse) => { + resolve({ + result: null, + error: { + type: error.status, + }, + }); + }, + }); + }); + } + + public ngOnDestroy(): void { + this.outputsSubject.complete(); + } +} + +/** + * A simple KPI data source to retrieve the average rating of Harry Potter and the Sorcerer's Stone (book) via googleapis + */ +@Injectable() +export class ErrorUnknownDataSource + extends DataSourceService + implements OnDestroy +{ + // This is the ID we'll use to identify the provider + public static providerId = "ErrorUnknownDataSource"; + + // Use this subject to communicate the data source's busy state + public busy = new BehaviorSubject(false); + + // In this example, getFilteredData is invoked every 10 minutes (Take a look at the refresher + // provider definition in the widget configuration below to see how the interval is set) + public async getFilteredData(): Promise { + this.busy.next(true); + const mockError = { + result: null, + error: { type: HttpStatusCode.Unknown }, + }; + this.busy.next(false); + return mockError; + } + + public ngOnDestroy(): void { + this.outputsSubject.complete(); + } +} + +/** + * A simple KPI data source to retrieve the ratings count of Harry Potter and the Sorcerer's Stone (book) via googleapis + */ +@Injectable() +export class ErrorForbiddenDataSource + extends DataSourceService + implements OnDestroy +{ + public static providerId = "ErrorForbiddenDataSource"; + + // Use this subject to communicate the data source's busy state + public busy = new BehaviorSubject(false); + + constructor(private http: HttpClient) { + super(); + } + + public async getFilteredData(): Promise { + this.busy.next(true); + // generate a 403 + return new Promise((resolve) => { + this.http + .get( + "http://www.mocky.io/v2/5ecc724a3200000f0023614a?mocky-delay=4000ms" + ) + .pipe(finalize(() => this.busy.next(false))) + .subscribe({ + error: (error: HttpErrorResponse) => { + resolve({ + result: null, + error: { + type: error.status, + }, + }); + }, + }); + }); + } + + public ngOnDestroy(): void { + this.outputsSubject.complete(); + } +} + +/** + * A simple KPI data source to retrieve the ratings count of Harry Potter and the Sorcerer's Stone (book) via googleapis + */ +@Injectable() +export class ErrorNotFoundDataSource + extends DataSourceService + implements OnDestroy +{ + public static providerId = "ErrorNotFoundDataSource"; + + // Use this subject to communicate the data source's busy state + public busy = new BehaviorSubject(false); + + constructor(private http: HttpClient) { + super(); + } + + public async getFilteredData(): Promise { + this.busy.next(true); + // generate a 404 + return new Promise((resolve) => { + this.http + .get( + "http://www.mocky.io/v2/5ec6bfd93200007800d75100?mocky-delay=1000ms" + ) + .pipe(finalize(() => this.busy.next(false))) + .subscribe({ + error: (error: HttpErrorResponse) => { + resolve({ + result: null, + error: { + type: error.status, + }, + }); + }, + }); + }); + } + + public ngOnDestroy(): void { + this.outputsSubject.complete(); + } +} + +/** + * A component that instantiates the dashboard + */ +@Component({ + selector: "widget-error-handling", + templateUrl: "./widget-error-handling.component.html", + styleUrls: ["./widget-error-handling.component.less"], + standalone: false, +}) +export class WidgetErrorHandlingComponent implements OnInit { + // This variable will hold all the data needed to define the layout and behavior of the widgets. + // Pass this to the dashboard component's dashboard input in the template. + public dashboard: IDashboard | undefined; + + // Angular gridster requires a configuration object even if it's empty. + // Pass this to the dashboard component's gridsterConfig input in the template. + public gridsterConfig: GridsterConfig = {}; + + // Boolean which dashboard takes in as an input if its true it allows you to move widgets around. + public editMode: boolean = false; + + constructor( + // WidgetTypesService provides the widget's necessary structure information + private widgetTypesService: WidgetTypesService, + + // In general, the ProviderRegistryService is used for making entities available for injection into dynamically loaded components. + private providerRegistry: ProviderRegistryService, + private changeDetectorRef: ChangeDetectorRef + ) {} + + public ngOnInit(): void { + // Grab the widget's default template which will be needed as a parameter for setNode. + const widgetTemplate = this.widgetTypesService.getWidgetType("kpi", 1); + // Register our data sources as dropdown options in the widget editor/configurator + // Note: This could also be done in the parent module's constructor so that + // multiple dashboards could have access to the same widget template modification. + this.widgetTypesService.setNode( + // This is the template we grabbed above with getWidgetType + widgetTemplate, + // We are setting the editor/configurator part of the widget template + "configurator", + // This indicates which node you are changing and we want to change + // the data source providers available for selection in the editor. + WellKnownPathKey.DataSourceProviders, + // We are setting the data sources available for selection in the editor + [ + ErrorUnknownDataSource.providerId, + ErrorForbiddenDataSource.providerId, + ErrorNotFoundDataSource.providerId, + AverageRatingKpiDataSource.providerId, + ] + ); + + // Register the data sources available for injection into the KPI tiles. + // Note: Each tile of a KPI widget is assigned its own instance of a data source + this.providerRegistry.setProviders({ + [ErrorUnknownDataSource.providerId]: { + provide: DATA_SOURCE, + useClass: ErrorUnknownDataSource, + // Any dependencies that need to be injected into the provider must be listed here + deps: [HttpClient], + }, + [ErrorForbiddenDataSource.providerId]: { + provide: DATA_SOURCE, + useClass: ErrorForbiddenDataSource, + deps: [HttpClient], + }, + [ErrorNotFoundDataSource.providerId]: { + provide: DATA_SOURCE, + useClass: ErrorNotFoundDataSource, + deps: [HttpClient], + }, + [AverageRatingKpiDataSource.providerId]: { + provide: DATA_SOURCE, + useClass: AverageRatingKpiDataSource, + // Any dependencies that need to be injected into the provider must be listed here + deps: [HttpClient], + }, + }); + + this.initializeDashboard(); + } + + /** Used for restoring widgets state */ + public reInitializeDashboard(): void { + // destroys the components and their providers so the dashboard can re init data + this.dashboard = undefined; + this.changeDetectorRef.detectChanges(); + + this.initializeDashboard(); + } + + public initializeDashboard(): void { + // We're using a static configuration object for this example (see widgetConfig at the bottom of the file), + // but this is where the widget's configuration could potentially be populated from a database + const kpiWidget = widgetConfig; + const widgetIndex: IWidgets = { + // Complete the KPI widget with information coming from its type definition + [kpiWidget.id]: + this.widgetTypesService.mergeWithWidgetType(kpiWidget), + }; + + // Setting the widget dimensions and position (this is for gridster) + const positions: Record = { + [kpiWidget.id]: { + cols: 4, + rows: 6, + y: 0, + x: 0, + }, + }; + + // Finally, assigning the variables we created above to the dashboard + this.dashboard = { + positions, + widgets: widgetIndex, + }; + } +} + +const widgetConfig: IWidget = { + id: "widget1", + type: "kpi", + pizzagna: { + [PizzagnaLayer.Configuration]: { + [DEFAULT_PIZZAGNA_ROOT]: { + providers: { + [WellKnownProviders.Refresher]: { + properties: { + // Configuring the refresher interval so that our data source is invoked every ten minutes + interval: 60 * 10, + enabled: true, + } as IRefresherProperties, + } as Partial, + }, + }, + header: { + properties: { + title: "Harry Potter and the Sorcerer's Stone", + subtitle: "By J. K. Rowling", + }, + }, + tiles: { + properties: { + nodes: ["kpi1"], + }, + }, + kpi1: { + id: "kpi1", + componentType: KpiComponent.lateLoadKey, + properties: { + widgetData: { + units: "out of 5 Stars", + label: "Average Rating", + }, + }, + providers: { + [WellKnownProviders.DataSource]: { + // Setting the data source providerId for the tile with id "kpi1" + providerId: ErrorUnknownDataSource.providerId, + } as IProviderConfiguration, + [WellKnownProviders.Adapter]: { + providerId: NOVA_KPI_DATASOURCE_ADAPTER, + properties: { + componentId: "kpi1", + propertyPath: "widgetData", + }, + } as IProviderConfiguration, + }, + }, + }, + }, +}; +\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\`, + "tutorials/widget-error-handling/widget-error-handling.module.ts": \\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\`// © 2022 SolarWinds Worldwide, LLC. All rights reserved. +// +// Permission is hereby granted, free of charge, to any person obtaining a copy +// of this software and associated documentation files (the "Software"), to +// deal in the Software without restriction, including without limitation the +// rights to use, copy, modify, merge, publish, distribute, sublicense, and/or +// sell copies of the Software, and to permit persons to whom the Software is +// furnished to do so, subject to the following conditions: +// +// The above copyright notice and this permission notice shall be included in +// all copies or substantial portions of the Software. +// +// THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR +// IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, +// FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE +// AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER +// LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, +// OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN +// THE SOFTWARE. + +import { CommonModule } from "@angular/common"; +import { HttpClientModule } from "@angular/common/http"; +import { NgModule } from "@angular/core"; +import { ReactiveFormsModule } from "@angular/forms"; +import { RouterModule } from "@angular/router"; + +import { + NuiButtonModule, + NuiDocsModule, + NuiFormFieldModule, + NuiIconModule, + NuiMessageModule, + NuiSwitchModule, + NuiTextboxModule, + DEMO_PATH_TOKEN, +} from "@nova-ui/bits"; +import { + NuiDashboardConfiguratorModule, + NuiDashboardsModule, +} from "@nova-ui/dashboards"; + +import { WidgetErrorHandlingDocsComponent } from "./widget-error-handling-docs.component"; +import { WidgetErrorHandlingComponent } from "./widget-error-handling.component"; +import { getDemoFiles } from "../../../../demo-files-factory"; + +const routes = [ + { + path: "", + component: WidgetErrorHandlingDocsComponent, + data: { + srlc: { + hideIndicator: true, + }, + showThemeSwitcher: true, + }, + }, + { + path: "example", + component: WidgetErrorHandlingComponent, + data: { + srlc: { + hideIndicator: true, + }, + }, + }, +]; + +@NgModule({ + imports: [ + CommonModule, + ReactiveFormsModule, + HttpClientModule, + NuiButtonModule, + NuiDashboardsModule, + NuiDashboardConfiguratorModule, + NuiDocsModule, + NuiFormFieldModule, + NuiIconModule, + NuiMessageModule, + NuiIconModule, + NuiTextboxModule, + NuiIconModule, + NuiSwitchModule, + RouterModule.forChild(routes), + ], + declarations: [ + WidgetErrorHandlingDocsComponent, + WidgetErrorHandlingComponent, + ], + providers: [ + { + provide: DEMO_PATH_TOKEN, + useValue: getDemoFiles("widget-error-handling"), + }, + ], +}) +export default class WidgetErrorHandlingModule {} +\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\`, + "types.ts": \\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\`// © 2022 SolarWinds Worldwide, LLC. All rights reserved. +// +// Permission is hereby granted, free of charge, to any person obtaining a copy +// of this software and associated documentation files (the "Software"), to +// deal in the Software without restriction, including without limitation the +// rights to use, copy, modify, merge, publish, distribute, sublicense, and/or +// sell copies of the Software, and to permit persons to whom the Software is +// furnished to do so, subject to the following conditions: +// +// The above copyright notice and this permission notice shall be included in +// all copies or substantial portions of the Software. +// +// THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR +// IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, +// FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE +// AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER +// LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, +// OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN +// THE SOFTWARE. + +export enum APOLLO_API_NAMESPACE { + COUNTRIES = "countries", +} +\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\`, + "widget-types/drilldown/drilldown-multi-request-widget/drilldown-multi-request-widget-example.component.ts": \\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\`// © 2022 SolarWinds Worldwide, LLC. All rights reserved. +// +// Permission is hereby granted, free of charge, to any person obtaining a copy +// of this software and associated documentation files (the "Software"), to +// deal in the Software without restriction, including without limitation the +// rights to use, copy, modify, merge, publish, distribute, sublicense, and/or +// sell copies of the Software, and to permit persons to whom the Software is +// furnished to do so, subject to the following conditions: +// +// The above copyright notice and this permission notice shall be included in +// all copies or substantial portions of the Software. +// +// THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR +// IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, +// FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE +// AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER +// LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, +// OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN +// THE SOFTWARE. + +import { HttpClient } from "@angular/common/http"; +import { + ChangeDetectorRef, + Component, + Injectable, + OnDestroy, + OnInit, +} from "@angular/core"; +import { GridsterConfig, GridsterItem } from "angular-gridster2"; +import { Apollo, gql } from "apollo-angular"; +import { BehaviorSubject, Observable, of, Subject } from "rxjs"; +// eslint-disable-next-line import/no-deprecated +import { finalize, map, switchMap, tap } from "rxjs/operators"; + +import { + DataSourceService, + IconStatus, + IDataField, + IFilters, + INovaFilters, +} from "@nova-ui/bits"; +import { + DATA_SOURCE, + DEFAULT_PIZZAGNA_ROOT, + IDashboard, + IDrilldownComponentsConfiguration, + IListWidgetConfiguration, + IProviderConfiguration, + IWidget, + IWidgets, + ListGroupItemComponent, + ListLeafItemComponent, + NOVA_DRILLDOWN_DATASOURCE_ADAPTER, + PizzagnaLayer, + ProviderRegistryService, + WellKnownPathKey, + WellKnownProviders, + WidgetTypesService, +} from "@nova-ui/dashboards"; + +import { APOLLO_API_NAMESPACE } from "../../../types"; + +/** + * A simple KPI data source to retrieve the average rating of Harry Potter and the Sorcerer's Stone (book) via googleapis + */ +@Injectable() +export class DrilldownDataSource + extends DataSourceService + implements OnDestroy +{ + // This is the ID we'll use to identify the provider + public static providerId = "DrilldownDataSource"; + + // Use this subject to communicate the data source's busy state + public busy = new BehaviorSubject(false); + + public dataFields: Partial[] = [ + { id: "Region", label: "Region name" }, + { id: "Subregion", label: "Subregion name" }, + ]; + + private drillState: string[] = []; + private groupBy: string[]; + private cache: any; + private lastDrillState: string[] = []; + private leafGroup: string = "country"; + private applyFilters$ = new Subject(); + + constructor(private http: HttpClient, private apollo: Apollo) { + super(); + + // TODO: remove Partial in vNext after marking dataType field as optional - NUI-5838 + ( + this.dataFieldsConfig.dataFields$ as BehaviorSubject< + Partial[] + > + ).next(this.dataFields); + + this.applyFilters$ + // eslint-disable-next-line import/no-deprecated + .pipe(switchMap((filters) => this.getData(filters))) + .subscribe(async (res) => { + this.outputsSubject.next(await this.getFilteredData(res)); + }); + } + + private groupedDataHistory: any[] = []; + + // In this example, getFilteredData is invoked every 10 minutes (Take a look at the refresher + // provider definition in the widget configuration below to see how the interval is set) + public async getFilteredData(data: any): Promise { + return of(data) + .pipe( + map((entries) => { + if (this.isDrillDown()) { + const activeDrillLvl = this.drillState.length; + const group = this.groupBy[activeDrillLvl]; + const lastGroupedValue = + this.getTransformedDataForGroup( + entries, + group, + getLast(this.drillState) + ); + + this.groupedDataHistory.push(lastGroupedValue); + + return lastGroupedValue; + } + + const mapIconsToEntries = entries.map((item: any) => ({ + ...item, + icon: "virtual-host", + icon_status: IconStatus.Up, + })); + this.groupedDataHistory.push(mapIconsToEntries); + const widgetInput = this.getOutput(entries); + + return widgetInput; + }) + ) + .toPromise(); + } + + public ngOnDestroy(): void { + this.outputsSubject.complete(); + } + + // redefine parent method + public async applyFilters(): Promise { + this.applyFilters$.next(this.getFilters()); + } + + private getQuery(key: string, value: string) { + const groupToRequestMap: Record = { + Region: \\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\`{ Region { name } }\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\`, + Subregion: \\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\`{ Subregion(filter: { region: { name: "\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\${value}" } } ) { name } }\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\`, + Country: \\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\`{ Country(filter: { subregion: { name: "\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\${value}" } } ) { name capital } }\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\`, + }; + + return gql\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\` + \\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\${groupToRequestMap[key]} + \\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\`; + } + + private getData(filters: INovaFilters): Observable { + this.drillState = filters.drillstate?.value; + this.groupBy = filters.group?.value; + const group = this.groupBy[this.drillState.length]; + const isDrillUp = this.drillState.length < this.lastDrillState.length; + + this.lastDrillState = [...this.drillState]; + + if (!this.drillState.length) { + this.groupedDataHistory.length = 0; + } + + this.busy.next(true); + + if (this.cache && (isDrillUp || this.isHome())) { + return of(this.cache).pipe( + map((data) => data.data[group]), + finalize(() => this.busy.next(false)) + ); + } else { + return this.apollo + .use(APOLLO_API_NAMESPACE.COUNTRIES) + .query({ + query: this.getQuery( + group || this.leafGroup, + getLast(this.drillState) + ), + }) + .pipe( + tap( + (data) => + (this.cache = { + data: { ...this.cache?.data, ...data?.data }, + }) + ), + map((data) => data.data[group || this.leafGroup]), + finalize(() => this.busy.next(false)) + ); + } + } + + private getTransformedDataForGroup( + data: any, + group: string, + drillStateValue: string + ) { + const fallback: string = \\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\`No \\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\${group} for \\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\${drillStateValue}\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\`; + const dataArr = Object.values(data).map((val: any) => ({ + id: val.name || fallback, + label: val.name || fallback, + statuses: [ + { key: "state_ok", value: val.name?.length }, + { + key: "status_unreachable", + value: generateNumberUpTo(100000), + }, + { key: "status_warning", value: generateNumberUpTo(10000) }, + { key: "status_unknown", value: generateNumberUpTo(1000) }, + ], + })); + + return dataArr; + } + + private isHome(): boolean { + return this.drillState.length === 0; + } + + private isDrillDown(): boolean { + return this.drillState.length !== this.groupBy.length; + } + + private getOutput(data: any) { + if (this.isHome()) { + this.groupedDataHistory.length = 0; + } + + const lastHistoryValue = getLast(this.groupedDataHistory); + + if (!lastHistoryValue) { + return data; + } + + return lastHistoryValue[getLast(this.drillState)] || lastHistoryValue; + } +} + +@Component({ + selector: "drilldown-multi-request-widget-example", + templateUrl: "./drilldown-multi-request-widget-example.component.html", + styleUrls: ["./drilldown-multi-request-widget-example.component.less"], + standalone: false, +}) +export class DrilldownMultiRequestWidgetExampleComponent implements OnInit { + // This variable will hold all the data needed to define the layout and behavior of the widgets. + // Pass this to the dashboard component's dashboard input in the template. + public dashboard: IDashboard | undefined; + + // Angular gridster requires a configuration object even if it's empty. + // Pass this to the dashboard component's gridsterConfig input in the template. + public gridsterConfig: GridsterConfig = {}; + + // Boolean passed as an input to the dashboard. When true, widgets can be moved, resized, removed, or edited + public editMode: boolean = false; + + constructor( + // WidgetTypesService provides the widget's necessary structure information + private widgetTypesService: WidgetTypesService, + private providerRegistry: ProviderRegistryService, + private changeDetectorRef: ChangeDetectorRef + ) {} + + public ngOnInit(): void { + // this.prepareNovaDashboards(); + this.initializeDashboard(); + const widgetTemplate = this.widgetTypesService.getWidgetType( + "drilldown", + 1 + ); + this.widgetTypesService.setNode( + widgetTemplate, + "configurator", + WellKnownPathKey.DataSourceProviders, + [DrilldownDataSource.providerId] + ); + + // Registering the data source for injection into the KPI tile. + // Note: Each tile of a KPI widget is assigned its own instance of the data source + this.providerRegistry.setProviders({ + [DrilldownDataSource.providerId]: { + provide: DATA_SOURCE, + useClass: DrilldownDataSource, + // Any dependencies that need to be injected into the provider must be listed here + deps: [HttpClient, Apollo], + }, + }); + } + + /** Used for restoring widgets state */ + public reInitializeDashboard(): void { + // destroys the components and their providers so the dashboard can re init data + this.dashboard = undefined; + this.changeDetectorRef.detectChanges(); + + this.initializeDashboard(); + } + + public initializeDashboard(): void { + // We're using a static configuration object for this example, but this is where + // the widget's configuration could potentially be populated from a database + const drilldownWidget = widgetConfig; + const widgets: IWidgets = { + // Complete the widget with information coming from its type definition + [drilldownWidget.id]: + this.widgetTypesService.mergeWithWidgetType(drilldownWidget), + }; + + // Setting the widget dimensions and position (this is for gridster) + const positions: Record = { + [drilldownWidget.id]: { + cols: 10, + rows: 10, + y: 0, + x: 0, + }, + }; + + // Finally, assigning the variables we created above to the dashboard + this.dashboard = { positions, widgets }; + } +} + +const widgetConfig: IWidget = { + id: "drilldown", + type: "drilldown", + pizzagna: { + [PizzagnaLayer.Configuration]: { + header: { + properties: { + title: "Drilldown Widget", + subtitle: "Countries BY continent THEN currency", + }, + }, + [DEFAULT_PIZZAGNA_ROOT]: { + providers: { + [WellKnownProviders.DataSource]: { + // Setting the data source providerId for the tile with id "kpi1" + providerId: DrilldownDataSource.providerId, + properties: {}, + } as IProviderConfiguration, + }, + }, + listWidget: { + providers: { + [WellKnownProviders.Adapter]: { + providerId: NOVA_DRILLDOWN_DATASOURCE_ADAPTER, + properties: { + // widget + navigationBarId: "navigationBar", + componentId: "listWidget", + dataPath: "data", + + // adapter props + drillstate: [], + groups: ["Region", "Subregion"], + groupBy: ["Region", "Subregion"], + + // components + componentsConfig: { + group: { + componentType: + ListGroupItemComponent.lateLoadKey, + properties: { + dataFieldIds: { + id: "id", + label: "label", + statuses: "statuses", + }, + }, + itemProperties: { + canNavigate: true, + }, + }, + leaf: { + componentType: + ListLeafItemComponent.lateLoadKey, + properties: { + dataFieldIds: { + icon: "icon", + status: "icon_status", + detailedUrl: "capital", + label: "name", + }, + }, + itemProperties: { + canNavigate: false, + }, + }, + } as IDrilldownComponentsConfiguration, + }, + }, + }, + properties: { + configuration: { + // FORMAT: + // componentType: ListLeafItemComponent.lateLoadKey, + // properties: { + // dataFieldIds: { + // icon: "", + // status: "code", + // detailedUrl: "capital", + // label: "name", + // }, + // }, + // + } as IListWidgetConfiguration, + }, + }, + }, + }, +}; + +const getLast = (arr: any[]) => arr[arr.length - 1]; + +const generateNumberUpTo = (upperLimit: number): number => + Math.floor(Math.random() * upperLimit + 1); +\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\`, + "widget-types/drilldown/drilldown-widget/data-mock.ts": \\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\`// © 2022 SolarWinds Worldwide, LLC. All rights reserved. +// +// Permission is hereby granted, free of charge, to any person obtaining a copy +// of this software and associated documentation files (the "Software"), to +// deal in the Software without restriction, including without limitation the +// rights to use, copy, modify, merge, publish, distribute, sublicense, and/or +// sell copies of the Software, and to permit persons to whom the Software is +// furnished to do so, subject to the following conditions: +// +// The above copyright notice and this permission notice shall be included in +// all copies or substantial portions of the Software. +// +// THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR +// IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, +// FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE +// AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER +// LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, +// OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN +// THE SOFTWARE. + +import { IconStatus } from "@nova-ui/bits"; + +export const GRAPH_DATA_MOCK = { + data: { + countries: [ + { + name: "Andorra", + code: "AD", + icon: "virtual-host", + icon_status: IconStatus.Up, + capital: "Andorra la Vella", + continent: { + name: "Europe", + }, + currency: "EUR", + languages: [ + { + name: "Catalan", + }, + ], + url: "https://en.wikipedia.org/wiki/Andorra", + }, + { + name: "United Arab Emirates", + code: "AE", + icon: "virtual-host", + icon_status: IconStatus.Up, + capital: "Abu Dhabi", + continent: { + name: "Asia", + }, + currency: "AED", + languages: [ + { + name: "Arabic", + }, + ], + url: "https://en.wikipedia.org/wiki/United_Arab_Emirates", + }, + { + name: "Afghanistan", + code: "AF", + icon: "virtual-host", + icon_status: IconStatus.Up, + capital: "Kabul", + continent: { + name: "Asia", + }, + currency: "AFN", + languages: [ + { + name: "Pashto", + }, + { + name: "Uzbek", + }, + { + name: "Turkmen", + }, + ], + url: "https://en.wikipedia.org/wiki/Afghanistan", + }, + { + name: "Antigua and Barbuda", + code: "AG", + icon: "virtual-host", + icon_status: IconStatus.Up, + capital: "Saint John's", + continent: { + name: "North America", + }, + currency: "XCD", + languages: [ + { + name: "English", + }, + ], + url: "https://en.wikipedia.org/wiki/Antigua_and_Barbuda", + }, + { + name: "Anguilla", + code: "AI", + icon: "virtual-host", + icon_status: IconStatus.Up, + capital: "The Valley", + continent: { + name: "North America", + }, + currency: "XCD", + languages: [ + { + name: "English", + }, + ], + url: "https://en.wikipedia.org/wiki/Anguilla", + }, + { + name: "Albania", + code: "AL", + icon: "virtual-host", + icon_status: IconStatus.Up, + capital: "Tirana", + continent: { + name: "Europe", + }, + currency: "ALL", + languages: [ + { + name: "Albanian", + }, + ], + url: "https://en.wikipedia.org/wiki/Albania", + }, + { + name: "Armenia", + code: "AM", + icon: "virtual-host", + icon_status: IconStatus.Up, + capital: "Yerevan", + continent: { + name: "Asia", + }, + currency: "AMD", + languages: [ + { + name: "Armenian", + }, + { + name: "Russian", + }, + ], + url: "https://en.wikipedia.org/wiki/Armenia", + }, + { + name: "Angola", + code: "AO", + icon: "virtual-host", + icon_status: IconStatus.Up, + capital: "Luanda", + continent: { + name: "Africa", + }, + currency: "AOA", + languages: [ + { + name: "Portuguese", + }, + ], + }, + { + name: "Antarctica", + code: "AQ", + icon: "virtual-host", + icon_status: IconStatus.Up, + capital: null, + continent: { + name: "Antarctica", + }, + currency: null, + languages: [], + }, + { + name: "Argentina", + code: "AR", + icon: "virtual-host", + icon_status: IconStatus.Up, + capital: "Buenos Aires", + continent: { + name: "South America", + }, + currency: "ARS", + languages: [ + { + name: "Spanish", + }, + { + name: "Guarani", + }, + ], + }, + { + name: "American Samoa", + code: "AS", + icon: "virtual-host", + icon_status: IconStatus.Up, + capital: "Pago Pago", + continent: { + name: "Oceania", + }, + currency: "USD", + languages: [ + { + name: "English", + }, + { + name: "Samoan", + }, + ], + }, + { + name: "Austria", + code: "AT", + icon: "virtual-host", + icon_status: IconStatus.Up, + capital: "Vienna", + continent: { + name: "Europe", + }, + currency: "EUR", + languages: [ + { + name: "German", + }, + ], + }, + { + name: "Australia", + code: "AU", + icon: "virtual-host", + icon_status: IconStatus.Up, + capital: "Canberra", + continent: { + name: "Oceania", + }, + currency: "AUD", + languages: [ + { + name: "English", + }, + ], + }, + { + name: "Aruba", + code: "AW", + icon: "virtual-host", + icon_status: IconStatus.Up, + capital: "Oranjestad", + continent: { + name: "North America", + }, + currency: "AWG", + languages: [ + { + name: "Dutch", + }, + { + name: "Panjabi / Punjabi", + }, + ], + }, + { + name: "Åland", + code: "AX", + icon: "virtual-host", + icon_status: IconStatus.Up, + capital: "Mariehamn", + continent: { + name: "Europe", + }, + currency: "EUR", + languages: [ + { + name: "Swedish", + }, + ], + }, + { + name: "Azerbaijan", + code: "AZ", + icon: "virtual-host", + icon_status: IconStatus.Up, + capital: "Baku", + continent: { + name: "Asia", + }, + currency: "AZN", + languages: [ + { + name: "Azerbaijani", + }, + ], + }, + { + name: "Bosnia and Herzegovina", + code: "BA", + icon: "virtual-host", + icon_status: IconStatus.Up, + capital: "Sarajevo", + continent: { + name: "Europe", + }, + currency: "BAM", + languages: [ + { + name: "Bosnian", + }, + { + name: "Croatian", + }, + { + name: "Serbian", + }, + ], + }, + { + name: "Barbados", + code: "BB", + icon: "virtual-host", + icon_status: IconStatus.Up, + capital: "Bridgetown", + continent: { + name: "North America", + }, + currency: "BBD", + languages: [ + { + name: "English", + }, + ], + }, + { + name: "Bangladesh", + code: "BD", + icon: "virtual-host", + icon_status: IconStatus.Up, + capital: "Dhaka", + continent: { + name: "Asia", + }, + currency: "BDT", + languages: [ + { + name: "Bengali", + }, + ], + }, + { + name: "Belgium", + code: "BE", + icon: "virtual-host", + icon_status: IconStatus.Up, + capital: "Brussels", + continent: { + name: "Europe", + }, + currency: "EUR", + languages: [ + { + name: "Dutch", + }, + { + name: "French", + }, + { + name: "German", + }, + ], + }, + { + name: "Burkina Faso", + code: "BF", + icon: "virtual-host", + icon_status: IconStatus.Up, + capital: "Ouagadougou", + continent: { + name: "Africa", + }, + currency: "XOF", + languages: [ + { + name: "French", + }, + { + name: "Peul", + }, + ], + }, + { + name: "Bulgaria", + code: "BG", + icon: "virtual-host", + icon_status: IconStatus.Up, + capital: "Sofia", + continent: { + name: "Europe", + }, + currency: "BGN", + languages: [ + { + name: "Bulgarian", + }, + ], + }, + { + name: "Bahrain", + code: "BH", + icon: "virtual-host", + icon_status: IconStatus.Up, + capital: "Manama", + continent: { + name: "Asia", + }, + currency: "BHD", + languages: [ + { + name: "Arabic", + }, + ], + }, + { + name: "Burundi", + code: "BI", + icon: "virtual-host", + icon_status: IconStatus.Up, + capital: "Bujumbura", + continent: { + name: "Africa", + }, + currency: "BIF", + languages: [ + { + name: "French", + }, + { + name: "Kirundi", + }, + ], + }, + { + name: "Benin", + code: "BJ", + icon: "virtual-host", + icon_status: IconStatus.Up, + capital: "Porto-Novo", + continent: { + name: "Africa", + }, + currency: "XOF", + languages: [ + { + name: "French", + }, + ], + }, + { + name: "Saint Barthélemy", + code: "BL", + icon: "virtual-host", + icon_status: IconStatus.Up, + capital: "Gustavia", + continent: { + name: "North America", + }, + currency: "EUR", + languages: [ + { + name: "French", + }, + ], + }, + { + name: "Bermuda", + code: "BM", + icon: "virtual-host", + icon_status: IconStatus.Up, + capital: "Hamilton", + continent: { + name: "North America", + }, + currency: "BMD", + languages: [ + { + name: "English", + }, + ], + }, + { + name: "Brunei", + code: "BN", + icon: "virtual-host", + icon_status: IconStatus.Up, + capital: "Bandar Seri Begawan", + continent: { + name: "Asia", + }, + currency: "BND", + languages: [ + { + name: "Malay", + }, + ], + }, + { + name: "Bolivia", + code: "BO", + icon: "virtual-host", + icon_status: IconStatus.Up, + capital: "Sucre", + continent: { + name: "South America", + }, + currency: "BOB,BOV", + languages: [ + { + name: "Spanish", + }, + { + name: "Aymara", + }, + { + name: "Quechua", + }, + ], + }, + { + name: "Bonaire", + code: "BQ", + icon: "virtual-host", + icon_status: IconStatus.Up, + capital: "Kralendijk", + continent: { + name: "North America", + }, + currency: "USD", + languages: [ + { + name: "Dutch", + }, + ], + }, + { + name: "Brazil", + code: "BR", + icon: "virtual-host", + icon_status: IconStatus.Up, + capital: "Brasília", + continent: { + name: "South America", + }, + currency: "BRL", + languages: [ + { + name: "Portuguese", + }, + ], + }, + { + name: "Bahamas", + code: "BS", + icon: "virtual-host", + icon_status: IconStatus.Up, + capital: "Nassau", + continent: { + name: "North America", + }, + currency: "BSD", + languages: [ + { + name: "English", + }, + ], + }, + { + name: "Bhutan", + code: "BT", + icon: "virtual-host", + icon_status: IconStatus.Up, + capital: "Thimphu", + continent: { + name: "Asia", + }, + currency: "BTN,INR", + languages: [ + { + name: "Dzongkha", + }, + ], + }, + { + name: "Bouvet Island", + code: "BV", + icon: "virtual-host", + icon_status: IconStatus.Up, + capital: null, + continent: { + name: "Antarctica", + }, + currency: "NOK", + languages: [ + { + name: "Norwegian", + }, + { + name: "Norwegian Bokmål", + }, + { + name: "Norwegian Nynorsk", + }, + ], + }, + { + name: "Botswana", + code: "BW", + icon: "virtual-host", + icon_status: IconStatus.Up, + capital: "Gaborone", + continent: { + name: "Africa", + }, + currency: "BWP", + languages: [ + { + name: "English", + }, + { + name: "Tswana", + }, + ], + }, + { + name: "Belarus", + code: "BY", + icon: "virtual-host", + icon_status: IconStatus.Up, + capital: "Minsk", + continent: { + name: "Europe", + }, + currency: "BYN", + languages: [ + { + name: "Belarusian", + }, + { + name: "Russian", + }, + ], + }, + { + name: "Belize", + code: "BZ", + icon: "virtual-host", + icon_status: IconStatus.Up, + capital: "Belmopan", + continent: { + name: "North America", + }, + currency: "BZD", + languages: [ + { + name: "English", + }, + { + name: "Spanish", + }, + ], + }, + { + name: "Canada", + code: "CA", + icon: "virtual-host", + icon_status: IconStatus.Up, + capital: "Ottawa", + continent: { + name: "North America", + }, + currency: "CAD", + languages: [ + { + name: "English", + }, + { + name: "French", + }, + ], + }, + { + name: "Cocos [Keeling] Islands", + code: "CC", + icon: "virtual-host", + icon_status: IconStatus.Up, + capital: "West Island", + continent: { + name: "Asia", + }, + currency: "AUD", + languages: [ + { + name: "English", + }, + ], + }, + { + name: "Democratic Republic of the Congo", + code: "CD", + icon: "virtual-host", + icon_status: IconStatus.Up, + capital: "Kinshasa", + continent: { + name: "Africa", + }, + currency: "CDF", + languages: [ + { + name: "French", + }, + { + name: "Lingala", + }, + { + name: "Kongo", + }, + { + name: "Swahili", + }, + { + name: "Luba-Katanga", + }, + ], + }, + { + name: "Central African Republic", + code: "CF", + icon: "virtual-host", + icon_status: IconStatus.Up, + capital: "Bangui", + continent: { + name: "Africa", + }, + currency: "XAF", + languages: [ + { + name: "French", + }, + { + name: "Sango", + }, + ], + }, + { + name: "Republic of the Congo", + code: "CG", + icon: "virtual-host", + icon_status: IconStatus.Up, + capital: "Brazzaville", + continent: { + name: "Africa", + }, + currency: "XAF", + languages: [ + { + name: "French", + }, + { + name: "Lingala", + }, + ], + }, + { + name: "Switzerland", + code: "CH", + icon: "virtual-host", + icon_status: IconStatus.Up, + capital: "Bern", + continent: { + name: "Europe", + }, + currency: "CHE,CHF,CHW", + languages: [ + { + name: "German", + }, + { + name: "French", + }, + { + name: "Italian", + }, + ], + }, + { + name: "Ivory Coast", + code: "CI", + icon: "virtual-host", + icon_status: IconStatus.Up, + capital: "Yamoussoukro", + continent: { + name: "Africa", + }, + currency: "XOF", + languages: [ + { + name: "French", + }, + ], + }, + { + name: "Cook Islands", + code: "CK", + icon: "virtual-host", + icon_status: IconStatus.Up, + capital: "Avarua", + continent: { + name: "Oceania", + }, + currency: "NZD", + languages: [ + { + name: "English", + }, + ], + }, + { + name: "Chile", + code: "CL", + icon: "virtual-host", + icon_status: IconStatus.Up, + capital: "Santiago", + continent: { + name: "South America", + }, + currency: "CLF,CLP", + languages: [ + { + name: "Spanish", + }, + ], + }, + { + name: "Cameroon", + code: "CM", + icon: "virtual-host", + icon_status: IconStatus.Up, + capital: "Yaoundé", + continent: { + name: "Africa", + }, + currency: "XAF", + languages: [ + { + name: "English", + }, + { + name: "French", + }, + ], + }, + { + name: "China", + code: "CN", + icon: "virtual-host", + icon_status: IconStatus.Up, + capital: "Beijing", + continent: { + name: "Asia", + }, + currency: "CNY", + languages: [ + { + name: "Chinese", + }, + ], + }, + { + name: "Colombia", + code: "CO", + icon: "virtual-host", + icon_status: IconStatus.Up, + capital: "Bogotá", + continent: { + name: "South America", + }, + currency: "COP", + languages: [ + { + name: "Spanish", + }, + ], + }, + { + name: "Costa Rica", + code: "CR", + icon: "virtual-host", + icon_status: IconStatus.Up, + capital: "San José", + continent: { + name: "North America", + }, + currency: "CRC", + languages: [ + { + name: "Spanish", + }, + ], + }, + { + name: "Cuba", + code: "CU", + icon: "virtual-host", + icon_status: IconStatus.Up, + capital: "Havana", + continent: { + name: "North America", + }, + currency: "CUC,CUP", + languages: [ + { + name: "Spanish", + }, + ], + }, + { + name: "Cape Verde", + code: "CV", + icon: "virtual-host", + icon_status: IconStatus.Up, + capital: "Praia", + continent: { + name: "Africa", + }, + currency: "CVE", + languages: [ + { + name: "Portuguese", + }, + ], + }, + { + name: "Curacao", + code: "CW", + icon: "virtual-host", + icon_status: IconStatus.Up, + capital: "Willemstad", + continent: { + name: "North America", + }, + currency: "ANG", + languages: [ + { + name: "Dutch", + }, + { + name: "Panjabi / Punjabi", + }, + { + name: "English", + }, + ], + }, + { + name: "Christmas Island", + code: "CX", + icon: "virtual-host", + icon_status: IconStatus.Up, + capital: "Flying Fish Cove", + continent: { + name: "Asia", + }, + currency: "AUD", + languages: [ + { + name: "English", + }, + ], + }, + { + name: "Cyprus", + code: "CY", + icon: "virtual-host", + icon_status: IconStatus.Up, + capital: "Nicosia", + continent: { + name: "Europe", + }, + currency: "EUR", + languages: [ + { + name: "Greek", + }, + { + name: "Turkish", + }, + { + name: "Armenian", + }, + ], + }, + { + name: "Czech Republic", + code: "CZ", + icon: "virtual-host", + icon_status: IconStatus.Up, + capital: "Prague", + continent: { + name: "Europe", + }, + currency: "CZK", + languages: [ + { + name: "Czech", + }, + { + name: "Slovak", + }, + ], + }, + { + name: "Germany", + code: "DE", + icon: "virtual-host", + icon_status: IconStatus.Up, + capital: "Berlin", + continent: { + name: "Europe", + }, + currency: "EUR", + languages: [ + { + name: "German", + }, + ], + }, + { + name: "Djibouti", + code: "DJ", + icon: "virtual-host", + icon_status: IconStatus.Up, + capital: "Djibouti", + continent: { + name: "Africa", + }, + currency: "DJF", + languages: [ + { + name: "French", + }, + { + name: "Arabic", + }, + ], + }, + { + name: "Denmark", + code: "DK", + icon: "virtual-host", + icon_status: IconStatus.Up, + capital: "Copenhagen", + continent: { + name: "Europe", + }, + currency: "DKK", + languages: [ + { + name: "Danish", + }, + ], + }, + { + name: "Dominica", + code: "DM", + icon: "virtual-host", + icon_status: IconStatus.Up, + capital: "Roseau", + continent: { + name: "North America", + }, + currency: "XCD", + languages: [ + { + name: "English", + }, + ], + }, + { + name: "Dominican Republic", + code: "DO", + icon: "virtual-host", + icon_status: IconStatus.Up, + capital: "Santo Domingo", + continent: { + name: "North America", + }, + currency: "DOP", + languages: [ + { + name: "Spanish", + }, + ], + }, + { + name: "Algeria", + code: "DZ", + icon: "virtual-host", + icon_status: IconStatus.Up, + capital: "Algiers", + continent: { + name: "Africa", + }, + currency: "DZD", + languages: [ + { + name: "Arabic", + }, + ], + }, + { + name: "Ecuador", + code: "EC", + icon: "virtual-host", + icon_status: IconStatus.Up, + capital: "Quito", + continent: { + name: "South America", + }, + currency: "USD", + languages: [ + { + name: "Spanish", + }, + ], + }, + { + name: "Estonia", + code: "EE", + icon: "virtual-host", + icon_status: IconStatus.Up, + capital: "Tallinn", + continent: { + name: "Europe", + }, + currency: "EUR", + languages: [ + { + name: "Estonian", + }, + ], + }, + { + name: "Egypt", + code: "EG", + icon: "virtual-host", + icon_status: IconStatus.Up, + capital: "Cairo", + continent: { + name: "Africa", + }, + currency: "EGP", + languages: [ + { + name: "Arabic", + }, + ], + }, + { + name: "Western Sahara", + code: "EH", + icon: "virtual-host", + icon_status: IconStatus.Up, + capital: "El Aaiún", + continent: { + name: "Africa", + }, + currency: "MAD,DZD,MRU", + languages: [ + { + name: "Spanish", + }, + ], + }, + { + name: "Eritrea", + code: "ER", + icon: "virtual-host", + icon_status: IconStatus.Up, + capital: "Asmara", + continent: { + name: "Africa", + }, + currency: "ERN", + languages: [ + { + name: "Tigrinya", + }, + { + name: "Arabic", + }, + { + name: "English", + }, + ], + }, + { + name: "Spain", + code: "ES", + icon: "virtual-host", + icon_status: IconStatus.Up, + capital: "Madrid", + continent: { + name: "Europe", + }, + currency: "EUR", + languages: [ + { + name: "Spanish", + }, + { + name: "Basque", + }, + { + name: "Catalan", + }, + { + name: "Galician", + }, + { + name: "Occitan", + }, + ], + }, + { + name: "Ethiopia", + code: "ET", + icon: "virtual-host", + icon_status: IconStatus.Up, + capital: "Addis Ababa", + continent: { + name: "Africa", + }, + currency: "ETB", + languages: [ + { + name: "Amharic", + }, + ], + }, + { + name: "Finland", + code: "FI", + icon: "virtual-host", + icon_status: IconStatus.Up, + capital: "Helsinki", + continent: { + name: "Europe", + }, + currency: "EUR", + languages: [ + { + name: "Finnish", + }, + { + name: "Swedish", + }, + ], + }, + { + name: "Fiji", + code: "FJ", + icon: "virtual-host", + icon_status: IconStatus.Up, + capital: "Suva", + continent: { + name: "Oceania", + }, + currency: "FJD", + languages: [ + { + name: "English", + }, + { + name: "Fijian", + }, + { + name: "Hindi", + }, + { + name: "Urdu", + }, + ], + }, + { + name: "Falkland Islands", + code: "FK", + icon: "virtual-host", + icon_status: IconStatus.Up, + capital: "Stanley", + continent: { + name: "South America", + }, + currency: "FKP", + languages: [ + { + name: "English", + }, + ], + }, + { + name: "Micronesia", + code: "FM", + icon: "virtual-host", + icon_status: IconStatus.Up, + capital: "Palikir", + continent: { + name: "Oceania", + }, + currency: "USD", + languages: [ + { + name: "English", + }, + ], + }, + { + name: "Faroe Islands", + code: "FO", + icon: "virtual-host", + icon_status: IconStatus.Up, + capital: "Tórshavn", + continent: { + name: "Europe", + }, + currency: "DKK", + languages: [ + { + name: "Faroese", + }, + ], + }, + { + name: "France", + code: "FR", + icon: "virtual-host", + icon_status: IconStatus.Up, + capital: "Paris", + continent: { + name: "Europe", + }, + currency: "EUR", + languages: [ + { + name: "French", + }, + ], + }, + { + name: "Gabon", + code: "GA", + icon: "virtual-host", + icon_status: IconStatus.Up, + capital: "Libreville", + continent: { + name: "Africa", + }, + currency: "XAF", + languages: [ + { + name: "French", + }, + ], + }, + { + name: "United Kingdom", + code: "GB", + icon: "virtual-host", + icon_status: IconStatus.Up, + capital: "London", + continent: { + name: "Europe", + }, + currency: "GBP", + languages: [ + { + name: "English", + }, + ], + }, + { + name: "Grenada", + code: "GD", + icon: "virtual-host", + icon_status: IconStatus.Up, + capital: "St. George's", + continent: { + name: "North America", + }, + currency: "XCD", + languages: [ + { + name: "English", + }, + ], + }, + { + name: "Georgia", + code: "GE", + icon: "virtual-host", + icon_status: IconStatus.Up, + capital: "Tbilisi", + continent: { + name: "Asia", + }, + currency: "GEL", + languages: [ + { + name: "Georgian", + }, + ], + }, + { + name: "French Guiana", + code: "GF", + icon: "virtual-host", + icon_status: IconStatus.Up, + capital: "Cayenne", + continent: { + name: "South America", + }, + currency: "EUR", + languages: [ + { + name: "French", + }, + ], + }, + { + name: "Guernsey", + code: "GG", + icon: "virtual-host", + icon_status: IconStatus.Up, + capital: "St. Peter Port", + continent: { + name: "Europe", + }, + currency: "GBP", + languages: [ + { + name: "English", + }, + { + name: "French", + }, + ], + }, + { + name: "Ghana", + code: "GH", + icon: "virtual-host", + icon_status: IconStatus.Up, + capital: "Accra", + continent: { + name: "Africa", + }, + currency: "GHS", + languages: [ + { + name: "English", + }, + ], + }, + { + name: "Gibraltar", + code: "GI", + icon: "virtual-host", + icon_status: IconStatus.Up, + capital: "Gibraltar", + continent: { + name: "Europe", + }, + currency: "GIP", + languages: [ + { + name: "English", + }, + ], + }, + { + name: "Greenland", + code: "GL", + icon: "virtual-host", + icon_status: IconStatus.Up, + capital: "Nuuk", + continent: { + name: "North America", + }, + currency: "DKK", + languages: [ + { + name: "Greenlandic", + }, + ], + }, + { + name: "Gambia", + code: "GM", + icon: "virtual-host", + icon_status: IconStatus.Up, + capital: "Banjul", + continent: { + name: "Africa", + }, + currency: "GMD", + languages: [ + { + name: "English", + }, + ], + }, + { + name: "Guinea", + code: "GN", + icon: "virtual-host", + icon_status: IconStatus.Up, + capital: "Conakry", + continent: { + name: "Africa", + }, + currency: "GNF", + languages: [ + { + name: "French", + }, + { + name: "Peul", + }, + ], + }, + { + name: "Guadeloupe", + code: "GP", + icon: "virtual-host", + icon_status: IconStatus.Up, + capital: "Basse-Terre", + continent: { + name: "North America", + }, + currency: "EUR", + languages: [ + { + name: "French", + }, + ], + }, + { + name: "Equatorial Guinea", + code: "GQ", + icon: "virtual-host", + icon_status: IconStatus.Up, + capital: "Malabo", + continent: { + name: "Africa", + }, + currency: "XAF", + languages: [ + { + name: "Spanish", + }, + { + name: "French", + }, + ], + }, + { + name: "Greece", + code: "GR", + icon: "virtual-host", + icon_status: IconStatus.Up, + capital: "Athens", + continent: { + name: "Europe", + }, + currency: "EUR", + languages: [ + { + name: "Greek", + }, + ], + }, + { + name: "South Georgia and the South Sandwich Islands", + code: "GS", + icon: "virtual-host", + icon_status: IconStatus.Up, + capital: "King Edward Point", + continent: { + name: "Antarctica", + }, + currency: "GBP", + languages: [ + { + name: "English", + }, + ], + }, + { + name: "Guatemala", + code: "GT", + icon: "virtual-host", + icon_status: IconStatus.Up, + capital: "Guatemala City", + continent: { + name: "North America", + }, + currency: "GTQ", + languages: [ + { + name: "Spanish", + }, + ], + }, + { + name: "Guam", + code: "GU", + icon: "virtual-host", + icon_status: IconStatus.Up, + capital: "Hagåtña", + continent: { + name: "Oceania", + }, + currency: "USD", + languages: [ + { + name: "English", + }, + { + name: "Chamorro", + }, + { + name: "Spanish", + }, + ], + }, + { + name: "Guinea-Bissau", + code: "GW", + icon: "virtual-host", + icon_status: IconStatus.Up, + capital: "Bissau", + continent: { + name: "Africa", + }, + currency: "XOF", + languages: [ + { + name: "Portuguese", + }, + ], + }, + { + name: "Guyana", + code: "GY", + icon: "virtual-host", + icon_status: IconStatus.Up, + capital: "Georgetown", + continent: { + name: "South America", + }, + currency: "GYD", + languages: [ + { + name: "English", + }, + ], + }, + { + name: "Hong Kong", + code: "HK", + icon: "virtual-host", + icon_status: IconStatus.Up, + capital: "City of Victoria", + continent: { + name: "Asia", + }, + currency: "HKD", + languages: [ + { + name: "Chinese", + }, + { + name: "English", + }, + ], + }, + { + name: "Heard Island and McDonald Islands", + code: "HM", + icon: "virtual-host", + icon_status: IconStatus.Up, + capital: null, + continent: { + name: "Antarctica", + }, + currency: "AUD", + languages: [ + { + name: "English", + }, + ], + }, + { + name: "Honduras", + code: "HN", + icon: "virtual-host", + icon_status: IconStatus.Up, + capital: "Tegucigalpa", + continent: { + name: "North America", + }, + currency: "HNL", + languages: [ + { + name: "Spanish", + }, + ], + }, + { + name: "Croatia", + code: "HR", + icon: "virtual-host", + icon_status: IconStatus.Up, + capital: "Zagreb", + continent: { + name: "Europe", + }, + currency: "HRK", + languages: [ + { + name: "Croatian", + }, + ], + }, + { + name: "Haiti", + code: "HT", + icon: "virtual-host", + icon_status: IconStatus.Up, + capital: "Port-au-Prince", + continent: { + name: "North America", + }, + currency: "HTG,USD", + languages: [ + { + name: "French", + }, + { + name: "Haitian", + }, + ], + }, + { + name: "Hungary", + code: "HU", + icon: "virtual-host", + icon_status: IconStatus.Up, + capital: "Budapest", + continent: { + name: "Europe", + }, + currency: "HUF", + languages: [ + { + name: "Hungarian", + }, + ], + }, + { + name: "Indonesia", + code: "ID", + icon: "virtual-host", + icon_status: IconStatus.Up, + capital: "Jakarta", + continent: { + name: "Asia", + }, + currency: "IDR", + languages: [ + { + name: "Indonesian", + }, + ], + }, + { + name: "Ireland", + code: "IE", + icon: "virtual-host", + icon_status: IconStatus.Up, + capital: "Dublin", + continent: { + name: "Europe", + }, + currency: "EUR", + languages: [ + { + name: "Irish", + }, + { + name: "English", + }, + ], + }, + { + name: "Israel", + code: "IL", + icon: "virtual-host", + icon_status: IconStatus.Up, + capital: "Jerusalem", + continent: { + name: "Asia", + }, + currency: "ILS", + languages: [ + { + name: "Hebrew", + }, + { + name: "Arabic", + }, + ], + }, + { + name: "Isle of Man", + code: "IM", + icon: "virtual-host", + icon_status: IconStatus.Up, + capital: "Douglas", + continent: { + name: "Europe", + }, + currency: "GBP", + languages: [ + { + name: "English", + }, + { + name: "Manx", + }, + ], + }, + { + name: "India", + code: "IN", + icon: "virtual-host", + icon_status: IconStatus.Up, + capital: "New Delhi", + continent: { + name: "Asia", + }, + currency: "INR", + languages: [ + { + name: "Hindi", + }, + { + name: "English", + }, + ], + }, + { + name: "British Indian Ocean Territory", + code: "IO", + icon: "virtual-host", + icon_status: IconStatus.Up, + capital: "Diego Garcia", + continent: { + name: "Asia", + }, + currency: "USD", + languages: [ + { + name: "English", + }, + ], + }, + { + name: "Iraq", + code: "IQ", + icon: "virtual-host", + icon_status: IconStatus.Up, + capital: "Baghdad", + continent: { + name: "Asia", + }, + currency: "IQD", + languages: [ + { + name: "Arabic", + }, + { + name: "Kurdish", + }, + ], + }, + { + name: "Iran", + code: "IR", + icon: "virtual-host", + icon_status: IconStatus.Up, + capital: "Tehran", + continent: { + name: "Asia", + }, + currency: "IRR", + languages: [ + { + name: "Persian", + }, + ], + }, + { + name: "Iceland", + code: "IS", + icon: "virtual-host", + icon_status: IconStatus.Up, + capital: "Reykjavik", + continent: { + name: "Europe", + }, + currency: "ISK", + languages: [ + { + name: "Icelandic", + }, + ], + }, + { + name: "Italy", + code: "IT", + icon: "virtual-host", + icon_status: IconStatus.Up, + capital: "Rome", + continent: { + name: "Europe", + }, + currency: "EUR", + languages: [ + { + name: "Italian", + }, + ], + }, + { + name: "Jersey", + code: "JE", + icon: "virtual-host", + icon_status: IconStatus.Up, + capital: "Saint Helier", + continent: { + name: "Europe", + }, + currency: "GBP", + languages: [ + { + name: "English", + }, + { + name: "French", + }, + ], + }, + { + name: "Jamaica", + code: "JM", + icon: "virtual-host", + icon_status: IconStatus.Up, + capital: "Kingston", + continent: { + name: "North America", + }, + currency: "JMD", + languages: [ + { + name: "English", + }, + ], + }, + { + name: "Jordan", + code: "JO", + icon: "virtual-host", + icon_status: IconStatus.Up, + capital: "Amman", + continent: { + name: "Asia", + }, + currency: "JOD", + languages: [ + { + name: "Arabic", + }, + ], + }, + { + name: "Japan", + code: "JP", + icon: "virtual-host", + icon_status: IconStatus.Up, + capital: "Tokyo", + continent: { + name: "Asia", + }, + currency: "JPY", + languages: [ + { + name: "Japanese", + }, + ], + }, + { + name: "Kenya", + code: "KE", + icon: "virtual-host", + icon_status: IconStatus.Up, + capital: "Nairobi", + continent: { + name: "Africa", + }, + currency: "KES", + languages: [ + { + name: "English", + }, + { + name: "Swahili", + }, + ], + }, + { + name: "Kyrgyzstan", + code: "KG", + icon: "virtual-host", + icon_status: IconStatus.Up, + capital: "Bishkek", + continent: { + name: "Asia", + }, + currency: "KGS", + languages: [ + { + name: "Kirghiz", + }, + { + name: "Russian", + }, + ], + }, + { + name: "Cambodia", + code: "KH", + icon: "virtual-host", + icon_status: IconStatus.Up, + capital: "Phnom Penh", + continent: { + name: "Asia", + }, + currency: "KHR", + languages: [ + { + name: "Cambodian", + }, + ], + }, + { + name: "Kiribati", + code: "KI", + icon: "virtual-host", + icon_status: IconStatus.Up, + capital: "South Tarawa", + continent: { + name: "Oceania", + }, + currency: "AUD", + languages: [ + { + name: "English", + }, + ], + }, + { + name: "Comoros", + code: "KM", + icon: "virtual-host", + icon_status: IconStatus.Up, + capital: "Moroni", + continent: { + name: "Africa", + }, + currency: "KMF", + languages: [ + { + name: "Arabic", + }, + { + name: "French", + }, + ], + }, + { + name: "Saint Kitts and Nevis", + code: "KN", + icon: "virtual-host", + icon_status: IconStatus.Up, + capital: "Basseterre", + continent: { + name: "North America", + }, + currency: "XCD", + languages: [ + { + name: "English", + }, + ], + }, + { + name: "North Korea", + code: "KP", + icon: "virtual-host", + icon_status: IconStatus.Up, + capital: "Pyongyang", + continent: { + name: "Asia", + }, + currency: "KPW", + languages: [ + { + name: "Korean", + }, + ], + }, + { + name: "South Korea", + code: "KR", + icon: "virtual-host", + icon_status: IconStatus.Up, + capital: "Seoul", + continent: { + name: "Asia", + }, + currency: "KRW", + languages: [ + { + name: "Korean", + }, + ], + }, + { + name: "Kuwait", + code: "KW", + icon: "virtual-host", + icon_status: IconStatus.Up, + capital: "Kuwait City", + continent: { + name: "Asia", + }, + currency: "KWD", + languages: [ + { + name: "Arabic", + }, + ], + }, + { + name: "Cayman Islands", + code: "KY", + icon: "virtual-host", + icon_status: IconStatus.Up, + capital: "George Town", + continent: { + name: "North America", + }, + currency: "KYD", + languages: [ + { + name: "English", + }, + ], + }, + { + name: "Kazakhstan", + code: "KZ", + icon: "virtual-host", + icon_status: IconStatus.Up, + capital: "Astana", + continent: { + name: "Asia", + }, + currency: "KZT", + languages: [ + { + name: "Kazakh", + }, + { + name: "Russian", + }, + ], + }, + { + name: "Laos", + code: "LA", + icon: "virtual-host", + icon_status: IconStatus.Up, + capital: "Vientiane", + continent: { + name: "Asia", + }, + currency: "LAK", + languages: [ + { + name: "Laotian", + }, + ], + }, + { + name: "Lebanon", + code: "LB", + icon: "virtual-host", + icon_status: IconStatus.Up, + capital: "Beirut", + continent: { + name: "Asia", + }, + currency: "LBP", + languages: [ + { + name: "Arabic", + }, + { + name: "French", + }, + ], + }, + { + name: "Saint Lucia", + code: "LC", + icon: "virtual-host", + icon_status: IconStatus.Up, + capital: "Castries", + continent: { + name: "North America", + }, + currency: "XCD", + languages: [ + { + name: "English", + }, + ], + }, + { + name: "Liechtenstein", + code: "LI", + icon: "virtual-host", + icon_status: IconStatus.Up, + capital: "Vaduz", + continent: { + name: "Europe", + }, + currency: "CHF", + languages: [ + { + name: "German", + }, + ], + }, + { + name: "Sri Lanka", + code: "LK", + icon: "virtual-host", + icon_status: IconStatus.Up, + capital: "Colombo", + continent: { + name: "Asia", + }, + currency: "LKR", + languages: [ + { + name: "Sinhalese", + }, + { + name: "Tamil", + }, + ], + }, + { + name: "Liberia", + code: "LR", + icon: "virtual-host", + icon_status: IconStatus.Up, + capital: "Monrovia", + continent: { + name: "Africa", + }, + currency: "LRD", + languages: [ + { + name: "English", + }, + ], + }, + { + name: "Lesotho", + code: "LS", + icon: "virtual-host", + icon_status: IconStatus.Up, + capital: "Maseru", + continent: { + name: "Africa", + }, + currency: "LSL,ZAR", + languages: [ + { + name: "English", + }, + { + name: "Southern Sotho", + }, + ], + }, + { + name: "Lithuania", + code: "LT", + icon: "virtual-host", + icon_status: IconStatus.Up, + capital: "Vilnius", + continent: { + name: "Europe", + }, + currency: "EUR", + languages: [ + { + name: "Lithuanian", + }, + ], + }, + { + name: "Luxembourg", + code: "LU", + icon: "virtual-host", + icon_status: IconStatus.Up, + capital: "Luxembourg", + continent: { + name: "Europe", + }, + currency: "EUR", + languages: [ + { + name: "French", + }, + { + name: "German", + }, + { + name: "Luxembourgish", + }, + ], + }, + { + name: "Latvia", + code: "LV", + icon: "virtual-host", + icon_status: IconStatus.Up, + capital: "Riga", + continent: { + name: "Europe", + }, + currency: "EUR", + languages: [ + { + name: "Latvian", + }, + ], + }, + { + name: "Libya", + code: "LY", + icon: "virtual-host", + icon_status: IconStatus.Up, + capital: "Tripoli", + continent: { + name: "Africa", + }, + currency: "LYD", + languages: [ + { + name: "Arabic", + }, + ], + }, + { + name: "Morocco", + code: "MA", + icon: "virtual-host", + icon_status: IconStatus.Up, + capital: "Rabat", + continent: { + name: "Africa", + }, + currency: "MAD", + languages: [ + { + name: "Arabic", + }, + ], + }, + { + name: "Monaco", + code: "MC", + icon: "virtual-host", + icon_status: IconStatus.Up, + capital: "Monaco", + continent: { + name: "Europe", + }, + currency: "EUR", + languages: [ + { + name: "French", + }, + ], + }, + { + name: "Moldova", + code: "MD", + icon: "virtual-host", + icon_status: IconStatus.Up, + capital: "Chișinău", + continent: { + name: "Europe", + }, + currency: "MDL", + languages: [ + { + name: "Romanian", + }, + ], + }, + { + name: "Montenegro", + code: "ME", + icon: "virtual-host", + icon_status: IconStatus.Up, + capital: "Podgorica", + continent: { + name: "Europe", + }, + currency: "EUR", + languages: [ + { + name: "Serbian", + }, + { + name: "Bosnian", + }, + { + name: "Albanian", + }, + { + name: "Croatian", + }, + ], + }, + { + name: "Saint Martin", + code: "MF", + icon: "virtual-host", + icon_status: IconStatus.Up, + capital: "Marigot", + continent: { + name: "North America", + }, + currency: "EUR", + languages: [ + { + name: "English", + }, + { + name: "French", + }, + { + name: "Dutch", + }, + ], + }, + { + name: "Madagascar", + code: "MG", + icon: "virtual-host", + icon_status: IconStatus.Up, + capital: "Antananarivo", + continent: { + name: "Africa", + }, + currency: "MGA", + languages: [ + { + name: "French", + }, + { + name: "Malagasy", + }, + ], + }, + { + name: "Marshall Islands", + code: "MH", + icon: "virtual-host", + icon_status: IconStatus.Up, + capital: "Majuro", + continent: { + name: "Oceania", + }, + currency: "USD", + languages: [ + { + name: "English", + }, + { + name: "Marshallese", + }, + ], + }, + { + name: "North Macedonia", + code: "MK", + icon: "virtual-host", + icon_status: IconStatus.Up, + capital: "Skopje", + continent: { + name: "Europe", + }, + currency: "MKD", + languages: [ + { + name: "Macedonian", + }, + ], + }, + { + name: "Mali", + code: "ML", + icon: "virtual-host", + icon_status: IconStatus.Up, + capital: "Bamako", + continent: { + name: "Africa", + }, + currency: "XOF", + languages: [ + { + name: "French", + }, + ], + }, + { + name: "Myanmar [Burma]", + code: "MM", + icon: "virtual-host", + icon_status: IconStatus.Up, + capital: "Naypyidaw", + continent: { + name: "Asia", + }, + currency: "MMK", + languages: [ + { + name: "Burmese", + }, + ], + }, + { + name: "Mongolia", + code: "MN", + icon: "virtual-host", + icon_status: IconStatus.Up, + capital: "Ulan Bator", + continent: { + name: "Asia", + }, + currency: "MNT", + languages: [ + { + name: "Mongolian", + }, + ], + }, + { + name: "Macao", + code: "MO", + icon: "virtual-host", + icon_status: IconStatus.Up, + capital: null, + continent: { + name: "Asia", + }, + currency: "MOP", + languages: [ + { + name: "Chinese", + }, + { + name: "Portuguese", + }, + ], + }, + { + name: "Northern Mariana Islands", + code: "MP", + icon: "virtual-host", + icon_status: IconStatus.Up, + capital: "Saipan", + continent: { + name: "Oceania", + }, + currency: "USD", + languages: [ + { + name: "English", + }, + { + name: "Chamorro", + }, + ], + }, + { + name: "Martinique", + code: "MQ", + icon: "virtual-host", + icon_status: IconStatus.Up, + capital: "Fort-de-France", + continent: { + name: "North America", + }, + currency: "EUR", + languages: [ + { + name: "French", + }, + ], + }, + { + name: "Mauritania", + code: "MR", + icon: "virtual-host", + icon_status: IconStatus.Up, + capital: "Nouakchott", + continent: { + name: "Africa", + }, + currency: "MRU", + languages: [ + { + name: "Arabic", + }, + ], + }, + { + name: "Montserrat", + code: "MS", + icon: "virtual-host", + icon_status: IconStatus.Up, + capital: "Plymouth", + continent: { + name: "North America", + }, + currency: "XCD", + languages: [ + { + name: "English", + }, + ], + }, + { + name: "Malta", + code: "MT", + icon: "virtual-host", + icon_status: IconStatus.Up, + capital: "Valletta", + continent: { + name: "Europe", + }, + currency: "EUR", + languages: [ + { + name: "Maltese", + }, + { + name: "English", + }, + ], + }, + { + name: "Mauritius", + code: "MU", + icon: "virtual-host", + icon_status: IconStatus.Up, + capital: "Port Louis", + continent: { + name: "Africa", + }, + currency: "MUR", + languages: [ + { + name: "English", + }, + ], + }, + { + name: "Maldives", + code: "MV", + icon: "virtual-host", + icon_status: IconStatus.Up, + capital: "Malé", + continent: { + name: "Asia", + }, + currency: "MVR", + languages: [ + { + name: "Divehi", + }, + ], + }, + { + name: "Malawi", + code: "MW", + icon: "virtual-host", + icon_status: IconStatus.Up, + capital: "Lilongwe", + continent: { + name: "Africa", + }, + currency: "MWK", + languages: [ + { + name: "English", + }, + { + name: "Chichewa", + }, + ], + }, + { + name: "Mexico", + code: "MX", + icon: "virtual-host", + icon_status: IconStatus.Up, + capital: "Mexico City", + continent: { + name: "North America", + }, + currency: "MXN", + languages: [ + { + name: "Spanish", + }, + ], + }, + { + name: "Malaysia", + code: "MY", + icon: "virtual-host", + icon_status: IconStatus.Up, + capital: "Kuala Lumpur", + continent: { + name: "Asia", + }, + currency: "MYR", + languages: [ + { + name: "Malay", + }, + ], + }, + { + name: "Mozambique", + code: "MZ", + icon: "virtual-host", + icon_status: IconStatus.Up, + capital: "Maputo", + continent: { + name: "Africa", + }, + currency: "MZN", + languages: [ + { + name: "Portuguese", + }, + ], + }, + { + name: "Namibia", + code: "NA", + icon: "virtual-host", + icon_status: IconStatus.Up, + capital: "Windhoek", + continent: { + name: "Africa", + }, + currency: "NAD,ZAR", + languages: [ + { + name: "English", + }, + { + name: "Afrikaans", + }, + ], + }, + { + name: "New Caledonia", + code: "NC", + icon: "virtual-host", + icon_status: IconStatus.Up, + capital: "Nouméa", + continent: { + name: "Oceania", + }, + currency: "XPF", + languages: [ + { + name: "French", + }, + ], + }, + { + name: "Niger", + code: "NE", + icon: "virtual-host", + icon_status: IconStatus.Up, + capital: "Niamey", + continent: { + name: "Africa", + }, + currency: "XOF", + languages: [ + { + name: "French", + }, + ], + }, + { + name: "Norfolk Island", + code: "NF", + icon: "virtual-host", + icon_status: IconStatus.Up, + capital: "Kingston", + continent: { + name: "Oceania", + }, + currency: "AUD", + languages: [ + { + name: "English", + }, + ], + }, + { + name: "Nigeria", + code: "NG", + icon: "virtual-host", + icon_status: IconStatus.Up, + capital: "Abuja", + continent: { + name: "Africa", + }, + currency: "NGN", + languages: [ + { + name: "English", + }, + ], + }, + { + name: "Nicaragua", + code: "NI", + icon: "virtual-host", + icon_status: IconStatus.Up, + capital: "Managua", + continent: { + name: "North America", + }, + currency: "NIO", + languages: [ + { + name: "Spanish", + }, + ], + }, + { + name: "Netherlands", + code: "NL", + icon: "virtual-host", + icon_status: IconStatus.Up, + capital: "Amsterdam", + continent: { + name: "Europe", + }, + currency: "EUR", + languages: [ + { + name: "Dutch", + }, + ], + }, + { + name: "Norway", + code: "NO", + icon: "virtual-host", + icon_status: IconStatus.Up, + capital: "Oslo", + continent: { + name: "Europe", + }, + currency: "NOK", + languages: [ + { + name: "Norwegian", + }, + { + name: "Norwegian Bokmål", + }, + { + name: "Norwegian Nynorsk", + }, + ], + }, + { + name: "Nepal", + code: "NP", + icon: "virtual-host", + icon_status: IconStatus.Up, + capital: "Kathmandu", + continent: { + name: "Asia", + }, + currency: "NPR", + languages: [ + { + name: "Nepali", + }, + ], + }, + { + name: "Nauru", + code: "NR", + icon: "virtual-host", + icon_status: IconStatus.Up, + capital: "Yaren", + continent: { + name: "Oceania", + }, + currency: "AUD", + languages: [ + { + name: "English", + }, + { + name: "Nauruan", + }, + ], + }, + { + name: "Niue", + code: "NU", + icon: "virtual-host", + icon_status: IconStatus.Up, + capital: "Alofi", + continent: { + name: "Oceania", + }, + currency: "NZD", + languages: [ + { + name: "English", + }, + ], + }, + { + name: "New Zealand", + code: "NZ", + icon: "virtual-host", + icon_status: IconStatus.Up, + capital: "Wellington", + continent: { + name: "Oceania", + }, + currency: "NZD", + languages: [ + { + name: "English", + }, + { + name: "Maori", + }, + ], + }, + { + name: "Oman", + code: "OM", + icon: "virtual-host", + icon_status: IconStatus.Up, + capital: "Muscat", + continent: { + name: "Asia", + }, + currency: "OMR", + languages: [ + { + name: "Arabic", + }, + ], + }, + { + name: "Panama", + code: "PA", + icon: "virtual-host", + icon_status: IconStatus.Up, + capital: "Panama City", + continent: { + name: "North America", + }, + currency: "PAB,USD", + languages: [ + { + name: "Spanish", + }, + ], + }, + { + name: "Peru", + code: "PE", + icon: "virtual-host", + icon_status: IconStatus.Up, + capital: "Lima", + continent: { + name: "South America", + }, + currency: "PEN", + languages: [ + { + name: "Spanish", + }, + ], + }, + { + name: "French Polynesia", + code: "PF", + icon: "virtual-host", + icon_status: IconStatus.Up, + capital: "Papeetē", + continent: { + name: "Oceania", + }, + currency: "XPF", + languages: [ + { + name: "French", + }, + ], + }, + { + name: "Papua New Guinea", + code: "PG", + icon: "virtual-host", + icon_status: IconStatus.Up, + capital: "Port Moresby", + continent: { + name: "Oceania", + }, + currency: "PGK", + languages: [ + { + name: "English", + }, + ], + }, + { + name: "Philippines", + code: "PH", + icon: "virtual-host", + icon_status: IconStatus.Up, + capital: "Manila", + continent: { + name: "Asia", + }, + currency: "PHP", + languages: [ + { + name: "English", + }, + ], + }, + { + name: "Pakistan", + code: "PK", + icon: "virtual-host", + icon_status: IconStatus.Up, + capital: "Islamabad", + continent: { + name: "Asia", + }, + currency: "PKR", + languages: [ + { + name: "English", + }, + { + name: "Urdu", + }, + ], + }, + { + name: "Poland", + code: "PL", + icon: "virtual-host", + icon_status: IconStatus.Up, + capital: "Warsaw", + continent: { + name: "Europe", + }, + currency: "PLN", + languages: [ + { + name: "Polish", + }, + ], + }, + { + name: "Saint Pierre and Miquelon", + code: "PM", + icon: "virtual-host", + icon_status: IconStatus.Up, + capital: "Saint-Pierre", + continent: { + name: "North America", + }, + currency: "EUR", + languages: [ + { + name: "French", + }, + ], + }, + { + name: "Pitcairn Islands", + code: "PN", + icon: "virtual-host", + icon_status: IconStatus.Up, + capital: "Adamstown", + continent: { + name: "Oceania", + }, + currency: "NZD", + languages: [ + { + name: "English", + }, + ], + }, + { + name: "Puerto Rico", + code: "PR", + icon: "virtual-host", + icon_status: IconStatus.Up, + capital: "San Juan", + continent: { + name: "North America", + }, + currency: "USD", + languages: [ + { + name: "Spanish", + }, + { + name: "English", + }, + ], + }, + { + name: "Palestine", + code: "PS", + icon: "virtual-host", + icon_status: IconStatus.Up, + capital: "Ramallah", + continent: { + name: "Asia", + }, + currency: "ILS", + languages: [ + { + name: "Arabic", + }, + ], + }, + { + name: "Portugal", + code: "PT", + icon: "virtual-host", + icon_status: IconStatus.Up, + capital: "Lisbon", + continent: { + name: "Europe", + }, + currency: "EUR", + languages: [ + { + name: "Portuguese", + }, + ], + }, + { + name: "Palau", + code: "PW", + icon: "virtual-host", + icon_status: IconStatus.Up, + capital: "Ngerulmud", + continent: { + name: "Oceania", + }, + currency: "USD", + languages: [ + { + name: "English", + }, + ], + }, + { + name: "Paraguay", + code: "PY", + icon: "virtual-host", + icon_status: IconStatus.Up, + capital: "Asunción", + continent: { + name: "South America", + }, + currency: "PYG", + languages: [ + { + name: "Spanish", + }, + { + name: "Guarani", + }, + ], + }, + { + name: "Qatar", + code: "QA", + icon: "virtual-host", + icon_status: IconStatus.Up, + capital: "Doha", + continent: { + name: "Asia", + }, + currency: "QAR", + languages: [ + { + name: "Arabic", + }, + ], + }, + { + name: "Réunion", + code: "RE", + icon: "virtual-host", + icon_status: IconStatus.Up, + capital: "Saint-Denis", + continent: { + name: "Africa", + }, + currency: "EUR", + languages: [ + { + name: "French", + }, + ], + }, + { + name: "Romania", + code: "RO", + icon: "virtual-host", + icon_status: IconStatus.Up, + capital: "Bucharest", + continent: { + name: "Europe", + }, + currency: "RON", + languages: [ + { + name: "Romanian", + }, + ], + }, + { + name: "Serbia", + code: "RS", + icon: "virtual-host", + icon_status: IconStatus.Up, + capital: "Belgrade", + continent: { + name: "Europe", + }, + currency: "RSD", + languages: [ + { + name: "Serbian", + }, + ], + }, + { + name: "Russia", + code: "RU", + icon: "virtual-host", + icon_status: IconStatus.Up, + capital: "Moscow", + continent: { + name: "Europe", + }, + currency: "RUB", + languages: [ + { + name: "Russian", + }, + ], + }, + { + name: "Rwanda", + code: "RW", + icon: "virtual-host", + icon_status: IconStatus.Up, + capital: "Kigali", + continent: { + name: "Africa", + }, + currency: "RWF", + languages: [ + { + name: "Rwandi", + }, + { + name: "English", + }, + { + name: "French", + }, + ], + }, + { + name: "Saudi Arabia", + code: "SA", + icon: "virtual-host", + icon_status: IconStatus.Up, + capital: "Riyadh", + continent: { + name: "Asia", + }, + currency: "SAR", + languages: [ + { + name: "Arabic", + }, + ], + }, + { + name: "Solomon Islands", + code: "SB", + icon: "virtual-host", + icon_status: IconStatus.Up, + capital: "Honiara", + continent: { + name: "Oceania", + }, + currency: "SBD", + languages: [ + { + name: "English", + }, + ], + }, + { + name: "Seychelles", + code: "SC", + icon: "virtual-host", + icon_status: IconStatus.Up, + capital: "Victoria", + continent: { + name: "Africa", + }, + currency: "SCR", + languages: [ + { + name: "French", + }, + { + name: "English", + }, + ], + }, + { + name: "Sudan", + code: "SD", + icon: "virtual-host", + icon_status: IconStatus.Up, + capital: "Khartoum", + continent: { + name: "Africa", + }, + currency: "SDG", + languages: [ + { + name: "Arabic", + }, + { + name: "English", + }, + ], + }, + { + name: "Sweden", + code: "SE", + icon: "virtual-host", + icon_status: IconStatus.Up, + capital: "Stockholm", + continent: { + name: "Europe", + }, + currency: "SEK", + languages: [ + { + name: "Swedish", + }, + ], + }, + { + name: "Singapore", + code: "SG", + icon: "virtual-host", + icon_status: IconStatus.Up, + capital: "Singapore", + continent: { + name: "Asia", + }, + currency: "SGD", + languages: [ + { + name: "English", + }, + { + name: "Malay", + }, + { + name: "Tamil", + }, + { + name: "Chinese", + }, + ], + }, + { + name: "Saint Helena", + code: "SH", + icon: "virtual-host", + icon_status: IconStatus.Up, + capital: "Jamestown", + continent: { + name: "Africa", + }, + currency: "SHP", + languages: [ + { + name: "English", + }, + ], + }, + { + name: "Slovenia", + code: "SI", + icon: "virtual-host", + icon_status: IconStatus.Up, + capital: "Ljubljana", + continent: { + name: "Europe", + }, + currency: "EUR", + languages: [ + { + name: "Slovenian", + }, + ], + }, + { + name: "Svalbard and Jan Mayen", + code: "SJ", + icon: "virtual-host", + icon_status: IconStatus.Up, + capital: "Longyearbyen", + continent: { + name: "Europe", + }, + currency: "NOK", + languages: [ + { + name: "Norwegian", + }, + ], + }, + { + name: "Slovakia", + code: "SK", + icon: "virtual-host", + icon_status: IconStatus.Up, + capital: "Bratislava", + continent: { + name: "Europe", + }, + currency: "EUR", + languages: [ + { + name: "Slovak", + }, + ], + }, + { + name: "Sierra Leone", + code: "SL", + icon: "virtual-host", + icon_status: IconStatus.Up, + capital: "Freetown", + continent: { + name: "Africa", + }, + currency: "SLL", + languages: [ + { + name: "English", + }, + ], + }, + { + name: "San Marino", + code: "SM", + icon: "virtual-host", + icon_status: IconStatus.Up, + capital: "City of San Marino", + continent: { + name: "Europe", + }, + currency: "EUR", + languages: [ + { + name: "Italian", + }, + ], + }, + { + name: "Senegal", + code: "SN", + icon: "virtual-host", + icon_status: IconStatus.Up, + capital: "Dakar", + continent: { + name: "Africa", + }, + currency: "XOF", + languages: [ + { + name: "French", + }, + ], + }, + { + name: "Somalia", + code: "SO", + icon: "virtual-host", + icon_status: IconStatus.Up, + capital: "Mogadishu", + continent: { + name: "Africa", + }, + currency: "SOS", + languages: [ + { + name: "Somalia", + }, + { + name: "Arabic", + }, + ], + }, + { + name: "Suriname", + code: "SR", + icon: "virtual-host", + icon_status: IconStatus.Up, + capital: "Paramaribo", + continent: { + name: "South America", + }, + currency: "SRD", + languages: [ + { + name: "Dutch", + }, + ], + }, + { + name: "South Sudan", + code: "SS", + icon: "virtual-host", + icon_status: IconStatus.Up, + capital: "Juba", + continent: { + name: "Africa", + }, + currency: "SSP", + languages: [ + { + name: "English", + }, + ], + }, + { + name: "São Tomé and Príncipe", + code: "ST", + icon: "virtual-host", + icon_status: IconStatus.Up, + capital: "São Tomé", + continent: { + name: "Africa", + }, + currency: "STN", + languages: [ + { + name: "Portuguese", + }, + ], + }, + { + name: "El Salvador", + code: "SV", + icon: "virtual-host", + icon_status: IconStatus.Up, + capital: "San Salvador", + continent: { + name: "North America", + }, + currency: "SVC,USD", + languages: [ + { + name: "Spanish", + }, + ], + }, + { + name: "Sint Maarten", + code: "SX", + icon: "virtual-host", + icon_status: IconStatus.Up, + capital: "Philipsburg", + continent: { + name: "North America", + }, + currency: "ANG", + languages: [ + { + name: "Dutch", + }, + { + name: "English", + }, + ], + }, + { + name: "Syria", + code: "SY", + icon: "virtual-host", + icon_status: IconStatus.Up, + capital: "Damascus", + continent: { + name: "Asia", + }, + currency: "SYP", + languages: [ + { + name: "Arabic", + }, + ], + }, + { + name: "Swaziland", + code: "SZ", + icon: "virtual-host", + icon_status: IconStatus.Up, + capital: "Lobamba", + continent: { + name: "Africa", + }, + currency: "SZL", + languages: [ + { + name: "English", + }, + { + name: "Swati", + }, + ], + }, + { + name: "Turks and Caicos Islands", + code: "TC", + icon: "virtual-host", + icon_status: IconStatus.Up, + capital: "Cockburn Town", + continent: { + name: "North America", + }, + currency: "USD", + languages: [ + { + name: "English", + }, + ], + }, + { + name: "Chad", + code: "TD", + icon: "virtual-host", + icon_status: IconStatus.Up, + capital: "N'Djamena", + continent: { + name: "Africa", + }, + currency: "XAF", + languages: [ + { + name: "French", + }, + { + name: "Arabic", + }, + ], + }, + { + name: "French Southern Territories", + code: "TF", + icon: "virtual-host", + icon_status: IconStatus.Up, + capital: "Port-aux-Français", + continent: { + name: "Antarctica", + }, + currency: "EUR", + languages: [ + { + name: "French", + }, + ], + }, + { + name: "Togo", + code: "TG", + icon: "virtual-host", + icon_status: IconStatus.Up, + capital: "Lomé", + continent: { + name: "Africa", + }, + currency: "XOF", + languages: [ + { + name: "French", + }, + ], + }, + { + name: "Thailand", + code: "TH", + icon: "virtual-host", + icon_status: IconStatus.Up, + capital: "Bangkok", + continent: { + name: "Asia", + }, + currency: "THB", + languages: [ + { + name: "Thai", + }, + ], + }, + { + name: "Tajikistan", + code: "TJ", + icon: "virtual-host", + icon_status: IconStatus.Up, + capital: "Dushanbe", + continent: { + name: "Asia", + }, + currency: "TJS", + languages: [ + { + name: "Tajik", + }, + { + name: "Russian", + }, + ], + }, + { + name: "Tokelau", + code: "TK", + icon: "virtual-host", + icon_status: IconStatus.Up, + capital: "Fakaofo", + continent: { + name: "Oceania", + }, + currency: "NZD", + languages: [ + { + name: "English", + }, + ], + }, + { + name: "East Timor", + code: "TL", + icon: "virtual-host", + icon_status: IconStatus.Up, + capital: "Dili", + continent: { + name: "Oceania", + }, + currency: "USD", + languages: [ + { + name: "Portuguese", + }, + ], + }, + { + name: "Turkmenistan", + code: "TM", + icon: "virtual-host", + icon_status: IconStatus.Up, + capital: "Ashgabat", + continent: { + name: "Asia", + }, + currency: "TMT", + languages: [ + { + name: "Turkmen", + }, + { + name: "Russian", + }, + ], + }, + { + name: "Tunisia", + code: "TN", + icon: "virtual-host", + icon_status: IconStatus.Up, + capital: "Tunis", + continent: { + name: "Africa", + }, + currency: "TND", + languages: [ + { + name: "Arabic", + }, + ], + }, + { + name: "Tonga", + code: "TO", + icon: "virtual-host", + icon_status: IconStatus.Up, + capital: "Nuku'alofa", + continent: { + name: "Oceania", + }, + currency: "TOP", + languages: [ + { + name: "English", + }, + { + name: "Tonga", + }, + ], + }, + { + name: "Turkey", + code: "TR", + icon: "virtual-host", + icon_status: IconStatus.Up, + capital: "Ankara", + continent: { + name: "Asia", + }, + currency: "TRY", + languages: [ + { + name: "Turkish", + }, + ], + }, + { + name: "Trinidad and Tobago", + code: "TT", + icon: "virtual-host", + icon_status: IconStatus.Up, + capital: "Port of Spain", + continent: { + name: "North America", + }, + currency: "TTD", + languages: [ + { + name: "English", + }, + ], + }, + { + name: "Tuvalu", + code: "TV", + icon: "virtual-host", + icon_status: IconStatus.Up, + capital: "Funafuti", + continent: { + name: "Oceania", + }, + currency: "AUD", + languages: [ + { + name: "English", + }, + ], + }, + { + name: "Taiwan", + code: "TW", + icon: "virtual-host", + icon_status: IconStatus.Up, + capital: "Taipei", + continent: { + name: "Asia", + }, + currency: "TWD", + languages: [ + { + name: "Chinese", + }, + ], + }, + { + name: "Tanzania", + code: "TZ", + icon: "virtual-host", + icon_status: IconStatus.Up, + capital: "Dodoma", + continent: { + name: "Africa", + }, + currency: "TZS", + languages: [ + { + name: "Swahili", + }, + { + name: "English", + }, + ], + }, + { + name: "Ukraine", + code: "UA", + icon: "virtual-host", + icon_status: IconStatus.Up, + capital: "Kyiv", + continent: { + name: "Europe", + }, + currency: "UAH", + languages: [ + { + name: "Ukrainian", + }, + ], + }, + { + name: "Uganda", + code: "UG", + icon: "virtual-host", + icon_status: IconStatus.Up, + capital: "Kampala", + continent: { + name: "Africa", + }, + currency: "UGX", + languages: [ + { + name: "English", + }, + { + name: "Swahili", + }, + ], + }, + { + name: "U.S. Minor Outlying Islands", + code: "UM", + icon: "virtual-host", + icon_status: IconStatus.Up, + capital: null, + continent: { + name: "Oceania", + }, + currency: "USD", + languages: [ + { + name: "English", + }, + ], + }, + { + name: "United States", + code: "US", + icon: "virtual-host", + icon_status: IconStatus.Up, + capital: "Washington D.C.", + continent: { + name: "North America", + }, + currency: "USD,USN,USS", + languages: [ + { + name: "English", + }, + ], + }, + { + name: "Uruguay", + code: "UY", + icon: "virtual-host", + icon_status: IconStatus.Up, + capital: "Montevideo", + continent: { + name: "South America", + }, + currency: "UYI,UYU", + languages: [ + { + name: "Spanish", + }, + ], + }, + { + name: "Uzbekistan", + code: "UZ", + icon: "virtual-host", + icon_status: IconStatus.Up, + capital: "Tashkent", + continent: { + name: "Asia", + }, + currency: "UZS", + languages: [ + { + name: "Uzbek", + }, + { + name: "Russian", + }, + ], + }, + { + name: "Vatican City", + code: "VA", + icon: "virtual-host", + icon_status: IconStatus.Up, + capital: "Vatican City", + continent: { + name: "Europe", + }, + currency: "EUR", + languages: [ + { + name: "Italian", + }, + { + name: "Latin", + }, + ], + }, + { + name: "Saint Vincent and the Grenadines", + code: "VC", + icon: "virtual-host", + icon_status: IconStatus.Up, + capital: "Kingstown", + continent: { + name: "North America", + }, + currency: "XCD", + languages: [ + { + name: "English", + }, + ], + }, + { + name: "Venezuela", + code: "VE", + icon: "virtual-host", + icon_status: IconStatus.Up, + capital: "Caracas", + continent: { + name: "South America", + }, + currency: "VES", + languages: [ + { + name: "Spanish", + }, + ], + }, + { + name: "British Virgin Islands", + code: "VG", + icon: "virtual-host", + icon_status: IconStatus.Up, + capital: "Road Town", + continent: { + name: "North America", + }, + currency: "USD", + languages: [ + { + name: "English", + }, + ], + }, + { + name: "U.S. Virgin Islands", + code: "VI", + icon: "virtual-host", + icon_status: IconStatus.Up, + capital: "Charlotte Amalie", + continent: { + name: "North America", + }, + currency: "USD", + languages: [ + { + name: "English", + }, + ], + }, + { + name: "Vietnam", + code: "VN", + icon: "virtual-host", + icon_status: IconStatus.Up, + capital: "Hanoi", + continent: { + name: "Asia", + }, + currency: "VND", + languages: [ + { + name: "Vietnamese", + }, + ], + }, + { + name: "Vanuatu", + code: "VU", + icon: "virtual-host", + icon_status: IconStatus.Up, + capital: "Port Vila", + continent: { + name: "Oceania", + }, + currency: "VUV", + languages: [ + { + name: "Bislama", + }, + { + name: "English", + }, + { + name: "French", + }, + ], + }, + { + name: "Wallis and Futuna", + code: "WF", + icon: "virtual-host", + icon_status: IconStatus.Up, + capital: "Mata-Utu", + continent: { + name: "Oceania", + }, + currency: "XPF", + languages: [ + { + name: "French", + }, + ], + }, + { + name: "Samoa", + code: "WS", + icon: "virtual-host", + icon_status: IconStatus.Up, + capital: "Apia", + continent: { + name: "Oceania", + }, + currency: "WST", + languages: [ + { + name: "Samoan", + }, + { + name: "English", + }, + ], + }, + { + name: "Kosovo", + code: "XK", + icon: "virtual-host", + icon_status: IconStatus.Up, + capital: "Pristina", + continent: { + name: "Europe", + }, + currency: "EUR", + languages: [ + { + name: "Albanian", + }, + { + name: "Serbian", + }, + ], + }, + { + name: "Yemen", + code: "YE", + icon: "virtual-host", + icon_status: IconStatus.Up, + capital: "Sana'a", + continent: { + name: "Asia", + }, + currency: "YER", + languages: [ + { + name: "Arabic", + }, + ], + }, + { + name: "Mayotte", + code: "YT", + icon: "virtual-host", + icon_status: IconStatus.Up, + capital: "Mamoudzou", + continent: { + name: "Africa", + }, + currency: "EUR", + languages: [ + { + name: "French", + }, + ], + }, + { + name: "South Africa", + code: "ZA", + icon: "virtual-host", + icon_status: IconStatus.Up, + capital: "Pretoria", + continent: { + name: "Africa", + }, + currency: "ZAR", + languages: [ + { + name: "Afrikaans", + }, + { + name: "English", + }, + { + name: "South Ndebele", + }, + { + name: "Southern Sotho", + }, + { + name: "Swati", + }, + { + name: "Tswana", + }, + { + name: "Tsonga", + }, + { + name: "Venda", + }, + { + name: "Xhosa", + }, + { + name: "Zulu", + }, + ], + }, + { + name: "Zambia", + code: "ZM", + icon: "virtual-host", + icon_status: IconStatus.Up, + capital: "Lusaka", + continent: { + name: "Africa", + }, + currency: "ZMW", + languages: [ + { + name: "English", + }, + ], + }, + { + name: "Zimbabwe", + code: "ZW", + icon: "virtual-host", + icon_status: IconStatus.Up, + capital: "Harare", + continent: { + name: "Africa", + }, + currency: "USD,ZAR,BWP,GBP,AUD,CNY,INR,JPY", + languages: [ + { + name: "English", + }, + { + name: "Shona", + }, + { + name: "North Ndebele", + }, + ], + }, + ], + }, +}; +\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\`, + "widget-types/drilldown/drilldown-widget/drilldown-widget-example.component.ts": \\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\`// © 2022 SolarWinds Worldwide, LLC. All rights reserved. +// +// Permission is hereby granted, free of charge, to any person obtaining a copy +// of this software and associated documentation files (the "Software"), to +// deal in the Software without restriction, including without limitation the +// rights to use, copy, modify, merge, publish, distribute, sublicense, and/or +// sell copies of the Software, and to permit persons to whom the Software is +// furnished to do so, subject to the following conditions: +// +// The above copyright notice and this permission notice shall be included in +// all copies or substantial portions of the Software. +// +// THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR +// IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, +// FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE +// AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER +// LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, +// OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN +// THE SOFTWARE. + +import { HttpClient } from "@angular/common/http"; +import { + ChangeDetectorRef, + Component, + Injectable, + OnDestroy, + OnInit, +} from "@angular/core"; +import { GridsterConfig, GridsterItem } from "angular-gridster2"; +import { Apollo, gql } from "apollo-angular"; +import groupBy from "lodash/groupBy"; +import { BehaviorSubject, Observable, of } from "rxjs"; +import { catchError, delay, filter, map } from "rxjs/operators"; + +import { + DataSourceFeatures, + IconStatus, + IDataField, + IDataSource, + IDataSourceFeatures, + IDataSourceFeaturesConfiguration, + INovaFilters, + LoggerService, + ServerSideDataSource, + IFilters, +} from "@nova-ui/bits"; +import { + DATA_SOURCE, + DEFAULT_PIZZAGNA_ROOT, + IDashboard, + IDrilldownComponentsConfiguration, + IListWidgetConfiguration, + IProviderConfiguration, + IWidget, + IWidgets, + ListGroupItemComponent, + ListLeafItemComponent, + NOVA_DRILLDOWN_DATASOURCE_ADAPTER, + PizzagnaLayer, + ProviderRegistryService, + WellKnownPathKey, + WellKnownProviders, + WidgetTypesService, +} from "@nova-ui/dashboards"; + +import { DrilldownDataSource } from "./mock-data-source"; + +/** + * A simple KPI data source to retrieve the average rating of Harry Potter and the Sorcerer's Stone (book) via googleapis + */ +@Injectable() +export class DrilldownDataSourceRealApi + extends ServerSideDataSource + implements OnDestroy, IDataSource +{ + // This is the ID we'll use to identify the provider + public static providerId = "DrilldownDataSourceRealApi"; + + // Use this subject to communicate the data source's busy state + public busy = new BehaviorSubject(false); + public dataFields: Partial[] = [ + { id: "regionName", label: "Region name" }, + { id: "subregionName", label: "Subregion name" }, + ]; + + public features: IDataSourceFeaturesConfiguration; + private supportedFeatures: IDataSourceFeatures = { + search: { enabled: true }, + }; + + private drillState: string[] = []; + private groupBy: string[]; + + constructor( + private logger: LoggerService, + private http: HttpClient, + private apollo: Apollo + ) { + super(); + this.features = new DataSourceFeatures(this.supportedFeatures); + // TODO: remove Partial in vNext after marking dataType field as optional - NUI-5838 + ( + this.dataFieldsConfig.dataFields$ as BehaviorSubject< + Partial[] + > + ).next(this.dataFields); + } + + private groupedDataHistory: Array> = []; + + // In this example, getFilteredData is invoked every 10 minutes (Take a look at the refresher + // provider definition in the widget configuration below to see how the interval is set) + public async getFilteredData(data: IFilters): Promise { + return of(data) + .pipe( + filter(() => !!this.drillState), + map((countries) => { + const lastHistory = () => getLast(this.groupedDataHistory); + + if (!this.drillState.length && !this.groupBy.length) { + return countries; + } + + // adding "ROOT" as a root level for drilling + const fullDrillState = ["ROOT", ...this.drillState]; + const activeDrillLvl = fullDrillState.length; + const historyLvl = this.groupedDataHistory.length; + + // checking how many lvls we have to group for drilling, in case some are missed + const drillLvlDiff = activeDrillLvl - historyLvl; + + if (!drillLvlDiff) { + return lastHistory() || countries; + } + + const drillToGroup = fullDrillState.slice( + fullDrillState.length - drillLvlDiff + ); + + for (const drill of drillToGroup) { + const drillIdx = fullDrillState.findIndex( + (v) => v === drill + ); + const group = this.groupBy[drillIdx]; + + if (group) { + const dataToGroup = lastHistory() + ? lastHistory()[drill] + : countries; + const lastGroupedValue = groupBy( + dataToGroup, + group + ); + + this.groupedDataHistory.push(lastGroupedValue); + } + } + + // take last if we have all data grouped + if (this.groupBy.length === this.drillState.length) { + return lastHistory()[getLast(this.drillState)]; + } + + // get groping and transform to raw data format + return this.getGroupsWidgetData(lastHistory()); + }) + ) + .toPromise(); + } + + public ngOnDestroy(): void { + this.outputsSubject.complete(); + } + + // This method is expected to return all data needed for repeat/paginator/filterGroups in order to work. + // In case of custom filtering participants feel free to extend INovaFilteringOutputs. + protected getBackendData(filters: INovaFilters): Observable { + const mainRequest = this.apollo.watchQuery<{ countries: any }>({ + query: this.generateQuery(filters), + }); + + return mainRequest.valueChanges.pipe( + // mock delay + delay(300), + // data mapping, !DS specific! + map((res) => res.data.countries), + // adds mock icons to be displayed on leaf nodes !DS specific! + map((res: any[]) => + res.map((v) => ({ + ...v, + icon: "virtual-host", + icon_status: IconStatus.Up, + subregionName: + v.subregion?.name || "No Subregion Specified", + regionName: + v.subregion?.region?.name || "No Region Specified", + })) + ), + catchError((e) => { + this.logger.error(e); + return of({} as any); + }) + ); + } + + private generateQuery(filters: INovaFilters) { + const { search } = filters; + const searchValue = search?.value ? \\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\`^[\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\${search.value}]*\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\` : ""; + + const queryString = \\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\` + query { + countries(filter: {name: {regex: "\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\${searchValue}"} }) { + name + native + capital + languages { + name + } + currencies + subdivisions { + name + } + } + } + \\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\`; + + return gql\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\` + \\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\${queryString} + \\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\`; + } + + // Overrides default ServerSideDataSource.beforeApplyFilters implementation + // to save some filters that are used internally + // -- !DS specific + protected beforeApplyFilters(filters: INovaFilters): void { + this.busy.next(true); + + this.drillState = filters.drillstate?.value; + this.groupBy = filters.group?.value; + + if (this.isHome()) { + this.groupedDataHistory.length = 0; + } + + if (this.isBack()) { + this.groupedDataHistory.length = this.groupedDataHistory.length - 1; + } + + if (this.getFilters()["search"] && this.filterChanged("search")) { + this.groupedDataHistory.length = 0; + } + } + + private getGroupsWidgetData(groupByObj: Record) { + return Object.keys(groupByObj).map((property) => ({ + id: property, + label: property, + // statuses that will be displayed on group item + statuses: [ + { key: "virtual-host", value: groupByObj[property].length }, + { + key: "acknowledge", + value: this.getPopulation(groupByObj[property]), + }, + ], + })); + } + + private isHome(): boolean { + return this.drillState?.length === 0; + } + + private isBack(): boolean { + return ( + this.groupedDataHistory?.length > this.drillState?.length && + !this.isHome() + ); + } + + /** + * Gets population for the country(ies) + */ + private getPopulation(countries: any[]) { + const totalPopulation = countries.reduce( + (acc, next) => (acc += next.population), + 0 + ); + return \\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\`\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\${totalPopulation * Math.pow(10, -3)} k\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\`; + } +} + +@Component({ + selector: "drilldown-widget-example", + templateUrl: "./drilldown-widget-example.component.html", + styleUrls: ["./drilldown-widget-example.component.less"], + standalone: false, +}) +export class DrilldownWidgetExampleComponent implements OnInit { + // This variable will hold all the data needed to define the layout and behavior of the widgets. + // Pass this to the dashboard component's dashboard input in the template. + public dashboard: IDashboard | undefined; + + // Angular gridster requires a configuration object even if it's empty. + // Pass this to the dashboard component's gridsterConfig input in the template. + public gridsterConfig: GridsterConfig = {}; + + // Boolean passed as an input to the dashboard. When true, widgets can be moved, resized, removed, or edited + public editMode: boolean = false; + + constructor( + // WidgetTypesService provides the widget's necessary structure information + private widgetTypesService: WidgetTypesService, + private providerRegistry: ProviderRegistryService, + private changeDetectorRef: ChangeDetectorRef + ) {} + + public ngOnInit(): void { + // Registering the data source for injection into the KPI tile. + // Note: Each tile of a KPI widget is assigned its own instance of the data source + this.providerRegistry.setProviders({ + [DrilldownDataSource.providerId]: { + provide: DATA_SOURCE, + useClass: DrilldownDataSource, + // Any dependencies that need to be injected into the provider must be listed here + deps: [HttpClient], + }, + [DrilldownDataSourceRealApi.providerId]: { + provide: DATA_SOURCE, + useClass: DrilldownDataSourceRealApi, + // Any dependencies that need to be injected into the provider must be listed here + deps: [LoggerService, HttpClient, Apollo], + }, + }); + + this.initializeDashboard(); + const widgetTemplate = this.widgetTypesService.getWidgetType( + "drilldown", + 1 + ); + this.widgetTypesService.setNode( + widgetTemplate, + "configurator", + WellKnownPathKey.DataSourceProviders, + [ + DrilldownDataSourceRealApi.providerId, + DrilldownDataSource.providerId, + ] + ); + } + + public initializeDashboard(): void { + // We're using a static configuration object for this example, but this is where + // the widget's configuration could potentially be populated from a database + const drilldownWidget = widgetConfig; + const widgets: IWidgets = { + // Complete the widget with information coming from its type definition + [drilldownWidget.id]: + this.widgetTypesService.mergeWithWidgetType(drilldownWidget), + }; + + // Setting the widget dimensions and position (this is for gridster) + const positions: Record = { + [drilldownWidget.id]: { + cols: 10, + rows: 10, + y: 0, + x: 0, + }, + }; + + // Finally, assigning the variables we created above to the dashboard + this.dashboard = { positions, widgets }; + } + + public reInitializeDashboard(): void { + // destroys the components and their providers so the dashboard can re init data + this.dashboard = undefined; + this.changeDetectorRef.detectChanges(); + + const adapterProperties = + widgetConfig.pizzagna[PizzagnaLayer.Configuration].listWidget + .providers?.adapter?.properties; + + if (adapterProperties) { + adapterProperties.drillstate = []; + } + + this.initializeDashboard(); + } +} + +const widgetConfig: IWidget = { + id: "drilldown", + type: "drilldown", + pizzagna: { + [PizzagnaLayer.Configuration]: { + [DEFAULT_PIZZAGNA_ROOT]: { + providers: { + [WellKnownProviders.DataSource]: { + // Setting the data source providerId for the tile with id "kpi1" + providerId: DrilldownDataSourceRealApi.providerId, + properties: {}, + } as IProviderConfiguration, + }, + }, + header: { + properties: { + title: "Drilldown Widget", + subtitle: "Search is case sensitive!", + }, + }, + listWidget: { + providers: { + [WellKnownProviders.Adapter]: { + providerId: NOVA_DRILLDOWN_DATASOURCE_ADAPTER, + properties: { + // widget + navigationBarId: "navigationBar", + componentId: "listWidget", + dataPath: "data", + + // adapter props + drillstate: [], + groupBy: ["regionName", "subregionName"], + groups: ["regionName", "subregionName"], + + // components + componentsConfig: { + group: { + componentType: + ListGroupItemComponent.lateLoadKey, + properties: { + dataFieldIds: { + id: "id", + label: "label", + statuses: "statuses", + }, + }, + itemProperties: { + canNavigate: true, + }, + }, + leaf: { + componentType: + ListLeafItemComponent.lateLoadKey, + properties: { + dataFieldIds: { + icon: "icon", + status: "icon_status", + detailedUrl: "capital", + label: "name", + }, + }, + itemProperties: { + canNavigate: false, + }, + }, + } as IDrilldownComponentsConfiguration, + }, + }, + }, + properties: { + configuration: { + // FORMAT: + // componentType: ListLeafItemComponent.lateLoadKey, + // properties: { + // dataFieldIds: { + // icon: "", + // status: "code", + // detailedUrl: "capital", + // label: "name", + // }, + // }, + // + } as IListWidgetConfiguration, + }, + }, + }, + }, +}; + +const getLast = (arr: any[]) => arr[arr.length - 1]; +\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\`, + "widget-types/drilldown/drilldown-widget/mock-data-source.ts": \\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\`// © 2022 SolarWinds Worldwide, LLC. All rights reserved. +// +// Permission is hereby granted, free of charge, to any person obtaining a copy +// of this software and associated documentation files (the "Software"), to +// deal in the Software without restriction, including without limitation the +// rights to use, copy, modify, merge, publish, distribute, sublicense, and/or +// sell copies of the Software, and to permit persons to whom the Software is +// furnished to do so, subject to the following conditions: +// +// The above copyright notice and this permission notice shall be included in +// all copies or substantial portions of the Software. +// +// THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR +// IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, +// FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE +// AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER +// LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, +// OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN +// THE SOFTWARE. + +import { Injectable, OnDestroy } from "@angular/core"; +import groupBy from "lodash/groupBy"; +import { BehaviorSubject, Observable, of, Subject } from "rxjs"; +import { + catchError, + delay, + finalize, + map, + // eslint-disable-next-line import/no-deprecated + switchMap, + tap, +} from "rxjs/operators"; + +import { + DataSourceService, + IDataField, + IDataSource, + IFilters, + INovaFilters, +} from "@nova-ui/bits"; + +import { GRAPH_DATA_MOCK } from "./data-mock"; + +/** + * A simple KPI data source to retrieve the average rating of Harry Potter and the Sorcerer's Stone (book) via googleapis + */ +@Injectable() +export class DrilldownDataSource + extends DataSourceService + implements IDataSource, OnDestroy +{ + // This is the ID we'll use to identify the provider + public static providerId = "DrilldownDataSource"; + + // Use this subject to communicate the data source's busy state + public busy = new BehaviorSubject(false); + public dataFields: Partial[] = [ + { id: "continent.name", label: "Continent name" }, + { id: "currency", label: "Currency" }, + ]; + + private drillState: string[] = []; + private groupBy: string[]; + private cache: any; + private applyFilters$ = new Subject(); + + constructor() { + super(); + + // TODO: remove Partial in vNext after marking dataType field as optional - NUI-5838 + ( + this.dataFieldsConfig.dataFields$ as BehaviorSubject< + Partial[] + > + ).next(this.dataFields); + + this.applyFilters$ + // eslint-disable-next-line import/no-deprecated + .pipe(switchMap((filters) => this.getData(filters))) + .subscribe(async (res) => { + this.outputsSubject.next(await this.getFilteredData(res)); + }); + } + + private groupedDataHistory: any[] = []; + + // In this example, getFilteredData is invoked every 10 minutes (Take a look at the refresher + // provider definition in the widget configuration below to see how the interval is set) + public async getFilteredData(data: any): Promise { + return of(data) + .pipe( + map((countries) => { + const widgetInput = this.getOutput(countries); + + if (this.isDrillDown()) { + const activeDrillLvl = this.drillState.length; + const group = this.groupBy[activeDrillLvl]; + const [lastGroupedValue, groupedData] = + this.getTransformedDataForGroup(widgetInput, group); + + this.groupedDataHistory.push(lastGroupedValue); + + return groupedData; + } + + return widgetInput; + }) + ) + .toPromise(); + } + + public ngOnDestroy(): void { + this.outputsSubject.complete(); + } + + // redefine parent method + public async applyFilters(): Promise { + this.applyFilters$.next(this.getFilters()); + } + + private getData(filters: INovaFilters): Observable { + this.drillState = filters.drillstate?.value; + this.groupBy = filters.group?.value; + + this.busy.next(true); + + return of(this.cache || GRAPH_DATA_MOCK).pipe( + delay(1000), + tap((data) => (this.cache = data)), + map((data) => data.data.countries), + catchError((e) => of([])), + finalize(() => this.busy.next(false)) + ); + } + + private getTransformedDataForGroup(data: any, groupName: string) { + const groupedDict = groupBy(data, groupName); + const dataArr = Object.keys(groupedDict).map((property) => ({ + id: property, + label: property, + // TODO: apply groups mapping here + statuses: [ + { key: "state_ok", value: groupedDict[property].length }, + { + key: "status_unreachable", + value: generateNumberUpTo(100000), + }, + { key: "status_warning", value: generateNumberUpTo(10000) }, + { key: "status_unknown", value: generateNumberUpTo(1000) }, + ], + })); + + return [groupedDict, dataArr]; + } + + private isHome(): boolean { + return !this.drillState || this.drillState.length === 0; + } + + private isBack(): boolean { + return ( + this.groupedDataHistory.length > this.drillState?.length && + !this.isHome() + ); + } + + private isDrillDown(): boolean { + return this.drillState?.length !== this.groupBy?.length; + } + + private getOutput(data: any) { + if (this.isHome()) { + this.groupedDataHistory.length = 0; + } + + if (this.isBack()) { + this.groupedDataHistory.length = this.groupedDataHistory.length - 1; + } + + const lastHistoryValue = getLast(this.groupedDataHistory); + + if (!lastHistoryValue) { + return data; + } + + return lastHistoryValue[getLast(this.drillState)] || lastHistoryValue; + } +} + +const getLast = (arr: any[]) => arr[arr.length - 1]; +const generateNumberUpTo = (upperLimit: number): number => + Math.floor(Math.random() * upperLimit + 1); +\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\`, + "widget-types/drilldown/drilldown-widget-docs.component.ts": \\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\`// © 2022 SolarWinds Worldwide, LLC. All rights reserved. +// +// Permission is hereby granted, free of charge, to any person obtaining a copy +// of this software and associated documentation files (the "Software"), to +// deal in the Software without restriction, including without limitation the +// rights to use, copy, modify, merge, publish, distribute, sublicense, and/or +// sell copies of the Software, and to permit persons to whom the Software is +// furnished to do so, subject to the following conditions: +// +// The above copyright notice and this permission notice shall be included in +// all copies or substantial portions of the Software. +// +// THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR +// IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, +// FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE +// AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER +// LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, +// OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN +// THE SOFTWARE. + +import { Component, OnInit } from "@angular/core"; + +import { mapContentFile } from "../../../../demo-files-factory"; + +@Component({ + selector: "nui-drilldown-docs", + templateUrl: "./drilldown-widget-docs.component.html", + standalone: false, +}) +export class DrilldownDocsComponent implements OnInit { + public widgetFileText = ""; + public configuratorFileText = ""; + + public predefinedGroping = \\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\` +listWidget: { + providers: { + [WellKnownProviders.Adapter]: { + providerId: NOVA_DRILLDOWN_DATASOURCE_ADAPTER, + properties: { + ... + // adapter props + drillstate: [], + groupBy: ["regionName", "subregionName"], + groups: ["regionName", "subregionName"], + ... + }, + }, + }, +}, +\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\`; + public featuredDeclaredText = \\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\` + private supportedFeatures: IDataSourceFeatures = { + search: { enabled: true }, + };\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\`; + public featuresUsedText = \\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\` + this.features = new DataSourceFeatures(this.supportedFeatures); + \\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\`; + + public async ngOnInit(): Promise { + this.widgetFileText = await import( + "./../../../../../../src/lib/widget-types/drilldown/drilldown-widget" + ).then(mapContentFile); + this.configuratorFileText = await import( + "./../../../../../../src/lib/widget-types/drilldown/drilldown-configurator" + ).then(mapContentFile); + } +} +\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\`, + "widget-types/drilldown/drilldown-widget-docs.module.ts": \\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\`// © 2022 SolarWinds Worldwide, LLC. All rights reserved. +// +// Permission is hereby granted, free of charge, to any person obtaining a copy +// of this software and associated documentation files (the "Software"), to +// deal in the Software without restriction, including without limitation the +// rights to use, copy, modify, merge, publish, distribute, sublicense, and/or +// sell copies of the Software, and to permit persons to whom the Software is +// furnished to do so, subject to the following conditions: +// +// The above copyright notice and this permission notice shall be included in +// all copies or substantial portions of the Software. +// +// THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR +// IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, +// FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE +// AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER +// LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, +// OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN +// THE SOFTWARE. + +import { NgModule } from "@angular/core"; +import { RouterModule, Routes } from "@angular/router"; + +// eslint-disable-next-line max-len +import { + NuiButtonModule, + NuiDocsModule, + NuiMessageModule, + NuiSwitchModule, + DEMO_PATH_TOKEN, +} from "@nova-ui/bits"; +import { NuiDashboardsModule } from "@nova-ui/dashboards"; + +import { DrilldownMultiRequestWidgetExampleComponent } from "./drilldown-multi-request-widget/drilldown-multi-request-widget-example.component"; +import { DrilldownWidgetExampleComponent } from "./drilldown-widget/drilldown-widget-example.component"; +import { DrilldownDocsComponent } from "./drilldown-widget-docs.component"; +import { getDemoFiles } from "../../../../demo-files-factory"; + +const routes: Routes = [ + { + path: "", + component: DrilldownDocsComponent, + data: { + srlc: { + hideIndicator: true, + }, + }, + }, + { + path: "example", + component: DrilldownWidgetExampleComponent, + data: { + srlc: { + hideIndicator: true, + }, + }, + }, + { + path: "multiple-requests", + component: DrilldownMultiRequestWidgetExampleComponent, + data: { + srlc: { + hideIndicator: true, + }, + }, + }, +]; + +@NgModule({ + imports: [ + RouterModule.forChild(routes), + NuiButtonModule, + NuiDocsModule, + NuiMessageModule, + NuiDashboardsModule, + NuiSwitchModule, + ], + declarations: [ + DrilldownDocsComponent, + DrilldownWidgetExampleComponent, + DrilldownMultiRequestWidgetExampleComponent, + ], + providers: [ + { + provide: DEMO_PATH_TOKEN, + useValue: getDemoFiles("drilldown"), + }, + ], +}) +export default class DrilldownDocsModule {} +\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\`, + "widget-types/embedded-content/embedded-content-docs.component.ts": \\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\`// © 2022 SolarWinds Worldwide, LLC. All rights reserved. +// +// Permission is hereby granted, free of charge, to any person obtaining a copy +// of this software and associated documentation files (the "Software"), to +// deal in the Software without restriction, including without limitation the +// rights to use, copy, modify, merge, publish, distribute, sublicense, and/or +// sell copies of the Software, and to permit persons to whom the Software is +// furnished to do so, subject to the following conditions: +// +// The above copyright notice and this permission notice shall be included in +// all copies or substantial portions of the Software. +// +// THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR +// IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, +// FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE +// AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER +// LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, +// OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN +// THE SOFTWARE. + +import { Component, OnInit } from "@angular/core"; + +import { mapContentFile } from "../../../../demo-files-factory"; + +@Component({ + selector: "nui-embedded-content-docs", + templateUrl: "./embedded-content-docs.component.html", + standalone: false, +}) +export class EmbeddedContentDocsComponent implements OnInit { + public embeddedContentWidgetFileText = ""; + public embeddedContentConfiguratorFileText = ""; + + public async ngOnInit(): Promise { + this.embeddedContentWidgetFileText = await import( + "./../../../../../../src/lib/widget-types/embedded-content/embedded-content-widget" + ).then(mapContentFile); + this.embeddedContentWidgetFileText = await import( + "./../../../../../../src/lib/widget-types/embedded-content/embedded-content-configurator" + ).then(mapContentFile); + } +} +\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\`, + "widget-types/embedded-content/embedded-content-docs.module.ts": \\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\`// © 2022 SolarWinds Worldwide, LLC. All rights reserved. +// +// Permission is hereby granted, free of charge, to any person obtaining a copy +// of this software and associated documentation files (the "Software"), to +// deal in the Software without restriction, including without limitation the +// rights to use, copy, modify, merge, publish, distribute, sublicense, and/or +// sell copies of the Software, and to permit persons to whom the Software is +// furnished to do so, subject to the following conditions: +// +// The above copyright notice and this permission notice shall be included in +// all copies or substantial portions of the Software. +// +// THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR +// IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, +// FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE +// AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER +// LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, +// OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN +// THE SOFTWARE. + +import { NgModule } from "@angular/core"; +import { RouterModule, Routes } from "@angular/router"; + +// eslint-disable-next-line max-len +import { + NuiButtonModule, + NuiDocsModule, + NuiMessageModule, + NuiSwitchModule, + DEMO_PATH_TOKEN, +} from "@nova-ui/bits"; +import { NuiDashboardsModule } from "@nova-ui/dashboards"; + +import { EmbeddedContentDocsComponent } from "./embedded-content-docs.component"; +import { EmbeddedContentWidgetExampleComponent } from "./embedded-content-widget-example/embedded-content-widget-example.component"; +import { getDemoFiles } from "../../../../demo-files-factory"; + +const routes: Routes = [ + { + path: "", + component: EmbeddedContentDocsComponent, + data: { + srlc: { + hideIndicator: true, + }, + }, + }, + { + path: "example", + component: EmbeddedContentWidgetExampleComponent, + data: { + srlc: { + hideIndicator: true, + }, + }, + }, +]; + +@NgModule({ + imports: [ + RouterModule.forChild(routes), + NuiButtonModule, + NuiDocsModule, + NuiMessageModule, + NuiDashboardsModule, + NuiSwitchModule, + ], + declarations: [ + EmbeddedContentDocsComponent, + EmbeddedContentWidgetExampleComponent, + ], + providers: [ + { + provide: DEMO_PATH_TOKEN, + useValue: getDemoFiles("embedded-content"), + }, + ], +}) +export default class EmbeddedContentDocsModule {} +\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\`, + "widget-types/embedded-content/embedded-content-widget-example/embedded-content-widget-example.component.ts": \\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\`// © 2022 SolarWinds Worldwide, LLC. All rights reserved. +// +// Permission is hereby granted, free of charge, to any person obtaining a copy +// of this software and associated documentation files (the "Software"), to +// deal in the Software without restriction, including without limitation the +// rights to use, copy, modify, merge, publish, distribute, sublicense, and/or +// sell copies of the Software, and to permit persons to whom the Software is +// furnished to do so, subject to the following conditions: +// +// The above copyright notice and this permission notice shall be included in +// all copies or substantial portions of the Software. +// +// THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR +// IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, +// FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE +// AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER +// LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, +// OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN +// THE SOFTWARE. + +import { ChangeDetectorRef, Component, OnInit } from "@angular/core"; +import { GridsterConfig, GridsterItem } from "angular-gridster2"; + +import { + ComponentRegistryService, + EmbeddedContentComponent, + EmbeddedContentConfigurationComponent, + EmbeddedContentMode, + IDashboard, + IWidget, + IWidgets, + PizzagnaLayer, + WidgetTypesService, +} from "@nova-ui/dashboards"; + +@Component({ + selector: "embedded-content-widget-example", + templateUrl: "./embedded-content-widget-example.component.html", + styleUrls: ["./embedded-content-widget-example.component.less"], + standalone: false, +}) +export class EmbeddedContentWidgetExampleComponent implements OnInit { + // This variable will hold all the data needed to define the layout and behavior of the widgets. + // Pass this to the dashboard component's dashboard input in the template. + public dashboard: IDashboard | undefined; + + // Angular gridster requires a configuration object even if it's empty. + // Pass this to the dashboard component's gridsterConfig input in the template. + public gridsterConfig: GridsterConfig = {}; + + // Boolean passed as an input to the dashboard. When true, widgets can be moved, resized, removed, or edited + public editMode: boolean = false; + + constructor( + // WidgetTypesService provides the widget's necessary structure information + private widgetTypesService: WidgetTypesService, + + private componentRegistry: ComponentRegistryService, + private changeDetectorRef: ChangeDetectorRef + ) {} + + public ngOnInit(): void { + this.prepareNovaDashboards(); + this.initializeDashboard(); + } + + /** Used for restoring widgets state */ + public reInitializeDashboard(): void { + // destroys the components and their providers so the dashboard can re init data + this.dashboard = undefined; + this.changeDetectorRef.detectChanges(); + + this.initializeDashboard(); + } + + public initializeDashboard(): void { + // We're using a static configuration object for this example, but this is where + // the widget's configuration could potentially be populated from a database + const embeddedContentWidget = widgetConfig; + const widgets: IWidgets = { + // Complete the widget with information coming from its type definition + [embeddedContentWidget.id]: + this.widgetTypesService.mergeWithWidgetType( + embeddedContentWidget + ), + }; + + // Setting the widget dimensions and position (this is for gridster) + const positions: Record = { + [embeddedContentWidget.id]: { + cols: 10, + rows: 10, + y: 0, + x: 0, + }, + }; + + // Finally, assigning the variables we created above to the dashboard + this.dashboard = { positions, widgets }; + } + + private prepareNovaDashboards() { + this.componentRegistry.registerByLateLoadKey(EmbeddedContentComponent); + this.componentRegistry.registerByLateLoadKey( + EmbeddedContentConfigurationComponent + ); + } +} + +const widgetConfig: IWidget = { + id: "embeddedContentWidgetId", + type: "embedded-content", + pizzagna: { + [PizzagnaLayer.Configuration]: { + header: { + properties: { + title: "Embedded Content Widget", + subtitle: "", + }, + }, + mainContent: { + properties: { + sanitized: true, + mode: EmbeddedContentMode.URL, + customEmbeddedContent: "https://www.ventusky.com/", + }, + }, + }, + }, +}; +\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\`, + "widget-types/kpi/kpi-docs.component.ts": \\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\`// © 2022 SolarWinds Worldwide, LLC. All rights reserved. +// +// Permission is hereby granted, free of charge, to any person obtaining a copy +// of this software and associated documentation files (the "Software"), to +// deal in the Software without restriction, including without limitation the +// rights to use, copy, modify, merge, publish, distribute, sublicense, and/or +// sell copies of the Software, and to permit persons to whom the Software is +// furnished to do so, subject to the following conditions: +// +// The above copyright notice and this permission notice shall be included in +// all copies or substantial portions of the Software. +// +// THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR +// IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, +// FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE +// AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER +// LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, +// OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN +// THE SOFTWARE. + +import { Component, OnInit } from "@angular/core"; + +import { mapContentFile } from "../../../../demo-files-factory"; + +@Component({ + selector: "nui-kpi-docs", + templateUrl: "./kpi-docs.component.html", + standalone: false, +}) +export class KpiDocsComponent implements OnInit { + public kpiWidgetFileText = ""; + public kpiConfiguratorFileText = ""; + + public async ngOnInit(): Promise { + this.kpiWidgetFileText = await import( + "./../../../../../../src/lib/widget-types/kpi/kpi-widget" + ).then(mapContentFile); + this.kpiConfiguratorFileText = await import( + "./../../../../../../src/lib/widget-types/kpi/kpi-configurator" + ).then(mapContentFile); + } +} +\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\`, + "widget-types/kpi/kpi-docs.module.ts": \\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\`// © 2022 SolarWinds Worldwide, LLC. All rights reserved. +// +// Permission is hereby granted, free of charge, to any person obtaining a copy +// of this software and associated documentation files (the "Software"), to +// deal in the Software without restriction, including without limitation the +// rights to use, copy, modify, merge, publish, distribute, sublicense, and/or +// sell copies of the Software, and to permit persons to whom the Software is +// furnished to do so, subject to the following conditions: +// +// The above copyright notice and this permission notice shall be included in +// all copies or substantial portions of the Software. +// +// THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR +// IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, +// FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE +// AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER +// LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, +// OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN +// THE SOFTWARE. + +import { NgModule } from "@angular/core"; +import { RouterModule, Routes } from "@angular/router"; + +import { + NuiButtonModule, + NuiDocsModule, + NuiMessageModule, + NuiSwitchModule, + DEMO_PATH_TOKEN, +} from "@nova-ui/bits"; +import { + KpiColorComparatorsRegistryService, + NuiDashboardsModule, +} from "@nova-ui/dashboards"; + +import { KpiDocsComponent } from "./kpi-docs.component"; +import { KpiSyncBrokerExampleComponent } from "./kpi-sync-broker/kpi-sync-broker-example.component"; +import { KpiSyncBrokerDocsComponent } from "./kpi-sync-broker-docs.component"; +import { KpiSyncBrokerForAllTilesExampleComponent } from "./kpi-sync-broker-for-all-tiles/kpi-sync-broker-for-all-tiles-example.component"; +import { KpiWidgetExampleComponent } from "./kpi-widget/kpi-widget-example.component"; +import { KpiWidgetBackgroundColorExampleComponent } from "./kpi-widget-background-color/kpi-widget-background-color-example.component"; +import { KpiWidgetBackgroundColorDocsComponent } from "./kpi-widget-background-color-docs.component"; +import { KpiWidgetInteractiveExampleComponent } from "./kpi-widget-interactive/kpi-widget-interactive-example.component"; +import { getDemoFiles } from "../../../../demo-files-factory"; + +const routes: Routes = [ + { + path: "", + component: KpiDocsComponent, + data: { + srlc: { + hideIndicator: true, + }, + showThemeSwitcher: true, + }, + }, + { + path: "example", + component: KpiWidgetExampleComponent, + data: { + srlc: { + hideIndicator: true, + }, + }, + }, + { + path: "background-color", + component: KpiWidgetBackgroundColorDocsComponent, + data: { + srlc: { + hideIndicator: true, + }, + }, + }, + { + path: "sync-broker", + component: KpiSyncBrokerDocsComponent, + data: { + srlc: { + hideIndicator: true, + }, + }, + }, +]; + +@NgModule({ + imports: [ + RouterModule.forChild(routes), + NuiButtonModule, + NuiDocsModule, + NuiMessageModule, + NuiDashboardsModule, + NuiSwitchModule, + ], + declarations: [ + KpiDocsComponent, + KpiWidgetExampleComponent, + KpiWidgetInteractiveExampleComponent, + KpiWidgetBackgroundColorDocsComponent, + KpiWidgetBackgroundColorExampleComponent, + KpiSyncBrokerDocsComponent, + KpiSyncBrokerExampleComponent, + KpiSyncBrokerForAllTilesExampleComponent, + ], + providers: [ + KpiColorComparatorsRegistryService, + { + provide: DEMO_PATH_TOKEN, + useValue: getDemoFiles("kpi"), + }, + ], +}) +export default class KpiDocsModule { + constructor( + private comparatorsRegistry: KpiColorComparatorsRegistryService + ) { + this.backgroundColorDocsSetup(); + } + + private backgroundColorDocsSetup() { + this.comparatorsRegistry.registerComparators({ + "!=": { + comparatorFn: (actual: any, reference: any) => + // eslint-disable-next-line eqeqeq + actual != reference, + label: "Not equal", + }, + }); + } +} +\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\`, + "widget-types/kpi/kpi-sync-broker/kpi-sync-broker-example.component.ts": \\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\`// © 2022 SolarWinds Worldwide, LLC. All rights reserved. +// +// Permission is hereby granted, free of charge, to any person obtaining a copy +// of this software and associated documentation files (the "Software"), to +// deal in the Software without restriction, including without limitation the +// rights to use, copy, modify, merge, publish, distribute, sublicense, and/or +// sell copies of the Software, and to permit persons to whom the Software is +// furnished to do so, subject to the following conditions: +// +// The above copyright notice and this permission notice shall be included in +// all copies or substantial portions of the Software. +// +// THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR +// IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, +// FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE +// AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER +// LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, +// OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN +// THE SOFTWARE. + +import { HttpClient, HttpErrorResponse } from "@angular/common/http"; +import { + ChangeDetectorRef, + Component, + Injectable, + OnDestroy, + OnInit, +} from "@angular/core"; +import { GridsterConfig, GridsterItem } from "angular-gridster2"; +import keyBy from "lodash/keyBy"; +import { BehaviorSubject, of } from "rxjs"; +import { delay, finalize, take } from "rxjs/operators"; + +import { DataSourceService, IFilteringOutputs } from "@nova-ui/bits"; +import { + DATA_SOURCE, + IDashboard, + IKpiData, + IProviderConfiguration, + IWidget, + KpiComponent, + NOVA_KPI_DATASOURCE_ADAPTER, + NOVA_KPI_SCALE_SYNC_BROKER, + PizzagnaLayer, + ProviderRegistryService, + WellKnownPathKey, + WellKnownProviders, + WidgetTypesService, +} from "@nova-ui/dashboards"; + +/** + * A simple KPI data source to retrieve the average rating of Harry Potter and the Sorcerer's Stone (book) via googleapis + */ +@Injectable() +export class AverageRatingKpiDataSource + extends DataSourceService + implements OnDestroy +{ + public static providerId = "AverageRatingKpiDataSource"; + public busy = new BehaviorSubject(false); + + constructor(private http: HttpClient) { + super(); + } + + public async getFilteredData(): Promise { + this.busy.next(true); + return new Promise((resolve) => { + // *** Make a resource request to an external API (if needed) + this.http + .get("https://www.googleapis.com/books/v1/volumes/5MQFrgEACAAJ") + .pipe(finalize(() => this.busy.next(false))) + .subscribe({ + next: (data: any) => { + resolve({ + result: { + value: data.volumeInfo.averageRating, + }, + }); + }, + error: (error: HttpErrorResponse) => { + resolve({ + result: null, + error: { + type: error.status, + }, + }); + }, + }); + }); + } + + public ngOnDestroy(): void { + this.outputsSubject.complete(); + } +} + +/** + * A simple KPI data source to retrieve the ratings count of Harry Potter and the Sorcerer's Stone (book) via googleapis + */ +@Injectable() +export class RatingsCountKpiDataSource + extends DataSourceService + implements OnDestroy +{ + public static providerId = "RatingsCountKpiDataSource"; + + // Use this subject to communicate the data source's busy state + public busy = new BehaviorSubject(false); + + constructor(private http: HttpClient) { + super(); + } + + public async getFilteredData(): Promise { + this.busy.next(true); + return new Promise((resolve) => { + this.http + .get("https://www.googleapis.com/books/v1/volumes/5MQFrgEACAAJ") + .pipe( + delay(2000), + finalize(() => this.busy.next(false)) + ) + .subscribe({ + next: (data: any) => { + resolve({ + result: { + value: data.volumeInfo.ratingsCount, + }, + }); + }, + error: (error: HttpErrorResponse) => { + resolve({ + result: null, + error: { + type: error.status, + }, + }); + }, + }); + }); + } + + public ngOnDestroy(): void { + this.outputsSubject.complete(); + } +} +/** + * A simple KPI data source to retrieve the ratings count of Harry Potter and the Sorcerer's Stone (book) via googleapis + */ +@Injectable() +export class MockKpiDataSource + extends DataSourceService + implements OnDestroy +{ + public static providerId = "MockKpiDataSource"; + + // Use this subject to communicate the data source's busy state + public busy = new BehaviorSubject(false); + + constructor() { + super(); + } + + public async getFilteredData(): Promise { + this.busy.next(true); + return new Promise((resolve) => { + of(3381342) + .pipe( + delay(5000), + take(1), + finalize(() => this.busy.next(false)) + ) + .subscribe({ + next: (data: any) => { + resolve({ + result: { + value: data, + }, + }); + }, + }); + }); + } + + public ngOnDestroy(): void { + this.outputsSubject.complete(); + } +} + +/** + * A component that instantiates the dashboard + */ +@Component({ + selector: "kpi-sync-broker-example", + templateUrl: "./kpi-sync-broker-example.component.html", + styleUrls: ["./kpi-sync-broker-example.component.less"], + standalone: false, +}) +export class KpiSyncBrokerExampleComponent implements OnInit { + public dashboard: IDashboard | undefined; + public gridsterConfig: GridsterConfig = {}; + public editMode: boolean = false; + + constructor( + private widgetTypesService: WidgetTypesService, + private providerRegistry: ProviderRegistryService, + private changeDetectorRef: ChangeDetectorRef + ) {} + + public ngOnInit(): void { + this.setupDashboard(); + + this.initializeDashboard(); + } + + private setupDashboard() { + const widgetTemplate = this.widgetTypesService.getWidgetType("kpi", 1); + + this.widgetTypesService.setNode( + widgetTemplate, + "configurator", + WellKnownPathKey.DataSourceProviders, + [ + AverageRatingKpiDataSource.providerId, + RatingsCountKpiDataSource.providerId, + MockKpiDataSource.providerId, + ] + ); + + this.providerRegistry.setProviders({ + [AverageRatingKpiDataSource.providerId]: { + provide: DATA_SOURCE, + useClass: AverageRatingKpiDataSource, + deps: [HttpClient], + }, + [RatingsCountKpiDataSource.providerId]: { + provide: DATA_SOURCE, + useClass: RatingsCountKpiDataSource, + deps: [HttpClient], + }, + [MockKpiDataSource.providerId]: { + provide: DATA_SOURCE, + useClass: MockKpiDataSource, + deps: [], + }, + }); + } + + /** Used for restoring widgets state */ + public reInitializeDashboard(): void { + // destroys the components and their providers so the dashboard can re init data + this.dashboard = undefined; + this.changeDetectorRef.detectChanges(); + + this.initializeDashboard(); + } + + private initializeDashboard(): void { + const widgetsWithStructure = widgetsConfig.map((w) => + this.widgetTypesService.mergeWithWidgetType(w) + ); + const widgetsIndex = keyBy(widgetsWithStructure, (w: IWidget) => w.id); + + const positions: Record = { + kpiWidgetId: { + cols: 3, + rows: 6, + y: 0, + x: 0, + }, + kpiWidgetId2: { + cols: 3, + rows: 6, + y: 0, + x: 0, + }, + }; + + this.dashboard = { + positions, + widgets: widgetsIndex, + }; + } +} + +const widgetsConfig: IWidget[] = [ + { + id: "kpiWidgetId", + type: "kpi", + pizzagna: { + [PizzagnaLayer.Configuration]: { + header: { + properties: { + title: "NO Sync Broker", + subtitle: "Values sizes are being not synced", + }, + }, + tiles: { + properties: { + nodes: ["kpi1", "kpi2", "kpi3"], + }, + }, + kpi1: { + id: "kpi1", + componentType: KpiComponent.lateLoadKey, + properties: { + widgetData: { + units: \\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\`out of 5 Stars\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\`, + label: \\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\`Average Rating\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\`, + backgroundColor: "lightpink", + }, + }, + providers: { + [WellKnownProviders.DataSource]: { + providerId: AverageRatingKpiDataSource.providerId, + } as IProviderConfiguration, + [WellKnownProviders.Adapter]: { + providerId: NOVA_KPI_DATASOURCE_ADAPTER, + properties: { + componentId: "kpi1", + propertyPath: "widgetData", + }, + } as IProviderConfiguration, + }, + }, + kpi2: { + id: "kpi2", + componentType: KpiComponent.lateLoadKey, + properties: { + widgetData: { + label: \\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\`Another label which might be a pretty long one\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\`, + units: \\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\`Which comes from somewhere\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\`, + backgroundColor: "skyblue", + }, + }, + providers: { + [WellKnownProviders.DataSource]: { + providerId: RatingsCountKpiDataSource.providerId, + } as IProviderConfiguration, + [WellKnownProviders.Adapter]: { + providerId: NOVA_KPI_DATASOURCE_ADAPTER, + properties: { + componentId: "kpi2", + propertyPath: "widgetData", + }, + } as IProviderConfiguration, + }, + }, + kpi3: { + id: "kpi3", + componentType: KpiComponent.lateLoadKey, + properties: { + widgetData: { + label: \\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\`Random\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\`, + units: \\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\`Data\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\`, + }, + }, + providers: { + [WellKnownProviders.DataSource]: { + providerId: MockKpiDataSource.providerId, + } as IProviderConfiguration, + [WellKnownProviders.Adapter]: { + providerId: NOVA_KPI_DATASOURCE_ADAPTER, + properties: { + componentId: "kpi3", + propertyPath: "widgetData", + }, + } as IProviderConfiguration, + }, + }, + }, + }, + }, + { + id: "kpiWidgetId2", + type: "kpi", + pizzagna: { + [PizzagnaLayer.Configuration]: { + header: { + properties: { + title: "WITH Sync Broker", + subtitle: + "Now the values of label, units, and value are being synced", + }, + }, + tiles: { + properties: { + nodes: ["kpi4", "kpi5", "kpi6"], + }, + providers: { + // This is where and how you set the sync broker provider + kpiScaleSyncBroker: { + providerId: NOVA_KPI_SCALE_SYNC_BROKER, + properties: { + scaleSyncConfig: [ + // You can decide which values to keep in sync. For instance, you can leave only 'label' id in the array below + { id: "value" }, + { id: "label" }, + { id: "units" }, + ], + }, + }, + }, + }, + kpi4: { + id: "kpi4", + componentType: KpiComponent.lateLoadKey, + properties: { + widgetData: { + units: \\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\`out of 5 Stars\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\`, + label: \\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\`Average Rating\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\`, + backgroundColor: "lightpink", + }, + }, + providers: { + [WellKnownProviders.DataSource]: { + providerId: AverageRatingKpiDataSource.providerId, + } as IProviderConfiguration, + [WellKnownProviders.Adapter]: { + providerId: NOVA_KPI_DATASOURCE_ADAPTER, + properties: { + componentId: "kpi4", + propertyPath: "widgetData", + }, + } as IProviderConfiguration, + }, + }, + kpi5: { + id: "kpi5", + componentType: KpiComponent.lateLoadKey, + properties: { + widgetData: { + label: \\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\`Another label which might be a pretty long one\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\`, + units: \\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\`Which comes from somewhere\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\`, + backgroundColor: "skyblue", + }, + }, + providers: { + [WellKnownProviders.DataSource]: { + providerId: RatingsCountKpiDataSource.providerId, + } as IProviderConfiguration, + [WellKnownProviders.Adapter]: { + providerId: NOVA_KPI_DATASOURCE_ADAPTER, + properties: { + componentId: "kpi5", + propertyPath: "widgetData", + }, + } as IProviderConfiguration, + }, + }, + kpi6: { + id: "kpi6", + componentType: KpiComponent.lateLoadKey, + properties: { + widgetData: { + label: \\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\`Random\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\`, + units: \\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\`Data\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\`, + }, + }, + providers: { + [WellKnownProviders.DataSource]: { + providerId: MockKpiDataSource.providerId, + } as IProviderConfiguration, + [WellKnownProviders.Adapter]: { + providerId: NOVA_KPI_DATASOURCE_ADAPTER, + properties: { + componentId: "kpi6", + propertyPath: "widgetData", + }, + } as IProviderConfiguration, + }, + }, + }, + }, + }, +]; +\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\`, + "widget-types/kpi/kpi-sync-broker-docs.component.ts": \\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\`// © 2022 SolarWinds Worldwide, LLC. All rights reserved. +// +// Permission is hereby granted, free of charge, to any person obtaining a copy +// of this software and associated documentation files (the "Software"), to +// deal in the Software without restriction, including without limitation the +// rights to use, copy, modify, merge, publish, distribute, sublicense, and/or +// sell copies of the Software, and to permit persons to whom the Software is +// furnished to do so, subject to the following conditions: +// +// The above copyright notice and this permission notice shall be included in +// all copies or substantial portions of the Software. +// +// THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR +// IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, +// FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE +// AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER +// LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, +// OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN +// THE SOFTWARE. + +import { Component } from "@angular/core"; + +@Component({ + selector: "kpi-sync-broker-docs", + templateUrl: "./kpi-sync-broker-docs.component.html", + standalone: false, +}) +export class KpiSyncBrokerDocsComponent { + public kpiScaleSyncBroker = \\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\` +"tiles": { + "providers": { + kpiScaleSyncBroker: { + providerId: NOVA_KPI_SCALE_SYNC_BROKER, + properties: { + scaleSyncConfig: [ + { id: "value" }, + { id: "label" }, + { id: "units" }, + ], + }, + }, + }, +}, +\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\`; + + public defineScaleBrokerOnDashboardSetup = \\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\` +// To add the sync broker globally to all the kpi tiles you may start with setting up the broker config +// Here you define which values to keep in sync +const brokerConfig = { + providerId: NOVA_KPI_SCALE_SYNC_BROKER, + properties: { + scaleSyncConfig: [ + { id: "value" }, + { id: "label" }, + { id: "units" }, + ], + }, + }; + +// And here is how you set the sync broker for every KPI widget in the dashboard. +// Later, you will be able to override this setting for each separate KPI widget in the configuration (just like it is shown in the third +// width of the example with the 'kpiWidgetId3') +this.widgetTypesService.setNode( + widgetTemplate, + "widget", + "tiles.providers.kpiScaleSyncBroker", + brokerConfig +); +\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\`; +} +\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\`, + "widget-types/kpi/kpi-sync-broker-for-all-tiles/kpi-sync-broker-for-all-tiles-example.component.ts": \\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\`// © 2022 SolarWinds Worldwide, LLC. All rights reserved. +// +// Permission is hereby granted, free of charge, to any person obtaining a copy +// of this software and associated documentation files (the "Software"), to +// deal in the Software without restriction, including without limitation the +// rights to use, copy, modify, merge, publish, distribute, sublicense, and/or +// sell copies of the Software, and to permit persons to whom the Software is +// furnished to do so, subject to the following conditions: +// +// The above copyright notice and this permission notice shall be included in +// all copies or substantial portions of the Software. +// +// THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR +// IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, +// FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE +// AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER +// LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, +// OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN +// THE SOFTWARE. + +import { HttpClient, HttpErrorResponse } from "@angular/common/http"; +import { + ChangeDetectorRef, + Component, + Injectable, + OnDestroy, + OnInit, +} from "@angular/core"; +import { GridsterConfig, GridsterItem } from "angular-gridster2"; +import keyBy from "lodash/keyBy"; +import { BehaviorSubject, of } from "rxjs"; +import { delay, finalize, take } from "rxjs/operators"; + +import { DataSourceService, IFilteringOutputs } from "@nova-ui/bits"; +import { + DATA_SOURCE, + IDashboard, + IKpiData, + IProviderConfiguration, + IWidget, + KpiComponent, + NOVA_KPI_DATASOURCE_ADAPTER, + NOVA_KPI_SCALE_SYNC_BROKER, + PizzagnaLayer, + ProviderRegistryService, + WellKnownPathKey, + WellKnownProviders, + WidgetTypesService, +} from "@nova-ui/dashboards"; + +/** + * A simple KPI data source to retrieve the average rating of Harry Potter and the Sorcerer's Stone (book) via googleapis + */ +@Injectable() +export class AverageRatingKpiDataSource + extends DataSourceService + implements OnDestroy +{ + public static providerId = "AverageRatingKpiDataSource"; + public busy = new BehaviorSubject(false); + + constructor(private http: HttpClient) { + super(); + } + + public async getFilteredData(): Promise { + this.busy.next(true); + return new Promise((resolve) => { + // *** Make a resource request to an external API (if needed) + this.http + .get("https://www.googleapis.com/books/v1/volumes/5MQFrgEACAAJ") + .pipe(finalize(() => this.busy.next(false))) + .subscribe({ + next: (data: any) => { + resolve({ + result: { + value: data.volumeInfo.averageRating, + }, + }); + }, + error: (error: HttpErrorResponse) => { + resolve({ + result: null, + error: { + type: error.status, + }, + }); + }, + }); + }); + } + + public ngOnDestroy(): void { + this.outputsSubject.complete(); + } +} + +/** + * A simple KPI data source to retrieve the ratings count of Harry Potter and the Sorcerer's Stone (book) via googleapis + */ +@Injectable() +export class RatingsCountKpiDataSource + extends DataSourceService + implements OnDestroy +{ + public static providerId = "RatingsCountKpiDataSource"; + + // Use this subject to communicate the data source's busy state + public busy = new BehaviorSubject(false); + + constructor(private http: HttpClient) { + super(); + } + + public async getFilteredData(): Promise { + this.busy.next(true); + return new Promise((resolve) => { + this.http + .get("https://www.googleapis.com/books/v1/volumes/5MQFrgEACAAJ") + .pipe( + delay(2000), + finalize(() => this.busy.next(false)) + ) + .subscribe({ + next: (data: any) => { + resolve({ + result: { + value: data.volumeInfo.ratingsCount, + }, + }); + }, + error: (error: HttpErrorResponse) => { + resolve({ + result: null, + error: { + type: error.status, + }, + }); + }, + }); + }); + } + + public ngOnDestroy(): void { + this.outputsSubject.complete(); + } +} +/** + * A simple KPI data source to retrieve the ratings count of Harry Potter and the Sorcerer's Stone (book) via googleapis + */ +@Injectable() +export class MockKpiDataSource + extends DataSourceService + implements OnDestroy +{ + public static providerId = "MockKpiDataSource"; + + // Use this subject to communicate the data source's busy state + public busy = new BehaviorSubject(false); + public value: number = 3381342; + + constructor() { + super(); + } + + public async getFilteredData(): Promise { + this.busy.next(true); + return new Promise((resolve) => { + of(this.value) + .pipe( + delay(5000), + take(1), + finalize(() => this.busy.next(false)) + ) + .subscribe({ + next: (data: any) => { + resolve({ + result: { + value: data, + }, + }); + }, + }); + }); + } + + public ngOnDestroy(): void { + this.outputsSubject.complete(); + } +} + +/** + * A component that instantiates the dashboard + */ +@Component({ + selector: "kpi-sync-broker-for-all-tiles-example", + templateUrl: "./kpi-sync-broker-for-all-tiles-example.component.html", + styleUrls: ["./kpi-sync-broker-for-all-tiles-example.component.less"], + standalone: false, +}) +export class KpiSyncBrokerForAllTilesExampleComponent implements OnInit { + public dashboard: IDashboard | undefined; + public gridsterConfig: GridsterConfig = {}; + public editMode: boolean = false; + + constructor( + private widgetTypesService: WidgetTypesService, + private providerRegistry: ProviderRegistryService, + private changeDetectorRef: ChangeDetectorRef + ) {} + + public ngOnInit(): void { + this.setupDashboard(); + + this.initializeDashboard(); + } + + /** Used for restoring widgets state */ + public reInitializeDashboard(): void { + // destroys the components and their providers so the dashboard can re init data + this.dashboard = undefined; + this.changeDetectorRef.detectChanges(); + + this.initializeDashboard(); + } + + private setupDashboard() { + // To add the sync broker globally to all the kpi tiles you may start with setting up the broker config + // Here you define which values to keep in sync + const brokerConfig = { + providerId: NOVA_KPI_SCALE_SYNC_BROKER, + properties: { + scaleSyncConfig: [ + { id: "value" }, + { id: "label" }, + { id: "units" }, + ], + }, + }; + const widgetTemplate = this.widgetTypesService.getWidgetType("kpi", 1); + + this.widgetTypesService.setNode( + widgetTemplate, + "configurator", + WellKnownPathKey.DataSourceProviders, + [ + AverageRatingKpiDataSource.providerId, + RatingsCountKpiDataSource.providerId, + MockKpiDataSource.providerId, + ] + ); + + // And here is how you set the sync broker for every KPI widget in the dashboard. + // Later, you will be able to override this setting for each separate KPI widget in the configuration (just like it is shown in the third + // width of the example with the 'kpiWidgetId3') + this.widgetTypesService.setNode( + widgetTemplate, + "widget", + "tiles.providers.kpiScaleSyncBroker", + brokerConfig + ); + + this.providerRegistry.setProviders({ + [AverageRatingKpiDataSource.providerId]: { + provide: DATA_SOURCE, + useClass: AverageRatingKpiDataSource, + deps: [HttpClient], + }, + [RatingsCountKpiDataSource.providerId]: { + provide: DATA_SOURCE, + useClass: RatingsCountKpiDataSource, + deps: [HttpClient], + }, + [MockKpiDataSource.providerId]: { + provide: DATA_SOURCE, + useClass: MockKpiDataSource, + deps: [], + }, + }); + } + + private initializeDashboard(): void { + const widgetsWithStructure = widgetsConfig.map((w) => + this.widgetTypesService.mergeWithWidgetType(w) + ); + const widgetsIndex = keyBy(widgetsWithStructure, (w: IWidget) => w.id); + + const positions: Record = { + kpiWidgetId: { + cols: 3, + rows: 6, + y: 0, + x: 0, + }, + kpiWidgetId2: { + cols: 3, + rows: 6, + y: 0, + x: 3, + }, + kpiWidgetId3: { + cols: 3, + rows: 6, + y: 0, + x: 6, + }, + }; + + this.dashboard = { + positions, + widgets: widgetsIndex, + }; + } +} + +const widgetsConfig: IWidget[] = [ + { + id: "kpiWidgetId", + type: "kpi", + pizzagna: { + [PizzagnaLayer.Configuration]: { + header: { + properties: { + title: "Sync Broker Applied for ALL Widgets", + subtitle: "Values are being synced", + }, + }, + tiles: { + properties: { + nodes: ["kpi1", "kpi2", "kpi3"], + }, + }, + kpi1: { + id: "kpi1", + componentType: KpiComponent.lateLoadKey, + properties: { + widgetData: { + units: \\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\`out of 5 Stars\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\`, + label: \\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\`Average Rating\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\`, + backgroundColor: "lightpink", + }, + }, + providers: { + [WellKnownProviders.DataSource]: { + providerId: AverageRatingKpiDataSource.providerId, + } as IProviderConfiguration, + [WellKnownProviders.Adapter]: { + providerId: NOVA_KPI_DATASOURCE_ADAPTER, + properties: { + componentId: "kpi1", + propertyPath: "widgetData", + }, + } as IProviderConfiguration, + }, + }, + kpi2: { + id: "kpi2", + componentType: KpiComponent.lateLoadKey, + properties: { + widgetData: { + label: \\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\`Another label which might be a pretty long one\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\`, + units: \\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\`Which comes from somewhere\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\`, + backgroundColor: "skyblue", + }, + }, + providers: { + [WellKnownProviders.DataSource]: { + providerId: RatingsCountKpiDataSource.providerId, + } as IProviderConfiguration, + [WellKnownProviders.Adapter]: { + providerId: NOVA_KPI_DATASOURCE_ADAPTER, + properties: { + componentId: "kpi2", + propertyPath: "widgetData", + }, + } as IProviderConfiguration, + }, + }, + kpi3: { + id: "kpi3", + componentType: KpiComponent.lateLoadKey, + properties: { + widgetData: { + label: \\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\`Random\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\`, + units: \\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\`Data\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\`, + }, + }, + providers: { + [WellKnownProviders.DataSource]: { + providerId: MockKpiDataSource.providerId, + } as IProviderConfiguration, + [WellKnownProviders.Adapter]: { + providerId: NOVA_KPI_DATASOURCE_ADAPTER, + properties: { + componentId: "kpi3", + propertyPath: "widgetData", + }, + } as IProviderConfiguration, + }, + }, + }, + }, + }, + { + id: "kpiWidgetId2", + type: "kpi", + pizzagna: { + [PizzagnaLayer.Configuration]: { + header: { + properties: { + title: "Sync Broker Applied for ALL Widgets", + subtitle: + "Now the values of label, units, and value are being synced", + }, + }, + tiles: { + properties: { + nodes: ["kpi1", "kpi2", "kpi3"], + }, + }, + kpi1: { + id: "kpi1", + componentType: KpiComponent.lateLoadKey, + properties: { + widgetData: { + units: \\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\`out of 5 Stars\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\`, + label: \\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\`Average Rating\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\`, + backgroundColor: "lightpink", + }, + }, + providers: { + [WellKnownProviders.DataSource]: { + providerId: AverageRatingKpiDataSource.providerId, + } as IProviderConfiguration, + [WellKnownProviders.Adapter]: { + providerId: NOVA_KPI_DATASOURCE_ADAPTER, + properties: { + componentId: "kpi1", + propertyPath: "widgetData", + }, + } as IProviderConfiguration, + }, + }, + kpi2: { + id: "kpi2", + componentType: KpiComponent.lateLoadKey, + properties: { + widgetData: { + label: \\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\`Another label which might be a pretty long one\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\`, + units: \\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\`Which comes from somewhere\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\`, + backgroundColor: "skyblue", + }, + }, + providers: { + [WellKnownProviders.DataSource]: { + providerId: RatingsCountKpiDataSource.providerId, + } as IProviderConfiguration, + [WellKnownProviders.Adapter]: { + providerId: NOVA_KPI_DATASOURCE_ADAPTER, + properties: { + componentId: "kpi2", + propertyPath: "widgetData", + }, + } as IProviderConfiguration, + }, + }, + kpi3: { + id: "kpi3", + componentType: KpiComponent.lateLoadKey, + properties: { + widgetData: { + label: \\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\`Random\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\`, + units: \\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\`Data\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\`, + }, + }, + providers: { + [WellKnownProviders.DataSource]: { + providerId: MockKpiDataSource.providerId, + } as IProviderConfiguration, + [WellKnownProviders.Adapter]: { + providerId: NOVA_KPI_DATASOURCE_ADAPTER, + properties: { + componentId: "kpi3", + propertyPath: "widgetData", + }, + } as IProviderConfiguration, + }, + }, + }, + }, + }, + { + id: "kpiWidgetId3", + type: "kpi", + pizzagna: { + [PizzagnaLayer.Configuration]: { + header: { + properties: { + title: "Here We Sync Only Labels and Units", + subtitle: + "Now only the label, and units are being synced", + }, + }, + tiles: { + properties: { + nodes: ["kpi1", "kpi2", "kpi3"], + }, + providers: { + // This is where and how you can override the globally set broker config + kpiScaleSyncBroker: { + providerId: NOVA_KPI_SCALE_SYNC_BROKER, + properties: { + scaleSyncConfig: [ + { id: "label" }, + { id: "units" }, + ], + }, + }, + }, + }, + kpi1: { + id: "kpi1", + componentType: KpiComponent.lateLoadKey, + properties: { + widgetData: { + units: \\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\`out of 5 Stars\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\`, + label: \\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\`Average Rating\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\`, + backgroundColor: "lightpink", + }, + }, + providers: { + [WellKnownProviders.DataSource]: { + providerId: AverageRatingKpiDataSource.providerId, + } as IProviderConfiguration, + [WellKnownProviders.Adapter]: { + providerId: NOVA_KPI_DATASOURCE_ADAPTER, + properties: { + componentId: "kpi1", + propertyPath: "widgetData", + }, + } as IProviderConfiguration, + }, + }, + kpi2: { + id: "kpi2", + componentType: KpiComponent.lateLoadKey, + properties: { + widgetData: { + label: \\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\`Another label which might be a pretty long one\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\`, + units: \\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\`Which comes from somewhere\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\`, + backgroundColor: "skyblue", + }, + }, + providers: { + [WellKnownProviders.DataSource]: { + providerId: RatingsCountKpiDataSource.providerId, + } as IProviderConfiguration, + [WellKnownProviders.Adapter]: { + providerId: NOVA_KPI_DATASOURCE_ADAPTER, + properties: { + componentId: "kpi2", + propertyPath: "widgetData", + }, + } as IProviderConfiguration, + }, + }, + kpi3: { + id: "kpi3", + componentType: KpiComponent.lateLoadKey, + properties: { + widgetData: { + label: \\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\`Random\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\`, + units: \\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\`Data\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\`, + }, + }, + providers: { + [WellKnownProviders.DataSource]: { + providerId: MockKpiDataSource.providerId, + } as IProviderConfiguration, + [WellKnownProviders.Adapter]: { + providerId: NOVA_KPI_DATASOURCE_ADAPTER, + properties: { + componentId: "kpi3", + propertyPath: "widgetData", + }, + } as IProviderConfiguration, + }, + }, + }, + }, + }, +]; +\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\`, + "widget-types/kpi/kpi-widget/kpi-widget-example.component.ts": \\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\`// © 2022 SolarWinds Worldwide, LLC. All rights reserved. +// +// Permission is hereby granted, free of charge, to any person obtaining a copy +// of this software and associated documentation files (the "Software"), to +// deal in the Software without restriction, including without limitation the +// rights to use, copy, modify, merge, publish, distribute, sublicense, and/or +// sell copies of the Software, and to permit persons to whom the Software is +// furnished to do so, subject to the following conditions: +// +// The above copyright notice and this permission notice shall be included in +// all copies or substantial portions of the Software. +// +// THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR +// IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, +// FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE +// AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER +// LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, +// OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN +// THE SOFTWARE. + +import { HttpClient, HttpErrorResponse } from "@angular/common/http"; +import { Component, Injectable, OnDestroy, OnInit } from "@angular/core"; +import { GridsterConfig, GridsterItem } from "angular-gridster2"; +import { BehaviorSubject } from "rxjs"; +import { finalize } from "rxjs/operators"; + +import { DataSourceService, IFilteringOutputs } from "@nova-ui/bits"; +import { + DATA_SOURCE, + DEFAULT_PIZZAGNA_ROOT, + IDashboard, + IKpiData, + IProviderConfiguration, + IRefresherProperties, + IWidget, + IWidgets, + KpiComponent, + NOVA_KPI_DATASOURCE_ADAPTER, + PizzagnaLayer, + ProviderRegistryService, + WellKnownPathKey, + WellKnownProviders, + WidgetTypesService, +} from "@nova-ui/dashboards"; + +/** + * A simple KPI data source to retrieve the average rating of Harry Potter and the Sorcerer's Stone (book) via googleapis + */ +@Injectable() +export class AverageRatingKpiDataSource + extends DataSourceService + implements OnDestroy +{ + // This is the ID we'll use to identify the provider + public static providerId = "AverageRatingKpiDataSource"; + + // Use this subject to communicate the data source's busy state + public busy = new BehaviorSubject(false); + + constructor(private http: HttpClient) { + super(); + } + + // In this example, getFilteredData is invoked every 10 minutes (Take a look at the refresher + // provider definition in the widget configuration below to see how the interval is set) + public async getFilteredData(): Promise { + this.busy.next(true); + return new Promise((resolve) => { + // *** Make a resource request to an external API (if needed) + this.http + .get("https://www.googleapis.com/books/v1/volumes/5MQFrgEACAAJ") + .pipe(finalize(() => this.busy.next(false))) + .subscribe({ + next: (data: any) => { + resolve({ + result: { + value: data.volumeInfo.averageRating, + }, + }); + }, + error: (error: HttpErrorResponse) => { + resolve({ + result: null, + error: { + type: error.status, + }, + }); + }, + }); + }); + } + + public ngOnDestroy(): void { + this.outputsSubject.complete(); + } +} + +/** + * A component that instantiates the dashboard + */ +@Component({ + selector: "kpi-widget-example", + templateUrl: "./kpi-widget-example.component.html", + styleUrls: ["./kpi-widget-example.component.less"], + standalone: false, +}) +export class KpiWidgetExampleComponent implements OnInit { + // This variable will hold all the data needed to define the layout and behavior of the widgets. + // Pass this to the dashboard component's dashboard input in the template. + public dashboard: IDashboard; + + // Angular gridster requires a configuration object even if it's empty. + // Pass this to the dashboard component's gridsterConfig input in the template. + public gridsterConfig: GridsterConfig = {}; + + // Boolean passed as an input to the dashboard. When true, widgets can be moved, resized, removed, or edited + public editMode: boolean = false; + + constructor( + // WidgetTypesService provides the widget's necessary structure information + private widgetTypesService: WidgetTypesService, + + // In general, the ProviderRegistryService is used for making entities available for injection into dynamically loaded components. + private providerRegistry: ProviderRegistryService + ) {} + + public ngOnInit(): void { + // Grabbing the widget's default template which will be needed as a parameter for setNode + const widgetTemplate = this.widgetTypesService.getWidgetType("kpi", 1); + // Registering our data sources as dropdown options in the widget editor/configurator + // Note: This could also be done in the parent module's constructor so that + // multiple dashboards could have access to the same widget template modification. + this.widgetTypesService.setNode( + // This is the template we grabbed above with getWidgetType + widgetTemplate, + // We are setting the editor/configurator part of the widget template + "configurator", + // This indicates which node you are changing and we want to change + // the data source providers available for selection in the editor. + WellKnownPathKey.DataSourceProviders, + // We are setting the data sources available for selection in the editor + [AverageRatingKpiDataSource.providerId] + ); + + // Registering the data source for injection into the KPI tile. + // Note: Each tile of a KPI widget is assigned its own instance of the data source + this.providerRegistry.setProviders({ + [AverageRatingKpiDataSource.providerId]: { + provide: DATA_SOURCE, + useClass: AverageRatingKpiDataSource, + // Any dependencies that need to be injected into the provider must be listed here + deps: [HttpClient], + }, + }); + + this.initializeDashboard(); + } + + public initializeDashboard(): void { + // We're using a static configuration object for this example, but this is where + // the widget's configuration could potentially be populated from a database + const kpiWidget = widgetConfig; + const widgetIndex: IWidgets = { + // Complete the KPI widget with information coming from its type definition + [kpiWidget.id]: + this.widgetTypesService.mergeWithWidgetType(kpiWidget), + }; + + // Setting the widget dimensions and position (this is for gridster) + const positions: Record = { + [kpiWidget.id]: { + cols: 4, + rows: 6, + y: 0, + x: 0, + }, + }; + + // Finally, assigning the variables we created above to the dashboard + this.dashboard = { + positions, + widgets: widgetIndex, + }; + } +} + +const widgetConfig: IWidget = { + id: "kpiWidgetId", + type: "kpi", + pizzagna: { + [PizzagnaLayer.Configuration]: { + [DEFAULT_PIZZAGNA_ROOT]: { + providers: { + [WellKnownProviders.Refresher]: { + properties: { + // Configuring the refresher interval so that our data source is invoked every ten minutes + interval: 60 * 10, + enabled: true, + } as IRefresherProperties, + } as Partial, + }, + }, + header: { + properties: { + title: "Harry Potter and the Sorcerer's Stone", + subtitle: "By J. K. Rowling", + }, + }, + tiles: { + properties: { + nodes: ["kpi1"], + }, + }, + kpi1: { + id: "kpi1", + componentType: KpiComponent.lateLoadKey, + properties: { + widgetData: { + units: \\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\`out of 5 Stars\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\`, + label: \\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\`Average Rating\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\`, + }, + }, + providers: { + [WellKnownProviders.DataSource]: { + // Setting the data source providerId for the tile with id "kpi1" + providerId: AverageRatingKpiDataSource.providerId, + } as IProviderConfiguration, + [WellKnownProviders.Adapter]: { + providerId: NOVA_KPI_DATASOURCE_ADAPTER, + properties: { + componentId: "kpi1", + propertyPath: "widgetData", + }, + } as IProviderConfiguration, + }, + }, + }, + }, +}; +\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\`, + "widget-types/kpi/kpi-widget-background-color/kpi-widget-background-color-example.component.ts": \\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\`// © 2022 SolarWinds Worldwide, LLC. All rights reserved. +// +// Permission is hereby granted, free of charge, to any person obtaining a copy +// of this software and associated documentation files (the "Software"), to +// deal in the Software without restriction, including without limitation the +// rights to use, copy, modify, merge, publish, distribute, sublicense, and/or +// sell copies of the Software, and to permit persons to whom the Software is +// furnished to do so, subject to the following conditions: +// +// The above copyright notice and this permission notice shall be included in +// all copies or substantial portions of the Software. +// +// THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR +// IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, +// FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE +// AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER +// LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, +// OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN +// THE SOFTWARE. + +import { HttpClient, HttpErrorResponse } from "@angular/common/http"; +import { + ChangeDetectorRef, + Component, + Injectable, + OnDestroy, + OnInit, +} from "@angular/core"; +import { GridsterConfig, GridsterItem } from "angular-gridster2"; +import { BehaviorSubject } from "rxjs"; +import { finalize } from "rxjs/operators"; + +import { DataSourceService, IFilteringOutputs } from "@nova-ui/bits"; +import { + DATA_SOURCE, + DEFAULT_KPI_BACKGROUND_COLORS, + IDashboard, + IKpiColorRules, + IKpiData, + IProviderConfiguration, + IWidget, + IWidgets, + KpiComponent, + NOVA_KPI_COLOR_PRIORITIZER, + NOVA_KPI_DATASOURCE_ADAPTER, + PizzagnaLayer, + ProviderRegistryService, + WellKnownPathKey, + WellKnownProviders, + WidgetTypesService, +} from "@nova-ui/dashboards"; + +/** + * A simple KPI data source to retrieve the average rating of Harry Potter and the Sorcerer's Stone (book) via googleapis + */ +@Injectable() +export class AverageRatingKpiDataSource + extends DataSourceService + implements OnDestroy +{ + public static providerId = "AverageRatingKpiDataSource"; + public busy = new BehaviorSubject(false); + + constructor(private http: HttpClient) { + super(); + } + + public async getFilteredData(): Promise { + this.busy.next(true); + return new Promise((resolve) => { + // *** Make a resource request to an external API (if needed) + this.http + .get("https://www.googleapis.com/books/v1/volumes/5MQFrgEACAAJ") + .pipe(finalize(() => this.busy.next(false))) + .subscribe({ + next: (data: any) => { + resolve({ + result: { + value: data.volumeInfo.averageRating, + // setting the color on the dataSource "Sea Green", + // uncomment to get the background color update from the "Data" layer + // backgroundColor: "var(--nui-color-chart-three)", + }, + }); + }, + error: (error: HttpErrorResponse) => { + resolve({ + result: null, + error: { + type: error.status, + }, + }); + }, + }); + }); + } + + public ngOnDestroy(): void { + this.outputsSubject.complete(); + } +} + +/** + * A component that instantiates the dashboard + */ +@Component({ + selector: "kpi-widget-background-color-example", + templateUrl: "./kpi-widget-background-color-example.component.html", + styleUrls: ["./kpi-widget-background-color-example.component.less"], + standalone: false, +}) +export class KpiWidgetBackgroundColorExampleComponent implements OnInit { + public dashboard: IDashboard | undefined; + public gridsterConfig: GridsterConfig = {}; + public editMode: boolean = false; + + constructor( + private widgetTypesService: WidgetTypesService, + private providerRegistry: ProviderRegistryService, + private changeDetectorRef: ChangeDetectorRef + ) {} + + public ngOnInit(): void { + this.setupDashboard(); + + // KPI tile default color setup + this.setupDefaultColorStructure(); + + // Sets the custom pallette to the 'Description' section + this.setupCustomPalletteDescription(); + + // Sets the custom pallette to the 'Background color rules' section + this.setupCustomPalletteRules(); + + this.initializeDashboard(); + } + + /** Used for restoring widgets state */ + public reInitializeDashboard(): void { + // destroys the components and their providers so the dashboard can re init data + this.dashboard = undefined; + this.changeDetectorRef.detectChanges(); + + this.initializeDashboard(); + } + + private setupCustomPalletteDescription() { + const kpiWidgetTemplate = this.widgetTypesService.getWidgetType( + "kpi", + 1 + ); + this.widgetTypesService.setNode( + kpiWidgetTemplate, + "configurator", + WellKnownPathKey.TileDescriptionBackgroundColors, + [ + { color: "var(--nui-color-chart-one)", label: "Blue" }, + { + color: "var(--nui-color-chart-one-light)", + label: "Blue Light", + }, + { + color: "var(--nui-color-chart-one-dark)", + label: "Blue Dark", + }, + ] + ); + } + + private setupCustomPalletteRules() { + const kpiWidgetTemplate = this.widgetTypesService.getWidgetType( + "kpi", + 1 + ); + this.widgetTypesService.setNode( + kpiWidgetTemplate, + "configurator", + WellKnownPathKey.TileBackgroundColorRulesBackgroundColors, + [ + { color: "red", label: "Native Red" }, + ...DEFAULT_KPI_BACKGROUND_COLORS, + ] + ); + } + + private setupDefaultColorStructure() { + const widgetTemplate = this.widgetTypesService.getWidgetType("kpi", 1); + this.widgetTypesService.setNode( + widgetTemplate, + "widget", + "tiles.properties.template.properties.widgetData.backgroundColor", + "red" + ); + } + + private setupDashboard() { + const widgetTemplate = this.widgetTypesService.getWidgetType("kpi", 1); + this.widgetTypesService.setNode( + widgetTemplate, + "configurator", + WellKnownPathKey.DataSourceProviders, + [AverageRatingKpiDataSource.providerId] + ); + + this.providerRegistry.setProviders({ + [AverageRatingKpiDataSource.providerId]: { + provide: DATA_SOURCE, + useClass: AverageRatingKpiDataSource, + deps: [HttpClient], + }, + }); + } + + private initializeDashboard(): void { + const kpiWidget = widgetConfig; + const widgetIndex: IWidgets = { + [kpiWidget.id]: + this.widgetTypesService.mergeWithWidgetType(kpiWidget), + }; + + const positions: Record = { + [kpiWidget.id]: { + cols: 4, + rows: 6, + y: 0, + x: 0, + }, + }; + + this.dashboard = { + positions, + widgets: widgetIndex, + }; + } +} + +const widgetConfig: IWidget = { + id: "kpiWidgetId", + type: "kpi", + pizzagna: { + [PizzagnaLayer.Configuration]: { + header: { + properties: { + title: "Harry Potter and the Sorcerer's Stone", + subtitle: "By J. K. Rowling", + }, + }, + tiles: { + properties: { + nodes: ["kpi1"], + }, + }, + kpi1: { + id: "kpi1", + componentType: KpiComponent.lateLoadKey, + properties: { + widgetData: { + units: \\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\`out of 5 Stars\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\`, + label: \\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\`Average Rating\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\`, + // Configuration color "Blue" + backgroundColor: "var(--nui-color-chart-one)", + }, + }, + providers: { + [WellKnownProviders.DataSource]: { + providerId: AverageRatingKpiDataSource.providerId, + } as IProviderConfiguration, + [WellKnownProviders.Adapter]: { + providerId: NOVA_KPI_DATASOURCE_ADAPTER, + properties: { + componentId: "kpi1", + propertyPath: "widgetData", + }, + } as IProviderConfiguration, + [WellKnownProviders.KpiColorPrioritizer]: { + providerId: NOVA_KPI_COLOR_PRIORITIZER, + properties: { + // Color Prioritizer Rules + // settings rules - if the value is more than "2" display "Violet" color + rules: [ + { + comparisonType: ">", + value: 2, + color: "var(--nui-color-chart-four)", + }, + ] as IKpiColorRules[], + }, + } as IProviderConfiguration, + }, + }, + }, + }, +}; +\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\`, + "widget-types/kpi/kpi-widget-background-color-docs.component.ts": \\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\`// © 2022 SolarWinds Worldwide, LLC. All rights reserved. +// +// Permission is hereby granted, free of charge, to any person obtaining a copy +// of this software and associated documentation files (the "Software"), to +// deal in the Software without restriction, including without limitation the +// rights to use, copy, modify, merge, publish, distribute, sublicense, and/or +// sell copies of the Software, and to permit persons to whom the Software is +// furnished to do so, subject to the following conditions: +// +// The above copyright notice and this permission notice shall be included in +// all copies or substantial portions of the Software. +// +// THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR +// IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, +// FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE +// AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER +// LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, +// OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN +// THE SOFTWARE. + +import { Component } from "@angular/core"; + +@Component({ + selector: "nui-kpi-background-color-docs", + templateUrl: "./kpi-widget-background-color-docs.component.html", + standalone: false, +}) +export class KpiWidgetBackgroundColorDocsComponent { + public comparatorsRegistryCode = \\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\` + this.comparatorsRegistry.registerComparators({ + "!=": { + comparatorFn: (actual: any, reference: any) => actual != reference, + label: "Not equal", + }, + }); + \\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\`; +} +\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\`, + "widget-types/kpi/kpi-widget-interactive/kpi-widget-interactive-example.component.ts": \\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\`// © 2022 SolarWinds Worldwide, LLC. All rights reserved. +// +// Permission is hereby granted, free of charge, to any person obtaining a copy +// of this software and associated documentation files (the "Software"), to +// deal in the Software without restriction, including without limitation the +// rights to use, copy, modify, merge, publish, distribute, sublicense, and/or +// sell copies of the Software, and to permit persons to whom the Software is +// furnished to do so, subject to the following conditions: +// +// The above copyright notice and this permission notice shall be included in +// all copies or substantial portions of the Software. +// +// THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR +// IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, +// FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE +// AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER +// LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, +// OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN +// THE SOFTWARE. + +import { HttpClient, HttpErrorResponse } from "@angular/common/http"; +import { Component, Injectable, OnDestroy, OnInit } from "@angular/core"; +import { GridsterConfig, GridsterItem } from "angular-gridster2"; +import { BehaviorSubject } from "rxjs"; +import { finalize } from "rxjs/operators"; + +import { DataSourceService, IFilteringOutputs } from "@nova-ui/bits"; +import { + DATA_SOURCE, + IDashboard, + IKpiData, + IProviderConfiguration, + IWidget, + IWidgets, + KpiComponent, + NOVA_KPI_DATASOURCE_ADAPTER, + NOVA_URL_INTERACTION_HANDLER, + PizzagnaLayer, + ProviderRegistryService, + WellKnownPathKey, + WellKnownProviders, + WidgetTypesService, +} from "@nova-ui/dashboards"; + +/** + * A simple KPI data source to retrieve the average rating of Harry Potter and the Sorcerer's Stone (book) via googleapis + */ +@Injectable() +export class BookRatingDataSource + extends DataSourceService + implements OnDestroy +{ + // This is the ID we'll use to identify the provider + public static providerId = "BookRatingDataSource"; + + // Use this subject to communicate the data source's busy state + public busy = new BehaviorSubject(false); + + constructor(private http: HttpClient) { + super(); + } + + // In this example, getFilteredData is invoked every 10 minutes (Take a look at the refresher + // provider definition in the widget configuration below to see how the interval is set) + public async getFilteredData(): Promise { + this.busy.next(true); + return new Promise((resolve) => { + // *** Make a resource request to an external API (if needed) + this.http + .get("https://www.googleapis.com/books/v1/volumes/zpvysRGsBlwC") + .pipe(finalize(() => this.busy.next(false))) + .subscribe({ + next: (data: any) => { + resolve({ + result: { + value: data.volumeInfo.averageRating, + link: data.volumeInfo.infoLink, + }, + }); + }, + error: (error: HttpErrorResponse) => { + resolve({ + result: null, + error: { + type: error.status, + }, + }); + }, + }); + }); + } + + public ngOnDestroy(): void { + this.outputsSubject.complete(); + } +} + +/** + * A component that instantiates the dashboard + */ +@Component({ + selector: "kpi-widget-interactive-example", + templateUrl: "./kpi-widget-interactive-example.component.html", + styleUrls: ["./kpi-widget-interactive-example.component.less"], + standalone: false, +}) +export class KpiWidgetInteractiveExampleComponent implements OnInit { + // This variable will hold all the data needed to define the layout and behavior of the widgets. + // Pass this to the dashboard component's dashboard input in the template. + public dashboard: IDashboard; + + // Angular gridster requires a configuration object even if it's empty. + // Pass this to the dashboard component's gridsterConfig input in the template. + public gridsterConfig: GridsterConfig = {}; + + // Boolean passed as an input to the dashboard. When true, widgets can be moved, resized, removed, or edited + public editMode: boolean = false; + + constructor( + // WidgetTypesService provides the widget's necessary structure information + private widgetTypesService: WidgetTypesService, + + // In general, the ProviderRegistryService is used for making entities available for injection into dynamically loaded components. + private providerRegistry: ProviderRegistryService + ) {} + + public ngOnInit(): void { + // Grabbing the widget's default template which will be needed as a parameter for setNode + const widgetTemplate = this.widgetTypesService.getWidgetType("kpi", 1); + // Registering our data sources as dropdown options in the widget editor/configurator + // Note: This could also be done in the parent module's constructor so that + // multiple dashboards could have access to the same widget template modification. + this.widgetTypesService.setNode( + // This is the template we grabbed above with getWidgetType + widgetTemplate, + // We are setting the editor/configurator part of the widget template + "configurator", + // This indicates which node you are changing and we want to change + // the data source providers available for selection in the editor. + WellKnownPathKey.DataSourceProviders, + // We are setting the data sources available for selection in the editor + [BookRatingDataSource.providerId] + ); + + // Registering the data source for injection into the KPI tile. + // Note: Each tile of a KPI widget is assigned its own instance of the data source + this.providerRegistry.setProviders({ + [BookRatingDataSource.providerId]: { + provide: DATA_SOURCE, + useClass: BookRatingDataSource, + // Any dependencies that need to be injected into the provider must be listed here + deps: [HttpClient], + }, + }); + + this.initializeDashboard(); + } + + public initializeDashboard(): void { + // We're using a static configuration object for this example, but this is where + // the widget's configuration could potentially be populated from a database + const kpiWidget = widgetConfig; + const widgetIndex: IWidgets = { + // Complete the KPI widget with information coming from its type definition + [kpiWidget.id]: + this.widgetTypesService.mergeWithWidgetType(kpiWidget), + }; + + // Setting the widget dimensions and position (this is for gridster) + const positions: Record = { + [kpiWidget.id]: { + cols: 4, + rows: 6, + y: 0, + x: 0, + }, + }; + + // Finally, assigning the variables we created above to the dashboard + this.dashboard = { + positions, + widgets: widgetIndex, + }; + } +} + +const widgetConfig: IWidget = { + id: "kpiWidgetId", + type: "kpi", + pizzagna: { + [PizzagnaLayer.Configuration]: { + header: { + properties: { + title: "Harry Potter and the Order of the Phoenix", + subtitle: "By: J. K. Rowling", + }, + }, + tiles: { + providers: { + interaction: { + // Configuring the UrlInteractionHandler for interactions on the tiles + providerId: NOVA_URL_INTERACTION_HANDLER, + properties: { + // the 'url' property tells the handler what link to use when interaction occurs on the series + url: "\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\${data.link}", + }, + }, + }, + properties: { + nodes: ["kpi1"], + }, + }, + kpi1: { + id: "kpi1", + componentType: KpiComponent.lateLoadKey, + properties: { + widgetData: { + units: \\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\`out of 5 stars\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\`, + label: \\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\`Average Rating\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\`, + value: 0, + // the link property that is passed to the UrlInteractionHandler when the title is clicked + // this will be updated in BookRatingDataSource's 'getFilteredData' call. + link: "http://www.google.com", + }, + }, + providers: { + [WellKnownProviders.DataSource]: { + // Setting the data source providerId for the tile with id "kpi1" + providerId: BookRatingDataSource.providerId, + } as IProviderConfiguration, + [WellKnownProviders.Adapter]: { + providerId: NOVA_KPI_DATASOURCE_ADAPTER, + properties: { + componentId: "kpi1", + propertyPath: "widgetData", + }, + } as IProviderConfiguration, + }, + }, + }, + }, +}; +\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\`, + "widget-types/proportional/models.ts": \\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\`export interface IMockBeerReview { + id: string; + name: string; + data: number[]; + icon: string; + link?: string; + value: string; + color?: string; +} +\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\`, + "widget-types/proportional/proportional-docs.component.ts": \\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\`// © 2022 SolarWinds Worldwide, LLC. All rights reserved. +// +// Permission is hereby granted, free of charge, to any person obtaining a copy +// of this software and associated documentation files (the "Software"), to +// deal in the Software without restriction, including without limitation the +// rights to use, copy, modify, merge, publish, distribute, sublicense, and/or +// sell copies of the Software, and to permit persons to whom the Software is +// furnished to do so, subject to the following conditions: +// +// The above copyright notice and this permission notice shall be included in +// all copies or substantial portions of the Software. +// +// THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR +// IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, +// FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE +// AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER +// LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, +// OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN +// THE SOFTWARE. + +import { Component, OnInit } from "@angular/core"; + +import { mapContentFile } from "../../../../demo-files-factory"; + +@Component({ + selector: "nui-proportional-docs", + templateUrl: "./proportional-docs.component.html", + standalone: false, +}) +export class ProportionalDocsComponent implements OnInit { + public proportionalWidgetFileText = ""; + public proportionalConfiguratorFileText = ""; + + public async ngOnInit(): Promise { + this.proportionalWidgetFileText = await import( + "./../../../../../../src/lib/widget-types/proportional/proportional-widget" + ).then(mapContentFile); + this.proportionalConfiguratorFileText = await import( + "./../../../../../../src/lib/widget-types/proportional/proportional-configurator" + ).then(mapContentFile); + } +} +\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\`, + "widget-types/proportional/proportional-docs.module.ts": \\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\`// © 2022 SolarWinds Worldwide, LLC. All rights reserved. +// +// Permission is hereby granted, free of charge, to any person obtaining a copy +// of this software and associated documentation files (the "Software"), to +// deal in the Software without restriction, including without limitation the +// rights to use, copy, modify, merge, publish, distribute, sublicense, and/or +// sell copies of the Software, and to permit persons to whom the Software is +// furnished to do so, subject to the following conditions: +// +// The above copyright notice and this permission notice shall be included in +// all copies or substantial portions of the Software. +// +// THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR +// IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, +// FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE +// AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER +// LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, +// OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN +// THE SOFTWARE. + +import { NgModule } from "@angular/core"; +import { RouterModule, Routes } from "@angular/router"; + +import { + NuiButtonModule, + NuiDocsModule, + NuiMessageModule, + NuiSwitchModule, + DEMO_PATH_TOKEN, +} from "@nova-ui/bits"; +import { NuiDashboardsModule } from "@nova-ui/dashboards"; + +import { ProportionalDocsComponent } from "./proportional-docs.component"; +import { ProportionalDonutContentDocsComponent } from "./proportional-donut-content-docs.component"; +import { ProportionalWidgetDonutContentFormattersExampleComponent } from "./proportional-donut-content-formatters/proportional-donut-content-formatters-example.component"; +import { ProportionalWidgetExampleComponent } from "./proportional-widget/proportional-widget-example.component"; +import { ProportionalWidgetInteractiveExampleComponent } from "./proportional-widget-interactive/proportional-widget-interactive-example.component"; +import { getDemoFiles } from "../../../../demo-files-factory"; + +const routes: Routes = [ + { + path: "", + component: ProportionalDocsComponent, + data: { + srlc: { + hideIndicator: true, + }, + showThemeSwitcher: true, + }, + }, + { + path: "example", + component: ProportionalWidgetExampleComponent, + data: { + srlc: { + hideIndicator: true, + }, + }, + }, + { + path: "donut-content-formatters", + component: ProportionalDonutContentDocsComponent, + data: { + srlc: { + hideIndicator: true, + }, + }, + }, + { + path: "donut-content-formatters-example", + component: ProportionalWidgetDonutContentFormattersExampleComponent, + data: { + srlc: { + hideIndicator: true, + }, + }, + }, + { + path: "proportional-widget-interactive-example", + component: ProportionalWidgetInteractiveExampleComponent, + data: { + srlc: { + hideIndicator: true, + }, + }, + }, +]; + +@NgModule({ + imports: [ + RouterModule.forChild(routes), + NuiButtonModule, + NuiDocsModule, + NuiDashboardsModule, + NuiMessageModule, + NuiSwitchModule, + ], + declarations: [ + ProportionalDocsComponent, + ProportionalWidgetExampleComponent, + ProportionalWidgetInteractiveExampleComponent, + ProportionalWidgetDonutContentFormattersExampleComponent, + ProportionalDonutContentDocsComponent, + ], + providers: [ + { + provide: DEMO_PATH_TOKEN, + useValue: getDemoFiles("proportional"), + }, + ], +}) +export default class ProportionalDocsModule {} +\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\`, + "widget-types/proportional/proportional-donut-content-docs.component.ts": \\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\`// © 2022 SolarWinds Worldwide, LLC. All rights reserved. +// +// Permission is hereby granted, free of charge, to any person obtaining a copy +// of this software and associated documentation files (the "Software"), to +// deal in the Software without restriction, including without limitation the +// rights to use, copy, modify, merge, publish, distribute, sublicense, and/or +// sell copies of the Software, and to permit persons to whom the Software is +// furnished to do so, subject to the following conditions: +// +// The above copyright notice and this permission notice shall be included in +// all copies or substantial portions of the Software. +// +// THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR +// IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, +// FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE +// AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER +// LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, +// OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN +// THE SOFTWARE. + +import { Component } from "@angular/core"; + +@Component({ + selector: "nui-proportional-donut-content-docs", + templateUrl: "./proportional-donut-content-docs.component.html", + standalone: false, +}) +export class ProportionalDonutContentDocsComponent { + public dataSourceDataFieldsConfig = \\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\` +public dataFieldsConfig: IProportionalDataFieldsConfig = { + dataFields$: new BehaviorSubject(this.dataFields), + chartSeriesDataFields$: new BehaviorSubject(this.chartSeriesDataFields), +}; + \\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\`; + + public widgetConfigSlice = \\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\` +"properties": { + "configuration": { + "chartOptions": { + donutContentConfig: { + formatter: { + componentType: SiUnitsFormatterComponent.lateLoadKey, + }, + aggregator: { + aggregatorType: sumAggregator.aggregatorType, + }, + }, + } + } +} + + \\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\`; +} +\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\`, + "widget-types/proportional/proportional-donut-content-formatters/proportional-donut-content-formatters-example.component.ts": \\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\`// © 2022 SolarWinds Worldwide, LLC. All rights reserved. +// +// Permission is hereby granted, free of charge, to any person obtaining a copy +// of this software and associated documentation files (the "Software"), to +// deal in the Software without restriction, including without limitation the +// rights to use, copy, modify, merge, publish, distribute, sublicense, and/or +// sell copies of the Software, and to permit persons to whom the Software is +// furnished to do so, subject to the following conditions: +// +// The above copyright notice and this permission notice shall be included in +// all copies or substantial portions of the Software. +// +// THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR +// IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, +// FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE +// AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER +// LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, +// OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN +// THE SOFTWARE. + +import { + ChangeDetectorRef, + Component, + Injectable, + OnDestroy, + OnInit, +} from "@angular/core"; +import { GridsterConfig, GridsterItem } from "angular-gridster2"; +import { BehaviorSubject } from "rxjs"; + +import { + DataSourceService, + IDataField, + IDataSource, + IFilteringOutputs, +} from "@nova-ui/bits"; +import { IAccessors, IChartAssistSeries } from "@nova-ui/charts"; +import { + DATA_SOURCE, + DEFAULT_LEGEND_FORMATTERS, + DEFAULT_PIZZAGNA_ROOT, + DEFAULT_PROPORTIONAL_CONTENT_AGGREGATORS, + DEFAULT_PROPORTIONAL_CONTENT_FORMATTERS, + DONUT_CONTENT_CONFIGURATION_SLICE, + IDashboard, + IDonutContentConfig, + IProportionalDataFieldsConfig, + IProportionalWidgetChartOptions, + IProportionalWidgetConfig, + IProviderConfiguration, + IWidget, + IWidgets, + LegendPlacement, + PizzagnaLayer, + ProportionalContentAggregatorsRegistryService, + ProportionalDonutContentFormattersRegistryService, + ProportionalLegendFormattersRegistryService, + ProportionalWidgetChartTypes, + ProviderRegistryService, + SiUnitsFormatterComponent, + sumAggregator, + WellKnownPathKey, + WellKnownProviders, + WidgetTypesService, +} from "@nova-ui/dashboards"; + +import { IMockBeerReview } from "../models"; + +/** + * A simple proportional data source to retrieve beer review counts by city + */ +@Injectable() +export class BeerReviewCountsByCityMockDataSource + extends DataSourceService> + implements IDataSource>, OnDestroy +{ + public static providerId = "BeerReviewCountsByCityMockDataSource"; + public busy = new BehaviorSubject(false); + + protected dataFields: IDataField[] = [ + { + id: "Brno", + label: "Brno", + // @ts-ignore + dataType: null, + }, + { + id: "kyiv", + label: "Kyiv", + // @ts-ignore + dataType: null, + }, + { + id: "austin", + label: "Austin", + // @ts-ignore + dataType: null, + }, + { + id: "lisbon", + label: "Lisbon", + // @ts-ignore + dataType: null, + }, + { + id: "sydney", + label: "Sydney", + // @ts-ignore + dataType: null, + }, + { + id: "nur-sultan", + label: "Nur-Sultan", + // @ts-ignore + dataType: null, + }, + ]; + protected chartSeriesDataFields: IDataField[] = [ + // default field in the chart series that is used for the aggregation + { + id: "data[0]", + label: "data", + // @ts-ignore + dataType: null, + }, + // any custom field in the chart series that is used for the aggregation + { + id: "customDonutContent", + label: "Custom Donut Content", + // @ts-ignore + dataType: null, + }, + ]; + + /** + * DataSource needs to implement the "IDataFieldsConfig" for this scenario. + * + * It's necessary to provide the "chartSeriesDataFields", + * that's why proportional widget dataSource has it's own interface for that - IProportionalDataFieldsConfig. + * + * dataFields$ - stands for possible series fields + * chartSeriesDataFields$ - stands for the fields IN the series + * + * see declaration of "dataFields" and "chartSeriesDataFields" for the example. + */ + public dataFieldsConfig: IProportionalDataFieldsConfig = { + dataFields$: new BehaviorSubject(this.dataFields), + chartSeriesDataFields$: new BehaviorSubject( + this.chartSeriesDataFields + ), + }; + + public async getFilteredData(): Promise { + this.busy.next(true); + return new Promise((resolve) => { + setTimeout(() => { + this.outputsSubject.next({ + result: getMockBeerReviewCountsByCity(), + }); + this.busy.next(false); + }, 300); + }); + } + + public ngOnDestroy(): void { + this.outputsSubject.complete(); + } +} + +/** + * A component that instantiates the dashboard + */ +@Component({ + selector: "proportional-widget-donut-content-formatters-example", + templateUrl: "./proportional-donut-content-formatters-example.component.html", + styleUrls: [ + "./proportional-donut-content-formatters-example.component.less", + ], + standalone: false, +}) +export class ProportionalWidgetDonutContentFormattersExampleComponent + implements OnInit +{ + public dashboard: IDashboard | undefined; + + // Angular gridster requires a configuration object even if it's empty. + // Pass this to the dashboard component's gridsterConfig input in the template. + public gridsterConfig: GridsterConfig = {}; + + // Boolean passed as an input to the dashboard. When true, widgets can be moved, resized, removed, or edited + public editMode: boolean = false; + + constructor( + // WidgetTypesService provides the widget's necessary structure information + private widgetTypesService: WidgetTypesService, + // In general, the ProviderRegistryService is used for making entities available for injection into dynamically loaded components. + private providerRegistry: ProviderRegistryService, + // registry for adding the formatter for donut content + contentFormattersRegistry: ProportionalDonutContentFormattersRegistryService, + // registry for adding the formatter for proportional legend + legendFormattersRegistry: ProportionalLegendFormattersRegistryService, + // registry for adding the aggregators for donut content + aggregatorRegistry: ProportionalContentAggregatorsRegistryService, + private changeDetectorRef: ChangeDetectorRef + ) { + // on the dashboard startup, it's necessary to add possible content formatters, legend formatters and content aggregators to the registry. + // using registry is a way for setting the available formatters. + legendFormattersRegistry.addItems(DEFAULT_LEGEND_FORMATTERS); + contentFormattersRegistry.addItems( + DEFAULT_PROPORTIONAL_CONTENT_FORMATTERS + ); + aggregatorRegistry.addItems(DEFAULT_PROPORTIONAL_CONTENT_AGGREGATORS); + } + + public ngOnInit(): void { + // Grabbing the widget's default template which will be needed as a parameter for setNode + const widgetTemplate = this.widgetTypesService.getWidgetType( + "proportional", + 1 + ); + + // Registering our data sources as dropdown options in the widget editor/configurator + // Note: This could also be done in the parent module's constructor so that + // multiple dashboards could have access to the same widget template modification. + this.widgetTypesService.setNode( + // This is the template we grabbed above with getWidgetType + widgetTemplate, + // We are setting the editor/configurator part of the widget template + "configurator", + // This indicates which node you are changing and we want to change + // the data source providers available for selection in the editor. + WellKnownPathKey.DataSourceProviders, + // We are setting the data sources available for selection in the editor + [BeerReviewCountsByCityMockDataSource.providerId] + ); + + // Setup of the configurator is done here + this.setupConfigurator(); + + // Registering the data source for injection into the Proportional widget. + this.providerRegistry.setProviders({ + [BeerReviewCountsByCityMockDataSource.providerId]: { + provide: DATA_SOURCE, + useClass: BeerReviewCountsByCityMockDataSource, + deps: [], + }, + }); + + this.initializeDashboard(); + } + + /** Used for restoring widgets state */ + public reInitializeDashboard(): void { + // destroys the components and their providers so the dashboard can re init data + this.dashboard = undefined; + this.changeDetectorRef.detectChanges(); + + this.initializeDashboard(); + } + + private initializeDashboard(): void { + // We're using a static configuration object for this example, but this is where + // the widget's configuration could potentially be populated from a database + const widgetIndex: IWidgets = { + // Complete the proportional widget with information coming from its type definition + [widgetConfig.id]: + this.widgetTypesService.mergeWithWidgetType(widgetConfig), + }; + + // Setting the widget dimensions and position (this is for gridster) + const positions: Record = { + [widgetConfig.id]: { + cols: 6, + rows: 6, + y: 0, + x: 0, + }, + }; + + // Finally, assigning the variables we created above to the dashboard + this.dashboard = { + positions, + widgets: widgetIndex, + }; + } + + /** + * Sets up the configurator sections for proportional donut + */ + private setupConfigurator() { + const widgetTemplate = this.widgetTypesService.getWidgetType( + "proportional", + 1 + ); + + // remove old "presentation", "chartOptionsEditor" and "donutContentConfiguration" sections from the configurator + delete widgetTemplate.configurator?.structure?.presentation; + delete widgetTemplate.configurator?.structure?.chartOptionsEditor; + delete widgetTemplate.configurator?.structure + ?.donutContentConfiguration; + + // add new "presentation" section + this.widgetTypesService.setNode( + widgetTemplate, + "configurator", + "presentation", + DONUT_CONTENT_CONFIGURATION_SLICE.presentation + ); + // add new "chartOptionsEditor" section + this.widgetTypesService.setNode( + widgetTemplate, + "configurator", + "chartOptionsEditor", + DONUT_CONTENT_CONFIGURATION_SLICE.chartOptionsEditor + ); + // add new "donutContentConfiguration" section + this.widgetTypesService.setNode( + widgetTemplate, + "configurator", + "donutContentConfiguration", + DONUT_CONTENT_CONFIGURATION_SLICE.donutContentConfiguration + ); + } +} + +const widgetConfig: IWidget = { + id: "proportionalWidgetId", + type: "proportional", + pizzagna: { + [PizzagnaLayer.Configuration]: { + [DEFAULT_PIZZAGNA_ROOT]: { + providers: {}, + }, + header: { + properties: { + title: "Beer Review Tally by City", + subtitle: "These People Love Beer", + }, + }, + chart: { + providers: { + [WellKnownProviders.DataSource]: { + // Setting the data source providerId for the chart + providerId: + BeerReviewCountsByCityMockDataSource.providerId, + } as IProviderConfiguration, + }, + properties: { + configuration: { + chartOptions: { + type: ProportionalWidgetChartTypes.DonutChart, + legendPlacement: LegendPlacement.Right, + // old configuration looks like this + // contentFormatter: { + // componentType: DonutContentSumFormatterComponent.lateLoadKey, + // }, + + // NEW configuration looks like this + donutContentConfig: { + formatter: { + componentType: + SiUnitsFormatterComponent.lateLoadKey, + }, + aggregator: { + aggregatorType: + sumAggregator.aggregatorType, + properties: { + // example of a default metric to be used for the percentage calculation + // activeMetricId: "austin", + }, + }, + } as IDonutContentConfig, + } as IProportionalWidgetChartOptions, + } as IProportionalWidgetConfig, + }, + }, + }, + }, +}; + +export function getMockBeerReviewCountsByCity(): IMockBeerReview[] { + return [ + { + id: "Brno", + name: "Brno", + data: [Math.round(Math.random() * 1000000)], + icon: "status_down", + link: "https://en.wikipedia.org/wiki/Brno", + value: "Brno", + customDonutContent: "Custom Brno", + }, + { + id: "kyiv", + name: "Kyiv", + data: [Math.round(Math.random() * 1000000)], + icon: "status_critical", + link: "https://en.wikipedia.org/wiki/Kyiv", + value: "Kyiv", + customDonutContent: "Custom Kyiv", + }, + { + id: "austin", + name: "Austin", + data: [Math.round(Math.random() * 1000000)], + icon: "status_warning", + link: "https://en.wikipedia.org/wiki/Austin", + value: "Austin", + customDonutContent: "Custom Austin", + }, + { + id: "lisbon", + name: "Lisbon", + data: [Math.round(Math.random() * 1000000)], + icon: "status_unknown", + link: "https://en.wikipedia.org/wiki/Lisbon", + value: "Lisbon", + customDonutContent: "Custom Lisbon", + }, + { + id: "sydney", + name: "Sydney", + data: [Math.round(Math.random() * 1000000)], + icon: "status_up", + link: "https://en.wikipedia.org/wiki/Sydney", + value: "Sydney", + customDonutContent: "Custom Sydney", + }, + { + id: "nur-sultan", + name: "Nur-Sultan", + data: [Math.round(Math.random() * 1000000)], + icon: "status_unmanaged", + link: "https://en.wikipedia.org/wiki/Nur-Sultan", + value: "Nur-Sultan", + customDonutContent: "Custom Nur-Sultan", + }, + ].sort((a, b) => a.data[0] - b.data[0]); +} +\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\`, + "widget-types/proportional/proportional-widget/proportional-widget-example.component.ts": \\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\`// © 2022 SolarWinds Worldwide, LLC. All rights reserved. +// +// Permission is hereby granted, free of charge, to any person obtaining a copy +// of this software and associated documentation files (the "Software"), to +// deal in the Software without restriction, including without limitation the +// rights to use, copy, modify, merge, publish, distribute, sublicense, and/or +// sell copies of the Software, and to permit persons to whom the Software is +// furnished to do so, subject to the following conditions: +// +// The above copyright notice and this permission notice shall be included in +// all copies or substantial portions of the Software. +// +// THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR +// IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, +// FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE +// AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER +// LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, +// OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN +// THE SOFTWARE. + +import { + ChangeDetectorRef, + Component, + Injectable, + OnDestroy, + OnInit, +} from "@angular/core"; +import { GridsterConfig, GridsterItem } from "angular-gridster2"; +import { BehaviorSubject } from "rxjs"; + +import { + DataSourceService, + IDataSource, + IFilteringOutputs, +} from "@nova-ui/bits"; +import { + DATA_SOURCE, + DEFAULT_PIZZAGNA_ROOT, + IDashboard, + IProportionalWidgetChartOptions, + IProportionalWidgetConfig, + IProportionalWidgetData, + IProviderConfiguration, + IRefresherProperties, + IWidget, + IWidgets, + LegendPlacement, + PizzagnaLayer, + ProportionalWidgetChartTypes, + ProviderRegistryService, + WellKnownPathKey, + WellKnownProviders, + WidgetTypesService, +} from "@nova-ui/dashboards"; + +import { IMockBeerReview } from "../models"; + +/** + * A simple proportional data source to retrieve beer review counts by city + */ +@Injectable() +export class BeerReviewCountsByCityMockDataSource + extends DataSourceService + implements IDataSource, OnDestroy +{ + // This is the ID we'll use to identify the provider + public static providerId = "BeerReviewCountsByCityMockDataSource"; + public busy = new BehaviorSubject(false); + + public async getFilteredData(): Promise { + this.busy.next(true); + return new Promise((resolve) => { + setTimeout(() => { + this.outputsSubject.next({ + result: getMockBeerReviewCountsByCity(), + }); + this.busy.next(false); + }, 300); + }); + } + + public ngOnDestroy(): void { + this.outputsSubject.complete(); + } +} + +/** + * A component that instantiates the dashboard + */ +@Component({ + selector: "proportional-widget-example", + templateUrl: "./proportional-widget-example.component.html", + styleUrls: ["./proportional-widget-example.component.less"], + standalone: false, +}) +export class ProportionalWidgetExampleComponent implements OnInit { + // This variable will hold all the data needed to define the layout and behavior of the widgets. + // Pass this to the dashboard component's dashboard input in the template. + public dashboard: IDashboard | undefined; + + // Angular gridster requires a configuration object even if it's empty. + // Pass this to the dashboard component's gridsterConfig input in the template. + public gridsterConfig: GridsterConfig = {}; + + // Boolean passed as an input to the dashboard. When true, widgets can be moved, resized, removed, or edited + public editMode: boolean = false; + + constructor( + // WidgetTypesService provides the widget's necessary structure information + private widgetTypesService: WidgetTypesService, + // In general, the ProviderRegistryService is used for making entities available for injection into dynamically loaded components. + private providerRegistry: ProviderRegistryService, + private changeDetectorRef: ChangeDetectorRef + ) {} + + public ngOnInit(): void { + // Grabbing the widget's default template which will be needed as a parameter for setNode + const widgetTemplate = this.widgetTypesService.getWidgetType( + "proportional", + 1 + ); + + // Registering our data sources as dropdown options in the widget editor/configurator + // Note: This could also be done in the parent module's constructor so that + // multiple dashboards could have access to the same widget template modification. + this.widgetTypesService.setNode( + // This is the template we grabbed above with getWidgetType + widgetTemplate, + // We are setting the editor/configurator part of the widget template + "configurator", + // This indicates which node you are changing and we want to change + // the data source providers available for selection in the editor. + WellKnownPathKey.DataSourceProviders, + // We are setting the data sources available for selection in the editor + [BeerReviewCountsByCityMockDataSource.providerId] + ); + + // Registering the data source for injection into the Proportional widget. + this.providerRegistry.setProviders({ + [BeerReviewCountsByCityMockDataSource.providerId]: { + provide: DATA_SOURCE, + useClass: BeerReviewCountsByCityMockDataSource, + deps: [], + }, + }); + + this.initializeDashboard(); + } + + /** Used for restoring widgets state */ + public reInitializeDashboard(): void { + // destroys the components and their providers so the dashboard can re init data + this.dashboard = undefined; + this.changeDetectorRef.detectChanges(); + + this.initializeDashboard(); + } + + public initializeDashboard(): void { + // We're using a static configuration object for this example, but this is where + // the widget's configuration could potentially be populated from a database + const widgetIndex: IWidgets = { + // Complete the proportional widget with information coming from its type definition + [widgetConfig.id]: + this.widgetTypesService.mergeWithWidgetType(widgetConfig), + }; + + // Setting the widget dimensions and position (this is for gridster) + const positions: Record = { + [widgetConfig.id]: { + cols: 5, + rows: 6, + y: 0, + x: 0, + }, + }; + + // Finally, assigning the variables we created above to the dashboard + this.dashboard = { + positions, + widgets: widgetIndex, + }; + } +} + +const widgetConfig: IWidget = { + id: "proportionalWidgetId", + type: "proportional", + pizzagna: { + [PizzagnaLayer.Configuration]: { + [DEFAULT_PIZZAGNA_ROOT]: { + providers: { + [WellKnownProviders.Refresher]: { + properties: { + // Configuring the refresher interval so that our data source is invoked every ten minutes + interval: 60 * 10, + enabled: true, + } as IRefresherProperties, + } as Partial, + }, + }, + header: { + properties: { + title: "Beer Review Tally by City", + subtitle: "These People Love Beer", + }, + }, + chart: { + providers: { + [WellKnownProviders.DataSource]: { + // Setting the data source providerId for the chart + providerId: + BeerReviewCountsByCityMockDataSource.providerId, + } as IProviderConfiguration, + }, + properties: { + configuration: { + chartOptions: { + type: ProportionalWidgetChartTypes.DonutChart, + legendPlacement: LegendPlacement.Right, + } as IProportionalWidgetChartOptions, + // You can optionally define custom colors for the chart by setting the 'chartColors' configuration property + // "chartColors": [ + // "var(--nui-color-chart-five)", + // "var(--nui-color-chart-six)", + // "var(--nui-color-chart-seven)", + // "var(--nui-color-chart-eight)", + // "var(--nui-color-chart-nine)", + // "var(--nui-color-chart-ten)", + // ], + // or use-mapped structure + chartColors: { + Brno: "var(--nui-color-chart-five)", + kyiv: "var(--nui-color-chart-six)", + austin: "var(--nui-color-chart-seven)", + lisbon: "var(--nui-color-chart-eight)", + sydney: "var(--nui-color-chart-nine)", + "nur-sultan": "var(--nui-color-chart-ten)", + }, + prioritizeWidgetColors: false, + } as IProportionalWidgetConfig, + }, + }, + }, + }, +}; + +export function getMockBeerReviewCountsByCity(): IMockBeerReview[] { + return [ + { + id: "Brno", + name: "Brno", + data: [Math.round(Math.random() * 100000)], + icon: "status_down", + link: "https://en.wikipedia.org/wiki/Brno", + value: "Brno", + color: "var(--nui-color-chart-one)", + }, + { + id: "kyiv", + name: "Kyiv", + data: [Math.round(Math.random() * 100000)], + icon: "status_critical", + link: "https://en.wikipedia.org/wiki/Kyiv", + value: "Kyiv", + color: "var(--nui-color-chart-two)", + }, + { + id: "austin", + name: "Austin", + data: [Math.round(Math.random() * 100000)], + icon: "status_warning", + link: "https://en.wikipedia.org/wiki/Austin", + value: "Austin", + color: "var(--nui-color-chart-three)", + }, + { + id: "lisbon", + name: "Lisbon", + data: [Math.round(Math.random() * 100000)], + icon: "status_unknown", + link: "https://en.wikipedia.org/wiki/Lisbon", + value: "Lisbon", + color: "var(--nui-color-chart-four)", + }, + { + id: "sydney", + name: "Sydney", + data: [Math.round(Math.random() * 100000)], + icon: "status_up", + link: "https://en.wikipedia.org/wiki/Sydney", + value: "Sydney", + color: "var(--nui-color-chart-five)", + }, + { + id: "nur-sultan", + name: "Nur-Sultan", + data: [Math.round(Math.random() * 100000)], + icon: "status_unmanaged", + link: "https://en.wikipedia.org/wiki/Nur-Sultan", + value: "Nur-Sultan", + color: "var(--nui-color-chart-six)", + }, + ].sort((a, b) => a.data[0] - b.data[0]); +} +\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\`, + "widget-types/proportional/proportional-widget-interactive/proportional-widget-interactive-example.component.ts": \\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\`// © 2022 SolarWinds Worldwide, LLC. All rights reserved. +// +// Permission is hereby granted, free of charge, to any person obtaining a copy +// of this software and associated documentation files (the "Software"), to +// deal in the Software without restriction, including without limitation the +// rights to use, copy, modify, merge, publish, distribute, sublicense, and/or +// sell copies of the Software, and to permit persons to whom the Software is +// furnished to do so, subject to the following conditions: +// +// The above copyright notice and this permission notice shall be included in +// all copies or substantial portions of the Software. +// +// THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR +// IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, +// FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE +// AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER +// LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, +// OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN +// THE SOFTWARE. + +import { + ChangeDetectorRef, + Component, + Injectable, + OnDestroy, + OnInit, +} from "@angular/core"; +import { GridsterConfig, GridsterItem } from "angular-gridster2"; +import keyBy from "lodash/keyBy"; +import { BehaviorSubject } from "rxjs"; + +import { + DataSourceService, + IDataSource, + IFilteringOutputs, +} from "@nova-ui/bits"; +import { + DATA_SOURCE, + DEFAULT_PIZZAGNA_ROOT, + IDashboard, + IProportionalWidgetChartOptions, + IProportionalWidgetConfig, + IProportionalWidgetData, + IProviderConfiguration, + IWidget, + LegendPlacement, + NOVA_URL_INTERACTION_HANDLER, + PizzagnaLayer, + ProportionalWidgetChartTypes, + ProviderRegistryService, + WellKnownPathKey, + WellKnownProviders, + WidgetTypesService, +} from "@nova-ui/dashboards"; + +import { IMockBeerReview } from "../models"; + +/** + * A simple proportional data source to retrieve beer review counts by city + */ +@Injectable() +export class ReviewCountsByCityMockDataSource + extends DataSourceService + implements IDataSource, OnDestroy +{ + // This is the ID we'll use to identify the provider + public static providerId = "ReviewCountsByCityMockDataSource"; + public busy = new BehaviorSubject(false); + + public async getFilteredData(): Promise { + this.busy.next(true); + return new Promise((resolve) => { + setTimeout(() => { + this.outputsSubject.next({ + result: getMockBeerReviewCountsByCity(), + }); + this.busy.next(false); + }, 300); + }); + } + + public ngOnDestroy(): void { + this.outputsSubject.complete(); + } +} + +/** + * A component that instantiates the dashboard + */ +@Component({ + selector: "proportional-widget-interactive-example", + templateUrl: "./proportional-widget-interactive-example.component.html", + styleUrls: ["./proportional-widget-interactive-example.component.less"], + standalone: false, +}) +export class ProportionalWidgetInteractiveExampleComponent implements OnInit { + // This variable will hold all the data needed to define the layout and behavior of the widgets. + // Pass this to the dashboard component's dashboard input in the template. + public dashboard: IDashboard | undefined; + + // Angular gridster requires a configuration object even if it's empty. + // Pass this to the dashboard component's gridsterConfig input in the template. + public gridsterConfig: GridsterConfig = {}; + + // Boolean passed as an input to the dashboard. When true, widgets can be moved, resized, removed, or edited + public editMode: boolean = false; + + constructor( + // WidgetTypesService provides the widget's necessary structure information + private widgetTypesService: WidgetTypesService, + // In general, the ProviderRegistryService is used for making entities available for injection into dynamically loaded components. + private providerRegistry: ProviderRegistryService, + private changeDetectorRef: ChangeDetectorRef + ) {} + + public ngOnInit(): void { + // Grabbing the widget's default template which will be needed as a parameter for setNode + const widgetTemplate = this.widgetTypesService.getWidgetType( + "proportional", + 1 + ); + + // Registering our data sources as dropdown options in the widget editor/configurator + // Note: This could also be done in the parent module's constructor so that + // multiple dashboards could have access to the same widget template modification. + this.widgetTypesService.setNode( + // This is the template we grabbed above with getWidgetType + widgetTemplate, + // We are setting the editor/configurator part of the widget template + "configurator", + // This indicates which node you are changing and we want to change + // the data source providers available for selection in the editor. + WellKnownPathKey.DataSourceProviders, + // We are setting the data sources available for selection in the editor + [ReviewCountsByCityMockDataSource.providerId] + ); + + // Registering the data source for injection into the Proportional widget. + this.providerRegistry.setProviders({ + [ReviewCountsByCityMockDataSource.providerId]: { + provide: DATA_SOURCE, + useClass: ReviewCountsByCityMockDataSource, + deps: [], + }, + }); + + this.initializeDashboard(); + } + + /** Used for restoring widgets state */ + public reInitializeDashboard(): void { + // destroys the components and their providers so the dashboard can re init data + this.dashboard = undefined; + this.changeDetectorRef.detectChanges(); + + this.initializeDashboard(); + } + + public initializeDashboard(): void { + // We're using a static configuration object for this example, but this is where + // the widget's configuration could potentially be populated from a database + const widgetsWithStructure = widgetConfigs.map((w) => + this.widgetTypesService.mergeWithWidgetType(w) + ); + const widgetsIndex = keyBy(widgetsWithStructure, (w: IWidget) => w.id); + + // Setting the widget dimensions and position (this is for gridster) + const positions: Record = { + [widgetConfigs[0].id]: { + cols: 6, + rows: 6, + y: 0, + x: 0, + }, + [widgetConfigs[1].id]: { + cols: 6, + rows: 6, + y: 0, + x: 6, + }, + }; + + // Finally, assigning the variables we created above to the dashboard + this.dashboard = { + positions, + widgets: widgetsIndex, + }; + } +} + +const widgetConfigs: IWidget[] = [ + { + id: "widget1", + type: "proportional", + pizzagna: { + [PizzagnaLayer.Configuration]: { + [DEFAULT_PIZZAGNA_ROOT]: { + providers: { + // Configuring the UrlInteractionHandler to handle interactions + [WellKnownProviders.InteractionHandler]: { + providerId: NOVA_URL_INTERACTION_HANDLER, + properties: { + // the 'url' property tells the handler what link to use when interaction occurs on the series + // if the series does not have a link we are passing one to the handler + url: "\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\${data.link || 'https://en.wikipedia.org/wiki/'+data.id}", + // by default the link is opened in the current window, set 'newWindow' to true to open in a new tab instead + // newWindow: true, + }, + }, + }, + }, + header: { + properties: { + title: "Proportional Widget", + subtitle: "With interaction handler", + }, + }, + chart: { + providers: { + [WellKnownProviders.DataSource]: { + // Setting the data source providerId for the chart + providerId: + ReviewCountsByCityMockDataSource.providerId, + } as IProviderConfiguration, + }, + properties: { + configuration: { + // Setting the interactive to true + interactive: true, + chartOptions: { + type: ProportionalWidgetChartTypes.VerticalBarChart, + legendPlacement: LegendPlacement.Bottom, + } as IProportionalWidgetChartOptions, + prioritizeWidgetColors: false, + } as IProportionalWidgetConfig, + }, + }, + }, + }, + }, + { + id: "widget2", + type: "proportional", + pizzagna: { + [PizzagnaLayer.Configuration]: { + header: { + properties: { + title: "Proportional Widget", + subtitle: "Without interaction handler", + }, + }, + chart: { + providers: { + [WellKnownProviders.DataSource]: { + // Setting the data source providerId for the chart + providerId: + ReviewCountsByCityMockDataSource.providerId, + } as IProviderConfiguration, + }, + properties: { + configuration: { + // interactive set to false so series without links are not styled like a link + interactive: false, + chartOptions: { + type: ProportionalWidgetChartTypes.HorizontalBarChart, + legendPlacement: LegendPlacement.Bottom, + } as IProportionalWidgetChartOptions, + prioritizeWidgetColors: false, + } as IProportionalWidgetConfig, + }, + }, + }, + }, + }, +]; + +export function getMockBeerReviewCountsByCity(): IMockBeerReview[] { + return [ + { + id: "Brno", + name: "Brno", + data: [Math.round(Math.random() * 100000)], + icon: "status_down", + link: "https://en.wikipedia.org/wiki/Brno", + value: "Brno", + color: "var(--nui-color-chart-one)", + }, + { + id: "kyiv", + name: "Kyiv", + data: [Math.round(Math.random() * 100000)], + icon: "status_critical", + link: "https://en.wikipedia.org/wiki/Kyiv", + value: "Kyiv", + color: "var(--nui-color-chart-two)", + }, + { + id: "austin", + name: "Austin", + data: [Math.round(Math.random() * 100000)], + icon: "status_warning", + value: "Austin", + color: "var(--nui-color-chart-three)", + }, + { + id: "lisbon", + name: "Lisbon", + data: [Math.round(Math.random() * 100000)], + icon: "status_unknown", + link: "https://en.wikipedia.org/wiki/Lisbon", + value: "Lisbon", + color: "var(--nui-color-chart-four)", + }, + { + id: "sydney", + name: "Sydney", + data: [Math.round(Math.random() * 100000)], + icon: "status_up", + value: "Sydney", + color: "var(--nui-color-chart-five)", + }, + { + id: "nur-sultan", + name: "Nur-Sultan", + data: [Math.round(Math.random() * 100000)], + icon: "status_unmanaged", + value: "Nur-Sultan", + color: "var(--nui-color-chart-six)", + }, + ].sort((a, b) => a.data[0] - b.data[0]); +} +\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\`, + "widget-types/risk-score/risk-score-docs.component.ts": \\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\`// © 2023 SolarWinds Worldwide, LLC. All rights reserved. +// +// Permission is hereby granted, free of charge, to any person obtaining a copy +// of this software and associated documentation files (the "Software"), to +// deal in the Software without restriction, including without limitation the +// rights to use, copy, modify, merge, publish, distribute, sublicense, and/or +// sell copies of the Software, and to permit persons to whom the Software is +// furnished to do so, subject to the following conditions: +// +// The above copyright notice and this permission notice shall be included in +// all copies or substantial portions of the Software. +// +// THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR +// IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, +// FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE +// AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER +// LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, +// OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN +// THE SOFTWARE. + +import { Component, OnInit } from "@angular/core"; + +import { mapContentFile } from "../../../../demo-files-factory"; + +@Component({ + selector: "nui-risk-score-docs", + templateUrl: "./risk-score-docs.component.html", + standalone: false, +}) +export class RiskScoreDocsComponent implements OnInit { + public riskScoreWidgetFileText = ""; + public riskScoreConfiguratorFileText = ""; + + public async ngOnInit(): Promise { + this.riskScoreWidgetFileText = await import( + "./../../../../../../src/lib/widget-types/risk-score/risk-score-widget" + ).then(mapContentFile); + this.riskScoreConfiguratorFileText = await import( + "./../../../../../../src/lib/widget-types/risk-score/risk-score-configurator" + ).then(mapContentFile); + } +} +\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\`, + "widget-types/risk-score/risk-score-docs.module.ts": \\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\`// © 2023 SolarWinds Worldwide, LLC. All rights reserved. +// +// Permission is hereby granted, free of charge, to any person obtaining a copy +// of this software and associated documentation files (the "Software"), to +// deal in the Software without restriction, including without limitation the +// rights to use, copy, modify, merge, publish, distribute, sublicense, and/or +// sell copies of the Software, and to permit persons to whom the Software is +// furnished to do so, subject to the following conditions: +// +// The above copyright notice and this permission notice shall be included in +// all copies or substantial portions of the Software. +// +// THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR +// IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, +// FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE +// AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER +// LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, +// OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN +// THE SOFTWARE. + +import { NgModule } from "@angular/core"; +import { RouterModule, Routes } from "@angular/router"; + +import { + DEMO_PATH_TOKEN, + NuiButtonModule, + NuiDocsModule, + NuiMessageModule, + NuiSwitchModule, +} from "@nova-ui/bits"; +import { NuiDashboardsModule } from "@nova-ui/dashboards"; + +import { RiskScoreDocsComponent } from "./risk-score-docs.component"; +import { RiskScoreWidgetExampleComponent } from "./risk-score-widget-example/risk-score-widget-example.component"; +import { getDemoFiles } from "../../../../demo-files-factory"; + +const routes: Routes = [ + { + path: "", + component: RiskScoreDocsComponent, + data: { + srlc: { + hideIndicator: true, + }, + showThemeSwitcher: true, + }, + }, + { + path: "example", + component: RiskScoreWidgetExampleComponent, + data: { + srlc: { + hideIndicator: true, + }, + }, + }, +]; + +@NgModule({ + imports: [ + RouterModule.forChild(routes), + NuiButtonModule, + NuiDocsModule, + NuiMessageModule, + NuiDashboardsModule, + NuiSwitchModule, + ], + declarations: [RiskScoreDocsComponent, RiskScoreWidgetExampleComponent], + providers: [ + { + provide: DEMO_PATH_TOKEN, + useValue: getDemoFiles("risk-score"), + }, + ], +}) +export default class RiskScoreDocsModule {} +\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\`, + "widget-types/risk-score/risk-score-widget-example/risk-score-widget-example.component.ts": \\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\`// © 2023 SolarWinds Worldwide, LLC. All rights reserved. +// +// Permission is hereby granted, free of charge, to any person obtaining a copy +// of this software and associated documentation files (the "Software"), to +// deal in the Software without restriction, including without limitation the +// rights to use, copy, modify, merge, publish, distribute, sublicense, and/or +// sell copies of the Software, and to permit persons to whom the Software is +// furnished to do so, subject to the following conditions: +// +// The above copyright notice and this permission notice shall be included in +// all copies or substantial portions of the Software. +// +// THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR +// IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, +// FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE +// AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER +// LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, +// OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN +// THE SOFTWARE. + +import { HttpClient, HttpErrorResponse } from "@angular/common/http"; +import { Component, Injectable, OnDestroy, OnInit } from "@angular/core"; +import { GridsterConfig, GridsterItem } from "angular-gridster2"; +import { BehaviorSubject } from "rxjs"; +import { finalize } from "rxjs/operators"; + +import { DataSourceService, IFilteringOutputs } from "@nova-ui/bits"; +import { + DATA_SOURCE, + DEFAULT_PIZZAGNA_ROOT, + IDashboard, + IRiskScoreData, + IProviderConfiguration, + IRefresherProperties, + IWidget, + IWidgets, + RiskScoreTileComponent, + NOVA_KPI_DATASOURCE_ADAPTER, + PizzagnaLayer, + ProviderRegistryService, + WellKnownPathKey, + WellKnownProviders, + WidgetTypesService, +} from "@nova-ui/dashboards"; + +/** + * A simple KPI data source to retrieve the average rating of Harry Potter and the Sorcerer's Stone (book) via googleapis + */ +@Injectable() +export class AverageRatingRiskScoreDataSource + extends DataSourceService + implements OnDestroy +{ + // This is the ID we'll use to identify the provider + public static providerId = "AverageRatingRiskScoreDataSource"; + + // Use this subject to communicate the data source's busy state + public busy = new BehaviorSubject(false); + + constructor(private http: HttpClient) { + super(); + } + + // In this example, getFilteredData is invoked every 10 minutes (Take a look at the refresher + // provider definition in the widget configuration below to see how the interval is set) + public async getFilteredData(): Promise { + this.busy.next(true); + return new Promise((resolve) => { + // *** Make a resource request to an external API (if needed) + this.http + .get("https://www.googleapis.com/books/v1/volumes/5MQFrgEACAAJ") + .pipe(finalize(() => this.busy.next(false))) + .subscribe({ + next: (data: any) => { + resolve({ + result: { + value: data.volumeInfo.averageRating, + }, + }); + }, + error: (error: HttpErrorResponse) => { + resolve({ + result: null, + error: { + type: error.status, + }, + }); + }, + }); + }); + } + + public ngOnDestroy(): void { + this.outputsSubject.complete(); + } +} + +/** + * A component that instantiates the dashboard + */ +@Component({ + selector: "risk-score-widget-example", + templateUrl: "./risk-score-widget-example.component.html", + styleUrls: ["./risk-score-widget-example.component.less"], + standalone: false, +}) +export class RiskScoreWidgetExampleComponent implements OnInit { + // This variable will hold all the data needed to define the layout and behavior of the widgets. + // Pass this to the dashboard component's dashboard input in the template. + public dashboard: IDashboard; + + // Angular gridster requires a configuration object even if it's empty. + // Pass this to the dashboard component's gridsterConfig input in the template. + public gridsterConfig: GridsterConfig = {}; + + // Boolean passed as an input to the dashboard. When true, widgets can be moved, resized, removed, or edited + public editMode: boolean = false; + + constructor( + // WidgetTypesService provides the widget's necessary structure information + private widgetTypesService: WidgetTypesService, + + // In general, the ProviderRegistryService is used for making entities available for injection into dynamically loaded components. + private providerRegistry: ProviderRegistryService + ) {} + + public ngOnInit(): void { + // Grabbing the widget's default template which will be needed as a parameter for setNode + const widgetTemplate = this.widgetTypesService.getWidgetType( + "risk-score", + 1 + ); + // Registering our data sources as dropdown options in the widget editor/configurator + // Note: This could also be done in the parent module's constructor so that + // multiple dashboards could have access to the same widget template modification. + this.widgetTypesService.setNode( + // This is the template we grabbed above with getWidgetType + widgetTemplate, + // We are setting the editor/configurator part of the widget template + "configurator", + // This indicates which node you are changing and we want to change + // the data source providers available for selection in the editor. + WellKnownPathKey.DataSourceProviders, + // We are setting the data sources available for selection in the editor + [AverageRatingRiskScoreDataSource.providerId] + ); + + // Registering the data source for injection into the KPI tile. + // Note: Each tile of a KPI widget is assigned its own instance of the data source + this.providerRegistry.setProviders({ + [AverageRatingRiskScoreDataSource.providerId]: { + provide: DATA_SOURCE, + useClass: AverageRatingRiskScoreDataSource, + // Any dependencies that need to be injected into the provider must be listed here + deps: [HttpClient], + }, + }); + + this.initializeDashboard(); + } + + public initializeDashboard(): void { + // We're using a static configuration object for this example, but this is where + // the widget's configuration could potentially be populated from a database + const riskScoreWidget = widgetConfig; + const widgetIndex: IWidgets = { + // Complete the KPI widget with information coming from its type definition + [riskScoreWidget.id]: + this.widgetTypesService.mergeWithWidgetType(riskScoreWidget), + }; + + // Setting the widget dimensions and position (this is for gridster) + const positions: Record = { + [riskScoreWidget.id]: { + cols: 4, + rows: 6, + y: 0, + x: 0, + }, + }; + + // Finally, assigning the variables we created above to the dashboard + this.dashboard = { + positions, + widgets: widgetIndex, + }; + } +} + +const widgetConfig: IWidget = { + id: "riskScoreWidgetId", + type: "risk-score", + pizzagna: { + [PizzagnaLayer.Configuration]: { + [DEFAULT_PIZZAGNA_ROOT]: { + providers: { + [WellKnownProviders.Refresher]: { + properties: { + // Configuring the refresher interval so that our data source is invoked every ten minutes + interval: 60 * 10, + enabled: true, + } as IRefresherProperties, + } as Partial, + }, + }, + header: { + properties: { + title: "Harry Potter and the Sorcerer's Stone", + subtitle: "By J. K. Rowling", + }, + }, + tiles: { + properties: { + nodes: ["riskScore1"], + }, + }, + riskScore1: { + id: "riskScore1", + componentType: RiskScoreTileComponent.lateLoadKey, + properties: { + widgetData: { + minValue: 0, + maxValue: 5, + useStaticLabel: false, + staticLabel: undefined, + label: \\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\`Average Rating\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\`, + description: \\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\`Harry Potter and the Sorcerer's Stone By J. K. Rowling Average Rating Risk Score\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\`, + }, + }, + providers: { + [WellKnownProviders.DataSource]: { + // Setting the data source providerId for the tile with id "kpi1" + providerId: AverageRatingRiskScoreDataSource.providerId, + } as IProviderConfiguration, + [WellKnownProviders.Adapter]: { + providerId: NOVA_KPI_DATASOURCE_ADAPTER, + properties: { + componentId: "riskScore1", + propertyPath: "widgetData", + }, + } as IProviderConfiguration, + }, + }, + }, + }, +}; +\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\`, + "widget-types/table/table-docs.component.ts": \\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\`// © 2022 SolarWinds Worldwide, LLC. All rights reserved. +// +// Permission is hereby granted, free of charge, to any person obtaining a copy +// of this software and associated documentation files (the "Software"), to +// deal in the Software without restriction, including without limitation the +// rights to use, copy, modify, merge, publish, distribute, sublicense, and/or +// sell copies of the Software, and to permit persons to whom the Software is +// furnished to do so, subject to the following conditions: +// +// The above copyright notice and this permission notice shall be included in +// all copies or substantial portions of the Software. +// +// THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR +// IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, +// FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE +// AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER +// LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, +// OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN +// THE SOFTWARE. + +import { Component, OnInit } from "@angular/core"; + +import { mapContentFile } from "../../../../demo-files-factory"; + +@Component({ + selector: "nui-table-docs", + templateUrl: "./table-docs.component.html", + standalone: false, +}) +export class TableDocsComponent implements OnInit { + public widgetFileText = ""; + public configuratorFileText = ""; + + public async ngOnInit(): Promise { + this.widgetFileText = await import( + "./../../../../../../src/lib/widget-types/table/table-widget" + ).then(mapContentFile); + this.configuratorFileText = await import( + "./../../../../../../src/lib/widget-types/table/table-configurator" + ).then(mapContentFile); + } +} +\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\`, + "widget-types/table/table-docs.module.ts": \\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\`// © 2022 SolarWinds Worldwide, LLC. All rights reserved. +// +// Permission is hereby granted, free of charge, to any person obtaining a copy +// of this software and associated documentation files (the "Software"), to +// deal in the Software without restriction, including without limitation the +// rights to use, copy, modify, merge, publish, distribute, sublicense, and/or +// sell copies of the Software, and to permit persons to whom the Software is +// furnished to do so, subject to the following conditions: +// +// The above copyright notice and this permission notice shall be included in +// all copies or substantial portions of the Software. +// +// THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR +// IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, +// FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE +// AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER +// LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, +// OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN +// THE SOFTWARE. + +import { NgModule } from "@angular/core"; +import { RouterModule, Routes } from "@angular/router"; + +import { DEMO_PATH_TOKEN } from "@nova-ui/bits"; +import { + NuiButtonModule, + NuiDocsModule, + NuiMessageModule, + NuiSwitchModule, +} from "@nova-ui/bits"; +import { + NuiDashboardsModule, + TableFormatterRegistryService, +} from "@nova-ui/dashboards"; + +import { TableDocsComponent } from "./table-docs.component"; +import { TablePaginatorDocsComponent } from "./table-paginator-docs.component"; +import { TableSelectableDocsComponent } from "./table-selectable-docs.component"; +import { TableWidgetExampleComponent } from "./table-widget/table-widget-example.component"; +import { TableWidgetInteractiveExampleComponent } from "./table-widget-interactive/table-widget-interactive-example.component"; +import { TableWidgetPaginatorExampleComponent } from "./table-widget-paginator/table-widget-paginator-example.component"; +import { TableWidgetSearchExampleComponent } from "./table-widget-search/table-widget-search-example.component"; +import { TableSearchDocsComponent } from "./table-widget-search-docs.component"; +import { TableWidgetSelectableMultiExampleComponent } from "./table-widget-selectable/table-widget-selectable-multi/table-widget-selectable-multi.example.component"; +import { TableWidgetSelectableRadioExampleComponent } from "./table-widget-selectable/table-widget-selectable-radio/table-widget-selectable-radio.example.component"; +import { TableWidgetSelectableSingleExampleComponent } from "./table-widget-selectable/table-widget-selectable-single/table-widget-selectable-single.example.component"; +import { TableWidgetSelectableExampleComponent } from "./table-widget-selectable/table-widget-selectable.example.component"; +import { DEFAULT_TABLE_FORMATTERS } from "../../../../../../src/lib/widget-types/table/default-table-formatters"; +import { getDemoFiles } from "../../../../demo-files-factory"; + +const routes: Routes = [ + { + path: "", + component: TableDocsComponent, + data: { + srlc: { + hideIndicator: true, + }, + showThemeSwitcher: true, + }, + }, + { + path: "example", + component: TableWidgetExampleComponent, + data: { + srlc: { + hideIndicator: true, + }, + }, + }, + { + path: "table-search", + component: TableSearchDocsComponent, + data: { + srlc: { + hideIndicator: true, + }, + showThemeSwitcher: true, + }, + }, + { + path: "table-paginator", + component: TablePaginatorDocsComponent, + data: { + srlc: { + hideIndicator: true, + }, + showThemeSwitcher: true, + }, + }, + { + path: "table-select", + component: TableSelectableDocsComponent, + data: { + srlc: { + hideIndicator: true, + }, + showThemeSwitcher: true, + }, + }, +]; + +@NgModule({ + imports: [ + RouterModule.forChild(routes), + NuiButtonModule, + NuiDocsModule, + NuiMessageModule, + NuiSwitchModule, + NuiDashboardsModule, + ], + declarations: [ + TableDocsComponent, + TableSearchDocsComponent, + TablePaginatorDocsComponent, + TableWidgetPaginatorExampleComponent, + TableSelectableDocsComponent, + TableWidgetInteractiveExampleComponent, + TableWidgetExampleComponent, + TableWidgetSearchExampleComponent, + TableWidgetSelectableExampleComponent, + TableWidgetSelectableMultiExampleComponent, + TableWidgetSelectableSingleExampleComponent, + TableWidgetSelectableRadioExampleComponent, + ], + providers: [ + { + provide: DEMO_PATH_TOKEN, + useValue: getDemoFiles("table"), + }, + ], +}) +export default class TableDocsModule { + constructor(tableFormattersRegistryService: TableFormatterRegistryService) { + tableFormattersRegistryService.addItems(DEFAULT_TABLE_FORMATTERS); + } +} +\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\`, + "widget-types/table/table-paginator-docs.component.ts": \\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\`// © 2022 SolarWinds Worldwide, LLC. All rights reserved. +// +// Permission is hereby granted, free of charge, to any person obtaining a copy +// of this software and associated documentation files (the "Software"), to +// deal in the Software without restriction, including without limitation the +// rights to use, copy, modify, merge, publish, distribute, sublicense, and/or +// sell copies of the Software, and to permit persons to whom the Software is +// furnished to do so, subject to the following conditions: +// +// The above copyright notice and this permission notice shall be included in +// all copies or substantial portions of the Software. +// +// THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR +// IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, +// FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE +// AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER +// LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, +// OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN +// THE SOFTWARE. + +import { Component } from "@angular/core"; + +@Component({ + selector: "nui-table-paginator-docs", + templateUrl: "./table-paginator-docs.component.html", + standalone: false, +}) +export class TablePaginatorDocsComponent { + public tableConfigurationText = \\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\` + "table": { + ... + properties: { + configuration: { + // define paginator configuration here + scrollType: ScrollType.paginator, + paginatorConfiguration: { + pageSize: 10, // Value have to be one of pageSizeSet values + pageSizeSet: [10, 20, 30], + }, + // If not specified, default is set to + // pageSize: 10, + // pageSizeSet: [10, 20, 50], + hasVirtualScroll: false, // Has to be speciefied because of backward compatibility + } as ITableWidgetConfig, + }, + }, + \\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\`; +} +\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\`, + "widget-types/table/table-selectable-docs.component.ts": \\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\`// © 2022 SolarWinds Worldwide, LLC. All rights reserved. +// +// Permission is hereby granted, free of charge, to any person obtaining a copy +// of this software and associated documentation files (the "Software"), to +// deal in the Software without restriction, including without limitation the +// rights to use, copy, modify, merge, publish, distribute, sublicense, and/or +// sell copies of the Software, and to permit persons to whom the Software is +// furnished to do so, subject to the following conditions: +// +// The above copyright notice and this permission notice shall be included in +// all copies or substantial portions of the Software. +// +// THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR +// IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, +// FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE +// AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER +// LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, +// OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN +// THE SOFTWARE. + +import { Component } from "@angular/core"; + +@Component({ + selector: "nui-table-selectable-docs", + templateUrl: "./table-selectable-docs.component.html", + standalone: false, +}) +export class TableSelectableDocsComponent { + public tableConfigurationText = \\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\` + "table": { + ... + properties: { + // enabling selection here + selectionConfiguration: { + // whether the selection is enabled or disabled + enabled: true, + // can be Multi | Radio | Single + selectionMode: TableSelectionMode.Multi, + // property that uniquely identifies row in a table + trackByProperty: "id", + // whether clicking on row should select it + clickableRow: true, + }, + }, + }, + \\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\`; + + public eventSubscriptionText = \\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\` +... +constructor(Inject(PIZZAGNA_EVENT_BUS) eventBus: EventBus) { + eventBus + .getStream(SELECTION) + // don't forget to unsubscribe! + .subscribe((selection: ISelection) => ...) +} +... + \\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\`; +} +\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\`, + "widget-types/table/table-widget/table-widget-example.component.ts": \\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\`// © 2022 SolarWinds Worldwide, LLC. All rights reserved. +// +// Permission is hereby granted, free of charge, to any person obtaining a copy +// of this software and associated documentation files (the "Software"), to +// deal in the Software without restriction, including without limitation the +// rights to use, copy, modify, merge, publish, distribute, sublicense, and/or +// sell copies of the Software, and to permit persons to whom the Software is +// furnished to do so, subject to the following conditions: +// +// The above copyright notice and this permission notice shall be included in +// all copies or substantial portions of the Software. +// +// THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR +// IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, +// FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE +// AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER +// LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, +// OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN +// THE SOFTWARE. + +import { ChangeDetectorRef, Component, OnInit } from "@angular/core"; +import { GridsterConfig, GridsterItem } from "angular-gridster2"; +import orderBy from "lodash/orderBy"; +import { BehaviorSubject, firstValueFrom, from } from "rxjs"; +import { map, tap } from "rxjs/operators"; + +import { + DataSourceService, + IDataField, + INovaFilteringOutputs, + INovaFilters, + nameof, +} from "@nova-ui/bits"; +import { + DATA_SOURCE, + IDashboard, + ITableWidgetColumnConfig, + IWidget, + IWidgets, + ProviderRegistryService, + WellKnownPathKey, + WellKnownProviders, + WidgetTypesService, +} from "@nova-ui/dashboards"; + +export const BREW_API_URL = "https://api.punkapi.com/v2/beers"; + +export interface IBrewInfo { + id: string; + name: string; + tagline: string; + first_brewed: string; + description: string; + brewers_tips: string; +} + +export interface IBrewDatasourceResponse { + brewInfo: IBrewInfo[]; + total: number; +} + +export class BeerDataSource extends DataSourceService { + public static providerId = "BeerDataSource"; + + private cache: IBrewInfo[] = []; + + public busy = new BehaviorSubject(false); + + public dataFields: Array = [ + { + id: nameof("id"), + label: "No", + dataType: "number", + sortable: true, + }, + // To indicate that a column should not be sortable, set the optional IDataField 'sortable' property to false + { + id: nameof("name"), + label: "Name", + dataType: "string", + sortable: true, + }, + { + id: nameof("tagline"), + label: "Tagline", + dataType: "string", + sortable: true, + }, + { + id: nameof("first_brewed"), + label: "First Brewed", + dataType: "string", + sortable: true, + }, + { + id: nameof("description"), + label: "Description", + dataType: "string", + sortable: false, + }, + { + id: nameof("brewers_tips"), + label: "Brewer's Tips", + dataType: "string", + sortable: false, + }, + ]; + + public async getFilteredData( + filters: INovaFilters + ): Promise { + const start = filters.virtualScroll?.value?.start ?? 0; + const end = filters.virtualScroll?.value?.end ?? 0; + + // Resetting cache on first page request + if (start === 0) { + this.cache = []; + } + + // extract sorter settings to send to the backend + // filters.sorterValue.sortBy; filters.sorterValue.direction + return firstValueFrom( + from(this.fetch(start, end)).pipe( + tap((response: IBrewDatasourceResponse | undefined) => { + if (!response) { + return; + } + this.cache = this.sortData( + this.cache.concat(response.brewInfo), + filters + ); + this.dataSubject.next(this.cache); + }), + map(() => ({ + repeat: { itemsSource: this.cache }, + dataFields: this.dataFields, + })) + ) + ); + } + + public async fetch( + start: number, + end: number + ): Promise { + const delta: number = end - start; + const currentPage: number = end / delta || 0; + const response: object | Array = await ( + await fetch( + \\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\`\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\${BREW_API_URL}/?page=\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\${currentPage}&per_page=\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\${delta}\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\` + ) + ).json(); + + // Note: In case request fails we should not proceed with mapping + if (!Array.isArray(response)) { + return undefined; + } + + return { + brewInfo: response.map((result: IBrewInfo) => ({ + id: result.id, + name: result.name, + tagline: result.tagline, + first_brewed: result.first_brewed, + description: result.description, + brewers_tips: result.brewers_tips, + })), + total: response.length, + }; + } + + private sortData(data: IBrewInfo[], filters: INovaFilters): IBrewInfo[] { + return orderBy( + data, + filters.sorter?.value?.sortBy, + filters.sorter?.value?.direction as "desc" | "asc" + ); + } +} + +/** + * A component that instantiates the dashboard + */ +@Component({ + selector: "table-widget-example", + templateUrl: "./table-widget-example.component.html", + styleUrls: ["./table-widget-example.component.less"], + standalone: false, +}) +export class TableWidgetExampleComponent implements OnInit { + // This variable will hold all the data needed to define the layout and behavior of the widgets. + // Pass this to the dashboard component's dashboard input in the template. + public dashboard: IDashboard | undefined; + + // Angular gridster requires a configuration object even if it's empty. + // Pass this to the dashboard component's gridsterConfig input in the template. + public gridsterConfig: GridsterConfig = {}; + + // Boolean passed as an input to the dashboard. When true, widgets can be moved, resized, removed, or edited + public editMode: boolean = false; + + constructor( + // WidgetTypesService provides the widget's necessary structure information + private widgetTypesService: WidgetTypesService, + // In general, the ProviderRegistryService is used for making entities available for injection into dynamically loaded components. + private providerRegistry: ProviderRegistryService, + private changeDetectorRef: ChangeDetectorRef + ) {} + + public ngOnInit(): void { + // Grabbing the widget's default template which will be needed as a parameter for setNode + const widgetTemplate = this.widgetTypesService.getWidgetType( + "table", + 1 + ); + + // Registering our data sources as dropdown options in the widget editor/configurator + // Note: This could also be done in the parent module's constructor so that + // multiple dashboards could have access to the same widget template modification. + this.widgetTypesService.setNode( + // This is the template we grabbed above with getWidgetType + widgetTemplate, + // We are setting the editor/configurator part of the widget template + "configurator", + // This indicates which node you are changing and we want to change + // the data source providers available for selection in the editor. + WellKnownPathKey.DataSourceProviders, + // We are setting the data sources available for selection in the editor + [BeerDataSource.providerId] + ); + + // Registering the data source for injection into the widget. + this.providerRegistry.setProviders({ + [BeerDataSource.providerId]: { + provide: DATA_SOURCE, + useClass: BeerDataSource, + // Any dependencies that need to be injected into the provider must be listed here + deps: [], + }, + }); + + this.initializeDashboard(); + } + + /** Used for restoring widgets state */ + public reInitializeDashboard(): void { + // destroys the components and their providers so the dashboard can re init data + this.dashboard = undefined; + this.changeDetectorRef.detectChanges(); + + this.initializeDashboard(); + } + + public initializeDashboard(): void { + // We're using a static configuration object for this example, but this is where + // the widget's configuration could potentially be populated from a database + const tableWidget = widgetConfig; + const widgetIndex: IWidgets = { + // Enhance the widget with information coming from it's type definition + [tableWidget.id]: + this.widgetTypesService.mergeWithWidgetType(tableWidget), + }; + + // Setting the widget dimensions and position (this is for gridster) + const positions: Record = { + [tableWidget.id]: { + cols: 12, + rows: 6, + y: 0, + x: 0, + }, + }; + + // Finally, assigning the variables we created above to the dashboard + this.dashboard = { + positions, + widgets: widgetIndex, + }; + } +} + +const TABLE_COLUMNS: ITableWidgetColumnConfig[] = [ + { + id: "column1", + label: $localize\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\`Beer Name\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\`, + isActive: true, + width: 185, + formatter: { + componentType: "RawFormatterComponent", + properties: { + dataFieldIds: { + value: "name", + }, + }, + }, + }, + { + id: "column2", + label: $localize\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\`Tagline\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\`, + isActive: true, + width: 250, + formatter: { + componentType: "RawFormatterComponent", + properties: { + dataFieldIds: { + value: "tagline", + }, + }, + }, + }, + { + id: "column3", + label: $localize\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\`First Brewed\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\`, + isActive: true, + width: 100, + formatter: { + componentType: "RawFormatterComponent", + properties: { + dataFieldIds: { + value: "first_brewed", + }, + }, + }, + }, + { + id: "column4", + label: $localize\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\`Description\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\`, + isActive: true, + formatter: { + componentType: "RawFormatterComponent", + properties: { + dataFieldIds: { + value: "description", + }, + }, + }, + }, +]; + +export const widgetConfig: IWidget = { + id: "tableWidgetId", + type: "table", + pizzagna: { + configuration: { + header: { + properties: { + title: "Stupendous Suds", + subtitle: "Try These Brilliant Brews", + }, + }, + table: { + providers: { + [WellKnownProviders.DataSource]: { + providerId: BeerDataSource.providerId, + }, + }, + properties: { + configuration: { + columns: TABLE_COLUMNS, + sortable: true, + sorterConfiguration: { + descendantSorting: false, + sortBy: "", + }, + hasVirtualScroll: true, + }, + }, + }, + }, + }, +}; +\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\`, + "widget-types/table/table-widget-interactive/table-widget-interactive-example.component.ts": \\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\`// © 2022 SolarWinds Worldwide, LLC. All rights reserved. +// +// Permission is hereby granted, free of charge, to any person obtaining a copy +// of this software and associated documentation files (the "Software"), to +// deal in the Software without restriction, including without limitation the +// rights to use, copy, modify, merge, publish, distribute, sublicense, and/or +// sell copies of the Software, and to permit persons to whom the Software is +// furnished to do so, subject to the following conditions: +// +// The above copyright notice and this permission notice shall be included in +// all copies or substantial portions of the Software. +// +// THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR +// IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, +// FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE +// AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER +// LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, +// OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN +// THE SOFTWARE. + +import { ChangeDetectorRef, Component, OnInit } from "@angular/core"; +import { GridsterConfig, GridsterItem } from "angular-gridster2"; +import orderBy from "lodash/orderBy"; +import { BehaviorSubject, firstValueFrom, from } from "rxjs"; +import { map, tap } from "rxjs/operators"; + +import { + DataSourceService, + IDataField, + INovaFilteringOutputs, + INovaFilters, + nameof, +} from "@nova-ui/bits"; +import { + DATA_SOURCE, + DEFAULT_PIZZAGNA_ROOT, + IDashboard, + ITableWidgetColumnConfig, + IWidget, + IWidgets, + NOVA_URL_INTERACTION_HANDLER, + ProviderRegistryService, + WellKnownPathKey, + WellKnownProviders, + WidgetTypesService, +} from "@nova-ui/dashboards"; + +export const BREW_API_URL = "https://api.punkapi.com/v2/beers"; + +export interface IBrewInfo { + id: string; + name: string; + tagline: string; + first_brewed: string; + description: string; + brewers_tips: string; +} + +export interface IBrewDatasourceResponse { + brewInfo: IBrewInfo[]; + total: number; +} + +export class MockBeerDataSource extends DataSourceService { + public static providerId = "MockBeerDataSource"; + + private cache: IBrewInfo[] = []; + + public busy = new BehaviorSubject(false); + + public dataFields: Array = [ + { + id: nameof("id"), + label: "No", + dataType: "number", + sortable: true, + }, + // To indicate that a column should not be sortable, set the optional IDataField 'sortable' property to false + { + id: nameof("name"), + label: "Name", + dataType: "string", + sortable: true, + }, + { + id: nameof("tagline"), + label: "Tagline", + dataType: "string", + sortable: true, + }, + { + id: nameof("first_brewed"), + label: "First Brewed", + dataType: "string", + sortable: true, + }, + { + id: nameof("description"), + label: "Description", + dataType: "string", + sortable: false, + }, + { + id: nameof("brewers_tips"), + label: "Brewer's Tips", + dataType: "string", + sortable: false, + }, + ]; + + public async getFilteredData( + filters: INovaFilters + ): Promise { + const start = filters.virtualScroll?.value?.start ?? 0; + const end = filters.virtualScroll?.value?.end ?? 0; + + // Resetting cache on first page request + if (start === 0) { + this.cache = []; + } + + // extract sorter settings to send to the backend + // filters.sorterValue.sortBy; filters.sorterValue.direction + return firstValueFrom( + from(this.fetch(start, end)).pipe( + tap((response) => { + if (!response) { + return; + } + this.cache = this.sortData( + this.cache.concat(response.brewInfo), + filters + ); + this.dataSubject.next(this.cache); + }), + map(() => ({ + repeat: { itemsSource: this.cache }, + dataFields: this.dataFields, + })) + ) + ); + } + + public async fetch( + start: number, + end: number + ): Promise { + const delta: number = end - start; + const currentPage: number = end / delta || 0; + const response: object | Array = await ( + await fetch( + \\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\`\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\${BREW_API_URL}/?page=\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\${currentPage}&per_page=\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\${delta}\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\` + ) + ).json(); + console.log( + "📘 table-widget-interactive-example.component: 85# -> response:", + response + ); + + // Note: In case request fails we should not proceed with mapping + if (!Array.isArray(response)) { + return undefined; + } + + return { + brewInfo: response.map((result: IBrewInfo) => ({ + id: result.id, + name: result.name, + tagline: result.tagline, + first_brewed: result.first_brewed, + description: result.description, + brewers_tips: result.brewers_tips, + })), + total: response.length, + }; + } + + private sortData(data: IBrewInfo[], filters: INovaFilters): IBrewInfo[] { + return orderBy( + data, + filters.sorter?.value?.sortBy, + filters.sorter?.value?.direction as "desc" | "asc" + ); + } +} + +/** + * A component that instantiates the dashboard + */ +@Component({ + selector: "table-widget-interactive-example", + templateUrl: "./table-widget-interactive-example.component.html", + styleUrls: ["./table-widget-interactive-example.component.less"], + standalone: false, +}) +export class TableWidgetInteractiveExampleComponent implements OnInit { + // This variable will hold all the data needed to define the layout and behavior of the widgets. + // Pass this to the dashboard component's dashboard input in the template. + public dashboard: IDashboard | undefined; + + // Angular gridster requires a configuration object even if it's empty. + // Pass this to the dashboard component's gridsterConfig input in the template. + public gridsterConfig: GridsterConfig = {}; + + // Boolean passed as an input to the dashboard. When true, widgets can be moved, resized, removed, or edited + public editMode: boolean = false; + + constructor( + // WidgetTypesService provides the widget's necessary structure information + private widgetTypesService: WidgetTypesService, + // In general, the ProviderRegistryService is used for making entities available for injection into dynamically loaded components. + private providerRegistry: ProviderRegistryService, + private changeDetectorRef: ChangeDetectorRef + ) {} + + public ngOnInit(): void { + // Grabbing the widget's default template which will be needed as a parameter for setNode + const widgetTemplate = this.widgetTypesService.getWidgetType( + "table", + 1 + ); + + // Registering our data sources as dropdown options in the widget editor/configurator + // Note: This could also be done in the parent module's constructor so that + // multiple dashboards could have access to the same widget template modification. + this.widgetTypesService.setNode( + // This is the template we grabbed above with getWidgetType + widgetTemplate, + // We are setting the editor/configurator part of the widget template + "configurator", + // This indicates which node you are changing and we want to change + // the data source providers available for selection in the editor. + WellKnownPathKey.DataSourceProviders, + // We are setting the data sources available for selection in the editor + [MockBeerDataSource.providerId] + ); + + // Registering the data source for injection into the widget. + this.providerRegistry.setProviders({ + [MockBeerDataSource.providerId]: { + provide: DATA_SOURCE, + useClass: MockBeerDataSource, + // Any dependencies that need to be injected into the provider must be listed here + deps: [], + }, + }); + + this.initializeDashboard(); + } + + /** Used for restoring widgets state */ + public reInitializeDashboard(): void { + // destroys the components and their providers so the dashboard can re init data + this.dashboard = undefined; + this.changeDetectorRef.detectChanges(); + + this.initializeDashboard(); + } + + public initializeDashboard(): void { + // We're using a static configuration object for this example, but this is where + // the widget's configuration could potentially be populated from a database + const tableWidget = widgetConfig; + const widgetIndex: IWidgets = { + // Enhance the widget with information coming from it's type definition + [tableWidget.id]: + this.widgetTypesService.mergeWithWidgetType(tableWidget), + }; + + // Setting the widget dimensions and position (this is for gridster) + const positions: Record = { + [tableWidget.id]: { + cols: 12, + rows: 6, + y: 0, + x: 0, + }, + }; + + // Finally, assigning the variables we created above to the dashboard + this.dashboard = { + positions, + widgets: widgetIndex, + }; + } +} + +const TABLE_COLUMNS: ITableWidgetColumnConfig[] = [ + { + id: "column1", + label: $localize\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\`Beer Name\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\`, + isActive: true, + width: 185, + formatter: { + componentType: "RawFormatterComponent", + properties: { + dataFieldIds: { + value: "name", + }, + }, + }, + }, + { + id: "column2", + label: $localize\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\`Tagline\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\`, + isActive: true, + width: 250, + formatter: { + componentType: "RawFormatterComponent", + properties: { + dataFieldIds: { + value: "tagline", + }, + }, + }, + }, + { + id: "column3", + label: $localize\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\`First Brewed\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\`, + isActive: true, + width: 100, + formatter: { + componentType: "RawFormatterComponent", + properties: { + dataFieldIds: { + value: "first_brewed", + }, + }, + }, + }, + { + id: "column4", + label: $localize\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\`Description\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\`, + isActive: true, + formatter: { + componentType: "RawFormatterComponent", + properties: { + dataFieldIds: { + value: "description", + }, + }, + }, + }, +]; + +export const widgetConfig: IWidget = { + id: "tableWidgetId", + type: "table", + pizzagna: { + configuration: { + [DEFAULT_PIZZAGNA_ROOT]: { + providers: { + [WellKnownProviders.InteractionHandler]: { + // Configuring the UrlInteractionHandler to handle interactions + providerId: NOVA_URL_INTERACTION_HANDLER, + properties: { + // the 'url' property tells the handler what link to use when interaction occurs on the series + url: "\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\${'https://untappd.com/search?q='+data.name}", + // by default the link is opened in the current window, set 'newWindow' to true to open in a new tab instead + newWindow: true, + }, + }, + }, + }, + header: { + properties: { + title: "Stupendous Suds", + subtitle: "Try These Brilliant Brews", + }, + }, + table: { + providers: { + [WellKnownProviders.DataSource]: { + providerId: MockBeerDataSource.providerId, + }, + }, + properties: { + configuration: { + // set interactions to true on the table + interactive: true, + columns: TABLE_COLUMNS, + sortable: true, + sorterConfiguration: { + descendantSorting: false, + sortBy: "", + }, + hasVirtualScroll: true, + }, + }, + }, + }, + }, +}; +\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\`, + "widget-types/table/table-widget-paginator/table-widget-paginator-example.component.ts": \\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\`// © 2022 SolarWinds Worldwide, LLC. All rights reserved. +// +// Permission is hereby granted, free of charge, to any person obtaining a copy +// of this software and associated documentation files (the "Software"), to +// deal in the Software without restriction, including without limitation the +// rights to use, copy, modify, merge, publish, distribute, sublicense, and/or +// sell copies of the Software, and to permit persons to whom the Software is +// furnished to do so, subject to the following conditions: +// +// The above copyright notice and this permission notice shall be included in +// all copies or substantial portions of the Software. +// +// THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR +// IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, +// FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE +// AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER +// LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, +// OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN +// THE SOFTWARE. + +import { HttpClient } from "@angular/common/http"; +import { ChangeDetectorRef, Component, OnInit } from "@angular/core"; +import { GridsterConfig, GridsterItem } from "angular-gridster2"; + +import { LoggerService } from "@nova-ui/bits"; +import { + DATA_SOURCE, + DEFAULT_PIZZAGNA_ROOT, + IDashboard, + IProviderConfiguration, + ITableWidgetConfig, + IWidget, + IWidgets, + NOVA_URL_INTERACTION_HANDLER, + PizzagnaLayer, + ProviderRegistryService, + RawFormatterComponent, + ScrollType, + WellKnownPathKey, + WellKnownProviders, + WidgetTypesService, +} from "@nova-ui/dashboards"; + +import { AcmeTableMockDataSource } from "../../../../prototypes/data/table/acme-table-mock-data-source.service"; + +/** + * A component that instantiates the dashboard + */ +@Component({ + selector: "table-widget-paginator-example", + templateUrl: "./table-widget-paginator-example.component.html", + styleUrls: ["./table-widget-paginator-example.component.less"], + standalone: false, +}) +export class TableWidgetPaginatorExampleComponent implements OnInit { + public dashboard: IDashboard | undefined; + public gridsterConfig: GridsterConfig = {}; + public editMode: boolean = false; + + constructor( + private widgetTypesService: WidgetTypesService, + private providerRegistry: ProviderRegistryService, + private changeDetectorRef: ChangeDetectorRef + ) {} + + public ngOnInit(): void { + const widgetTemplate = this.widgetTypesService.getWidgetType( + "table", + 1 + ); + this.widgetTypesService.setNode( + widgetTemplate, + "configurator", + WellKnownPathKey.DataSourceProviders, + [AcmeTableMockDataSource.providerId] + ); + + this.providerRegistry.setProviders({ + [AcmeTableMockDataSource.providerId]: { + provide: DATA_SOURCE, + useClass: AcmeTableMockDataSource, + deps: [LoggerService, HttpClient], + }, + }); + + this.initializeDashboard(); + } + + /** Used for restoring widgets state */ + public reInitializeDashboard(): void { + // destroys the components and their providers so the dashboard can re init data + this.dashboard = undefined; + this.changeDetectorRef.detectChanges(); + + this.initializeDashboard(); + } + + public initializeDashboard(): void { + const tableWithPaginator = tableWidgetWithPaginator; + const tableWithVirtualScroll = tableWidgetWithVirtualScroll; + + const widgetIndex: IWidgets = { + [tableWithPaginator.id]: + this.widgetTypesService.mergeWithWidgetType(tableWithPaginator), + [tableWithVirtualScroll.id]: + this.widgetTypesService.mergeWithWidgetType( + tableWithVirtualScroll + ), + }; + + const positions: Record = { + [tableWithPaginator.id]: { + cols: 6, + rows: 6, + y: 0, + x: 0, + }, + [tableWithVirtualScroll.id]: { + cols: 6, + rows: 6, + y: 0, + x: 0, + }, + }; + + this.dashboard = { + positions, + widgets: widgetIndex, + }; + } +} + +export const tableWidgetWithPaginator: IWidget = { + id: "widget1", + type: "table", + pizzagna: { + [PizzagnaLayer.Configuration]: { + [DEFAULT_PIZZAGNA_ROOT]: { + providers: { + [WellKnownProviders.InteractionHandler]: { + providerId: NOVA_URL_INTERACTION_HANDLER, + }, + }, + }, + header: { + properties: { + title: "Table Widget with paginator!", + subtitle: "Basic table widget", + collapsible: true, + }, + }, + table: { + providers: { + [WellKnownProviders.DataSource]: { + providerId: AcmeTableMockDataSource.providerId, + } as IProviderConfiguration, + }, + properties: { + configuration: { + interactive: true, + columns: [ + { + id: "column1", + label: "No.", + isActive: true, + formatter: { + componentType: + RawFormatterComponent.lateLoadKey, + properties: { + dataFieldIds: { + value: "position", + }, + }, + }, + }, + { + id: "column2", + label: "Name", + isActive: true, + formatter: { + componentType: + RawFormatterComponent.lateLoadKey, + properties: { + dataFieldIds: { + value: "name", + }, + }, + }, + }, + { + id: "column3", + label: "Status", + isActive: true, + formatter: { + componentType: + RawFormatterComponent.lateLoadKey, + properties: { + dataFieldIds: { + value: "status", + }, + }, + }, + }, + ], + sorterConfiguration: { + descendantSorting: false, + sortBy: "column1", + }, + scrollType: ScrollType.paginator, + paginatorConfiguration: { + pageSize: 5, + pageSizeSet: [5, 10, 20, 30], + }, + hasVirtualScroll: false, + searchConfiguration: { + enabled: true, + }, + } as ITableWidgetConfig, + }, + }, + }, + }, +}; + +export const tableWidgetWithVirtualScroll: IWidget = { + id: "widget2", + type: "table", + pizzagna: { + [PizzagnaLayer.Configuration]: { + [DEFAULT_PIZZAGNA_ROOT]: { + providers: { + [WellKnownProviders.InteractionHandler]: { + providerId: NOVA_URL_INTERACTION_HANDLER, + }, + }, + }, + header: { + properties: { + title: "Table Widget with virtual scroll!", + subtitle: "Basic table widget", + collapsible: true, + }, + }, + table: { + providers: { + [WellKnownProviders.DataSource]: { + providerId: AcmeTableMockDataSource.providerId, + } as IProviderConfiguration, + }, + properties: { + configuration: { + interactive: true, + columns: [ + { + id: "column1", + label: "No.", + isActive: true, + formatter: { + componentType: + RawFormatterComponent.lateLoadKey, + properties: { + dataFieldIds: { + value: "position", + }, + }, + }, + }, + { + id: "column2", + label: "Name", + isActive: true, + formatter: { + componentType: + RawFormatterComponent.lateLoadKey, + properties: { + dataFieldIds: { + value: "name", + }, + }, + }, + }, + { + id: "column3", + label: "Status", + isActive: true, + formatter: { + componentType: + RawFormatterComponent.lateLoadKey, + properties: { + dataFieldIds: { + value: "status", + }, + }, + }, + }, + ], + sorterConfiguration: { + descendantSorting: false, + sortBy: "column1", + }, + hasVirtualScroll: true, + searchConfiguration: { + enabled: true, + }, + } as ITableWidgetConfig, + }, + }, + }, + }, +}; +\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\`, + "widget-types/table/table-widget-search/table-widget-search-example.component.ts": \\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\`// © 2022 SolarWinds Worldwide, LLC. All rights reserved. +// +// Permission is hereby granted, free of charge, to any person obtaining a copy +// of this software and associated documentation files (the "Software"), to +// deal in the Software without restriction, including without limitation the +// rights to use, copy, modify, merge, publish, distribute, sublicense, and/or +// sell copies of the Software, and to permit persons to whom the Software is +// furnished to do so, subject to the following conditions: +// +// The above copyright notice and this permission notice shall be included in +// all copies or substantial portions of the Software. +// +// THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR +// IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, +// FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE +// AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER +// LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, +// OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN +// THE SOFTWARE. + +import { HttpClient } from "@angular/common/http"; +import { + ChangeDetectorRef, + Component, + Injectable, + OnInit, +} from "@angular/core"; +import { GridsterConfig, GridsterItem } from "angular-gridster2"; +import isEqual from "lodash/isEqual"; +import isNil from "lodash/isNil"; +import { BehaviorSubject, firstValueFrom, Observable, of, Subject } from "rxjs"; +import { + catchError, + delay, + finalize, + map, + // eslint-disable-next-line import/no-deprecated + switchMap, + tap, +} from "rxjs/operators"; + +import { + DataSourceFeatures, + DataSourceService, + IDataField, + IDataSource, + IDataSourceFeatures, + IDataSourceFeaturesConfiguration, + IDataSourceOutput, + IFilter, + IFilters, + INovaFilteringOutputs, + INovaFilters, + LoggerService, +} from "@nova-ui/bits"; +import { + DATA_SOURCE, + IDashboard, + ITableWidgetConfig, + IWidget, + IWidgets, + ProviderRegistryService, + WellKnownPathKey, + WellKnownProviders, + WidgetTypesService, +} from "@nova-ui/dashboards"; + +import { GBOOKS_API_URL } from "../../../../prototypes/data/table/constants"; + +interface IGBooksApiResponse { + kind: string; + totalItems: number; + items: IGBooksItemModel[]; + [key: string]: any; +} + +interface IGBooksItemModel { + id: string; + volumeInfo: { + title: string; + subtitle: string; + authors: string[]; + [key: string]: any; + }; + accessInfo: { [key: string]: any }; + saleInfo: { [key: string]: any }; +} + +interface IGBooksData { + books: IGBooksVolume[]; + totalItems: number; +} + +interface IGBooksVolume { + title: string; + authors: string; +} + +type searchableColumnType = "title" | "authors"; + +@Injectable() +export class AcmeTableGBooksDataSource + extends DataSourceService + implements IDataSource +{ + public static providerId = "AcmeTableGBooksDataSource"; + public static mockError = false; + + public searchableColumn: searchableColumnType = "title"; + + public page: number = 1; + public busy = new BehaviorSubject(false); + public features: IDataSourceFeaturesConfiguration; + + private cache = Array.from({ length: 0 }); + private previousFilters: INovaFilters; + // DataSource Features declared + private supportedFeatures: IDataSourceFeatures = { + search: { enabled: true }, + pagination: { enabled: true }, + }; + private columnToQueryParamMap: { [k in searchableColumnType]: string } = { + title: "intitle", + authors: "inauthor", + }; + + private applyFilters$ = new Subject(); + + public dataFields: Array = [ + { + id: "title", + label: $localize\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\`Title\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\`, + dataType: "string", + sortable: false, + }, + { + id: "authors", + label: $localize\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\`Authors\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\`, + dataType: "string", + sortable: false, + }, + ]; + + constructor(private logger: LoggerService, private http: HttpClient) { + super(); + // Using Nova DataSourceFeatures implementation for the features + this.features = new DataSourceFeatures(this.supportedFeatures); + + this.applyFilters$ + // eslint-disable-next-line import/no-deprecated + .pipe(switchMap((filters) => this.getData(filters))) + .subscribe(async (res) => { + this.outputsSubject.next(await this.getFilteredData(res)); + }); + } + + public async getFilteredData( + booksData: IGBooksData + ): Promise> { + return firstValueFrom( + of(booksData).pipe( + tap((response) => { + this.cache = this.cache.concat(response.books); + }), + map((response) => ({ + result: { + repeat: { itemsSource: this.cache }, + paginator: { total: response.totalItems }, + dataFields: this.dataFields, + }, + })) + ) + ); + } + + private getData(filters: INovaFilters): Observable { + if ( + this.isNewSearchTerm(filters.search) && + filters.virtualScroll?.value.start === 0 + ) { + this.cache = []; + } + + return this.http + .get(this.getComposedUrl(filters)) + .pipe( + tap(() => this.busy.next(true)), + delay(300), // mock + map((response) => ({ + books: + response.items?.map((volume) => ({ + title: volume.volumeInfo.title, + authors: + volume.volumeInfo.authors?.join(", ") || "", + })) || [], + totalItems: response.totalItems, + })), + catchError((e) => { + this.logger.error(e); + return of({ + books: [], + totalItems: 0, + }); + }), + finalize(() => { + this.busy.next(false); + this.previousFilters = filters; + }) + ); + } + + private getComposedUrl(filters: INovaFilters) { + const initialUrl = \\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\`\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\${GBOOKS_API_URL}?q=\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\`; + const maxResults = \\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\`maxResults=\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\${ + (filters.virtualScroll?.value.end || 0) - + (filters.virtualScroll?.value.start || 0) + }\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\`; + + const virtualScrollPart = filters.virtualScroll + ? \\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\`startIndex=\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\${filters.virtualScroll.value.start}\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\` + : ""; + + const searchQueryParam = + this.columnToQueryParamMap[this.searchableColumn]; + const searchPart = filters.search + ? \\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\`\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\${searchQueryParam}:\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\${filters.search.value}\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\` + : "_"; // google books api requires some criteria to do the search + + return \\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\`\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\${initialUrl}\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\${searchPart}&\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\${maxResults}&\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\${virtualScrollPart}&filter=full\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\`; + } + + private isNewSearchTerm(search: IFilter | undefined) { + return ( + !isNil(search?.value) && + !isEqual(search?.value, this.previousFilters?.search?.value) + ); + } + + // redefine parent method + public async applyFilters(): Promise { + this.applyFilters$.next(this.getFilters()); + } +} + +/** + * A component that instantiates the dashboard + */ +@Component({ + selector: "table-widget-search-example", + templateUrl: "./table-widget-search-example.component.html", + styleUrls: ["./table-widget-search-example.component.less"], + standalone: false, +}) +export class TableWidgetSearchExampleComponent implements OnInit { + public dashboard: IDashboard | undefined; + public gridsterConfig: GridsterConfig = {}; + public editMode: boolean = false; + + constructor( + private widgetTypesService: WidgetTypesService, + private providerRegistry: ProviderRegistryService, + private changeDetectorRef: ChangeDetectorRef + ) {} + + public ngOnInit(): void { + const widgetTemplate = this.widgetTypesService.getWidgetType( + "table", + 1 + ); + this.widgetTypesService.setNode( + widgetTemplate, + "configurator", + WellKnownPathKey.DataSourceProviders, + [AcmeTableGBooksDataSource.providerId] + ); + + this.providerRegistry.setProviders({ + [AcmeTableGBooksDataSource.providerId]: { + provide: DATA_SOURCE, + useClass: AcmeTableGBooksDataSource, + deps: [LoggerService, HttpClient], + }, + }); + + this.initializeDashboard(); + } + + /** Used for restoring widgets state */ + public reInitializeDashboard(): void { + // destroys the components and their providers so the dashboard can re init data + this.dashboard = undefined; + this.changeDetectorRef.detectChanges(); + + this.initializeDashboard(); + } + + public initializeDashboard(): void { + const tableWidget = widgetConfig; + const widgetIndex: IWidgets = { + [tableWidget.id]: + this.widgetTypesService.mergeWithWidgetType(tableWidget), + }; + + const positions: Record = { + [tableWidget.id]: { + cols: 12, + rows: 6, + y: 0, + x: 0, + }, + }; + + this.dashboard = { + positions, + widgets: widgetIndex, + }; + } +} + +export const widgetConfig: IWidget = { + id: "tableWidgetId", + type: "table", + pizzagna: { + configuration: { + header: { + properties: { + title: "Google Books", + }, + }, + table: { + providers: { + [WellKnownProviders.DataSource]: { + providerId: AcmeTableGBooksDataSource.providerId, + }, + }, + properties: { + configuration: { + columns: [ + { + id: "column1", + label: $localize\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\`Title\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\`, + isActive: true, + formatter: { + componentType: "RawFormatterComponent", + properties: { + dataFieldIds: { + value: "title", + }, + }, + }, + }, + { + id: "column2", + label: $localize\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\`Author\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\`, + isActive: true, + formatter: { + componentType: "RawFormatterComponent", + properties: { + dataFieldIds: { + value: "authors", + }, + }, + }, + }, + ], + sortable: false, + // define search configuration here + searchConfiguration: { + enabled: true, + // following properties below can be configured as well + // searchTerm: "search criteria here", + // searchDebounce: 300, + }, + hasVirtualScroll: true, + } as ITableWidgetConfig, + }, + }, + }, + }, +}; +\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\`, + "widget-types/table/table-widget-search-docs.component.ts": \\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\`// © 2022 SolarWinds Worldwide, LLC. All rights reserved. +// +// Permission is hereby granted, free of charge, to any person obtaining a copy +// of this software and associated documentation files (the "Software"), to +// deal in the Software without restriction, including without limitation the +// rights to use, copy, modify, merge, publish, distribute, sublicense, and/or +// sell copies of the Software, and to permit persons to whom the Software is +// furnished to do so, subject to the following conditions: +// +// The above copyright notice and this permission notice shall be included in +// all copies or substantial portions of the Software. +// +// THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR +// IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, +// FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE +// AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER +// LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, +// OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN +// THE SOFTWARE. + +import { Component } from "@angular/core"; + +@Component({ + selector: "nui-table-search-docs", + templateUrl: "./table-widget-search-docs.component.html", + standalone: false, +}) +export class TableSearchDocsComponent { + public featuredDeclaredText = \\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\` + private supportedFeatures: IDataSourceFeatures = { + search: { enabled: true }, + pagination: { enabled: true }, + };\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\`; + public featuresUsedText = \\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\` + this.features = new DataSourceFeatures(this.supportedFeatures); + \\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\`; + public tableConfigurationText = \\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\` + "table": { + ... + properties: { + configuration: { + // define search configuration here + searchConfiguration: { + enabled: true, + // following optional properties below can be configured as well + // searchTerm: "search criteria here", + // searchDebounce: 300, + }, + } as ITableWidgetConfig, + }, + }, + \\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\`; +} +\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\`, + "widget-types/table/table-widget-selectable/table-widget-selectable-multi/table-widget-selectable-multi.example.component.ts": \\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\`// © 2022 SolarWinds Worldwide, LLC. All rights reserved. +// +// Permission is hereby granted, free of charge, to any person obtaining a copy +// of this software and associated documentation files (the "Software"), to +// deal in the Software without restriction, including without limitation the +// rights to use, copy, modify, merge, publish, distribute, sublicense, and/or +// sell copies of the Software, and to permit persons to whom the Software is +// furnished to do so, subject to the following conditions: +// +// The above copyright notice and this permission notice shall be included in +// all copies or substantial portions of the Software. +// +// THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR +// IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, +// FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE +// AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER +// LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, +// OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN +// THE SOFTWARE. + +import { Component } from "@angular/core"; + +import { TableSelectionMode } from "@nova-ui/bits"; +import { TableWidgetSelectionConfig } from "@nova-ui/dashboards"; + +/** + * A component that instantiates the dashboard + */ +@Component({ + selector: "table-widget-selectable-multi-example", + templateUrl: "./table-widget-selectable-multi.example.component.html", + styleUrls: ["./table-widget-selectable-multi.example.component.less"], + standalone: false, +}) +export class TableWidgetSelectableMultiExampleComponent { + public selectionConfiguration: TableWidgetSelectionConfig = { + enabled: true, + selectionMode: TableSelectionMode.Multi, + trackByProperty: "id", + clickableRow: true, + }; +} +\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\`, + "widget-types/table/table-widget-selectable/table-widget-selectable-radio/table-widget-selectable-radio.example.component.ts": \\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\`// © 2022 SolarWinds Worldwide, LLC. All rights reserved. +// +// Permission is hereby granted, free of charge, to any person obtaining a copy +// of this software and associated documentation files (the "Software"), to +// deal in the Software without restriction, including without limitation the +// rights to use, copy, modify, merge, publish, distribute, sublicense, and/or +// sell copies of the Software, and to permit persons to whom the Software is +// furnished to do so, subject to the following conditions: +// +// The above copyright notice and this permission notice shall be included in +// all copies or substantial portions of the Software. +// +// THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR +// IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, +// FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE +// AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER +// LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, +// OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN +// THE SOFTWARE. + +import { Component } from "@angular/core"; + +import { TableSelectionMode } from "@nova-ui/bits"; +import { TableWidgetSelectionConfig } from "@nova-ui/dashboards"; + +/** + * A component that instantiates the dashboard + */ +@Component({ + selector: "table-widget-selectable-radio-example", + templateUrl: "./table-widget-selectable-radio.example.component.html", + styleUrls: ["./table-widget-selectable-radio.example.component.less"], + standalone: false, +}) +export class TableWidgetSelectableRadioExampleComponent { + public selectionConfiguration: TableWidgetSelectionConfig = { + enabled: true, + selectionMode: TableSelectionMode.Radio, + trackByProperty: "id", + clickableRow: true, + }; +} +\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\`, + "widget-types/table/table-widget-selectable/table-widget-selectable-single/table-widget-selectable-single.example.component.ts": \\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\`// © 2022 SolarWinds Worldwide, LLC. All rights reserved. +// +// Permission is hereby granted, free of charge, to any person obtaining a copy +// of this software and associated documentation files (the "Software"), to +// deal in the Software without restriction, including without limitation the +// rights to use, copy, modify, merge, publish, distribute, sublicense, and/or +// sell copies of the Software, and to permit persons to whom the Software is +// furnished to do so, subject to the following conditions: +// +// The above copyright notice and this permission notice shall be included in +// all copies or substantial portions of the Software. +// +// THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR +// IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, +// FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE +// AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER +// LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, +// OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN +// THE SOFTWARE. + +import { Component } from "@angular/core"; + +import { TableSelectionMode } from "@nova-ui/bits"; +import { TableWidgetSelectionConfig } from "@nova-ui/dashboards"; + +/** + * A component that instantiates the dashboard + */ +@Component({ + selector: "table-widget-selectable-single-example", + templateUrl: "./table-widget-selectable-single.example.component.html", + styleUrls: ["./table-widget-selectable-single.example.component.less"], + standalone: false, +}) +export class TableWidgetSelectableSingleExampleComponent { + public selectionConfiguration: TableWidgetSelectionConfig = { + enabled: true, + selectionMode: TableSelectionMode.Single, + trackByProperty: "id", + }; +} +\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\`, + "widget-types/table/table-widget-selectable/table-widget-selectable.example.component.ts": \\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\`// © 2022 SolarWinds Worldwide, LLC. All rights reserved. +// +// Permission is hereby granted, free of charge, to any person obtaining a copy +// of this software and associated documentation files (the "Software"), to +// deal in the Software without restriction, including without limitation the +// rights to use, copy, modify, merge, publish, distribute, sublicense, and/or +// sell copies of the Software, and to permit persons to whom the Software is +// furnished to do so, subject to the following conditions: +// +// The above copyright notice and this permission notice shall be included in +// all copies or substantial portions of the Software. +// +// THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR +// IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, +// FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE +// AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER +// LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, +// OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN +// THE SOFTWARE. + +import { HttpClient } from "@angular/common/http"; +import { ChangeDetectorRef, Component, Input, OnInit } from "@angular/core"; +import { GridsterConfig, GridsterItem } from "angular-gridster2"; + +import { LoggerService, TableSelectionMode } from "@nova-ui/bits"; +import { + DATA_SOURCE, + DEFAULT_PIZZAGNA_ROOT, + IDashboard, + IProviderConfiguration, + ITableWidgetConfig, + IWidget, + IWidgets, + NOVA_URL_INTERACTION_HANDLER, + PizzagnaLayer, + ProviderRegistryService, + RawFormatterComponent, + TableWidgetSelectionConfig, + WellKnownPathKey, + WellKnownProviders, + WidgetTypesService, +} from "@nova-ui/dashboards"; + +import { AcmeTableMockDataSource } from "../../../../prototypes/data/table/acme-table-mock-data-source.service"; + +/** + * A component that instantiates the dashboard + */ +@Component({ + selector: "table-widget-selectable-example", + templateUrl: "./table-widget-selectable.example.component.html", + styleUrls: ["./table-widget-selectable.example.component.less"], + standalone: false, +}) +export class TableWidgetSelectableExampleComponent implements OnInit { + public dashboard: IDashboard | undefined; + public gridsterConfig: GridsterConfig = {}; + public editMode: boolean = false; + + @Input() public selectionConfiguration: TableWidgetSelectionConfig = { + enabled: false, + selectionMode: TableSelectionMode.None, + }; + + constructor( + private widgetTypesService: WidgetTypesService, + private providerRegistry: ProviderRegistryService, + private changeDetectorRef: ChangeDetectorRef + ) {} + + public ngOnInit(): void { + const widgetTemplate = this.widgetTypesService.getWidgetType( + "table", + 1 + ); + this.widgetTypesService.setNode( + widgetTemplate, + "configurator", + WellKnownPathKey.DataSourceProviders, + [AcmeTableMockDataSource.providerId] + ); + + this.providerRegistry.setProviders({ + [AcmeTableMockDataSource.providerId]: { + provide: DATA_SOURCE, + useClass: AcmeTableMockDataSource, + deps: [LoggerService, HttpClient], + }, + }); + + this.initializeDashboard(); + } + + /** Used for restoring widgets state */ + public reInitializeDashboard(): void { + // destroys the components and their providers so the dashboard can re init data + this.dashboard = undefined; + this.changeDetectorRef.detectChanges(); + + this.initializeDashboard(); + } + + public initializeDashboard(): void { + const tableWidget = this.widgetConfig; + const widgetIndex: IWidgets = { + [tableWidget.id]: + this.widgetTypesService.mergeWithWidgetType(tableWidget), + }; + + const positions: Record = { + [tableWidget.id]: { + cols: 12, + rows: 6, + y: 0, + x: 0, + }, + }; + + this.dashboard = { + positions, + widgets: widgetIndex, + }; + } + + private get widgetConfig(): IWidget { + return { + id: "widget1", + type: "table", + pizzagna: { + [PizzagnaLayer.Configuration]: { + [DEFAULT_PIZZAGNA_ROOT]: { + providers: { + [WellKnownProviders.InteractionHandler]: { + providerId: NOVA_URL_INTERACTION_HANDLER, + }, + }, + }, + header: { + properties: { + title: "Table Widget with Selection!", + subtitle: "Basic table widget", + collapsible: true, + }, + }, + table: { + providers: { + [WellKnownProviders.DataSource]: { + providerId: AcmeTableMockDataSource.providerId, + } as IProviderConfiguration, + }, + properties: { + configuration: { + // enabling selection here + selectionConfiguration: + this.selectionConfiguration, + columns: [ + { + id: "column1", + label: "No.", + isActive: true, + formatter: { + componentType: + RawFormatterComponent.lateLoadKey, + properties: { + dataFieldIds: { + value: "position", + }, + }, + }, + }, + { + id: "column2", + label: "Name", + isActive: true, + formatter: { + componentType: + RawFormatterComponent.lateLoadKey, + properties: { + dataFieldIds: { + value: "name", + }, + }, + }, + }, + { + id: "column3", + label: "Status", + isActive: true, + formatter: { + componentType: + RawFormatterComponent.lateLoadKey, + properties: { + dataFieldIds: { + value: "status", + }, + }, + }, + }, + ], + } as ITableWidgetConfig, + }, + }, + }, + }, + }; + } +} +\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\`, + "widget-types/timeseries/timeseries-docs.component.ts": \\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\`// © 2022 SolarWinds Worldwide, LLC. All rights reserved. +// +// Permission is hereby granted, free of charge, to any person obtaining a copy +// of this software and associated documentation files (the "Software"), to +// deal in the Software without restriction, including without limitation the +// rights to use, copy, modify, merge, publish, distribute, sublicense, and/or +// sell copies of the Software, and to permit persons to whom the Software is +// furnished to do so, subject to the following conditions: +// +// The above copyright notice and this permission notice shall be included in +// all copies or substantial portions of the Software. +// +// THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR +// IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, +// FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE +// AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER +// LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, +// OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN +// THE SOFTWARE. + +import { Component, OnInit } from "@angular/core"; + +import { mapContentFile } from "../../../../demo-files-factory"; + +@Component({ + selector: "nui-timeseries-docs", + templateUrl: "./timeseries-docs.component.html", + standalone: false, +}) +export class TimeseriesDocsComponent implements OnInit { + public timeseriesWidgetFileText = ""; + public timeseriesConfiguratorFileText = ""; + + async ngOnInit(): Promise { + this.timeseriesWidgetFileText = await import( + "./../../../../../../src/lib/widget-types/timeseries/timeseries-widget" + ).then(mapContentFile); + this.timeseriesConfiguratorFileText = await import( + "./../../../../../../src/lib/widget-types/timeseries/timeseries-configurator" + ).then(mapContentFile); + } +} +\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\`, + "widget-types/timeseries/timeseries-docs.module.ts": \\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\`// © 2022 SolarWinds Worldwide, LLC. All rights reserved. +// +// Permission is hereby granted, free of charge, to any person obtaining a copy +// of this software and associated documentation files (the "Software"), to +// deal in the Software without restriction, including without limitation the +// rights to use, copy, modify, merge, publish, distribute, sublicense, and/or +// sell copies of the Software, and to permit persons to whom the Software is +// furnished to do so, subject to the following conditions: +// +// The above copyright notice and this permission notice shall be included in +// all copies or substantial portions of the Software. +// +// THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR +// IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, +// FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE +// AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER +// LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, +// OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN +// THE SOFTWARE. + +import { NgModule } from "@angular/core"; +import { RouterModule, Routes } from "@angular/router"; + +import { DEMO_PATH_TOKEN } from "@nova-ui/bits"; +import { + NuiButtonModule, + NuiDocsModule, + NuiMessageModule, + NuiSwitchModule, +} from "@nova-ui/bits"; +import { NuiDashboardsModule } from "@nova-ui/dashboards"; + +import { TimeseriesDocsComponent } from "./timeseries-docs.component"; +import { TimeseriesWidgetExampleComponent } from "./timeseries-widget-example/timeseries-widget-example.component"; +import { TimeseriesWidgetInteractiveExampleComponent } from "./timeseries-widget-interactive-example/timeseries-widget-interactive-example.component"; +import { TimeseriesWidgetStatusBarExampleComponent } from "./timeseries-widget-status-bar-example/timeseries-widget-status-bar-example.component"; +import { getDemoFiles } from "../../../../demo-files-factory"; + +const routes: Routes = [ + { + path: "", + component: TimeseriesDocsComponent, + data: { + srlc: { + hideIndicator: true, + }, + showThemeSwitcher: true, + }, + }, + { + path: "example", + component: TimeseriesWidgetExampleComponent, + data: { + srlc: { + hideIndicator: true, + }, + }, + }, +]; + +@NgModule({ + imports: [ + RouterModule.forChild(routes), + NuiButtonModule, + NuiDocsModule, + NuiMessageModule, + NuiSwitchModule, + NuiDashboardsModule, + ], + declarations: [ + TimeseriesDocsComponent, + TimeseriesWidgetExampleComponent, + TimeseriesWidgetInteractiveExampleComponent, + TimeseriesWidgetStatusBarExampleComponent, + ], + providers: [ + { + provide: DEMO_PATH_TOKEN, + useValue: getDemoFiles("timeseries"), + }, + ], +}) +export default class TimeseriesDocsModule {} +\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\`, + "widget-types/timeseries/timeseries-widget-example/timeseries-widget-example.component.ts": \\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\`// © 2022 SolarWinds Worldwide, LLC. All rights reserved. +// +// Permission is hereby granted, free of charge, to any person obtaining a copy +// of this software and associated documentation files (the "Software"), to +// deal in the Software without restriction, including without limitation the +// rights to use, copy, modify, merge, publish, distribute, sublicense, and/or +// sell copies of the Software, and to permit persons to whom the Software is +// furnished to do so, subject to the following conditions: +// +// The above copyright notice and this permission notice shall be included in +// all copies or substantial portions of the Software. +// +// THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR +// IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, +// FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE +// AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER +// LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, +// OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN +// THE SOFTWARE. + +import { + ChangeDetectorRef, + Component, + Injectable, + OnInit, +} from "@angular/core"; +import { GridsterConfig, GridsterItem } from "angular-gridster2"; +import cloneDeep from "lodash/cloneDeep"; +import keyBy from "lodash/keyBy"; +import moment, { Moment } from "moment/moment"; +import { BehaviorSubject } from "rxjs"; + +import { + DataSourceService, + IDataSource, + INovaFilters, + ITimeframe, +} from "@nova-ui/bits"; +import { + DATA_SOURCE, + DEFAULT_PIZZAGNA_ROOT, + IDashboard, + IDataSourceOutput, + IProviderConfiguration, + ISerializableTimeframe, + ITimeseriesItemConfiguration, + ITimeseriesOutput, + ITimeseriesScaleConfig, + ITimeseriesWidgetConfig, + ITimeseriesWidgetData, + ITimeseriesWidgetSeriesData, + IWidget, + LegendPlacement, + PizzagnaLayer, + ProviderRegistryService, + TimeseriesChartPreset, + TimeseriesScaleType, + WellKnownPathKey, + WellKnownProviders, + WidgetTypesService, +} from "@nova-ui/dashboards"; + +/** + * A simple Timeseries data source implementation + */ +@Injectable() +export class BeerVsReadingMockDataSource + extends DataSourceService + implements IDataSource +{ + public static providerId = "BeerVsReadingMockDataSource"; + + public busy = new BehaviorSubject(false); + + public async getFilteredData( + filters: INovaFilters + ): Promise> { + // In this example we're using some static mock data located at the bottom of this file. In a real-world + // scenario, the data for the chart would likely be retrieved via an asynchronous backend call. + let filteredData = getData(); + + this.busy.next(true); + + // Filtering using the filter registered by the TimeFrameBar + const timeframeFilter = filters.timeframe?.value as ITimeframe; + if (timeframeFilter) { + filteredData = filteredData.map((item: ITimeseriesWidgetData) => ({ + id: item.id, + name: item.name, + description: item.description, + data: item.data.filter( + (seriesData: ITimeseriesWidgetSeriesData) => + filterDates( + seriesData.x, + timeframeFilter.startDatetime, + timeframeFilter.endDatetime + ) + ), + })); + } + + this.busy.next(false); + + return { result: { series: filteredData } }; + } +} + +function filterDates(dateToCheck: Date, startDate: Moment, endDate: Moment) { + const mom = moment(dateToCheck); + return ( + mom.isBetween(startDate, endDate) || + mom.isSame(startDate) || + mom.isSame(endDate) + ); +} + +/** + * A component that instantiates the dashboard + */ +@Component({ + selector: "timeseries-widget-example", + templateUrl: "./timeseries-widget-example.component.html", + styleUrls: ["./timeseries-widget-example.component.less"], + standalone: false, +}) +export class TimeseriesWidgetExampleComponent implements OnInit { + // This variable will hold all the data needed to define the layout and behavior of the widgets. + // Pass this to the dashboard component's dashboard input in the template. + public dashboard: IDashboard | undefined; + + // Angular gridster requires a configuration object even if it's empty. + // Pass this to the dashboard component's gridsterConfig input in the template. + public gridsterConfig: GridsterConfig = {}; + + // Boolean passed as an input to the dashboard. When true, widgets can be moved, resized, removed, or edited + public editMode: boolean = false; + + constructor( + // WidgetTypesService provides the widget's necessary structure information + private widgetTypesService: WidgetTypesService, + + // In general, the ProviderRegistryService is used for making entities available for injection into dynamically loaded components. + private providerRegistry: ProviderRegistryService, + + // Angular's ChangeDetectorRef + private changeDetectorRef: ChangeDetectorRef + ) {} + + public ngOnInit(): void { + // Grabbing the widget's default template which will be needed as a parameter for setNode + const widgetTemplate = this.widgetTypesService.getWidgetType( + "timeseries", + 1 + ); + // Registering our data sources as dropdown options in the widget editor/configurator + // Note: This could also be done in the parent module's constructor so that + // multiple dashboards could have access to the same widget template modification. + this.widgetTypesService.setNode( + // This is the template we grabbed above with getWidgetType + widgetTemplate, + // We are setting the editor/configurator part of the widget template + "configurator", + // This indicates which node you are changing and we want to change + // the data source providers available for selection in the editor. + WellKnownPathKey.DataSourceProviders, + // We are setting the data sources available for selection in the editor + [BeerVsReadingMockDataSource.providerId] + ); + + // Registering the data source for injection into the widget. + this.providerRegistry.setProviders({ + [BeerVsReadingMockDataSource.providerId]: { + provide: DATA_SOURCE, + useClass: BeerVsReadingMockDataSource, + deps: [], + }, + }); + + this.initializeDashboard(); + } + + public initializeDashboard(): void { + // We're using a static configuration object for this example, but this is where + // the widget's configuration could potentially be populated from a database + const widgetsWithStructure = widgetConfigs.map((w) => + this.widgetTypesService.mergeWithWidgetType(w) + ); + const widgetsIndex = keyBy(widgetsWithStructure, (w: IWidget) => w.id); + + // Finally, assigning the variables we created above to the dashboard + this.dashboard = { + positions: cloneDeep(positions), + widgets: widgetsIndex, + }; + } + + /** Used for restoring widgets state */ + public reInitializeDashboard(): void { + // destroys the components and their providers so the dashboard can re init data + this.dashboard = undefined; + this.changeDetectorRef.detectChanges(); + + this.initializeDashboard(); + } +} + +const widgetConfigs: IWidget[] = [ + { + id: "lineWidgetId", + type: "timeseries", + pizzagna: { + [PizzagnaLayer.Configuration]: { + [DEFAULT_PIZZAGNA_ROOT]: { + providers: { + [WellKnownProviders.DataSource]: { + // Setting the initially selected data source providerId + providerId: BeerVsReadingMockDataSource.providerId, + } as IProviderConfiguration, + }, + }, + header: { + properties: { + title: "Line Chart", + subtitle: "Survey of 1000 Solarians", + }, + }, + chart: { + providers: { + [WellKnownProviders.Adapter]: { + properties: { + // Setting the series and corresponding labels to initially display on the chart + series: [ + { + id: "series-1", + label: "Beer Tasting", + selectedSeriesId: "series-1", + }, + { + id: "series-2", + label: "Reading", + selectedSeriesId: "series-2", + }, + ] as ITimeseriesItemConfiguration[], + }, + } as Partial, + }, + properties: { + // Setting the general chart configuration + configuration: { + legendPlacement: LegendPlacement.Right, + enableZoom: true, + leftAxisLabel: "Solarians (%)", + // You can optionally define custom colors for the chart by setting the 'chartColors' configuration property + // "chartColors": [ + // "var(--nui-color-chart-eight)", + // "var(--nui-color-chart-nine)", + // "var(--nui-color-chart-ten)", + // ], + } as ITimeseriesWidgetConfig, + }, + }, + timeframeSelection: { + properties: { + // Setting the initial timeframe selected in the timeframe bar + timeframe: { + selectedPresetId: "last7Days", + } as ISerializableTimeframe, + minDate: moment().subtract(60, "days").format(), + maxDate: moment().format(), + }, + }, + }, + }, + }, + { + id: "stackedAreaWidgetId", + type: "timeseries", + pizzagna: { + [PizzagnaLayer.Configuration]: { + [DEFAULT_PIZZAGNA_ROOT]: { + providers: { + [WellKnownProviders.DataSource]: { + // Setting the initially selected data source providerId + providerId: BeerVsReadingMockDataSource.providerId, + } as IProviderConfiguration, + }, + }, + header: { + properties: { + title: "Stacked Area Chart", + subtitle: "Survey of 1000 Solarians", + }, + }, + chart: { + providers: { + [WellKnownProviders.Adapter]: { + properties: { + // Setting the series and corresponding labels to initially display on the chart + series: [ + { + id: "series-1", + label: "Beer Tasting", + selectedSeriesId: "series-1", + }, + { + id: "series-2", + label: "Reading", + selectedSeriesId: "series-2", + }, + ] as ITimeseriesItemConfiguration[], + }, + } as Partial, + }, + properties: { + // Setting the general chart configuration + configuration: { + legendPlacement: LegendPlacement.Right, + enableZoom: true, + // Setting the preset to stacked area + preset: TimeseriesChartPreset.StackedArea, + leftAxisLabel: "Solarians (%)", + // You can optionally define custom colors for the chart by setting the 'chartColors' configuration property + // "chartColors": [ + // "var(--nui-color-chart-eight)", + // "var(--nui-color-chart-nine)", + // "var(--nui-color-chart-ten)", + // ], + } as ITimeseriesWidgetConfig, + }, + }, + timeframeSelection: { + properties: { + // Setting the initial timeframe selected in the timeframe bar + timeframe: { + selectedPresetId: "last7Days", + } as ISerializableTimeframe, + minDate: moment().subtract(60, "days").format(), + maxDate: moment().format(), + }, + }, + }, + }, + }, + { + id: "stackedPercentageAreaWidgetId", + type: "timeseries", + pizzagna: { + [PizzagnaLayer.Configuration]: { + [DEFAULT_PIZZAGNA_ROOT]: { + providers: { + [WellKnownProviders.DataSource]: { + // Setting the initially selected data source providerId + providerId: BeerVsReadingMockDataSource.providerId, + } as IProviderConfiguration, + }, + }, + header: { + properties: { + title: "Stacked Percentage Area Chart", + subtitle: "Survey of 1000 Solarians", + }, + }, + chart: { + providers: { + [WellKnownProviders.Adapter]: { + properties: { + // Setting the series and corresponding labels to initially display on the chart + series: [ + { + id: "series-1", + label: "Beer Tasting", + selectedSeriesId: "series-1", + }, + { + id: "series-2", + label: "Reading", + selectedSeriesId: "series-2", + }, + ] as ITimeseriesItemConfiguration[], + }, + } as Partial, + }, + properties: { + // Setting the general chart configuration + configuration: { + legendPlacement: LegendPlacement.Right, + enableZoom: true, + // Setting the preset to stacked percentage area + preset: TimeseriesChartPreset.StackedPercentageArea, + leftAxisLabel: "Solarians (%)", + // You can optionally define custom colors for the chart by setting the 'chartColors' configuration property + // "chartColors": [ + // "var(--nui-color-chart-eight)", + // "var(--nui-color-chart-nine)", + // "var(--nui-color-chart-ten)", + // ], + } as ITimeseriesWidgetConfig, + }, + }, + timeframeSelection: { + properties: { + // Setting the initial timeframe selected in the timeframe bar + timeframe: { + selectedPresetId: "last7Days", + } as ISerializableTimeframe, + minDate: moment().subtract(60, "days").format(), + maxDate: moment().format(), + }, + }, + }, + }, + }, + { + id: "stackedBarWidgetId", + type: "timeseries", + pizzagna: { + [PizzagnaLayer.Configuration]: { + [DEFAULT_PIZZAGNA_ROOT]: { + providers: { + [WellKnownProviders.DataSource]: { + // Setting the initially selected data source providerId + providerId: BeerVsReadingMockDataSource.providerId, + } as IProviderConfiguration, + }, + }, + header: { + properties: { + title: "Stacked Bar Chart", + subtitle: "Survey of 1000 Solarians", + }, + }, + chart: { + providers: { + [WellKnownProviders.Adapter]: { + properties: { + // Setting the series and corresponding labels to initially display on the chart + series: [ + { + id: "series-1", + label: "Beer Tasting", + selectedSeriesId: "series-1", + }, + { + id: "series-2", + label: "Reading", + selectedSeriesId: "series-2", + }, + ] as ITimeseriesItemConfiguration[], + }, + } as Partial, + }, + properties: { + configuration: { + legendPlacement: LegendPlacement.Right, + enableZoom: true, + leftAxisLabel: "Solarians (%)", + // Setting the preset to stacked bar + preset: TimeseriesChartPreset.StackedBar, + scales: { + x: { + type: TimeseriesScaleType.TimeInterval, + properties: { + interval: 24 * 60 * 60, + }, + } as ITimeseriesScaleConfig, + }, + } as ITimeseriesWidgetConfig, + }, + }, + timeframeSelection: { + properties: { + // Setting the initial timeframe selected in the timeframe bar + timeframe: { + selectedPresetId: "last7Days", + } as ISerializableTimeframe, + minDate: moment().subtract(60, "days").format(), + maxDate: moment().format(), + }, + }, + }, + }, + }, +]; + +// using startOf("day") so that each band for the bar chart corresponds to a calendar day +const now = moment().startOf("day"); + +export const getData = (): ITimeseriesWidgetData[] => [ + { + id: "series-1", + name: "Beer Tasting", + description: "Havin' some suds", + data: [ + { x: now.clone().subtract(20, "day").toDate(), y: 30 }, + { x: now.clone().subtract(19, "day").toDate(), y: 35 }, + { x: now.clone().subtract(18, "day").toDate(), y: 33 }, + { x: now.clone().subtract(17, "day").toDate(), y: 40 }, + { x: now.clone().subtract(16, "day").toDate(), y: 35 }, + { x: now.clone().subtract(15, "day").toDate(), y: 30 }, + { x: now.clone().subtract(14, "day").toDate(), y: 35 }, + { x: now.clone().subtract(13, "day").toDate(), y: 15 }, + { x: now.clone().subtract(12, "day").toDate(), y: 30 }, + { x: now.clone().subtract(11, "day").toDate(), y: 45 }, + { x: now.clone().subtract(10, "day").toDate(), y: 60 }, + { x: now.clone().subtract(9, "day").toDate(), y: 54 }, + { x: now.clone().subtract(8, "day").toDate(), y: 42 }, + { x: now.clone().subtract(7, "day").toDate(), y: 44 }, + { x: now.clone().subtract(6, "day").toDate(), y: 54 }, + { x: now.clone().subtract(5, "day").toDate(), y: 43 }, + { x: now.clone().subtract(4, "day").toDate(), y: 76 }, + { x: now.clone().subtract(3, "day").toDate(), y: 54 }, + { x: now.clone().subtract(2, "day").toDate(), y: 42 }, + { x: now.clone().subtract(1, "day").toDate(), y: 34 }, + ], + }, + { + id: "series-2", + name: "Reading", + description: "Hittin' the books", + data: [ + { x: now.clone().subtract(20, "day").toDate(), y: 60 }, + { x: now.clone().subtract(19, "day").toDate(), y: 64 }, + { x: now.clone().subtract(18, "day").toDate(), y: 70 }, + { x: now.clone().subtract(17, "day").toDate(), y: 55 }, + { x: now.clone().subtract(16, "day").toDate(), y: 55 }, + { x: now.clone().subtract(15, "day").toDate(), y: 45 }, + { x: now.clone().subtract(14, "day").toDate(), y: 60 }, + { x: now.clone().subtract(13, "day").toDate(), y: 65 }, + { x: now.clone().subtract(12, "day").toDate(), y: 63 }, + { x: now.clone().subtract(11, "day").toDate(), y: 60 }, + { x: now.clone().subtract(10, "day").toDate(), y: 61 }, + { x: now.clone().subtract(9, "day").toDate(), y: 65 }, + { x: now.clone().subtract(8, "day").toDate(), y: 63 }, + { x: now.clone().subtract(7, "day").toDate(), y: 58 }, + { x: now.clone().subtract(6, "day").toDate(), y: 64 }, + { x: now.clone().subtract(5, "day").toDate(), y: 63 }, + { x: now.clone().subtract(4, "day").toDate(), y: 60 }, + { x: now.clone().subtract(3, "day").toDate(), y: 62 }, + { x: now.clone().subtract(2, "day").toDate(), y: 61 }, + { x: now.clone().subtract(1, "day").toDate(), y: 62 }, + ], + }, +]; + +// Setting the widget dimensions and position (this is for gridster) +const positions: Record = { + [widgetConfigs[0].id]: { + cols: 6, + rows: 6, + y: 0, + x: 0, + }, + [widgetConfigs[1].id]: { + cols: 6, + rows: 6, + y: 0, + x: 6, + }, + [widgetConfigs[3].id]: { + cols: 6, + rows: 6, + y: 6, + x: 0, + }, + [widgetConfigs[2].id]: { + cols: 6, + rows: 6, + y: 6, + x: 6, + }, +}; +\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\`, + "widget-types/timeseries/timeseries-widget-interactive-example/timeseries-widget-interactive-example.component.ts": \\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\`// © 2022 SolarWinds Worldwide, LLC. All rights reserved. +// +// Permission is hereby granted, free of charge, to any person obtaining a copy +// of this software and associated documentation files (the "Software"), to +// deal in the Software without restriction, including without limitation the +// rights to use, copy, modify, merge, publish, distribute, sublicense, and/or +// sell copies of the Software, and to permit persons to whom the Software is +// furnished to do so, subject to the following conditions: +// +// The above copyright notice and this permission notice shall be included in +// all copies or substantial portions of the Software. +// +// THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR +// IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, +// FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE +// AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER +// LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, +// OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN +// THE SOFTWARE. + +import { + ChangeDetectorRef, + Component, + Injectable, + OnInit, +} from "@angular/core"; +import { GridsterConfig, GridsterItem } from "angular-gridster2"; +import cloneDeep from "lodash/cloneDeep"; +import keyBy from "lodash/keyBy"; +import moment, { Moment } from "moment/moment"; +import { BehaviorSubject } from "rxjs"; + +import { + DataSourceService, + IDataSource, + INovaFilters, + ITimeframe, +} from "@nova-ui/bits"; +import { + DATA_SOURCE, + DEFAULT_PIZZAGNA_ROOT, + IDashboard, + IDataSourceOutput, + IProviderConfiguration, + ISerializableTimeframe, + ITimeseriesItemConfiguration, + ITimeseriesOutput, + ITimeseriesScaleConfig, + ITimeseriesWidgetConfig, + ITimeseriesWidgetData, + ITimeseriesWidgetSeriesData, + IWidget, + NOVA_URL_INTERACTION_HANDLER, + LegendPlacement, + PizzagnaLayer, + ProviderRegistryService, + TimeseriesChartPreset, + TimeseriesScaleType, + WellKnownPathKey, + WellKnownProviders, + WidgetTypesService, +} from "@nova-ui/dashboards"; + +/** + * A simple Timeseries data source implementation + */ +@Injectable() +export class TimeseriesMockDataSource + extends DataSourceService + implements IDataSource +{ + public static providerId = "TimeseriesMockDataSource"; + + public busy = new BehaviorSubject(false); + + public async getFilteredData( + filters: INovaFilters + ): Promise> { + // In this example we're using some static mock data located at the bottom of this file. In a real-world + // scenario, the data for the chart would likely be retrieved via an asynchronous backend call. + let filteredData = getData(); + + this.busy.next(true); + + // Filtering using the filter registered by the TimeFrameBar + const timeframeFilter = filters.timeframe?.value as ITimeframe; + if (timeframeFilter) { + filteredData = filteredData.map((item: ITimeseriesWidgetData) => ({ + id: item.id, + name: item.name, + description: item.description, + // the filtered data should return the provided links if they are set. + link: item?.link, + secondaryLink: item?.secondaryLink, + data: item.data.filter( + (seriesData: ITimeseriesWidgetSeriesData) => + filterDates( + seriesData.x, + timeframeFilter.startDatetime, + timeframeFilter.endDatetime + ) + ), + })); + } + + this.busy.next(false); + + return { result: { series: filteredData } }; + } +} + +function filterDates(dateToCheck: Date, startDate: Moment, endDate: Moment) { + const mom = moment(dateToCheck); + return ( + mom.isBetween(startDate, endDate) || + mom.isSame(startDate) || + mom.isSame(endDate) + ); +} + +/** + * A component that instantiates the dashboard + */ +@Component({ + selector: "timeseries-widget-interactive-example", + templateUrl: "./timeseries-widget-interactive-example.component.html", + styleUrls: ["./timeseries-widget-interactive-example.component.less"], + standalone: false, +}) +export class TimeseriesWidgetInteractiveExampleComponent implements OnInit { + // This variable will hold all the data needed to define the layout and behavior of the widgets. + // Pass this to the dashboard component's dashboard input in the template. + public dashboard: IDashboard | undefined; + + // Angular gridster requires a configuration object even if it's empty. + // Pass this to the dashboard component's gridsterConfig input in the template. + public gridsterConfig: GridsterConfig = {}; + + // Boolean passed as an input to the dashboard. When true, widgets can be moved, resized, removed, or edited + public editMode: boolean = false; + + constructor( + // WidgetTypesService provides the widget's necessary structure information + private widgetTypesService: WidgetTypesService, + + // In general, the ProviderRegistryService is used for making entities available for injection into dynamically loaded components. + private providerRegistry: ProviderRegistryService, + + // Angular's ChangeDetectorRef + private changeDetectorRef: ChangeDetectorRef + ) {} + + public ngOnInit(): void { + // Grabbing the widget's default template which will be needed as a parameter for setNode + const widgetTemplate = this.widgetTypesService.getWidgetType( + "timeseries", + 1 + ); + // Registering our data sources as dropdown options in the widget editor/configurator + // Note: This could also be done in the parent module's constructor so that + // multiple dashboards could have access to the same widget template modification. + this.widgetTypesService.setNode( + // This is the template we grabbed above with getWidgetType + widgetTemplate, + // We are setting the editor/configurator part of the widget template + "configurator", + // This indicates which node you are changing and we want to change + // the data source providers available for selection in the editor. + WellKnownPathKey.DataSourceProviders, + // We are setting the data sources available for selection in the editor + [TimeseriesMockDataSource.providerId] + ); + + // Registering the data source for injection into the widget. + this.providerRegistry.setProviders({ + [TimeseriesMockDataSource.providerId]: { + provide: DATA_SOURCE, + useClass: TimeseriesMockDataSource, + deps: [], + }, + }); + + this.initializeDashboard(); + } + + public initializeDashboard(): void { + // We're using a static configuration object for this example, but this is where + // the widget's configuration could potentially be populated from a database + const widgetsWithStructure = widgetConfigs.map((w) => + this.widgetTypesService.mergeWithWidgetType(w) + ); + const widgetsIndex = keyBy(widgetsWithStructure, (w: IWidget) => w.id); + + // Finally, assigning the variables we created above to the dashboard + this.dashboard = { + positions: cloneDeep(positions), + widgets: widgetsIndex, + }; + } + + /** Used for restoring widgets state */ + public reInitializeDashboard(): void { + // destroys the components and their providers so the dashboard can re init data + this.dashboard = undefined; + this.changeDetectorRef.detectChanges(); + + this.initializeDashboard(); + } +} + +const widgetConfigs: IWidget[] = [ + { + id: "lineWidgetId", + type: "timeseries", + pizzagna: { + [PizzagnaLayer.Configuration]: { + [DEFAULT_PIZZAGNA_ROOT]: { + providers: { + [WellKnownProviders.DataSource]: { + // Setting the initially selected data source providerId + providerId: TimeseriesMockDataSource.providerId, + } as IProviderConfiguration, + [WellKnownProviders.InteractionHandler]: { + // Setting the UrlInteractionHandler as an interactionHandler + providerId: NOVA_URL_INTERACTION_HANDLER, + properties: { + // the 'url' property tells the handler what link to use when interaction occurs on the series + url: "\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\${data.link || 'https://en.wikipedia.org/wiki/'+data.legendDescriptionPrimary}", + // by default the link is opened in the current window, set 'newWindow' to true to open in a new tab instead + // newWindow: true, + }, + }, + }, + }, + header: { + properties: { + title: "Line Chart", + subtitle: "Basic Timeseries with Interaction", + }, + }, + chart: { + providers: { + [WellKnownProviders.Adapter]: { + properties: { + // Setting the series and corresponding labels to initially display on the chart + series: [ + { + id: "series-1", + label: "Nur-Sultan", + selectedSeriesId: "series-1", + }, + { + id: "series-2", + label: "Brno", + selectedSeriesId: "series-2", + }, + { + id: "series-3", + label: "Lisbon", + selectedSeriesId: "series-3", + }, + { + id: "series-4", + label: "Austin", + selectedSeriesId: "series-4", + }, + ] as ITimeseriesItemConfiguration[], + }, + } as Partial, + }, + properties: { + // Setting the general chart configuration + configuration: { + // setting interaction to 'series' will make all series in the chart interactable + interaction: "series", + legendPlacement: LegendPlacement.Right, + enableZoom: true, + } as ITimeseriesWidgetConfig, + }, + }, + timeframeSelection: { + properties: { + timeframe: { + selectedPresetId: "last7Days", + } as ISerializableTimeframe, + minDate: moment().subtract(60, "days").format(), + maxDate: moment().format(), + }, + }, + }, + }, + }, + { + id: "stackedBarWidgetId", + type: "timeseries", + pizzagna: { + [PizzagnaLayer.Configuration]: { + [DEFAULT_PIZZAGNA_ROOT]: { + providers: { + [WellKnownProviders.DataSource]: { + // Setting the initially selected data source providerId + providerId: TimeseriesMockDataSource.providerId, + } as IProviderConfiguration, + }, + }, + header: { + properties: { + title: "Stacked Bar Chart", + subtitle: + "Basic Timeseries without Interaction Handler", + }, + }, + chart: { + providers: { + [WellKnownProviders.Adapter]: { + properties: { + // Setting the series and corresponding labels to initially display on the chart + series: [ + { + id: "series-1", + label: "Nur-Sultan", + selectedSeriesId: "series-1", + }, + { + id: "series-2", + label: "Brno", + selectedSeriesId: "series-2", + }, + { + id: "series-3", + label: "Lisbon", + selectedSeriesId: "series-3", + }, + { + id: "series-4", + label: "Austin", + selectedSeriesId: "series-4", + }, + ] as ITimeseriesItemConfiguration[], + }, + } as Partial, + }, + properties: { + // Setting the general chart configuration + configuration: { + legendPlacement: LegendPlacement.Right, + enableZoom: true, + // Setting the preset to stacked bar + preset: TimeseriesChartPreset.StackedBar, + scales: { + x: { + type: TimeseriesScaleType.TimeInterval, + properties: { + interval: 24 * 60 * 60, + }, + } as ITimeseriesScaleConfig, + }, + } as ITimeseriesWidgetConfig, + }, + }, + timeframeSelection: { + properties: { + // Setting the initial timeframe selected in the timeframe bar + timeframe: { + selectedPresetId: "last7Days", + } as ISerializableTimeframe, + minDate: moment().subtract(60, "days").format(), + maxDate: moment().format(), + }, + }, + }, + }, + }, +]; + +// using startOf("day") so that each band for the bar chart corresponds to a calendar day +const startOfToday = moment().startOf("day").toDate(); + +export const getData = (): ITimeseriesWidgetData[] => [ + { + id: "series-1", + name: "Nur-Sultan", + description: "'link' only", + link: "https://en.wikipedia.org/wiki/Nur-Sultan", + data: [ + { x: moment(startOfToday).subtract(59, "day").toDate(), y: 35 }, + { x: moment(startOfToday).subtract(58, "day").toDate(), y: 33 }, + { x: moment(startOfToday).subtract(57, "day").toDate(), y: 40 }, + { x: moment(startOfToday).subtract(56, "day").toDate(), y: 35 }, + { x: moment(startOfToday).subtract(55, "day").toDate(), y: 30 }, + { x: moment(startOfToday).subtract(54, "day").toDate(), y: 35 }, + { x: moment(startOfToday).subtract(53, "day").toDate(), y: 15 }, + { x: moment(startOfToday).subtract(52, "day").toDate(), y: 30 }, + { x: moment(startOfToday).subtract(51, "day").toDate(), y: 35 }, + { x: moment(startOfToday).subtract(50, "day").toDate(), y: 34 }, + { x: moment(startOfToday).subtract(49, "day").toDate(), y: 35 }, + { x: moment(startOfToday).subtract(48, "day").toDate(), y: 33 }, + { x: moment(startOfToday).subtract(47, "day").toDate(), y: 40 }, + { x: moment(startOfToday).subtract(46, "day").toDate(), y: 35 }, + { x: moment(startOfToday).subtract(45, "day").toDate(), y: 30 }, + { x: moment(startOfToday).subtract(44, "day").toDate(), y: 35 }, + { x: moment(startOfToday).subtract(43, "day").toDate(), y: 15 }, + { x: moment(startOfToday).subtract(42, "day").toDate(), y: 30 }, + { x: moment(startOfToday).subtract(41, "day").toDate(), y: 35 }, + { x: moment(startOfToday).subtract(40, "day").toDate(), y: 34 }, + { x: moment(startOfToday).subtract(39, "day").toDate(), y: 35 }, + { x: moment(startOfToday).subtract(38, "day").toDate(), y: 33 }, + { x: moment(startOfToday).subtract(37, "day").toDate(), y: 40 }, + { x: moment(startOfToday).subtract(36, "day").toDate(), y: 35 }, + { x: moment(startOfToday).subtract(35, "day").toDate(), y: 30 }, + { x: moment(startOfToday).subtract(34, "day").toDate(), y: 35 }, + { x: moment(startOfToday).subtract(33, "day").toDate(), y: 15 }, + { x: moment(startOfToday).subtract(32, "day").toDate(), y: 30 }, + { x: moment(startOfToday).subtract(31, "day").toDate(), y: 35 }, + { x: moment(startOfToday).subtract(30, "day").toDate(), y: 34 }, + { x: moment(startOfToday).subtract(29, "day").toDate(), y: 35 }, + { x: moment(startOfToday).subtract(28, "day").toDate(), y: 33 }, + { x: moment(startOfToday).subtract(27, "day").toDate(), y: 40 }, + { x: moment(startOfToday).subtract(26, "day").toDate(), y: 35 }, + { x: moment(startOfToday).subtract(25, "day").toDate(), y: 30 }, + { x: moment(startOfToday).subtract(24, "day").toDate(), y: 35 }, + { x: moment(startOfToday).subtract(23, "day").toDate(), y: 15 }, + { x: moment(startOfToday).subtract(22, "day").toDate(), y: 30 }, + { x: moment(startOfToday).subtract(21, "day").toDate(), y: 35 }, + { x: moment(startOfToday).subtract(20, "day").toDate(), y: 34 }, + { x: moment(startOfToday).subtract(19, "day").toDate(), y: 35 }, + { x: moment(startOfToday).subtract(18, "day").toDate(), y: 33 }, + { x: moment(startOfToday).subtract(17, "day").toDate(), y: 40 }, + { x: moment(startOfToday).subtract(16, "day").toDate(), y: 35 }, + { x: moment(startOfToday).subtract(15, "day").toDate(), y: 30 }, + { x: moment(startOfToday).subtract(14, "day").toDate(), y: 35 }, + { x: moment(startOfToday).subtract(13, "day").toDate(), y: 15 }, + { x: moment(startOfToday).subtract(12, "day").toDate(), y: 30 }, + { x: moment(startOfToday).subtract(11, "day").toDate(), y: 35 }, + { x: moment(startOfToday).subtract(10, "day").toDate(), y: 34 }, + { x: moment(startOfToday).subtract(9, "day").toDate(), y: 33 }, + { x: moment(startOfToday).subtract(8, "day").toDate(), y: 35 }, + { x: moment(startOfToday).subtract(7, "day").toDate(), y: 36 }, + { x: moment(startOfToday).subtract(6, "day").toDate(), y: 34 }, + { x: moment(startOfToday).subtract(5, "day").toDate(), y: 33 }, + { x: moment(startOfToday).subtract(4, "day").toDate(), y: 30 }, + { x: moment(startOfToday).subtract(3, "day").toDate(), y: 32 }, + { x: moment(startOfToday).subtract(2, "day").toDate(), y: 31 }, + { x: moment(startOfToday).subtract(1, "day").toDate(), y: 34 }, + { x: moment(startOfToday).toDate(), y: 25 }, + ], + }, + { + id: "series-2", + name: "Brno", + description: "'link' and 'secondaryLink'", + link: "https://en.wikipedia.org/wiki/Brno", + secondaryLink: "https://en.wikipedia.org/wiki/Europe", + data: [ + { x: moment(startOfToday).subtract(59, "day").toDate(), y: 35 }, + { x: moment(startOfToday).subtract(58, "day").toDate(), y: 33 }, + { x: moment(startOfToday).subtract(57, "day").toDate(), y: 40 }, + { x: moment(startOfToday).subtract(56, "day").toDate(), y: 35 }, + { x: moment(startOfToday).subtract(55, "day").toDate(), y: 30 }, + { x: moment(startOfToday).subtract(54, "day").toDate(), y: 35 }, + { x: moment(startOfToday).subtract(53, "day").toDate(), y: 15 }, + { x: moment(startOfToday).subtract(52, "day").toDate(), y: 30 }, + { x: moment(startOfToday).subtract(51, "day").toDate(), y: 35 }, + { x: moment(startOfToday).subtract(50, "day").toDate(), y: 34 }, + { x: moment(startOfToday).subtract(49, "day").toDate(), y: 35 }, + { x: moment(startOfToday).subtract(48, "day").toDate(), y: 33 }, + { x: moment(startOfToday).subtract(47, "day").toDate(), y: 40 }, + { x: moment(startOfToday).subtract(46, "day").toDate(), y: 35 }, + { x: moment(startOfToday).subtract(45, "day").toDate(), y: 30 }, + { x: moment(startOfToday).subtract(44, "day").toDate(), y: 35 }, + { x: moment(startOfToday).subtract(43, "day").toDate(), y: 15 }, + { x: moment(startOfToday).subtract(42, "day").toDate(), y: 30 }, + { x: moment(startOfToday).subtract(41, "day").toDate(), y: 35 }, + { x: moment(startOfToday).subtract(40, "day").toDate(), y: 34 }, + { x: moment(startOfToday).subtract(39, "day").toDate(), y: 35 }, + { x: moment(startOfToday).subtract(38, "day").toDate(), y: 33 }, + { x: moment(startOfToday).subtract(37, "day").toDate(), y: 40 }, + { x: moment(startOfToday).subtract(36, "day").toDate(), y: 35 }, + { x: moment(startOfToday).subtract(35, "day").toDate(), y: 30 }, + { x: moment(startOfToday).subtract(34, "day").toDate(), y: 35 }, + { x: moment(startOfToday).subtract(33, "day").toDate(), y: 15 }, + { x: moment(startOfToday).subtract(32, "day").toDate(), y: 30 }, + { x: moment(startOfToday).subtract(31, "day").toDate(), y: 35 }, + { x: moment(startOfToday).subtract(30, "day").toDate(), y: 34 }, + { x: moment(startOfToday).subtract(29, "day").toDate(), y: 35 }, + { x: moment(startOfToday).subtract(28, "day").toDate(), y: 33 }, + { x: moment(startOfToday).subtract(27, "day").toDate(), y: 40 }, + { x: moment(startOfToday).subtract(26, "day").toDate(), y: 35 }, + { x: moment(startOfToday).subtract(25, "day").toDate(), y: 30 }, + { x: moment(startOfToday).subtract(24, "day").toDate(), y: 35 }, + { x: moment(startOfToday).subtract(23, "day").toDate(), y: 15 }, + { x: moment(startOfToday).subtract(22, "day").toDate(), y: 30 }, + { x: moment(startOfToday).subtract(21, "day").toDate(), y: 35 }, + { x: moment(startOfToday).subtract(20, "day").toDate(), y: 34 }, + { x: moment(startOfToday).subtract(19, "day").toDate(), y: 64 }, + { x: moment(startOfToday).subtract(18, "day").toDate(), y: 70 }, + { x: moment(startOfToday).subtract(17, "day").toDate(), y: 55 }, + { x: moment(startOfToday).subtract(16, "day").toDate(), y: 55 }, + { x: moment(startOfToday).subtract(15, "day").toDate(), y: 45 }, + { x: moment(startOfToday).subtract(14, "day").toDate(), y: 10 }, + { x: moment(startOfToday).subtract(13, "day").toDate(), y: 65 }, + { x: moment(startOfToday).subtract(12, "day").toDate(), y: 35 }, + { x: moment(startOfToday).subtract(11, "day").toDate(), y: 60 }, + { x: moment(startOfToday).subtract(10, "day").toDate(), y: 61 }, + { x: moment(startOfToday).subtract(9, "day").toDate(), y: 65 }, + { x: moment(startOfToday).subtract(8, "day").toDate(), y: 63 }, + { x: moment(startOfToday).subtract(7, "day").toDate(), y: 58 }, + { x: moment(startOfToday).subtract(6, "day").toDate(), y: 64 }, + { x: moment(startOfToday).subtract(5, "day").toDate(), y: 63 }, + { x: moment(startOfToday).subtract(4, "day").toDate(), y: 60 }, + { x: moment(startOfToday).subtract(3, "day").toDate(), y: 62 }, + { x: moment(startOfToday).subtract(2, "day").toDate(), y: 61 }, + { x: moment(startOfToday).subtract(1, "day").toDate(), y: 62 }, + { x: moment(startOfToday).toDate(), y: 55 }, + ], + }, + { + id: "series-3", + name: "Lisbon", + description: "'secondaryLink' only", + secondaryLink: "https://en.wikipedia.org/wiki/Lisbon", + data: [ + { x: moment(startOfToday).subtract(59, "day").toDate(), y: 35 }, + { x: moment(startOfToday).subtract(58, "day").toDate(), y: 33 }, + { x: moment(startOfToday).subtract(57, "day").toDate(), y: 40 }, + { x: moment(startOfToday).subtract(56, "day").toDate(), y: 35 }, + { x: moment(startOfToday).subtract(55, "day").toDate(), y: 30 }, + { x: moment(startOfToday).subtract(54, "day").toDate(), y: 35 }, + { x: moment(startOfToday).subtract(53, "day").toDate(), y: 15 }, + { x: moment(startOfToday).subtract(52, "day").toDate(), y: 30 }, + { x: moment(startOfToday).subtract(51, "day").toDate(), y: 35 }, + { x: moment(startOfToday).subtract(50, "day").toDate(), y: 34 }, + { x: moment(startOfToday).subtract(49, "day").toDate(), y: 35 }, + { x: moment(startOfToday).subtract(48, "day").toDate(), y: 33 }, + { x: moment(startOfToday).subtract(47, "day").toDate(), y: 40 }, + { x: moment(startOfToday).subtract(46, "day").toDate(), y: 35 }, + { x: moment(startOfToday).subtract(45, "day").toDate(), y: 30 }, + { x: moment(startOfToday).subtract(44, "day").toDate(), y: 35 }, + { x: moment(startOfToday).subtract(43, "day").toDate(), y: 15 }, + { x: moment(startOfToday).subtract(42, "day").toDate(), y: 30 }, + { x: moment(startOfToday).subtract(41, "day").toDate(), y: 35 }, + { x: moment(startOfToday).subtract(40, "day").toDate(), y: 34 }, + { x: moment(startOfToday).subtract(39, "day").toDate(), y: 35 }, + { x: moment(startOfToday).subtract(38, "day").toDate(), y: 33 }, + { x: moment(startOfToday).subtract(37, "day").toDate(), y: 40 }, + { x: moment(startOfToday).subtract(36, "day").toDate(), y: 35 }, + { x: moment(startOfToday).subtract(35, "day").toDate(), y: 30 }, + { x: moment(startOfToday).subtract(34, "day").toDate(), y: 35 }, + { x: moment(startOfToday).subtract(33, "day").toDate(), y: 15 }, + { x: moment(startOfToday).subtract(32, "day").toDate(), y: 30 }, + { x: moment(startOfToday).subtract(31, "day").toDate(), y: 35 }, + { x: moment(startOfToday).subtract(30, "day").toDate(), y: 34 }, + { x: moment(startOfToday).subtract(29, "day").toDate(), y: 35 }, + { x: moment(startOfToday).subtract(28, "day").toDate(), y: 33 }, + { x: moment(startOfToday).subtract(27, "day").toDate(), y: 40 }, + { x: moment(startOfToday).subtract(26, "day").toDate(), y: 35 }, + { x: moment(startOfToday).subtract(25, "day").toDate(), y: 30 }, + { x: moment(startOfToday).subtract(24, "day").toDate(), y: 35 }, + { x: moment(startOfToday).subtract(23, "day").toDate(), y: 15 }, + { x: moment(startOfToday).subtract(22, "day").toDate(), y: 30 }, + { x: moment(startOfToday).subtract(21, "day").toDate(), y: 35 }, + { x: moment(startOfToday).subtract(20, "day").toDate(), y: 34 }, + { x: moment(startOfToday).subtract(19, "day").toDate(), y: 80 }, + { x: moment(startOfToday).subtract(18, "day").toDate(), y: 70 }, + { x: moment(startOfToday).subtract(17, "day").toDate(), y: 95 }, + { x: moment(startOfToday).subtract(16, "day").toDate(), y: 90 }, + { x: moment(startOfToday).subtract(15, "day").toDate(), y: 85 }, + { x: moment(startOfToday).subtract(14, "day").toDate(), y: 70 }, + { x: moment(startOfToday).subtract(13, "day").toDate(), y: 75 }, + { x: moment(startOfToday).subtract(12, "day").toDate(), y: 69 }, + { x: moment(startOfToday).subtract(11, "day").toDate(), y: 75 }, + { x: moment(startOfToday).subtract(10, "day").toDate(), y: 81 }, + { x: moment(startOfToday).subtract(9, "day").toDate(), y: 93 }, + { x: moment(startOfToday).subtract(8, "day").toDate(), y: 83 }, + { x: moment(startOfToday).subtract(7, "day").toDate(), y: 70 }, + { x: moment(startOfToday).subtract(6, "day").toDate(), y: 74 }, + { x: moment(startOfToday).subtract(5, "day").toDate(), y: 73 }, + { x: moment(startOfToday).subtract(4, "day").toDate(), y: 68 }, + { x: moment(startOfToday).subtract(3, "day").toDate(), y: 72 }, + { x: moment(startOfToday).subtract(2, "day").toDate(), y: 61 }, + { x: moment(startOfToday).subtract(1, "day").toDate(), y: 69 }, + { x: moment(startOfToday).toDate(), y: 60 }, + ], + }, + { + id: "series-4", + name: "Austin", + description: "No links", + data: [ + { x: moment(startOfToday).subtract(59, "day").toDate(), y: 25 }, + { x: moment(startOfToday).subtract(58, "day").toDate(), y: 43 }, + { x: moment(startOfToday).subtract(57, "day").toDate(), y: 40 }, + { x: moment(startOfToday).subtract(56, "day").toDate(), y: 65 }, + { x: moment(startOfToday).subtract(55, "day").toDate(), y: 30 }, + { x: moment(startOfToday).subtract(54, "day").toDate(), y: 25 }, + { x: moment(startOfToday).subtract(53, "day").toDate(), y: 45 }, + { x: moment(startOfToday).subtract(52, "day").toDate(), y: 30 }, + { x: moment(startOfToday).subtract(51, "day").toDate(), y: 85 }, + { x: moment(startOfToday).subtract(50, "day").toDate(), y: 74 }, + { x: moment(startOfToday).subtract(49, "day").toDate(), y: 55 }, + { x: moment(startOfToday).subtract(48, "day").toDate(), y: 23 }, + { x: moment(startOfToday).subtract(47, "day").toDate(), y: 40 }, + { x: moment(startOfToday).subtract(46, "day").toDate(), y: 15 }, + { x: moment(startOfToday).subtract(45, "day").toDate(), y: 20 }, + { x: moment(startOfToday).subtract(44, "day").toDate(), y: 65 }, + { x: moment(startOfToday).subtract(43, "day").toDate(), y: 25 }, + { x: moment(startOfToday).subtract(42, "day").toDate(), y: 40 }, + { x: moment(startOfToday).subtract(41, "day").toDate(), y: 25 }, + { x: moment(startOfToday).subtract(40, "day").toDate(), y: 54 }, + { x: moment(startOfToday).subtract(39, "day").toDate(), y: 65 }, + { x: moment(startOfToday).subtract(38, "day").toDate(), y: 33 }, + { x: moment(startOfToday).subtract(37, "day").toDate(), y: 50 }, + { x: moment(startOfToday).subtract(36, "day").toDate(), y: 45 }, + { x: moment(startOfToday).subtract(35, "day").toDate(), y: 20 }, + { x: moment(startOfToday).subtract(34, "day").toDate(), y: 25 }, + { x: moment(startOfToday).subtract(33, "day").toDate(), y: 35 }, + { x: moment(startOfToday).subtract(32, "day").toDate(), y: 20 }, + { x: moment(startOfToday).subtract(31, "day").toDate(), y: 35 }, + { x: moment(startOfToday).subtract(30, "day").toDate(), y: 14 }, + { x: moment(startOfToday).subtract(29, "day").toDate(), y: 55 }, + { x: moment(startOfToday).subtract(28, "day").toDate(), y: 23 }, + { x: moment(startOfToday).subtract(27, "day").toDate(), y: 10 }, + { x: moment(startOfToday).subtract(26, "day").toDate(), y: 5 }, + { x: moment(startOfToday).subtract(25, "day").toDate(), y: 20 }, + { x: moment(startOfToday).subtract(24, "day").toDate(), y: 35 }, + { x: moment(startOfToday).subtract(23, "day").toDate(), y: 15 }, + { x: moment(startOfToday).subtract(22, "day").toDate(), y: 30 }, + { x: moment(startOfToday).subtract(21, "day").toDate(), y: 35 }, + { x: moment(startOfToday).subtract(20, "day").toDate(), y: 34 }, + { x: moment(startOfToday).subtract(19, "day").toDate(), y: 50 }, + { x: moment(startOfToday).subtract(18, "day").toDate(), y: 60 }, + { x: moment(startOfToday).subtract(17, "day").toDate(), y: 95 }, + { x: moment(startOfToday).subtract(16, "day").toDate(), y: 80 }, + { x: moment(startOfToday).subtract(15, "day").toDate(), y: 65 }, + { x: moment(startOfToday).subtract(14, "day").toDate(), y: 80 }, + { x: moment(startOfToday).subtract(13, "day").toDate(), y: 85 }, + { x: moment(startOfToday).subtract(12, "day").toDate(), y: 69 }, + { x: moment(startOfToday).subtract(11, "day").toDate(), y: 65 }, + { x: moment(startOfToday).subtract(10, "day").toDate(), y: 71 }, + { x: moment(startOfToday).subtract(9, "day").toDate(), y: 73 }, + { x: moment(startOfToday).subtract(8, "day").toDate(), y: 43 }, + { x: moment(startOfToday).subtract(7, "day").toDate(), y: 70 }, + { x: moment(startOfToday).subtract(6, "day").toDate(), y: 84 }, + { x: moment(startOfToday).subtract(5, "day").toDate(), y: 73 }, + { x: moment(startOfToday).subtract(4, "day").toDate(), y: 38 }, + { x: moment(startOfToday).subtract(3, "day").toDate(), y: 72 }, + { x: moment(startOfToday).subtract(2, "day").toDate(), y: 81 }, + { x: moment(startOfToday).subtract(1, "day").toDate(), y: 59 }, + { x: moment(startOfToday).toDate(), y: 60 }, + ], + }, +]; +// Setting the widget dimensions and position (this is for gridster) +const positions: Record = { + [widgetConfigs[0].id]: { + cols: 6, + rows: 6, + y: 0, + x: 0, + }, + [widgetConfigs[1].id]: { + cols: 6, + rows: 6, + y: 0, + x: 6, + }, +}; +\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\`, + "widget-types/timeseries/timeseries-widget-status-bar-example/timeseries-widget-status-bar-example.component.ts": \\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\`// © 2022 SolarWinds Worldwide, LLC. All rights reserved. +// +// Permission is hereby granted, free of charge, to any person obtaining a copy +// of this software and associated documentation files (the "Software"), to +// deal in the Software without restriction, including without limitation the +// rights to use, copy, modify, merge, publish, distribute, sublicense, and/or +// sell copies of the Software, and to permit persons to whom the Software is +// furnished to do so, subject to the following conditions: +// +// The above copyright notice and this permission notice shall be included in +// all copies or substantial portions of the Software. +// +// THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR +// IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, +// FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE +// AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER +// LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, +// OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN +// THE SOFTWARE. + +import { + ChangeDetectorRef, + Component, + Injectable, + OnInit, +} from "@angular/core"; +import { GridsterConfig, GridsterItem } from "angular-gridster2"; +import keyBy from "lodash/keyBy"; +import moment, { Moment } from "moment/moment"; +import { BehaviorSubject } from "rxjs"; + +import { + DataSourceService, + IDataSource, + IDataSourceOutput, + INovaFilters, + ITimeframe, +} from "@nova-ui/bits"; +import { CHART_PALETTE_CS_S_EXTENDED } from "@nova-ui/charts"; +import { + applyStatusEndpoints, + DATA_SOURCE, + DEFAULT_PIZZAGNA_ROOT, + IDashboard, + IProviderConfiguration, + ISerializableTimeframe, + ITimeseriesItemConfiguration, + ITimeseriesOutput, + ITimeseriesScaleConfig, + ITimeseriesWidgetConfig, + ITimeseriesWidgetData, + ITimeseriesWidgetSeriesData, + ITimeseriesWidgetStatusData, + IWidget, + LegendPlacement, + PizzagnaLayer, + ProviderRegistryService, + TimeseriesChartPreset, + TimeseriesScaleType, + WellKnownPathKey, + WellKnownProviders, + WidgetTypesService, +} from "@nova-ui/dashboards"; + +/** + * A simple Timeseries data source implementation with continuous (non-interval-based) output + */ +@Injectable() +export class TimeseriesStatusContinuousDataSource + extends DataSourceService + implements IDataSource> +{ + public static providerId = "TimeseriesStatusContinuousDataSource"; + + public busy = new BehaviorSubject(false); + + public async getFilteredData( + filters: INovaFilters + ): Promise< + IDataSourceOutput> + > { + // In this example we're using some static mock data located at the bottom of this file. In a real-world + // scenario, the data for the chart would likely be retrieved via an asynchronous backend call. + const data = getContinuousData(); + let filteredData = data; + + this.busy.next(true); + + // Filtering using the filter registered by the TimeFrameBar + const timeframeFilter = filters.timeframe?.value as ITimeframe; + if (timeframeFilter) { + filteredData = filteredData.map((item: ITimeseriesWidgetData) => ({ + id: item.id, + name: item.name, + description: item.description, + data: item.data.filter( + (seriesData: ITimeseriesWidgetSeriesData) => + filterDates( + seriesData.x, + timeframeFilter.startDatetime, + timeframeFilter.endDatetime + ) + ), + })); + + // apply endpoints on the filtered status data so that when the status chart is zoomed (filtered), + // each status visualizations is ensured to have valid start and end values + filteredData = applyStatusEndpoints( + timeframeFilter, + filteredData, + data + ); + } + + this.busy.next(false); + return { result: { series: filteredData } }; + } +} + +/** + * A simple Timeseries data source implementation with interval-based output + */ +@Injectable() +export class TimeseriesStatusIntervalDataSource + extends DataSourceService + implements IDataSource> +{ + public static providerId = "TimeseriesStatusIntervalDataSource"; + + public busy = new BehaviorSubject(false); + + public async getFilteredData( + filters: INovaFilters + ): Promise< + IDataSourceOutput> + > { + // In this example we're using some static mock data located at the bottom of this file. In a real-world + // scenario, the data for the chart would likely be retrieved via an asynchronous backend call. + const data = getIntervalData(); + let filteredData = data; + + this.busy.next(true); + + // Filtering using the filter registered by the TimeFrameBar + const timeframeFilter = filters.timeframe?.value as ITimeframe; + if (timeframeFilter) { + filteredData = filteredData.map((item: ITimeseriesWidgetData) => ({ + id: item.id, + name: item.name, + description: item.description, + data: item.data.filter( + (seriesData: ITimeseriesWidgetSeriesData) => + filterDates( + seriesData.x, + timeframeFilter.startDatetime, + timeframeFilter.endDatetime + ) + ), + })); + + // Note: There's no need to apply filter endpoints to the status data in this case since we know it's visualized in regular intervals + } + + this.busy.next(false); + return { result: { series: filteredData } }; + } +} + +function filterDates(dateToCheck: Date, startDate: Moment, endDate: Moment) { + const mom = moment(dateToCheck); + return ( + mom.isBetween(startDate, endDate) || + mom.isSame(startDate) || + mom.isSame(endDate) + ); +} + +/** + * A component that instantiates the dashboard + */ +@Component({ + selector: "timeseries-widget-status-bar-example", + templateUrl: "./timeseries-widget-status-bar-example.component.html", + styleUrls: ["./timeseries-widget-status-bar-example.component.less"], + standalone: false, +}) +export class TimeseriesWidgetStatusBarExampleComponent implements OnInit { + // This variable will hold all the data needed to define the layout and behavior of the widgets. + // Pass this to the dashboard component's dashboard input in the template. + public dashboard: IDashboard | undefined; + + // Angular gridster requires a configuration object even if it's empty. + // Pass this to the dashboard component's gridsterConfig input in the template. + public gridsterConfig: GridsterConfig = {}; + + // Boolean passed as an input to the dashboard. When true, widgets can be moved, resized, removed, or edited + public editMode: boolean = false; + + constructor( + // WidgetTypesService provides the widget's necessary structure information + private widgetTypesService: WidgetTypesService, + + // In general, the ProviderRegistryService is used for making entities available for injection into dynamically loaded components. + private providerRegistry: ProviderRegistryService, + private changeDetectorRef: ChangeDetectorRef + ) {} + + public ngOnInit(): void { + // Grabbing the widget's default template which will be needed as a parameter for setNode + const widgetTemplate = this.widgetTypesService.getWidgetType( + "timeseries", + 1 + ); + // Registering our data sources as dropdown options in the widget editor/configurator + // Note: This could also be done in the parent module's constructor so that + // multiple dashboards could have access to the same widget template modification. + this.widgetTypesService.setNode( + // This is the template we grabbed above with getWidgetType + widgetTemplate, + // We are setting the editor/configurator part of the widget template + "configurator", + // This indicates which node you are changing and we want to change + // the data source providers available for selection in the editor. + WellKnownPathKey.DataSourceProviders, + // We are setting the data sources available for selection in the editor + [ + TimeseriesStatusContinuousDataSource.providerId, + TimeseriesStatusIntervalDataSource.providerId, + ] + ); + + // Registering the data source for injection into the widget. + this.providerRegistry.setProviders({ + [TimeseriesStatusContinuousDataSource.providerId]: { + provide: DATA_SOURCE, + useClass: TimeseriesStatusContinuousDataSource, + deps: [], + }, + [TimeseriesStatusIntervalDataSource.providerId]: { + provide: DATA_SOURCE, + useClass: TimeseriesStatusIntervalDataSource, + deps: [], + }, + }); + + this.initializeDashboard(); + } + + /** Used for restoring widgets state */ + public reInitializeDashboard(): void { + // destroys the components and their providers so the dashboard can re init data + this.dashboard = undefined; + this.changeDetectorRef.detectChanges(); + + this.initializeDashboard(); + } + + public initializeDashboard(): void { + // We're using a static configuration object for this example, but this is where + // the widget's configuration could potentially be populated from a database + const widgetsWithStructure = widgetConfigs.map((w) => + this.widgetTypesService.mergeWithWidgetType(w) + ); + const widgetsIndex = keyBy(widgetsWithStructure, (w: IWidget) => w.id); + + // Finally, assigning the variables we created above to the dashboard + this.dashboard = { + positions, + widgets: widgetsIndex, + }; + } +} + +const widgetConfigs: IWidget[] = [ + { + id: "statusChartWidgetId", + type: "timeseries", + pizzagna: { + [PizzagnaLayer.Configuration]: { + [DEFAULT_PIZZAGNA_ROOT]: { + providers: { + [WellKnownProviders.DataSource]: { + // Setting the initially selected data source providerId + providerId: + TimeseriesStatusContinuousDataSource.providerId, + } as IProviderConfiguration, + }, + }, + header: { + properties: { + title: "Status Bar Chart with Continuous (Non-Interval) Scale", + subtitle: "Basic Timeseries Widget", + }, + }, + chart: { + providers: { + [WellKnownProviders.Adapter]: { + properties: { + // Setting the series and corresponding labels to initially display on the chart + series: [ + { + id: "series-1", + label: "Node Status", + selectedSeriesId: "series-1", + }, + { + id: "series-2", + label: "Node Status", + selectedSeriesId: "series-2", + }, + ] as ITimeseriesItemConfiguration[], + }, + } as Partial, + }, + properties: { + configuration: { + legendPlacement: LegendPlacement.Right, + enableZoom: true, + // Setting the preset to status bar + preset: TimeseriesChartPreset.StatusBar, + } as ITimeseriesWidgetConfig, + }, + }, + timeframeSelection: { + properties: { + // Setting the initial timeframe selected in the timeframe bar + timeframe: { + selectedPresetId: "last7Days", + } as ISerializableTimeframe, + maxDate: moment().format(), + }, + }, + }, + }, + }, + { + id: "statusIntervalChartWidgetId", + type: "timeseries", + pizzagna: { + [PizzagnaLayer.Configuration]: { + [DEFAULT_PIZZAGNA_ROOT]: { + providers: { + [WellKnownProviders.DataSource]: { + // Setting the initially selected data source providerId + providerId: + TimeseriesStatusIntervalDataSource.providerId, + } as IProviderConfiguration, + }, + }, + header: { + properties: { + title: "Status Bar Chart with Interval Scale", + subtitle: "Basic Timeseries Widget", + }, + }, + chart: { + providers: { + [WellKnownProviders.Adapter]: { + properties: { + // Setting the series and corresponding labels to initially display on the chart + series: [ + { + id: "series-1", + label: "Node Status", + selectedSeriesId: "series-1", + }, + { + id: "series-2", + label: "Node Status", + selectedSeriesId: "series-2", + }, + ] as ITimeseriesItemConfiguration[], + }, + } as Partial, + }, + properties: { + configuration: { + legendPlacement: LegendPlacement.Right, + enableZoom: true, + // Setting the preset to status bar + preset: TimeseriesChartPreset.StatusBar, + scales: { + x: { + type: TimeseriesScaleType.TimeInterval, + properties: { + // one-day interval in seconds + interval: 24 * 60 * 60, + }, + } as ITimeseriesScaleConfig, + }, + } as ITimeseriesWidgetConfig, + }, + }, + timeframeSelection: { + properties: { + // Setting the initial timeframe selected in the timeframe bar + timeframe: { + selectedPresetId: "last7Days", + } as ISerializableTimeframe, + maxDate: moment().format(), + }, + }, + }, + }, + }, +]; + +export const startOfToday = (): Moment => moment().startOf("day"); + +export const getContinuousData = + (): ITimeseriesWidgetData[] => { + const series: ITimeseriesWidgetData[] = [ + { + id: "series-1", + name: "Node Status", + description: "lastchance.demo.lab", + data: [ + // the 'x' value is set to the time and 'y' to the status at that given time + { + x: startOfToday().subtract(20, "day").toDate(), + y: Status.Warning, + }, + { + x: startOfToday().subtract(19, "day").toDate(), + y: Status.Down, + }, + { + x: startOfToday().subtract(17, "day").toDate(), + y: Status.Critical, + }, + { + x: startOfToday().subtract(16, "day").toDate(), + y: Status.Warning, + }, + { + x: startOfToday().subtract(15, "day").toDate(), + y: Status.Down, + }, + { + x: startOfToday().subtract(14, "day").toDate(), + y: Status.Critical, + }, + { + x: startOfToday().subtract(12, "day").toDate(), + y: Status.Unknown, + }, + { + x: startOfToday().subtract(10, "day").toDate(), + y: Status.Up, + }, + { + x: startOfToday().subtract(9, "day").toDate(), + y: Status.Critical, + }, + { + x: startOfToday().subtract(6, "day").toDate(), + y: Status.Up, + }, + { + x: startOfToday().subtract(3, "day").toDate(), + y: Status.Warning, + }, + { + x: startOfToday().subtract(2, "day").toDate(), + y: Status.Critical, + }, + { + x: startOfToday().subtract(1, "day").toDate(), + y: Status.Up, + }, + // This data point will be ignored and is only here to provide an endpoint for the previous status. + { x: moment().toDate(), y: Status.Up }, + ], + }, + { + id: "series-2", + name: "Node Status", + description: "newhope.demo.lab", + data: [ + { + x: startOfToday().subtract(19, "day").toDate(), + y: Status.Critical, + }, + { + x: startOfToday().subtract(18, "day").toDate(), + y: Status.Unknown, + }, + { + x: startOfToday().subtract(17, "day").toDate(), + y: Status.Warning, + }, + { + x: startOfToday().subtract(15, "day").toDate(), + y: Status.Down, + }, + { + x: startOfToday().subtract(8, "day").toDate(), + y: Status.Critical, + }, + { + x: startOfToday().subtract(7, "day").toDate(), + y: Status.Down, + }, + { + x: startOfToday().subtract(6, "day").toDate(), + y: Status.Up, + }, + { + x: startOfToday().subtract(5, "day").toDate(), + y: Status.Critical, + }, + { + x: startOfToday().subtract(4, "day").toDate(), + y: Status.Up, + }, + { + x: startOfToday().subtract(3, "day").toDate(), + y: Status.Warning, + }, + { + x: startOfToday().subtract(2, "day").toDate(), + y: Status.Up, + }, + { + x: startOfToday().subtract(1, "day").toDate(), + y: Status.Down, + }, + // This data point will be ignored and is only here to provide an endpoint for the previous status. + { x: moment().toDate(), y: Status.Down }, + ], + }, + ]; + + for (const s of series) { + // here are we setting the color and icon associated to the status for each data point + s.data = s.data.map((d: any, i: number) => ({ + ...d, + color: statusColors[d.y as Status], + // The thickness of the line is dependant on the status. If the status equals 'Up' then 'thick' is set to false. + thick: d.y !== Status.Up, + icon: "status_" + d.y, + })); + } + + return series; + }; + +// Note that the output of this function is spaced evenly at one-day intervals +export const getIntervalData = + (): ITimeseriesWidgetData[] => { + const series: ITimeseriesWidgetData[] = [ + { + id: "series-1", + name: "Node Status", + description: "lastchance.demo.lab", + data: [ + // the 'x' value is set to the time and 'y' to the status at that given time + { + x: startOfToday().subtract(20, "day").toDate(), + y: Status.Up, + }, + { + x: startOfToday().subtract(19, "day").toDate(), + y: Status.Critical, + }, + { + x: startOfToday().subtract(18, "day").toDate(), + y: Status.Warning, + }, + { + x: startOfToday().subtract(17, "day").toDate(), + y: Status.Down, + }, + { + x: startOfToday().subtract(16, "day").toDate(), + y: Status.Critical, + }, + { + x: startOfToday().subtract(15, "day").toDate(), + y: Status.Down, + }, + { + x: startOfToday().subtract(14, "day").toDate(), + y: Status.Up, + }, + { + x: startOfToday().subtract(13, "day").toDate(), + y: Status.Warning, + }, + { + x: startOfToday().subtract(12, "day").toDate(), + y: Status.Up, + }, + { + x: startOfToday().subtract(11, "day").toDate(), + y: Status.Critical, + }, + { + x: startOfToday().subtract(10, "day").toDate(), + y: Status.Up, + }, + { + x: startOfToday().subtract(9, "day").toDate(), + y: Status.Down, + }, + { + x: startOfToday().subtract(8, "day").toDate(), + y: Status.Critical, + }, + { + x: startOfToday().subtract(7, "day").toDate(), + y: Status.Warning, + }, + { + x: startOfToday().subtract(6, "day").toDate(), + y: Status.Down, + }, + { + x: startOfToday().subtract(5, "day").toDate(), + y: Status.Critical, + }, + { + x: startOfToday().subtract(4, "day").toDate(), + y: Status.Down, + }, + { + x: startOfToday().subtract(3, "day").toDate(), + y: Status.Up, + }, + { + x: startOfToday().subtract(2, "day").toDate(), + y: Status.Warning, + }, + { + x: startOfToday().subtract(1, "day").toDate(), + y: Status.Critical, + }, + { x: startOfToday().toDate(), y: Status.Up }, + ], + }, + { + id: "series-2", + name: "Node Status", + description: "newhope.demo.lab", + data: [ + { + x: startOfToday().subtract(20, "day").toDate(), + y: Status.Up, + }, + { + x: startOfToday().subtract(19, "day").toDate(), + y: Status.Up, + }, + { + x: startOfToday().subtract(18, "day").toDate(), + y: Status.Down, + }, + { + x: startOfToday().subtract(17, "day").toDate(), + y: Status.Critical, + }, + { + x: startOfToday().subtract(16, "day").toDate(), + y: Status.Down, + }, + { + x: startOfToday().subtract(15, "day").toDate(), + y: Status.Up, + }, + { + x: startOfToday().subtract(14, "day").toDate(), + y: Status.Critical, + }, + { + x: startOfToday().subtract(13, "day").toDate(), + y: Status.Up, + }, + { + x: startOfToday().subtract(12, "day").toDate(), + y: Status.Critical, + }, + { + x: startOfToday().subtract(11, "day").toDate(), + y: Status.Warning, + }, + { + x: startOfToday().subtract(10, "day").toDate(), + y: Status.Up, + }, + { + x: startOfToday().subtract(9, "day").toDate(), + y: Status.Down, + }, + { + x: startOfToday().subtract(8, "day").toDate(), + y: Status.Up, + }, + { + x: startOfToday().subtract(7, "day").toDate(), + y: Status.Down, + }, + { + x: startOfToday().subtract(6, "day").toDate(), + y: Status.Critical, + }, + { + x: startOfToday().subtract(5, "day").toDate(), + y: Status.Down, + }, + { + x: startOfToday().subtract(4, "day").toDate(), + y: Status.Up, + }, + { + x: startOfToday().subtract(3, "day").toDate(), + y: Status.Critical, + }, + { + x: startOfToday().subtract(2, "day").toDate(), + y: Status.Up, + }, + { + x: startOfToday().subtract(1, "day").toDate(), + y: Status.Warning, + }, + { x: startOfToday().toDate(), y: Status.Critical }, + ], + }, + ]; + + for (const s of series) { + // here are we setting the color and icon associated to the status for each data point + s.data = s.data.map((d: any, i: number) => ({ + ...d, + color: statusColors[d.y as Status], + // The thickness of the line is dependant on the status. If the status equals 'Up' then 'thick' is set to false. + thick: d.y !== Status.Up, + icon: "status_" + d.y, + })); + } + + return series; + }; + +// An enumeration of statuses +enum Status { + Unknown = "unknown", + Up = "up", + Warning = "warning", + Down = "down", + Critical = "critical", +} + +// This is the map used for setting the color of each status bar +const statusColors: Record = { + [Status.Unknown]: CHART_PALETTE_CS_S_EXTENDED[6], + [Status.Up]: CHART_PALETTE_CS_S_EXTENDED[8], + [Status.Warning]: CHART_PALETTE_CS_S_EXTENDED[4], + [Status.Down]: CHART_PALETTE_CS_S_EXTENDED[0], + [Status.Critical]: CHART_PALETTE_CS_S_EXTENDED[2], +}; + +// Setting the widget dimensions and position (this is for gridster) +const positions: Record = { + [widgetConfigs[0].id]: { + cols: 12, + rows: 4, + y: 0, + x: 0, + }, + [widgetConfigs[1].id]: { + cols: 12, + rows: 4, + y: 4, + x: 0, + }, +}; +\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\`, + "widget-types/view-components/kpi-tile-view-basic/kpi-tile-view-basic-example.component.ts": \\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\`// © 2022 SolarWinds Worldwide, LLC. All rights reserved. +// +// Permission is hereby granted, free of charge, to any person obtaining a copy +// of this software and associated documentation files (the "Software"), to +// deal in the Software without restriction, including without limitation the +// rights to use, copy, modify, merge, publish, distribute, sublicense, and/or +// sell copies of the Software, and to permit persons to whom the Software is +// furnished to do so, subject to the following conditions: +// +// The above copyright notice and this permission notice shall be included in +// all copies or substantial portions of the Software. +// +// THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR +// IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, +// FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE +// AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER +// LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, +// OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN +// THE SOFTWARE. + +import { Component } from "@angular/core"; + +/** + * Basic KPI Tile View example demonstrating standalone usage + * without any Pizzagna framework dependencies. + */ +@Component({ + selector: "kpi-tile-view-basic-example", + templateUrl: "./kpi-tile-view-basic-example.component.html", + standalone: false, +}) +export class KpiTileViewBasicExampleComponent {} +\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\`, + "widget-types/view-components/kpi-tile-view-interactive/kpi-tile-view-interactive-example.component.ts": \\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\`// © 2022 SolarWinds Worldwide, LLC. All rights reserved. +// +// Permission is hereby granted, free of charge, to any person obtaining a copy +// of this software and associated documentation files (the "Software"), to +// deal in the Software without restriction, including without limitation the +// rights to use, copy, modify, merge, publish, distribute, sublicense, and/or +// sell copies of the Software, and to permit persons to whom the Software is +// furnished to do so, subject to the following conditions: +// +// The above copyright notice and this permission notice shall be included in +// all copies or substantial portions of the Software. +// +// THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR +// IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, +// FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE +// AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER +// LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, +// OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN +// THE SOFTWARE. + +import { Component, TemplateRef, ViewChild } from "@angular/core"; + +/** + * Interactive KPI Tile View example with custom value formatting + * and click event handling. + */ +@Component({ + selector: "kpi-tile-view-interactive-example", + templateUrl: "./kpi-tile-view-interactive-example.component.html", + standalone: false, +}) +export class KpiTileViewInteractiveExampleComponent { + public currentValue = 1_247; + public lastClickedTile = ""; + + @ViewChild("customValueTpl", { static: true }) + public customValueTpl: TemplateRef; + + public onTileClick(): void { + this.lastClickedTile = "Active Sessions"; + } + + public onUptimeClick(): void { + this.lastClickedTile = "Uptime"; + } +} +\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\`, + "widget-types/view-components/proportional-chart-view-bar/proportional-chart-view-bar-example.component.ts": \\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\`// © 2022 SolarWinds Worldwide, LLC. All rights reserved. +// +// Permission is hereby granted, free of charge, to any person obtaining a copy +// of this software and associated documentation files (the "Software"), to +// deal in the Software without restriction, including without limitation the +// rights to use, copy, modify, merge, publish, distribute, sublicense, and/or +// sell copies of the Software, and to permit persons to whom the Software is +// furnished to do so, subject to the following conditions: +// +// The above copyright notice and this permission notice shall be included in +// all copies or substantial portions of the Software. +// +// THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR +// IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, +// FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE +// AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER +// LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, +// OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN +// THE SOFTWARE. + +import { Component } from "@angular/core"; + +import { IProportionalDataItem } from "@nova-ui/dashboards"; + +/** + * Proportional Chart View - Bar chart with interaction. + * Demonstrates horizontal bar chart variant with click handling. + */ +@Component({ + selector: "proportional-chart-view-bar-example", + templateUrl: "./proportional-chart-view-bar-example.component.html", + standalone: false, +}) +export class ProportionalChartViewBarExampleComponent { + public lastClicked: IProportionalDataItem | null = null; + + public colors: Array = [ + "#0058e9", + "#2cc079", + "#f3a002", + "#dc3545", + "#8a2be2", + ]; + + public chartData: Array = [ + { id: "chrome", name: "Chrome", value: 64 }, + { id: "firefox", name: "Firefox", value: 18 }, + { id: "safari", name: "Safari", value: 10 }, + { id: "edge", name: "Edge", value: 5 }, + { id: "other", name: "Other", value: 3 }, + ]; + + public onItemClick(item: IProportionalDataItem): void { + this.lastClicked = item; + } +} +\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\`, + "widget-types/view-components/proportional-chart-view-donut/proportional-chart-view-donut-example.component.ts": \\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\`// © 2022 SolarWinds Worldwide, LLC. All rights reserved. +// +// Permission is hereby granted, free of charge, to any person obtaining a copy +// of this software and associated documentation files (the "Software"), to +// deal in the Software without restriction, including without limitation the +// rights to use, copy, modify, merge, publish, distribute, sublicense, and/or +// sell copies of the Software, and to permit persons to whom the Software is +// furnished to do so, subject to the following conditions: +// +// The above copyright notice and this permission notice shall be included in +// all copies or substantial portions of the Software. +// +// THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR +// IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, +// FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE +// AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER +// LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, +// OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN +// THE SOFTWARE. + +import { Component } from "@angular/core"; + +import { IProportionalDataItem } from "@nova-ui/dashboards"; + +/** + * Proportional Chart View - Donut example. + * Demonstrates standalone usage of the proportional chart view + * without any Pizzagna framework dependencies. + */ +@Component({ + selector: "proportional-chart-view-donut-example", + templateUrl: "./proportional-chart-view-donut-example.component.html", + standalone: false, +}) +export class ProportionalChartViewDonutExampleComponent { + public colors: Record = { + down: "#dc3545", + up: "#2cc079", + warning: "#f3a002", + unknown: "#707070", + }; + + public chartData: Array = [ + { id: "up", name: "Up", value: 78 }, + { id: "down", name: "Down", value: 8 }, + { id: "warning", name: "Warning", value: 12 }, + { id: "unknown", name: "Unknown", value: 2 }, + ]; +} +\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\`, + "widget-types/view-components/view-components-docs.component.ts": \\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\`// © 2022 SolarWinds Worldwide, LLC. All rights reserved. +// +// Permission is hereby granted, free of charge, to any person obtaining a copy +// of this software and associated documentation files (the "Software"), to +// deal in the Software without restriction, including without limitation the +// rights to use, copy, modify, merge, publish, distribute, sublicense, and/or +// sell copies of the Software, and to permit persons to whom the Software is +// furnished to do so, subject to the following conditions: +// +// The above copyright notice and this permission notice shall be included in +// all copies or substantial portions of the Software. +// +// THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR +// IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, +// FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE +// AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER +// LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, +// OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN +// THE SOFTWARE. + +import { Component } from "@angular/core"; + +@Component({ + selector: "nui-view-components-docs", + templateUrl: "./view-components-docs.component.html", + standalone: false, +}) +export class ViewComponentsDocsComponent {} +\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\`, + "widget-types/view-components/view-components-docs.module.ts": \\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\`// © 2022 SolarWinds Worldwide, LLC. All rights reserved. +// +// Permission is hereby granted, free of charge, to any person obtaining a copy +// of this software and associated documentation files (the "Software"), to +// deal in the Software without restriction, including without limitation the +// rights to use, copy, modify, merge, publish, distribute, sublicense, and/or +// sell copies of the Software, and to permit persons to whom the Software is +// furnished to do so, subject to the following conditions: +// +// The above copyright notice and this permission notice shall be included in +// all copies or substantial portions of the Software. +// +// THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR +// IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, +// FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE +// AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER +// LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, +// OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN +// THE SOFTWARE. + +import { CommonModule } from "@angular/common"; +import { NgModule } from "@angular/core"; +import { RouterModule, Routes } from "@angular/router"; + +import { + NuiDocsModule, + NuiIconModule, + NuiMessageModule, + DEMO_PATH_TOKEN, +} from "@nova-ui/bits"; +import { NuiDashboardViewsModule } from "@nova-ui/dashboards"; + +import { getDemoFiles } from "../../../../demo-files-factory"; +import { KpiTileViewBasicExampleComponent } from "./kpi-tile-view-basic/kpi-tile-view-basic-example.component"; +import { KpiTileViewInteractiveExampleComponent } from "./kpi-tile-view-interactive/kpi-tile-view-interactive-example.component"; +import { ProportionalChartViewBarExampleComponent } from "./proportional-chart-view-bar/proportional-chart-view-bar-example.component"; +import { ProportionalChartViewDonutExampleComponent } from "./proportional-chart-view-donut/proportional-chart-view-donut-example.component"; +import { ViewComponentsDocsComponent } from "./view-components-docs.component"; + +const routes: Routes = [ + { + path: "", + component: ViewComponentsDocsComponent, + data: { + srlc: { + hideIndicator: true, + }, + showThemeSwitcher: true, + }, + }, + { + path: "kpi-tile-view-basic", + component: KpiTileViewBasicExampleComponent, + data: { + srlc: { + hideIndicator: true, + }, + }, + }, + { + path: "proportional-chart-view-donut", + component: ProportionalChartViewDonutExampleComponent, + data: { + srlc: { + hideIndicator: true, + }, + }, + }, + { + path: "proportional-chart-view-bar", + component: ProportionalChartViewBarExampleComponent, + data: { + srlc: { + hideIndicator: true, + }, + }, + }, +]; + +@NgModule({ + imports: [ + CommonModule, + RouterModule.forChild(routes), + NuiDocsModule, + NuiMessageModule, + NuiIconModule, + NuiDashboardViewsModule, + ], + declarations: [ + ViewComponentsDocsComponent, + KpiTileViewBasicExampleComponent, + KpiTileViewInteractiveExampleComponent, + ProportionalChartViewDonutExampleComponent, + ProportionalChartViewBarExampleComponent, + ], + providers: [ + { + provide: DEMO_PATH_TOKEN, + useValue: getDemoFiles("view-components"), + }, + ], +}) +export default class ViewComponentsDocsModule {} +\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\`, + "widget-types/widget-types.module.ts": \\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\`// © 2022 SolarWinds Worldwide, LLC. All rights reserved. +// +// Permission is hereby granted, free of charge, to any person obtaining a copy +// of this software and associated documentation files (the "Software"), to +// deal in the Software without restriction, including without limitation the +// rights to use, copy, modify, merge, publish, distribute, sublicense, and/or +// sell copies of the Software, and to permit persons to whom the Software is +// furnished to do so, subject to the following conditions: +// +// The above copyright notice and this permission notice shall be included in +// all copies or substantial portions of the Software. +// +// THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR +// IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, +// FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE +// AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER +// LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, +// OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN +// THE SOFTWARE. + +import { NgModule, Type } from "@angular/core"; +import { RouterModule, Routes } from "@angular/router"; + +import { NuiDocsModule } from "@nova-ui/bits"; +import { + ConfiguratorHeadingService, + NuiDashboardsModule, +} from "@nova-ui/dashboards"; + +export enum WidgetTypesRoute { + kpi = "kpi", + riskScore = "risk-score", + timeseries = "timeseries", + table = "table", + proportional = "proportional", + embedded = "embedded", + drilldown = "drilldown", + viewComponents = "view-components", +} + +const routes: Routes = [ + { + path: WidgetTypesRoute.kpi, + loadChildren: async () => + import("./kpi/kpi-docs.module") as object as Promise>, + data: { + srlc: { + hideIndicator: true, + }, + }, + }, + { + path: WidgetTypesRoute.riskScore, + loadChildren: async () => + import("./risk-score/risk-score-docs.module") as object as Promise< + Type + >, + data: { + srlc: { + hideIndicator: true, + }, + }, + }, + { + path: WidgetTypesRoute.timeseries, + loadChildren: async () => + import("./timeseries/timeseries-docs.module") as object as Promise< + Type + >, + data: { + srlc: { + hideIndicator: true, + }, + }, + }, + { + path: WidgetTypesRoute.table, + loadChildren: async () => + import("./table/table-docs.module") as object as Promise>, + data: { + srlc: { + hideIndicator: true, + }, + }, + }, + { + path: WidgetTypesRoute.proportional, + loadChildren: async () => + import( + "./proportional/proportional-docs.module" + ) as object as Promise>, + data: { + srlc: { + hideIndicator: true, + }, + }, + }, + { + path: WidgetTypesRoute.embedded, + loadChildren: async () => + import( + "./embedded-content/embedded-content-docs.module" + ) as object as Promise>, + data: { + srlc: { + hideIndicator: true, + }, + }, + }, + { + path: WidgetTypesRoute.drilldown, + loadChildren: async () => + import( + "./drilldown/drilldown-widget-docs.module" + ) as object as Promise>, + data: { + srlc: { + hideIndicator: true, + }, + }, + }, + { + path: WidgetTypesRoute.viewComponents, + loadChildren: async () => + import( + "./view-components/view-components-docs.module" + ) as object as Promise>, + data: { + srlc: { + hideIndicator: true, + }, + }, + }, +]; + +@NgModule({ + imports: [ + RouterModule.forChild(routes), + NuiDocsModule, + NuiDashboardsModule, + ], + providers: [ConfiguratorHeadingService], +}) +export default class WidgetTypesModule {} +\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\`, +}; +\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\`, + "overview/hero/dashboard/hero-dashboard.component.ts": \\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\`// © 2022 SolarWinds Worldwide, LLC. All rights reserved. +// +// Permission is hereby granted, free of charge, to any person obtaining a copy +// of this software and associated documentation files (the "Software"), to +// deal in the Software without restriction, including without limitation the +// rights to use, copy, modify, merge, publish, distribute, sublicense, and/or +// sell copies of the Software, and to permit persons to whom the Software is +// furnished to do so, subject to the following conditions: +// +// The above copyright notice and this permission notice shall be included in +// all copies or substantial portions of the Software. +// +// THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR +// IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, +// FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE +// AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER +// LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, +// OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN +// THE SOFTWARE. + +import { HttpClient } from "@angular/common/http"; +import { + ChangeDetectionStrategy, + Component, + OnInit, + ViewEncapsulation, +} from "@angular/core"; +import keyBy from "lodash/keyBy"; + +import { LoggerService, ThemeSwitchService } from "@nova-ui/bits"; +import { + DATA_SOURCE, + IDashboard, + IWidget, + ProviderRegistryService, + WidgetTypesService, +} from "@nova-ui/dashboards"; + +import { positions, widgets } from "./widget-configs"; +import { + HarryPotterAverageRatingDataSource, + HarryPotterRatingsCountDataSource, +} from "../data/kpi-datasources"; +import { + BeerReviewCountsByCityMockDataSource, + BeerReviewCountsByCityMockDataSource2, +} from "../data/proportional-datasources"; +import { BeerDataSource } from "../data/table/beer-data-source"; +import { RandomUserDataSource } from "../data/table/random-user-data-source"; +import { + BeerVsReadingMockDataSource, + LoungingVsFrisbeeGolfMockDataSource, +} from "../data/timeseries-data-sources"; + +/** + * A component that instantiates the dashboard + */ +@Component({ + selector: "hero-dashboard", + templateUrl: "./hero-dashboard.component.html", + styleUrls: ["./hero-dashboard.component.less"], + encapsulation: ViewEncapsulation.Emulated, + changeDetection: ChangeDetectionStrategy.Default, + standalone: false, +}) +export class HeroDashboardComponent implements OnInit { + public dashboard: IDashboard = { + positions: {}, + widgets: {}, + }; + + public gridsterConfig = {}; + public editMode = false; + + constructor( + private providerRegistry: ProviderRegistryService, + public themeSwitcherService: ThemeSwitchService, + private widgetTypesService: WidgetTypesService + ) { + this.providerRegistry.setProviders({ + [HarryPotterAverageRatingDataSource.providerId]: { + provide: DATA_SOURCE, + useClass: HarryPotterAverageRatingDataSource, + deps: [HttpClient], + }, + [HarryPotterRatingsCountDataSource.providerId]: { + provide: DATA_SOURCE, + useClass: HarryPotterRatingsCountDataSource, + deps: [HttpClient], + }, + [BeerReviewCountsByCityMockDataSource.providerId]: { + provide: DATA_SOURCE, + useClass: BeerReviewCountsByCityMockDataSource, + deps: [], + }, + [BeerReviewCountsByCityMockDataSource2.providerId]: { + provide: DATA_SOURCE, + useClass: BeerReviewCountsByCityMockDataSource2, + deps: [], + }, + [RandomUserDataSource.providerId]: { + provide: DATA_SOURCE, + useClass: RandomUserDataSource, + deps: [LoggerService], + }, + [BeerDataSource.providerId]: { + provide: DATA_SOURCE, + useClass: BeerDataSource, + deps: [LoggerService], + }, + [BeerVsReadingMockDataSource.providerId]: { + provide: DATA_SOURCE, + useClass: BeerVsReadingMockDataSource, + deps: [], + }, + [LoungingVsFrisbeeGolfMockDataSource.providerId]: { + provide: DATA_SOURCE, + useClass: LoungingVsFrisbeeGolfMockDataSource, + deps: [], + }, + }); + } + + public ngOnInit(): void { + const widgetsWithStructure = widgets.map((w) => ({ + ...w, + pizzagna: { + ...this.widgetTypesService.getWidgetType(w.type, w.version) + .widget, + ...w.pizzagna, + }, + })); + const widgetsIndex = keyBy(widgetsWithStructure, (w: IWidget) => w.id); + + this.dashboard = { + positions: positions, + widgets: widgetsIndex, + }; + } +} +\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\`, + "overview/hero/dashboard/widget-configs.ts": \\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\`// © 2022 SolarWinds Worldwide, LLC. All rights reserved. +// +// Permission is hereby granted, free of charge, to any person obtaining a copy +// of this software and associated documentation files (the "Software"), to +// deal in the Software without restriction, including without limitation the +// rights to use, copy, modify, merge, publish, distribute, sublicense, and/or +// sell copies of the Software, and to permit persons to whom the Software is +// furnished to do so, subject to the following conditions: +// +// The above copyright notice and this permission notice shall be included in +// all copies or substantial portions of the Software. +// +// THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR +// IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, +// FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE +// AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER +// LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, +// OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN +// THE SOFTWARE. + +import { GridsterItem } from "angular-gridster2"; + +import { IWidget } from "@nova-ui/dashboards"; + +import { kpiConfig } from "../widget-configs/kpi"; +import { proportionalConfig } from "../widget-configs/proportional"; +import { tableConfig } from "../widget-configs/table"; +import { timeseriesConfig } from "../widget-configs/timeseries"; + +export const positions: Record = { + [tableConfig.id]: { + cols: 7, + rows: 7, + y: 0, + x: 0, + }, + [proportionalConfig.id]: { + cols: 5, + rows: 7, + y: 0, + x: 7, + }, + [kpiConfig.id]: { + cols: 6, + rows: 7, + y: 7, + x: 0, + }, + [timeseriesConfig.id]: { + cols: 6, + rows: 7, + y: 7, + x: 6, + }, +}; + +export const widgets: IWidget[] = [ + { + ...tableConfig, + }, + { + ...proportionalConfig, + }, + { + ...kpiConfig, + }, + { + ...timeseriesConfig, + }, +]; +\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\`, + "overview/hero/data/kpi-datasources.ts": \\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\`// © 2022 SolarWinds Worldwide, LLC. All rights reserved. +// +// Permission is hereby granted, free of charge, to any person obtaining a copy +// of this software and associated documentation files (the "Software"), to +// deal in the Software without restriction, including without limitation the +// rights to use, copy, modify, merge, publish, distribute, sublicense, and/or +// sell copies of the Software, and to permit persons to whom the Software is +// furnished to do so, subject to the following conditions: +// +// The above copyright notice and this permission notice shall be included in +// all copies or substantial portions of the Software. +// +// THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR +// IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, +// FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE +// AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER +// LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, +// OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN +// THE SOFTWARE. + +import { HttpClient, HttpErrorResponse } from "@angular/common/http"; +import { Injectable, OnDestroy } from "@angular/core"; +import { BehaviorSubject } from "rxjs"; +import { finalize } from "rxjs/operators"; + +import { DataSourceService, IFilteringOutputs } from "@nova-ui/bits"; +import { IKpiData } from "@nova-ui/dashboards"; + +import { GOOGLE_BOOKS_URL } from "./table/constants"; + +@Injectable() +export class HarryPotterAverageRatingDataSource + extends DataSourceService + implements OnDestroy +{ + public static providerId = "HarryPotterAverageRatingDataSource"; + + public busy = new BehaviorSubject(false); + + constructor(private http: HttpClient) { + super(); + } + + public async getFilteredData(): Promise { + this.busy.next(true); + return new Promise((resolve) => { + // *** Make a resource request to an external API (if needed) + this.http + .get(\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\`\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\${GOOGLE_BOOKS_URL}/5MQFrgEACAAJ\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\`) + .pipe(finalize(() => this.busy.next(false))) + .subscribe({ + next: (data: any) => { + resolve({ + result: { + value: data.volumeInfo.averageRating, + }, + }); + }, + error: (error: HttpErrorResponse) => { + resolve({ + result: null, + error: { + type: error.status, + }, + }); + }, + }); + }); + } + + public ngOnDestroy(): void { + this.outputsSubject.complete(); + } +} + +@Injectable() +export class HarryPotterRatingsCountDataSource + extends DataSourceService + implements OnDestroy +{ + public static providerId = "HarryPotterRatingsCountDataSource"; + + public busy = new BehaviorSubject(false); + + constructor(private http: HttpClient) { + super(); + } + + public async getFilteredData(): Promise { + this.busy.next(true); + return new Promise((resolve) => { + this.http + .get(\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\`\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\${GOOGLE_BOOKS_URL}/5MQFrgEACAAJ\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\`) + .pipe(finalize(() => this.busy.next(false))) + .subscribe({ + next: (data: any) => { + resolve({ + result: { + value: data.volumeInfo.ratingsCount, + }, + }); + }, + error: (error: HttpErrorResponse) => { + resolve({ + result: null, + error: { + type: error.status, + }, + }); + }, + }); + }); + } + + public ngOnDestroy(): void { + this.outputsSubject.complete(); + } +} +\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\`, + "overview/hero/data/proportional-datasources.ts": \\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\`// © 2022 SolarWinds Worldwide, LLC. All rights reserved. +// +// Permission is hereby granted, free of charge, to any person obtaining a copy +// of this software and associated documentation files (the "Software"), to +// deal in the Software without restriction, including without limitation the +// rights to use, copy, modify, merge, publish, distribute, sublicense, and/or +// sell copies of the Software, and to permit persons to whom the Software is +// furnished to do so, subject to the following conditions: +// +// The above copyright notice and this permission notice shall be included in +// all copies or substantial portions of the Software. +// +// THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR +// IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, +// FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE +// AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER +// LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, +// OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN +// THE SOFTWARE. + +import { Injectable, OnDestroy } from "@angular/core"; +import { BehaviorSubject } from "rxjs"; + +import { + DataSourceService, + IDataSource, + IFilteringOutputs, +} from "@nova-ui/bits"; + +import { + getMockBeerReviewCountsByCity, + getMockBeerReviewCountsByCity2, + IProportionalWidgetData, +} from "./widget-data"; + +@Injectable() +export class BeerReviewCountsByCityMockDataSource + extends DataSourceService + implements IDataSource, OnDestroy +{ + // This is the ID we'll use to identify the provider + public static providerId = "BeerReviewCountsByCityMockDataSource"; + public busy = new BehaviorSubject(false); + + public async getFilteredData(): Promise { + this.busy.next(true); + return new Promise((resolve) => { + setTimeout(() => { + this.outputsSubject.next({ + result: getMockBeerReviewCountsByCity(), + }); + this.busy.next(false); + }, 300); + }); + } + + public ngOnDestroy(): void { + this.outputsSubject.complete(); + } +} + +@Injectable() +export class BeerReviewCountsByCityMockDataSource2 + extends DataSourceService + implements IDataSource, OnDestroy +{ + // This is the ID we'll use to identify the provider + public static providerId = "BeerReviewCountsByCityMockDataSource2"; + public busy = new BehaviorSubject(false); + + public async getFilteredData(): Promise { + this.busy.next(true); + return new Promise((resolve) => { + setTimeout(() => { + this.outputsSubject.next({ + result: getMockBeerReviewCountsByCity2(), + }); + this.busy.next(false); + }, 1500); + }); + } + + public ngOnDestroy(): void { + this.outputsSubject.complete(); + } +} +\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\`, + "overview/hero/data/table/beer-data-source.ts": \\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\`// © 2022 SolarWinds Worldwide, LLC. All rights reserved. +// +// Permission is hereby granted, free of charge, to any person obtaining a copy +// of this software and associated documentation files (the "Software"), to +// deal in the Software without restriction, including without limitation the +// rights to use, copy, modify, merge, publish, distribute, sublicense, and/or +// sell copies of the Software, and to permit persons to whom the Software is +// furnished to do so, subject to the following conditions: +// +// The above copyright notice and this permission notice shall be included in +// all copies or substantial portions of the Software. +// +// THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR +// IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, +// FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE +// AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER +// LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, +// OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN +// THE SOFTWARE. + +import { ListRange } from "@angular/cdk/collections"; +import { Injectable } from "@angular/core"; +import isEqual from "lodash/isEqual"; +import orderBy from "lodash/orderBy"; +import { BehaviorSubject } from "rxjs"; + +import { + DataSourceService, + IDataField, + INovaFilteringOutputs, + INovaFilters, + ISorterFilter, + LoggerService, +} from "@nova-ui/bits"; + +import { IBrewDatasourceResponse, IBrewInfo } from "../types"; +import { BREW_API_URL } from "./constants"; + +@Injectable() +export class BeerDataSource extends DataSourceService { + public static providerId = "BeerDataSource"; + + private cache = Array.from({ length: 0 }); + private lastSortValue?: ISorterFilter; + private lastVirtualScroll?: ListRange; + private totalItems: number = 325; + + public page: number = 1; + public busy = new BehaviorSubject(false); + + public dataFields: Array = [ + { id: "id", label: "No", dataType: "number" }, + { id: "name", label: "Name", dataType: "string" }, + { id: "tagline", label: "Tagline", dataType: "string" }, + { id: "first_brewed", label: "First Brewed", dataType: "string" }, + { id: "description", label: "Description", dataType: "string" }, + { id: "brewers_tips", label: "Brewer's Tips", dataType: "string" }, + ]; + + constructor(private logger: LoggerService) { + super(); + } + + public async getFilteredData( + filters: INovaFilters + ): Promise { + const start = filters.virtualScroll?.value?.start ?? 0; + const end = filters.virtualScroll?.value?.end ?? 0; + const delta = end - start; + + // This condition handles sorting. We want to sort columns without fetching another chunk of data. + // Since the data is being fetched when scrolled, we compare virtual scroll indexes here in the condition as well. + if (filters.sorter?.value) { + if ( + !isEqual(this.lastSortValue, filters.sorter.value) && + isEqual(this.lastVirtualScroll, filters.virtualScroll?.value) + ) { + const totalPages = Math.ceil( + delta ? this.totalItems / delta : 1 + ); + const itemsPerPage: number = Math.max( + delta < 80 ? delta : 80, + 1 + ); + let response: Array | null = null; + let map: IBrewDatasourceResponse; + + if (filters.sorter?.value?.direction === "desc") { + this.cache = []; + for (let i = 0; i < this.page; ++i) { + response = await ( + await fetch( + \\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\`\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\${BREW_API_URL}/?page=\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\${ + totalPages - i || 1 + }&per_page=\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\${itemsPerPage}\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\` + ) + ).json(); + + // since the last page contains only 5 items we need to fetch another page to give virtual scroll enough space to work + if (response && response.length < itemsPerPage) { + this.page++; + } + map = { + brewInfo: response?.map((result: IBrewInfo) => ({ + id: result.id, + name: result.name, + tagline: result.tagline, + first_brewed: result.first_brewed, + description: result.description, + brewers_tips: result.brewers_tips, + })), + total: response?.length, + } as IBrewDatasourceResponse; + this.cache = + totalPages - i !== 0 + ? this.cache.concat(map.brewInfo) + : this.cache; + } + } + + if (filters.sorter?.value?.direction === "asc") { + this.cache = []; + for (let i = 0; i < this.page; i++) { + response = await ( + await fetch( + \\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\`\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\${BREW_API_URL}/?page=\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\${ + i + 1 + }&per_page=\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\${itemsPerPage}\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\` + ) + ).json(); + map = { + brewInfo: response?.map((result: IBrewInfo) => ({ + id: result.id, + name: result.name, + tagline: result.tagline, + first_brewed: result.first_brewed, + description: result.description, + brewers_tips: result.brewers_tips, + })), + total: response?.length, + } as IBrewDatasourceResponse; + this.cache = this.cache.concat(map.brewInfo); + } + } + + this.lastSortValue = filters.sorter?.value; + this.lastVirtualScroll = filters.virtualScroll?.value; + + return { + repeat: { itemsSource: this.sortData(this.cache, filters) }, + paginator: { total: this.totalItems }, + dataFields: this.dataFields, + }; + } + } + + this.busy.next(true); + return new Promise((resolve) => { + setTimeout(() => { + this.getData(start, end, filters).then( + (response: INovaFilteringOutputs) => { + if (!response) { + return; + } + + this.cache = this.cache.concat(response.brewInfo); + + this.dataSubject.next(this.cache); + resolve({ + repeat: { + itemsSource: this.sortData(this.cache, filters), + }, + paginator: { total: this.totalItems }, + dataFields: this.dataFields, + }); + + this.lastSortValue = filters.sorter?.value; + this.lastVirtualScroll = filters.virtualScroll?.value; + this.busy.next(false); + } + ); + }, 500); + }); + } + + public async getData( + start: number = 0, + end: number = 20, + filters: INovaFilters + ): Promise { + const delta = end - start; + const totalPages = Math.ceil(delta ? this.totalItems / delta : 1); + let response: Array | null = null; + // The api.punk.com is able to return only 80 items per page + const itemsPerPage: number = Math.max(delta < 80 ? delta : 80, 1); + + if (filters.sorter?.value?.direction === "asc") { + response = await ( + await fetch( + \\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\`\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\${BREW_API_URL}/?page=\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\${this.page}&per_page=\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\${itemsPerPage}\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\` + ) + ).json(); + } + + if (filters.sorter?.value?.direction === "desc") { + response = await ( + await fetch( + \\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\`\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\${BREW_API_URL}/?page=\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\${ + totalPages - this.page + }&per_page=\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\${itemsPerPage}\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\` + ) + ).json(); + } + + if (!filters.sorter) { + response = await ( + await fetch( + \\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\`\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\${BREW_API_URL}/?page=\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\${this.page}&per_page=\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\${itemsPerPage}\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\` + ) + ).json(); + } + return { + brewInfo: response?.map((result: IBrewInfo, i: number) => ({ + id: result.id, + name: result.name, + tagline: result.tagline, + first_brewed: result.first_brewed, + description: result.description, + brewers_tips: result.brewers_tips, + })), + total: response?.length, + } as IBrewDatasourceResponse; + } + + private sortData(data: IBrewInfo[], filters: INovaFilters) { + return orderBy( + data, + filters.sorter?.value?.sortBy, + filters.sorter?.value?.direction as "desc" | "asc" + ); + } +} +\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\`, + "overview/hero/data/table/constants.ts": \\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\`// © 2022 SolarWinds Worldwide, LLC. All rights reserved. +// +// Permission is hereby granted, free of charge, to any person obtaining a copy +// of this software and associated documentation files (the "Software"), to +// deal in the Software without restriction, including without limitation the +// rights to use, copy, modify, merge, publish, distribute, sublicense, and/or +// sell copies of the Software, and to permit persons to whom the Software is +// furnished to do so, subject to the following conditions: +// +// The above copyright notice and this permission notice shall be included in +// all copies or substantial portions of the Software. +// +// THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR +// IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, +// FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE +// AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER +// LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, +// OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN +// THE SOFTWARE. + +export const corsProxy = "https://cors-anywhere.herokuapp.com"; +export const RANDOMUSER_API_URL = "https://randomuser.me"; +export const BREW_API_URL = "https://api.punkapi.com/v2/beers"; +export const GOOGLE_BOOKS_URL = "https://www.googleapis.com/books/v1/volumes"; +export const apiRoute = "api/1.3"; +export const responseError = \\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\`Error responding from server. Please visit \\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\${RANDOMUSER_API_URL} and \\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\${corsProxy} to see if they're available\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\`; +\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\`, + "overview/hero/data/table/random-user-data-source.ts": \\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\`// © 2022 SolarWinds Worldwide, LLC. All rights reserved. +// +// Permission is hereby granted, free of charge, to any person obtaining a copy +// of this software and associated documentation files (the "Software"), to +// deal in the Software without restriction, including without limitation the +// rights to use, copy, modify, merge, publish, distribute, sublicense, and/or +// sell copies of the Software, and to permit persons to whom the Software is +// furnished to do so, subject to the following conditions: +// +// The above copyright notice and this permission notice shall be included in +// all copies or substantial portions of the Software. +// +// THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR +// IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, +// FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE +// AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER +// LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, +// OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN +// THE SOFTWARE. + +import { ListRange } from "@angular/cdk/collections"; +import { Injectable } from "@angular/core"; +import isEqual from "lodash/isEqual"; +import orderBy from "lodash/orderBy"; +import { BehaviorSubject } from "rxjs"; + +import { + DataSourceService, + IDataField, + INovaFilteringOutputs, + INovaFilters, + ISorterFilter, + LoggerService, +} from "@nova-ui/bits"; + +import { + IRandomUserResponse, + IRandomUserResults, + IRandomUserTableModel, + UsersQueryResponse, +} from "../types"; +import { + apiRoute, + corsProxy, + RANDOMUSER_API_URL, + responseError, +} from "./constants"; + +@Injectable() +export class RandomUserDataSource extends DataSourceService { + public static providerId = "RandomUserDataSource"; + + private readonly seed = "sw"; + + private cache = Array.from({ length: 0 }); + private lastSortValue?: ISorterFilter; + private lastVirtualScroll?: ListRange; + + public page: number = 1; + public busy = new BehaviorSubject(false); + + public dataFields: Array = [ + { id: "no", label: $localize\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\`No\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\`, dataType: "number" }, + { id: "nameTitle", label: $localize\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\`Title\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\`, dataType: "string" }, + { id: "nameFirst", label: $localize\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\`First\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\`, dataType: "string" }, + { id: "nameLast", label: $localize\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\`Last\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\`, dataType: "string" }, + { id: "gender", label: $localize\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\`Gender\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\`, dataType: "string" }, + { id: "country", label: $localize\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\`Country\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\`, dataType: "string" }, + { id: "city", label: $localize\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\`City\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\`, dataType: "string" }, + { id: "postcode", label: $localize\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\`Postcode\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\`, dataType: "number" }, + { id: "email", label: $localize\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\`E-Mail\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\`, dataType: "string" }, + { id: "cell", label: $localize\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\`Cell\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\`, dataType: "string" }, + ]; + + constructor(private logger: LoggerService) { + super(); + } + + public async getFilteredData( + filters: INovaFilters + ): Promise { + // This condition handles sorting. We want to sort columns without fetching another chunk of data. + // Since the data is being fetched when scrolled, we compare virtual scroll indexes here in the condition as well. + if (filters.sorter?.value) { + if ( + !isEqual(this.lastSortValue, filters.sorter.value) && + isEqual(this.lastVirtualScroll, filters.virtualScroll?.value) + ) { + this.lastSortValue = filters.sorter?.value; + this.lastVirtualScroll = filters.virtualScroll?.value; + + return { + repeat: { itemsSource: this.sortData(this.cache, filters) }, + paginator: { total: 200 }, + dataFields: this.dataFields, + }; + } + } + this.busy.next(true); + + const virtualScrollFilter = + filters.virtualScroll && filters.virtualScroll.value; + const start = virtualScrollFilter + ? filters.virtualScroll?.value.start + : 0; + const end = virtualScrollFilter ? filters.virtualScroll?.value.end : 0; + + // We're returning Promise with setTimeout here to make the response from the server longer, as the API being used sends responses + // almost immediately. We need it longer to be able the show the spinner component on data load + return new Promise((resolve) => { + setTimeout(() => { + this.getData(start, end).then( + (response: INovaFilteringOutputs | undefined) => { + if (!response) { + return; + } + + this.cache = this.cache.concat(response.users); + + this.dataSubject.next(this.cache); + resolve({ + repeat: { + itemsSource: this.sortData(this.cache, filters), + }, + // This API can return thousands of results, however doesn't return the max number of results, + // so we set the max number of result manually here. + paginator: { total: 200 }, + dataFields: this.dataFields, + }); + + this.lastSortValue = filters.sorter?.value; + this.lastVirtualScroll = filters.virtualScroll?.value; + this.busy.next(false); + } + ); + }, 300); + }); + } + + public async getData( + start: number = 0, + end: number = 20 + ): Promise { + let response: IRandomUserResponse | null = null; + try { + response = await ( + await fetch( + \\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\`\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\${corsProxy}/\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\${RANDOMUSER_API_URL}/\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\${apiRoute}/?page=\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\${ + this.page + }&results=\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\${end - start}&seed=\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\${this.seed}\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\` + ) + ).json(); + return { + users: response?.results.map( + (result: IRandomUserResults, i: number) => ({ + no: this.cache.length + i + 1, + nameTitle: result.name.title, + nameFirst: result.name.first, + nameLast: result.name.last, + gender: result.gender, + country: result.location.country, + city: result.location.city, + postcode: result.location.postcode, + email: result.email, + cell: result.cell, + }) + ), + total: response?.results.length, + start: start, + } as UsersQueryResponse; + } catch (e) { + this.logger.error(responseError); + } + } + + private sortData(data: IRandomUserTableModel[], filters: INovaFilters) { + return orderBy( + data, + filters.sorter?.value?.sortBy, + filters.sorter?.value?.direction as "desc" | "asc" + ); + } +} +\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\`, + "overview/hero/data/table/types.ts": \\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\`// © 2022 SolarWinds Worldwide, LLC. All rights reserved. +// +// Permission is hereby granted, free of charge, to any person obtaining a copy +// of this software and associated documentation files (the "Software"), to +// deal in the Software without restriction, including without limitation the +// rights to use, copy, modify, merge, publish, distribute, sublicense, and/or +// sell copies of the Software, and to permit persons to whom the Software is +// furnished to do so, subject to the following conditions: +// +// The above copyright notice and this permission notice shall be included in +// all copies or substantial portions of the Software. +// +// THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR +// IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, +// FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE +// AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER +// LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, +// OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN +// THE SOFTWARE. + +import { IDataField, INovaFilteringOutputs } from "@nova-ui/bits"; +export interface BasicTableModel { + position: number; + name: string; + features: any; + status: string; + checks: any; + "cpu-load": number; + firstUrl: string; + firstUrlLabel: string; + secondUrl: string; + secondUrlLabel: string; +} + +export interface ITableDataSourceOutput extends INovaFilteringOutputs { + dataFields: IDataField[]; +} +\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\`, + "overview/hero/data/timeseries-data-sources.ts": \\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\`// © 2022 SolarWinds Worldwide, LLC. All rights reserved. +// +// Permission is hereby granted, free of charge, to any person obtaining a copy +// of this software and associated documentation files (the "Software"), to +// deal in the Software without restriction, including without limitation the +// rights to use, copy, modify, merge, publish, distribute, sublicense, and/or +// sell copies of the Software, and to permit persons to whom the Software is +// furnished to do so, subject to the following conditions: +// +// The above copyright notice and this permission notice shall be included in +// all copies or substantial portions of the Software. +// +// THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR +// IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, +// FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE +// AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER +// LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, +// OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN +// THE SOFTWARE. + +import { Injectable } from "@angular/core"; +import { Moment } from "moment/moment"; +import { BehaviorSubject } from "rxjs"; + +import { DataSourceService, IDataSource, INovaFilters } from "@nova-ui/bits"; +import { + ITimeseriesOutput, + ITimeseriesWidgetData, + ITimeseriesWidgetSeriesData, +} from "@nova-ui/dashboards"; + +import { + BEER_VS_READING_DATA, + LOUNGING_VS_ULTIMATE_FRISBEE_DATA, +} from "./widget-data"; + +@Injectable() +export class BeerVsReadingMockDataSource + extends DataSourceService + implements IDataSource +{ + public static providerId = "BeerVsReadingMockDataSource"; + + public busy = new BehaviorSubject(false); + + constructor() { + super(); + } + + public async getFilteredData( + filters: INovaFilters + ): Promise { + this.busy.next(true); + const result = await delay( + { series: getData(filters, BEER_VS_READING_DATA) }, + 1000 + ); + this.busy.next(false); + return result; + } +} + +@Injectable() +export class LoungingVsFrisbeeGolfMockDataSource + extends DataSourceService + implements IDataSource +{ + public static providerId = "LoungingVsFrisbeeGolfMockDataSource"; + + public busy = new BehaviorSubject(false); + + constructor() { + super(); + } + + public async getFilteredData( + filters: INovaFilters + ): Promise { + this.busy.next(true); + const result = await delay( + { series: getData(filters, LOUNGING_VS_ULTIMATE_FRISBEE_DATA) }, + 1000 + ); + this.busy.next(false); + return result; + } +} + +function getData( + filters: INovaFilters, + data: ITimeseriesWidgetData[] +): ITimeseriesWidgetData[] { + const timeframeFilter = filters.timeframe; + let filteredData = data; + // TIME FRAME PICKER FILTERING + if (timeframeFilter) { + filteredData = filteredData.map((item: ITimeseriesWidgetData) => ({ + id: item.id, + name: item.name, + description: item.description, + data: item.data.filter((seriesData: ITimeseriesWidgetSeriesData) => + filterDates( + seriesData.x, + timeframeFilter.value.startDatetime, + timeframeFilter.value.endDatetime + ) + ), + })); + } + + return filteredData; +} + +function filterDates(dateToCheck: Moment, startDate: Moment, endDate: Moment) { + return ( + dateToCheck.isBetween(startDate, endDate) || + dateToCheck.isSame(startDate) || + dateToCheck.isSame(endDate) + ); +} + +async function delay( + value: ITimeseriesOutput, + ms: number +): Promise { + return new Promise((resolve) => setTimeout(() => resolve(value), ms)); +} +\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\`, + "overview/hero/data/types.ts": \\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\`// © 2022 SolarWinds Worldwide, LLC. All rights reserved. +// +// Permission is hereby granted, free of charge, to any person obtaining a copy +// of this software and associated documentation files (the "Software"), to +// deal in the Software without restriction, including without limitation the +// rights to use, copy, modify, merge, publish, distribute, sublicense, and/or +// sell copies of the Software, and to permit persons to whom the Software is +// furnished to do so, subject to the following conditions: +// +// The above copyright notice and this permission notice shall be included in +// all copies or substantial portions of the Software. +// +// THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR +// IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, +// FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE +// AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER +// LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, +// OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN +// THE SOFTWARE. + +export interface UsersQueryResponse { + users: IRandomUserTableModel[]; + total: number; + start: number; +} + +export interface IRandomUserResponse { + info: Array; + results: Array; +} + +export interface IRandomUserInfo { + page: number; + results: number; + seed: string; + version: string; +} + +export interface IRandomUserResults { + cell: string; + dob: { + age: number; + date: string; + }; + email: string; + gender: string; + id: any; + location: IRandomUserLocation; + login: { + md5: string; + password: string; + salt: string; + sha1: string; + sha256: string; + username: string; + uuid: string; + }; + name: { + title: string; + first: string; + last: string; + }; + nat: string; + phone: string; + picture: { + large: string; + medium: string; + thumbnail: string; + }; + registered: { + date: string; + age: number; + }; +} + +export interface IRandomUserTableModel { + no: number; + nameTitle: string; + nameFirst: string; + nameLast: string; + gender: string; + country: string; + city: string; + postcode: number; + email: string; + cell: string; +} + +export interface IRandomUserLocation { + city: string; + coordinates: { latitude: string; longitude: string }; + country: string; + postcode: number; + state: string; + street: { number: number; name: string }; + timezone: any; +} + +export interface IBrewInfo { + id: number; + name: string; + tagline: string; + first_brewed: string; + description: string; + brewers_tips: string; +} + +export interface IBrewDatasourceResponse { + brewInfo: IBrewInfo[]; + total: number; +} +\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\`, + "overview/hero/data/widget-data.ts": \\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\`// © 2022 SolarWinds Worldwide, LLC. All rights reserved. +// +// Permission is hereby granted, free of charge, to any person obtaining a copy +// of this software and associated documentation files (the "Software"), to +// deal in the Software without restriction, including without limitation the +// rights to use, copy, modify, merge, publish, distribute, sublicense, and/or +// sell copies of the Software, and to permit persons to whom the Software is +// furnished to do so, subject to the following conditions: +// +// The above copyright notice and this permission notice shall be included in +// all copies or substantial portions of the Software. +// +// THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR +// IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, +// FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE +// AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER +// LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, +// OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN +// THE SOFTWARE. + +import moment from "moment/moment"; + +import { ITimeseriesWidgetData } from "@nova-ui/dashboards"; + +import { BasicTableModel } from "./table/types"; + +export interface IProportionalWidgetData { + id: string; + name: string; + data: number[]; + link: string; + value: string; +} + +export function getMockBeerReviewCountsByCity(): IProportionalWidgetData[] { + return [ + { + id: "Brno", + name: "Brno", + data: [Math.round(Math.random() * 100000)], + link: "https://en.wikipedia.org/wiki/Brno", + value: "Brno", + }, + { + id: "kyiv", + name: "Kyiv", + data: [Math.round(Math.random() * 100000)], + link: "https://en.wikipedia.org/wiki/Kyiv", + value: "Kyiv", + }, + { + id: "austin", + name: "Austin", + data: [Math.round(Math.random() * 100000)], + link: "https://en.wikipedia.org/wiki/Austin", + value: "Austin", + }, + { + id: "lisbon", + name: "Lisbon", + data: [Math.round(Math.random() * 100000)], + link: "https://en.wikipedia.org/wiki/Lisbon", + value: "Lisbon", + }, + { + id: "sydney", + name: "Sydney", + data: [Math.round(Math.random() * 100000)], + link: "https://en.wikipedia.org/wiki/Sydney", + value: "Sydney", + }, + { + id: "nur-sultan", + name: "Nur-Sultan", + data: [Math.round(Math.random() * 100000)], + link: "https://en.wikipedia.org/wiki/Nur-Sultan", + value: "Nur-Sultan", + }, + ].sort((a, b) => a.data[0] - b.data[0]); +} + +export function getMockBeerReviewCountsByCity2(): IProportionalWidgetData[] { + return [ + { + id: "london", + name: "London", + data: [Math.round(Math.random() * 100000)], + link: "https://en.wikipedia.org/wiki/London", + value: "London", + }, + { + id: "paris", + name: "Paris", + data: [Math.round(Math.random() * 100000)], + link: "https://en.wikipedia.org/wiki/Paris", + value: "Paris", + }, + { + id: "rio", + name: "Rio", + data: [Math.round(Math.random() * 100000)], + link: "https://en.wikipedia.org/wiki/Rio_de_Janeiro", + value: "Rio", + }, + ].sort((a, b) => a.data[0] - b.data[0]); +} + +export const BEER_VS_READING_DATA: ITimeseriesWidgetData[] = [ + { + id: "series-1", + name: "Beer Tasting", + description: "Havin' some suds", + data: [ + { x: moment().subtract(10, "day"), y: 30 }, + { x: moment().subtract(9, "day"), y: 35 }, + { x: moment().subtract(8, "day"), y: 33 }, + { x: moment().subtract(7, "day"), y: 40 }, + { x: moment().subtract(6, "day"), y: 35 }, + { x: moment().subtract(5, "day"), y: 30 }, + { x: moment().subtract(4, "day"), y: 35 }, + { x: moment().subtract(3, "day"), y: 15 }, + { x: moment().subtract(2, "day"), y: 30 }, + { x: moment().subtract(1, "day"), y: 35 }, + { x: moment().subtract(24, "hour"), y: 34 }, + { x: moment().subtract(15, "hour"), y: 33 }, + { x: moment().subtract(10, "hour"), y: 35 }, + { x: moment().subtract(5, "hour"), y: 36 }, + { x: moment().subtract(1, "hour"), y: 34 }, + { x: moment().subtract(50, "minute"), y: 33 }, + { x: moment().subtract(40, "minute"), y: 30 }, + { x: moment().subtract(30, "minute"), y: 32 }, + { x: moment().subtract(20, "minute"), y: 31 }, + { x: moment().subtract(10, "minute"), y: 34 }, + ], + }, + { + id: "series-2", + name: "Reading", + description: "Hittin' the books", + data: [ + { x: moment().subtract(10, "day"), y: 60 }, + { x: moment().subtract(9, "day"), y: 64 }, + { x: moment().subtract(8, "day"), y: 70 }, + { x: moment().subtract(7, "day"), y: 55 }, + { x: moment().subtract(6, "day"), y: 55 }, + { x: moment().subtract(5, "day"), y: 45 }, + { x: moment().subtract(4, "day"), y: 10 }, + { x: moment().subtract(3, "day"), y: 65 }, + { x: moment().subtract(2, "day"), y: 35 }, + { x: moment().subtract(1, "day"), y: 60 }, + { x: moment().subtract(24, "hour"), y: 61 }, + { x: moment().subtract(15, "hour"), y: 65 }, + { x: moment().subtract(10, "hour"), y: 63 }, + { x: moment().subtract(5, "hour"), y: 58 }, + { x: moment().subtract(1, "hour"), y: 64 }, + { x: moment().subtract(50, "minute"), y: 63 }, + { x: moment().subtract(40, "minute"), y: 60 }, + { x: moment().subtract(30, "minute"), y: 62 }, + { x: moment().subtract(20, "minute"), y: 61 }, + { x: moment().subtract(10, "minute"), y: 62 }, + ], + }, +]; + +export const LOUNGING_VS_ULTIMATE_FRISBEE_DATA: ITimeseriesWidgetData[] = [ + { + id: "series-a", + name: "Lounging", + description: "Shootin' the Breeze", + data: [ + { x: moment().subtract(10, "day"), y: 10 }, + { x: moment().subtract(9, "day"), y: 15 }, + { x: moment().subtract(8, "day"), y: 13 }, + { x: moment().subtract(7, "day"), y: 20 }, + { x: moment().subtract(6, "day"), y: 15 }, + { x: moment().subtract(5, "day"), y: 10 }, + { x: moment().subtract(4, "day"), y: 15 }, + { x: moment().subtract(3, "day"), y: 5 }, + { x: moment().subtract(2, "day"), y: 10 }, + { x: moment().subtract(1, "day"), y: 15 }, + { x: moment().subtract(24, "hour"), y: 14 }, + { x: moment().subtract(15, "hour"), y: 13 }, + { x: moment().subtract(10, "hour"), y: 15 }, + { x: moment().subtract(5, "hour"), y: 16 }, + { x: moment().subtract(1, "hour"), y: 14 }, + { x: moment().subtract(50, "minute"), y: 13 }, + { x: moment().subtract(40, "minute"), y: 10 }, + { x: moment().subtract(30, "minute"), y: 12 }, + { x: moment().subtract(20, "minute"), y: 11 }, + { x: moment().subtract(10, "minute"), y: 14 }, + ], + }, + { + id: "series-b", + name: "Frisbee Golfing", + description: "Golfin' with a disc", + data: [ + { x: moment().subtract(10, "day"), y: 80 }, + { x: moment().subtract(9, "day"), y: 84 }, + { x: moment().subtract(8, "day"), y: 80 }, + { x: moment().subtract(7, "day"), y: 75 }, + { x: moment().subtract(6, "day"), y: 95 }, + { x: moment().subtract(5, "day"), y: 85 }, + { x: moment().subtract(4, "day"), y: 80 }, + { x: moment().subtract(3, "day"), y: 85 }, + { x: moment().subtract(2, "day"), y: 85 }, + { x: moment().subtract(1, "day"), y: 80 }, + { x: moment().subtract(24, "hour"), y: 81 }, + { x: moment().subtract(15, "hour"), y: 85 }, + { x: moment().subtract(10, "hour"), y: 83 }, + { x: moment().subtract(5, "hour"), y: 88 }, + { x: moment().subtract(1, "hour"), y: 84 }, + { x: moment().subtract(50, "minute"), y: 83 }, + { x: moment().subtract(40, "minute"), y: 80 }, + { x: moment().subtract(30, "minute"), y: 82 }, + { x: moment().subtract(20, "minute"), y: 81 }, + { x: moment().subtract(10, "minute"), y: 82 }, + ], + }, +]; + +export const TABLE_DATA: BasicTableModel[] = [ + { + position: 1, + name: "FOCUS-SVR-02258", + features: ["remote-access-vpn-tunnel", "patch-manager01"], + status: "Active", + checks: { + icon: "status_up", + num: 25, + }, + "cpu-load": 86, + firstUrl: "https://en.wikipedia.org/wiki/Brno", + firstUrlLabel: "Brno", + secondUrl: "https://en.wikipedia.org/wiki/VMware_Workstation", + secondUrlLabel: "Workstation", + }, + { + position: 2, + name: "FOCUS-SVR-03312", + features: ["tools", "database", "orion-ape-backup"], + status: "Active", + checks: { + icon: "status_critical", + num: 25, + }, + "cpu-load": 47, + firstUrl: "https://en.wikipedia.org/wiki/Brno", + firstUrlLabel: "Brno", + secondUrl: "https://en.wikipedia.org/wiki/VMware_Workstation", + secondUrlLabel: "Workstation", + }, + { + position: 3, + name: "FOCUS-SVR-02258", + features: [ + "remote-access-vpn-tunnel", + "database", + "orion-ape-backup", + "patch-manager01", + ], + status: "Active", + checks: { + icon: "status_down", + num: 25, + }, + "cpu-load": 53, + firstUrl: "https://en.wikipedia.org/wiki/Kyiv", + firstUrlLabel: "Kyiv", + secondUrl: "https://en.wikipedia.org/wiki/VMware_Workstation", + secondUrlLabel: "Workstation", + }, + { + position: 4, + name: "Man-LT-JYJ4AD5", + features: [ + "remote-access-vpn-tunnel", + "tools", + "database", + "orion-ape-backup", + ], + status: "Active", + checks: { + icon: "status_up", + num: 25, + }, + "cpu-load": 32, + firstUrl: "https://en.wikipedia.org/wiki/Kyiv", + firstUrlLabel: "Kyiv", + secondUrl: "https://en.wikipedia.org/wiki/VirtualBox", + secondUrlLabel: "VirtualBox", + }, + { + position: 5, + name: "Man-LT-JYJ425", + features: [ + "remote-access-vpn-tunnel", + "tools", + "database", + "orion-ape-backup", + ], + status: "Active", + checks: { + icon: "status_up", + num: 25, + }, + "cpu-load": 22, + firstUrl: "https://en.wikipedia.org/wiki/Kyiv", + firstUrlLabel: "Kyiv", + secondUrl: "https://en.wikipedia.org/wiki/VirtualBox", + secondUrlLabel: "VirtualBox", + }, + { + position: 6, + name: "Man-LT-JYJ4333", + features: [ + "remote-access-vpn-tunnel", + "tools", + "database", + "orion-ape-backup", + ], + status: "Active", + checks: { + icon: "status_up", + num: 25, + }, + "cpu-load": 12, + firstUrl: "https://en.wikipedia.org/wiki/Kyiv", + firstUrlLabel: "Kyiv", + secondUrl: "https://en.wikipedia.org/wiki/VirtualBox", + secondUrlLabel: "VirtualBox", + }, + { + position: 7, + name: "FOCUS-SVR-02258", + features: ["remote-access-vpn-tunnel", "patch-manager01"], + status: "Active", + checks: { + icon: "status_up", + num: 25, + }, + "cpu-load": 86, + firstUrl: "https://en.wikipedia.org/wiki/Austin", + firstUrlLabel: "Austin", + secondUrl: "https://en.wikipedia.org/wiki/VMware_Workstation", + secondUrlLabel: "Workstation", + }, + { + position: 8, + name: "Man-LT-JYJ4AD5", + features: [ + "remote-access-vpn-tunnel", + "tools", + "database", + "orion-ape-backup", + "patch-manager01", + ], + status: "Active", + checks: { + icon: "status_inactive", + num: 25, + }, + "cpu-load": 35, + firstUrl: "https://en.wikipedia.org/wiki/Austin", + firstUrlLabel: "Austin", + secondUrl: "https://en.wikipedia.org/wiki/VMware_Workstation", + secondUrlLabel: "Workstation", + }, + { + position: 9, + name: "Man-LT-JYJ4AD5", + features: [ + "remote-access-vpn-tunnel", + "tools", + "database", + "patch-manager01", + ], + status: "Active", + checks: { + icon: "status_up", + num: 25, + }, + "cpu-load": 32, + firstUrl: "https://en.wikipedia.org/wiki/Brno", + firstUrlLabel: "Brno", + secondUrl: "https://en.wikipedia.org/wiki/VMware_Workstation", + secondUrlLabel: "Workstation", + }, + { + position: 10, + name: "Man-LT-JYJ4AD5", + features: [ + "remote-access-vpn-tunnel", + "database", + "orion-ape-backup", + "patch-manager01", + ], + status: "Active", + checks: { + icon: "status_up", + num: 25, + }, + "cpu-load": 64, + firstUrl: "https://en.wikipedia.org/wiki/Kyiv", + firstUrlLabel: "Kyiv", + secondUrl: "https://en.wikipedia.org/wiki/VMware_Workstation", + secondUrlLabel: "Workstation", + }, + { + position: 11, + name: "Man-LT-111", + features: [], + status: "Active", + checks: { + icon: "status_external", + num: 25, + }, + "cpu-load": 55, + firstUrl: "https://en.wikipedia.org/wiki/Brno", + firstUrlLabel: "Brno", + secondUrl: "https://en.wikipedia.org/wiki/VMware_Workstation", + secondUrlLabel: "Workstation", + }, + { + position: 12, + name: "Man-LT-2222", + features: [ + "remote-access-vpn-tunnel", + "tools", + "database", + "orion-ape-backup", + "patch-manager01", + ], + status: "Active", + checks: { + icon: "status_inactive", + num: 25, + }, + "cpu-load": 34, + firstUrl: "https://en.wikipedia.org/wiki/Brno", + firstUrlLabel: "Brno", + secondUrl: "https://en.wikipedia.org/wiki/VMware_Workstation", + secondUrlLabel: "Workstation", + }, + { + position: 13, + name: "Man-LT-333333", + features: [ + "remote-access-vpn-tunnel", + "tools", + "database", + "patch-manager01", + ], + status: "Active", + checks: { + icon: "status_up", + num: 25, + }, + "cpu-load": 56, + firstUrl: "https://en.wikipedia.org/wiki/Kyiv", + firstUrlLabel: "Kyiv", + secondUrl: "https://en.wikipedia.org/wiki/VMware_Workstation", + secondUrlLabel: "Workstation", + }, + { + position: 14, + name: "Man-LT-444444", + features: [ + "remote-access-vpn-tunnel", + "database", + "orion-ape-backup", + "patch-manager01", + ], + status: "Active", + checks: { + icon: "status_up", + num: 25, + }, + "cpu-load": 26, + firstUrl: "https://en.wikipedia.org/wiki/Kyiv", + firstUrlLabel: "Kyiv", + secondUrl: "https://en.wikipedia.org/wiki/VMware_Workstation", + secondUrlLabel: "Workstation", + }, + { + position: 15, + name: "Man-LT-555555", + features: [ + "remote-access-vpn-tunnel", + "database", + "orion-ape-backup", + "patch-manager01", + ], + status: "Active", + checks: { + icon: "status_up", + num: 25, + }, + "cpu-load": 76, + firstUrl: "https://en.wikipedia.org/wiki/Austin", + firstUrlLabel: "Austin", + secondUrl: "https://en.wikipedia.org/wiki/VMware_Workstation", + secondUrlLabel: "Workstation", + }, + { + position: 16, + name: "FOCUS-SVR-02258", + features: ["remote-access-vpn-tunnel", "patch-manager01"], + status: "Active", + checks: { + icon: "status_up", + num: 25, + }, + "cpu-load": 86, + firstUrl: "https://en.wikipedia.org/wiki/Brno", + firstUrlLabel: "Brno", + secondUrl: "https://en.wikipedia.org/wiki/VMware_Workstation", + secondUrlLabel: "Workstation", + }, + { + position: 17, + name: "FOCUS-SVR-03312", + features: ["tools", "database", "orion-ape-backup"], + status: "Active", + checks: { + icon: "status_critical", + num: 25, + }, + "cpu-load": 47, + firstUrl: "https://en.wikipedia.org/wiki/Brno", + firstUrlLabel: "Brno", + secondUrl: "https://en.wikipedia.org/wiki/VMware_Workstation", + secondUrlLabel: "Workstation", + }, + { + position: 18, + name: "FOCUS-SVR-02258", + features: [ + "remote-access-vpn-tunnel", + "database", + "orion-ape-backup", + "patch-manager01", + ], + status: "Active", + checks: { + icon: "status_down", + num: 25, + }, + "cpu-load": 53, + firstUrl: "https://en.wikipedia.org/wiki/Kyiv", + firstUrlLabel: "Kyiv", + secondUrl: "https://en.wikipedia.org/wiki/VMware_Workstation", + secondUrlLabel: "Workstation", + }, + { + position: 19, + name: "Man-LT-JYJ4AD5", + features: [ + "remote-access-vpn-tunnel", + "tools", + "database", + "orion-ape-backup", + ], + status: "Active", + checks: { + icon: "status_up", + num: 25, + }, + "cpu-load": 32, + firstUrl: "https://en.wikipedia.org/wiki/Kyiv", + firstUrlLabel: "Kyiv", + secondUrl: "https://en.wikipedia.org/wiki/VirtualBox", + secondUrlLabel: "VirtualBox", + }, + { + position: 20, + name: "Man-LT-JYJ425", + features: [ + "remote-access-vpn-tunnel", + "tools", + "database", + "orion-ape-backup", + ], + status: "Active", + checks: { + icon: "status_up", + num: 25, + }, + "cpu-load": 22, + firstUrl: "https://en.wikipedia.org/wiki/Kyiv", + firstUrlLabel: "Kyiv", + secondUrl: "https://en.wikipedia.org/wiki/VirtualBox", + secondUrlLabel: "VirtualBox", + }, + { + position: 21, + name: "Man-LT-JYJ4333", + features: [ + "remote-access-vpn-tunnel", + "tools", + "database", + "orion-ape-backup", + ], + status: "Active", + checks: { + icon: "status_up", + num: 25, + }, + "cpu-load": 12, + firstUrl: "https://en.wikipedia.org/wiki/Kyiv", + firstUrlLabel: "Kyiv", + secondUrl: "https://en.wikipedia.org/wiki/VirtualBox", + secondUrlLabel: "VirtualBox", + }, + { + position: 22, + name: "FOCUS-SVR-02258", + features: ["remote-access-vpn-tunnel", "patch-manager01"], + status: "Active", + checks: { + icon: "status_up", + num: 25, + }, + "cpu-load": 86, + firstUrl: "https://en.wikipedia.org/wiki/Austin", + firstUrlLabel: "Austin", + secondUrl: "https://en.wikipedia.org/wiki/VMware_Workstation", + secondUrlLabel: "Workstation", + }, + { + position: 23, + name: "Man-LT-JYJ4AD5", + features: [ + "remote-access-vpn-tunnel", + "tools", + "database", + "orion-ape-backup", + "patch-manager01", + ], + status: "Active", + checks: { + icon: "status_inactive", + num: 25, + }, + "cpu-load": 35, + firstUrl: "https://en.wikipedia.org/wiki/Austin", + firstUrlLabel: "Austin", + secondUrl: "https://en.wikipedia.org/wiki/VMware_Workstation", + secondUrlLabel: "Workstation", + }, + { + position: 24, + name: "Man-LT-JYJ4AD5", + features: [ + "remote-access-vpn-tunnel", + "tools", + "database", + "patch-manager01", + ], + status: "Active", + checks: { + icon: "status_up", + num: 25, + }, + "cpu-load": 32, + firstUrl: "https://en.wikipedia.org/wiki/Brno", + firstUrlLabel: "Brno", + secondUrl: "https://en.wikipedia.org/wiki/VMware_Workstation", + secondUrlLabel: "Workstation", + }, + { + position: 25, + name: "Man-LT-JYJ4AD5", + features: [ + "remote-access-vpn-tunnel", + "database", + "orion-ape-backup", + "patch-manager01", + ], + status: "Active", + checks: { + icon: "status_up", + num: 25, + }, + "cpu-load": 64, + firstUrl: "https://en.wikipedia.org/wiki/Kyiv", + firstUrlLabel: "Kyiv", + secondUrl: "https://en.wikipedia.org/wiki/VMware_Workstation", + secondUrlLabel: "Workstation", + }, + { + position: 26, + name: "Man-LT-111", + features: [], + status: "Active", + checks: { + icon: "status_external", + num: 25, + }, + "cpu-load": 55, + firstUrl: "https://en.wikipedia.org/wiki/Brno", + firstUrlLabel: "Brno", + secondUrl: "https://en.wikipedia.org/wiki/VMware_Workstation", + secondUrlLabel: "Workstation", + }, + { + position: 27, + name: "Man-LT-2222", + features: [ + "remote-access-vpn-tunnel", + "tools", + "database", + "orion-ape-backup", + "patch-manager01", + ], + status: "Active", + checks: { + icon: "status_inactive", + num: 25, + }, + "cpu-load": 34, + firstUrl: "https://en.wikipedia.org/wiki/Brno", + firstUrlLabel: "Brno", + secondUrl: "https://en.wikipedia.org/wiki/VMware_Workstation", + secondUrlLabel: "Workstation", + }, + { + position: 28, + name: "Man-LT-333333", + features: [ + "remote-access-vpn-tunnel", + "tools", + "database", + "patch-manager01", + ], + status: "Active", + checks: { + icon: "status_up", + num: 25, + }, + "cpu-load": 56, + firstUrl: "https://en.wikipedia.org/wiki/Kyiv", + firstUrlLabel: "Kyiv", + secondUrl: "https://en.wikipedia.org/wiki/VMware_Workstation", + secondUrlLabel: "Workstation", + }, + { + position: 29, + name: "Man-LT-444444", + features: [ + "remote-access-vpn-tunnel", + "database", + "orion-ape-backup", + "patch-manager01", + ], + status: "Active", + checks: { + icon: "status_up", + num: 25, + }, + "cpu-load": 26, + firstUrl: "https://en.wikipedia.org/wiki/Kyiv", + firstUrlLabel: "Kyiv", + secondUrl: "https://en.wikipedia.org/wiki/VMware_Workstation", + secondUrlLabel: "Workstation", + }, + { + position: 30, + name: "Man-LT-555555", + features: [ + "remote-access-vpn-tunnel", + "database", + "orion-ape-backup", + "patch-manager01", + ], + status: "Active", + checks: { + icon: "status_up", + num: 25, + }, + "cpu-load": 76, + firstUrl: "https://en.wikipedia.org/wiki/Austin", + firstUrlLabel: "Austin", + secondUrl: "https://en.wikipedia.org/wiki/VMware_Workstation", + secondUrlLabel: "Workstation", + }, + { + position: 31, + name: "FOCUS-SVR-02258", + features: ["remote-access-vpn-tunnel", "patch-manager01"], + status: "Active", + checks: { + icon: "status_up", + num: 25, + }, + "cpu-load": 86, + firstUrl: "https://en.wikipedia.org/wiki/Brno", + firstUrlLabel: "Brno", + secondUrl: "https://en.wikipedia.org/wiki/VMware_Workstation", + secondUrlLabel: "Workstation", + }, + { + position: 32, + name: "FOCUS-SVR-03312", + features: ["tools", "database", "orion-ape-backup"], + status: "Active", + checks: { + icon: "status_critical", + num: 25, + }, + "cpu-load": 47, + firstUrl: "https://en.wikipedia.org/wiki/Brno", + firstUrlLabel: "Brno", + secondUrl: "https://en.wikipedia.org/wiki/VMware_Workstation", + secondUrlLabel: "Workstation", + }, + { + position: 33, + name: "FOCUS-SVR-02258", + features: [ + "remote-access-vpn-tunnel", + "database", + "orion-ape-backup", + "patch-manager01", + ], + status: "Active", + checks: { + icon: "status_down", + num: 25, + }, + "cpu-load": 53, + firstUrl: "https://en.wikipedia.org/wiki/Kyiv", + firstUrlLabel: "Kyiv", + secondUrl: "https://en.wikipedia.org/wiki/VMware_Workstation", + secondUrlLabel: "Workstation", + }, + { + position: 34, + name: "Man-LT-JYJ4AD5", + features: [ + "remote-access-vpn-tunnel", + "tools", + "database", + "orion-ape-backup", + ], + status: "Active", + checks: { + icon: "status_up", + num: 25, + }, + "cpu-load": 32, + firstUrl: "https://en.wikipedia.org/wiki/Kyiv", + firstUrlLabel: "Kyiv", + secondUrl: "https://en.wikipedia.org/wiki/VirtualBox", + secondUrlLabel: "VirtualBox", + }, + { + position: 35, + name: "Man-LT-JYJ425", + features: [ + "remote-access-vpn-tunnel", + "tools", + "database", + "orion-ape-backup", + ], + status: "Active", + checks: { + icon: "status_up", + num: 25, + }, + "cpu-load": 22, + firstUrl: "https://en.wikipedia.org/wiki/Kyiv", + firstUrlLabel: "Kyiv", + secondUrl: "https://en.wikipedia.org/wiki/VirtualBox", + secondUrlLabel: "VirtualBox", + }, + { + position: 36, + name: "Man-LT-JYJ4333", + features: [ + "remote-access-vpn-tunnel", + "tools", + "database", + "orion-ape-backup", + ], + status: "Active", + checks: { + icon: "status_up", + num: 25, + }, + "cpu-load": 12, + firstUrl: "https://en.wikipedia.org/wiki/Kyiv", + firstUrlLabel: "Kyiv", + secondUrl: "https://en.wikipedia.org/wiki/VirtualBox", + secondUrlLabel: "VirtualBox", + }, + { + position: 37, + name: "FOCUS-SVR-02258", + features: ["remote-access-vpn-tunnel", "patch-manager01"], + status: "Active", + checks: { + icon: "status_up", + num: 25, + }, + "cpu-load": 86, + firstUrl: "https://en.wikipedia.org/wiki/Austin", + firstUrlLabel: "Austin", + secondUrl: "https://en.wikipedia.org/wiki/VMware_Workstation", + secondUrlLabel: "Workstation", + }, + { + position: 38, + name: "Man-LT-JYJ4AD5", + features: [ + "remote-access-vpn-tunnel", + "tools", + "database", + "orion-ape-backup", + "patch-manager01", + ], + status: "Active", + checks: { + icon: "status_inactive", + num: 25, + }, + "cpu-load": 35, + firstUrl: "https://en.wikipedia.org/wiki/Austin", + firstUrlLabel: "Austin", + secondUrl: "https://en.wikipedia.org/wiki/VMware_Workstation", + secondUrlLabel: "Workstation", + }, + { + position: 39, + name: "Man-LT-JYJ4AD5", + features: [ + "remote-access-vpn-tunnel", + "tools", + "database", + "patch-manager01", + ], + status: "Active", + checks: { + icon: "status_up", + num: 25, + }, + "cpu-load": 32, + firstUrl: "https://en.wikipedia.org/wiki/Brno", + firstUrlLabel: "Brno", + secondUrl: "https://en.wikipedia.org/wiki/VMware_Workstation", + secondUrlLabel: "Workstation", + }, + { + position: 40, + name: "Man-LT-JYJ4AD5", + features: [ + "remote-access-vpn-tunnel", + "database", + "orion-ape-backup", + "patch-manager01", + ], + status: "Active", + checks: { + icon: "status_up", + num: 25, + }, + "cpu-load": 64, + firstUrl: "https://en.wikipedia.org/wiki/Kyiv", + firstUrlLabel: "Kyiv", + secondUrl: "https://en.wikipedia.org/wiki/VMware_Workstation", + secondUrlLabel: "Workstation", + }, + { + position: 41, + name: "Man-LT-111", + features: [], + status: "Active", + checks: { + icon: "status_external", + num: 25, + }, + "cpu-load": 55, + firstUrl: "https://en.wikipedia.org/wiki/Brno", + firstUrlLabel: "Brno", + secondUrl: "https://en.wikipedia.org/wiki/VMware_Workstation", + secondUrlLabel: "Workstation", + }, + { + position: 42, + name: "Man-LT-2222", + features: [ + "remote-access-vpn-tunnel", + "tools", + "database", + "orion-ape-backup", + "patch-manager01", + ], + status: "Active", + checks: { + icon: "status_inactive", + num: 25, + }, + "cpu-load": 34, + firstUrl: "https://en.wikipedia.org/wiki/Brno", + firstUrlLabel: "Brno", + secondUrl: "https://en.wikipedia.org/wiki/VMware_Workstation", + secondUrlLabel: "Workstation", + }, + { + position: 43, + name: "Man-LT-333333", + features: [ + "remote-access-vpn-tunnel", + "tools", + "database", + "patch-manager01", + ], + status: "Active", + checks: { + icon: "status_up", + num: 25, + }, + "cpu-load": 56, + firstUrl: "https://en.wikipedia.org/wiki/Kyiv", + firstUrlLabel: "Kyiv", + secondUrl: "https://en.wikipedia.org/wiki/VMware_Workstation", + secondUrlLabel: "Workstation", + }, + { + position: 44, + name: "Man-LT-444444", + features: [ + "remote-access-vpn-tunnel", + "database", + "orion-ape-backup", + "patch-manager01", + ], + status: "Active", + checks: { + icon: "status_up", + num: 25, + }, + "cpu-load": 26, + firstUrl: "https://en.wikipedia.org/wiki/Kyiv", + firstUrlLabel: "Kyiv", + secondUrl: "https://en.wikipedia.org/wiki/VMware_Workstation", + secondUrlLabel: "Workstation", + }, + { + position: 45, + name: "Man-LT-555555", + features: [ + "remote-access-vpn-tunnel", + "database", + "orion-ape-backup", + "patch-manager01", + ], + status: "Active", + checks: { + icon: "status_up", + num: 25, + }, + "cpu-load": 76, + firstUrl: "https://en.wikipedia.org/wiki/Austin", + firstUrlLabel: "Austin", + secondUrl: "https://en.wikipedia.org/wiki/VMware_Workstation", + secondUrlLabel: "Workstation", + }, +]; +\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\`, + "overview/hero/widget-configs/kpi.ts": \\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\`// © 2022 SolarWinds Worldwide, LLC. All rights reserved. +// +// Permission is hereby granted, free of charge, to any person obtaining a copy +// of this software and associated documentation files (the "Software"), to +// deal in the Software without restriction, including without limitation the +// rights to use, copy, modify, merge, publish, distribute, sublicense, and/or +// sell copies of the Software, and to permit persons to whom the Software is +// furnished to do so, subject to the following conditions: +// +// The above copyright notice and this permission notice shall be included in +// all copies or substantial portions of the Software. +// +// THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR +// IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, +// FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE +// AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER +// LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, +// OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN +// THE SOFTWARE. + +import { + DEFAULT_PIZZAGNA_ROOT, + IProviderConfiguration, + IRefresherProperties, + IWidget, + KpiComponent, + NOVA_KPI_DATASOURCE_ADAPTER, + PizzagnaLayer, + WellKnownProviders, +} from "@nova-ui/dashboards"; + +import { + HarryPotterAverageRatingDataSource, + HarryPotterRatingsCountDataSource, +} from "../data/kpi-datasources"; + +export const kpiConfig: IWidget = { + id: "kpiWidgetId", + type: "kpi", + pizzagna: { + [PizzagnaLayer.Configuration]: { + [DEFAULT_PIZZAGNA_ROOT]: { + providers: { + [WellKnownProviders.Refresher]: { + properties: { + // Configuring the refresher interval so that our data source is invoked every ten minutes + interval: 60 * 10, + enabled: true, + } as IRefresherProperties, + } as Partial, + }, + }, + header: { + properties: { + title: "Harry Potter and the Sorcerer's Stone", + subtitle: "By J. K. Rowling", + }, + }, + tiles: { + properties: { + nodes: ["kpi1", "kpi2"], + }, + }, + kpi1: { + id: "kpi1", + componentType: KpiComponent.lateLoadKey, + properties: { + widgetData: { + label: "Average Rating", + backgroundColor: "var(--nui-color-chart-three)", + units: "out of 5 Stars", + }, + }, + providers: { + [WellKnownProviders.DataSource]: { + // Setting the data source providerId for the tile with id "kpi1" + providerId: + HarryPotterAverageRatingDataSource.providerId, + } as IProviderConfiguration, + [WellKnownProviders.Adapter]: { + providerId: NOVA_KPI_DATASOURCE_ADAPTER, + properties: { + componentId: "kpi1", + propertyPath: "widgetData", + }, + } as IProviderConfiguration, + }, + }, + kpi2: { + id: "kpi2", + componentType: KpiComponent.lateLoadKey, + properties: { + widgetData: { + label: "Reader Feedback", + units: "Ratings", + }, + }, + providers: { + [WellKnownProviders.DataSource]: { + // Setting the data source providerId for the tile with id "kpi" + providerId: + HarryPotterRatingsCountDataSource.providerId, + } as IProviderConfiguration, + [WellKnownProviders.Adapter]: { + providerId: NOVA_KPI_DATASOURCE_ADAPTER, + properties: { + componentId: "kpi2", + propertyPath: "widgetData", + }, + } as IProviderConfiguration, + }, + }, + }, + }, +}; +\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\`, + "overview/hero/widget-configs/proportional.ts": \\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\`// © 2022 SolarWinds Worldwide, LLC. All rights reserved. +// +// Permission is hereby granted, free of charge, to any person obtaining a copy +// of this software and associated documentation files (the "Software"), to +// deal in the Software without restriction, including without limitation the +// rights to use, copy, modify, merge, publish, distribute, sublicense, and/or +// sell copies of the Software, and to permit persons to whom the Software is +// furnished to do so, subject to the following conditions: +// +// The above copyright notice and this permission notice shall be included in +// all copies or substantial portions of the Software. +// +// THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR +// IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, +// FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE +// AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER +// LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, +// OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN +// THE SOFTWARE. + +import { + DEFAULT_PIZZAGNA_ROOT, + IProportionalWidgetChartOptions, + IProviderConfiguration, + IWidget, + LegendPlacement, + PizzagnaLayer, + ProportionalWidgetChartTypes, + WellKnownProviders, +} from "@nova-ui/dashboards"; + +import { BeerReviewCountsByCityMockDataSource } from "../data/proportional-datasources"; + +export const proportionalConfig: IWidget = { + id: "proportionalWidgetId", + type: "proportional", + pizzagna: { + [PizzagnaLayer.Configuration]: { + [DEFAULT_PIZZAGNA_ROOT]: { + providers: { + [WellKnownProviders.Refresher]: { + properties: { + interval: 0, + }, + }, + }, + }, + header: { + properties: { + title: "Beer Review Tally by City", + subtitle: "These People Love Beer", + }, + }, + chart: { + providers: { + [WellKnownProviders.DataSource]: { + providerId: + BeerReviewCountsByCityMockDataSource.providerId, + } as IProviderConfiguration, + }, + properties: { + configuration: { + chartOptions: { + type: ProportionalWidgetChartTypes.DonutChart, + legendPlacement: LegendPlacement.Right, + } as IProportionalWidgetChartOptions, + }, + }, + }, + }, + }, +}; +\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\`, + "overview/hero/widget-configs/risk-score.ts": \\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\`// © 2023 SolarWinds Worldwide, LLC. All rights reserved. +// +// Permission is hereby granted, free of charge, to any person obtaining a copy +// of this software and associated documentation files (the "Software"), to +// deal in the Software without restriction, including without limitation the +// rights to use, copy, modify, merge, publish, distribute, sublicense, and/or +// sell copies of the Software, and to permit persons to whom the Software is +// furnished to do so, subject to the following conditions: +// +// The above copyright notice and this permission notice shall be included in +// all copies or substantial portions of the Software. +// +// THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR +// IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, +// FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE +// AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER +// LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, +// OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN +// THE SOFTWARE. + +import { + DEFAULT_PIZZAGNA_ROOT, + IProviderConfiguration, + IRefresherProperties, + IWidget, + RiskScoreTileComponent, + NOVA_KPI_DATASOURCE_ADAPTER, + PizzagnaLayer, + WellKnownProviders, +} from "@nova-ui/dashboards"; + +import { HarryPotterAverageRatingDataSource } from "../data/kpi-datasources"; + +export const riskScoreConfig: IWidget = { + id: "riskScoreWidgetId", + type: "risk-score", + pizzagna: { + [PizzagnaLayer.Configuration]: { + [DEFAULT_PIZZAGNA_ROOT]: { + providers: { + [WellKnownProviders.Refresher]: { + properties: { + // Configuring the refresher interval so that our data source is invoked every ten minutes + interval: 60 * 10, + enabled: true, + } as IRefresherProperties, + } as Partial, + }, + }, + header: { + properties: { + title: "Harry Potter and the Sorcerer's Stone", + subtitle: "By J. K. Rowling", + }, + }, + tiles: { + properties: { + nodes: ["riskScore1"], + }, + }, + riskScore1: { + id: "riskScore1", + componentType: RiskScoreTileComponent.lateLoadKey, + properties: { + widgetData: { + minValue: 0, + maxValue: 5, + useStaticLabel: false, + staticLabel: undefined, + label: \\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\`Average Rating\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\`, + description: \\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\`Harry Potter and the Sorcerer's Stone By J. K. Rowling Average Rating Risk Score\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\`, + }, + }, + providers: { + [WellKnownProviders.DataSource]: { + // Setting the data source providerId for the tile with id "riskScore1" + providerId: + HarryPotterAverageRatingDataSource.providerId, + } as IProviderConfiguration, + [WellKnownProviders.Adapter]: { + providerId: NOVA_KPI_DATASOURCE_ADAPTER, + properties: { + componentId: "riskScore1", + propertyPath: "widgetData", + }, + } as IProviderConfiguration, + }, + }, + }, + }, +}; +\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\`, + "overview/hero/widget-configs/table.ts": \\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\`// © 2022 SolarWinds Worldwide, LLC. All rights reserved. +// +// Permission is hereby granted, free of charge, to any person obtaining a copy +// of this software and associated documentation files (the "Software"), to +// deal in the Software without restriction, including without limitation the +// rights to use, copy, modify, merge, publish, distribute, sublicense, and/or +// sell copies of the Software, and to permit persons to whom the Software is +// furnished to do so, subject to the following conditions: +// +// The above copyright notice and this permission notice shall be included in +// all copies or substantial portions of the Software. +// +// THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR +// IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, +// FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE +// AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER +// LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, +// OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN +// THE SOFTWARE. + +import { + ITableWidgetColumnConfig, + ITableWidgetSorterConfig, + IWidget, + PizzagnaLayer, + RawFormatterComponent, + WellKnownProviders, +} from "@nova-ui/dashboards"; + +import { BeerDataSource } from "../data/table/beer-data-source"; + +export const tableConfig: IWidget = { + id: "tableWidgetId", + type: "table", + pizzagna: { + [PizzagnaLayer.Configuration]: { + header: { + properties: { + title: "Stupendous Suds", + subtitle: "Try These Brilliant Brews", + }, + }, + table: { + providers: { + [WellKnownProviders.DataSource]: { + providerId: BeerDataSource.providerId, + }, + }, + properties: { + configuration: { + columns: [ + { + id: "column1", + label: "Beer Name", + isActive: true, + width: 185, + formatter: { + componentType: + RawFormatterComponent.lateLoadKey, + properties: { + dataFieldIds: { + value: "name", + }, + }, + }, + }, + { + id: "column2", + label: "Tagline", + isActive: true, + width: 250, + formatter: { + componentType: + RawFormatterComponent.lateLoadKey, + properties: { + dataFieldIds: { + value: "tagline", + }, + }, + }, + }, + { + id: "column3", + label: "First Brewed", + isActive: true, + formatter: { + componentType: + RawFormatterComponent.lateLoadKey, + properties: { + dataFieldIds: { + value: "first_brewed", + }, + }, + }, + }, + { + id: "column4", + label: "Description", + isActive: true, + width: 275, + formatter: { + componentType: + RawFormatterComponent.lateLoadKey, + properties: { + dataFieldIds: { + value: "description", + }, + }, + }, + }, + ] as ITableWidgetColumnConfig[], + sorterConfiguration: { + descendantSorting: false, + sortBy: "", + } as ITableWidgetSorterConfig, + hasVirtualScroll: true, + }, + }, + }, + }, + }, +}; +\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\`, + "overview/hero/widget-configs/timeseries.ts": \\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\`// © 2022 SolarWinds Worldwide, LLC. All rights reserved. +// +// Permission is hereby granted, free of charge, to any person obtaining a copy +// of this software and associated documentation files (the "Software"), to +// deal in the Software without restriction, including without limitation the +// rights to use, copy, modify, merge, publish, distribute, sublicense, and/or +// sell copies of the Software, and to permit persons to whom the Software is +// furnished to do so, subject to the following conditions: +// +// The above copyright notice and this permission notice shall be included in +// all copies or substantial portions of the Software. +// +// THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR +// IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, +// FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE +// AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER +// LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, +// OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN +// THE SOFTWARE. + +import moment from "moment/moment"; + +import { + DEFAULT_PIZZAGNA_ROOT, + IProviderConfiguration, + ISerializableTimeframe, + ITimeseriesItemConfiguration, + IWidget, + LegendPlacement, + WellKnownProviders, +} from "@nova-ui/dashboards"; + +import { BeerVsReadingMockDataSource } from "../data/timeseries-data-sources"; + +export const timeseriesConfig: IWidget = { + id: "timeseriesWidgetId", + type: "timeseries", + pizzagna: { + configuration: { + [DEFAULT_PIZZAGNA_ROOT]: { + providers: { + [WellKnownProviders.DataSource]: { + providerId: BeerVsReadingMockDataSource.providerId, + } as IProviderConfiguration, + }, + }, + header: { + properties: { + title: "Primary Leisure Activity Over Time", + subtitle: "Survey of 1000 Solarians", + }, + }, + chart: { + providers: { + [WellKnownProviders.Adapter]: { + properties: { + series: [ + { + id: "series-1", + label: "Beer Tasting", + selectedSeriesId: "series-1", + }, + { + id: "series-2", + label: "Reading", + selectedSeriesId: "series-2", + }, + ] as ITimeseriesItemConfiguration[], + }, + } as Partial, + }, + properties: { + configuration: { + legendPlacement: LegendPlacement.Right, + enableZoom: true, + leftAxisLabel: "Solarians (%)", + }, + }, + }, + timeframeSelection: { + properties: { + timeframe: { + selectedPresetId: "last7Days", + } as ISerializableTimeframe, + minDate: moment().subtract(10, "days").format(), + maxDate: moment().format(), + }, + }, + }, + }, +}; +\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\`, + "overview/overview-docs.component.ts": \\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\`// © 2022 SolarWinds Worldwide, LLC. All rights reserved. +// +// Permission is hereby granted, free of charge, to any person obtaining a copy +// of this software and associated documentation files (the "Software"), to +// deal in the Software without restriction, including without limitation the +// rights to use, copy, modify, merge, publish, distribute, sublicense, and/or +// sell copies of the Software, and to permit persons to whom the Software is +// furnished to do so, subject to the following conditions: +// +// The above copyright notice and this permission notice shall be included in +// all copies or substantial portions of the Software. +// +// THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR +// IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, +// FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE +// AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER +// LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, +// OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN +// THE SOFTWARE. + +import { Component } from "@angular/core"; + +@Component({ + selector: "nui-dashboard-overview-docs", + templateUrl: "./overview-docs.component.html", + standalone: false, +}) +export class OverviewDocsComponent {} +\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\`, + "overview/overview.module.ts": \\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\`// © 2022 SolarWinds Worldwide, LLC. All rights reserved. +// +// Permission is hereby granted, free of charge, to any person obtaining a copy +// of this software and associated documentation files (the "Software"), to +// deal in the Software without restriction, including without limitation the +// rights to use, copy, modify, merge, publish, distribute, sublicense, and/or +// sell copies of the Software, and to permit persons to whom the Software is +// furnished to do so, subject to the following conditions: +// +// The above copyright notice and this permission notice shall be included in +// all copies or substantial portions of the Software. +// +// THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR +// IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, +// FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE +// AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER +// LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, +// OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN +// THE SOFTWARE. + +import { CommonModule } from "@angular/common"; +import { NgModule } from "@angular/core"; +import { RouterModule } from "@angular/router"; + +import { + NuiBusyModule, + NuiButtonModule, + NuiDocsModule, + NuiIconModule, + NuiMessageModule, + NuiSwitchModule, +} from "@nova-ui/bits"; +import { + ConfiguratorHeadingService, + IFormatterDefinition, + LinkFormatterComponent, + NuiDashboardsModule, + WellKnownPathKey, + WidgetTypesService, +} from "@nova-ui/dashboards"; + +import { HeroDashboardComponent } from "./hero/dashboard/hero-dashboard.component"; +import { + HarryPotterAverageRatingDataSource, + HarryPotterRatingsCountDataSource, +} from "./hero/data/kpi-datasources"; +import { + BeerReviewCountsByCityMockDataSource, + BeerReviewCountsByCityMockDataSource2, +} from "./hero/data/proportional-datasources"; +import { BeerDataSource } from "./hero/data/table/beer-data-source"; +import { RandomUserDataSource } from "./hero/data/table/random-user-data-source"; +import { + BeerVsReadingMockDataSource, + LoungingVsFrisbeeGolfMockDataSource, +} from "./hero/data/timeseries-data-sources"; +import { OverviewDocsComponent } from "./overview-docs.component"; + +const routes = [ + { + path: "", + component: OverviewDocsComponent, + data: { + srlc: { + hideIndicator: true, + }, + showThemeSwitcher: true, + }, + }, + { + path: "hero", + component: HeroDashboardComponent, + data: { + srlc: { + hideIndicator: true, + }, + }, + }, +]; + +@NgModule({ + imports: [ + CommonModule, + NuiDashboardsModule, + NuiBusyModule, + NuiButtonModule, + NuiDocsModule, + NuiMessageModule, + NuiSwitchModule, + NuiIconModule, + RouterModule.forChild(routes), + ], + declarations: [OverviewDocsComponent, HeroDashboardComponent], + providers: [ConfiguratorHeadingService], +}) +export default class OverviewModule { + constructor(private widgetTypesService: WidgetTypesService) { + this.setupDataSourceProviders(); + this.setupProportionalLegendFormatters(); + } + + private setupDataSourceProviders() { + this.setDataSourceProviders("table", [ + RandomUserDataSource.providerId, + BeerDataSource.providerId, + ]); + this.setDataSourceProviders("kpi", [ + HarryPotterAverageRatingDataSource.providerId, + HarryPotterRatingsCountDataSource.providerId, + ]); + this.setDataSourceProviders("risk-score", [ + HarryPotterAverageRatingDataSource.providerId, + HarryPotterRatingsCountDataSource.providerId, + ]); + this.setDataSourceProviders("proportional", [ + BeerReviewCountsByCityMockDataSource.providerId, + BeerReviewCountsByCityMockDataSource2.providerId, + ]); + this.setDataSourceProviders("timeseries", [ + BeerVsReadingMockDataSource.providerId, + LoungingVsFrisbeeGolfMockDataSource.providerId, + ]); + } + + private setDataSourceProviders(type: string, providers: string[]) { + const widgetTemplate = this.widgetTypesService.getWidgetType(type, 1); + this.widgetTypesService.setNode( + widgetTemplate, + "configurator", + WellKnownPathKey.DataSourceProviders, + providers + ); + } + + private setupProportionalLegendFormatters() { + const formatters: IFormatterDefinition[] = [ + { + componentType: LinkFormatterComponent.lateLoadKey, + label: $localize\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\`Link\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\`, + dataTypes: { + value: "label", + link: "link", + }, + }, + ]; + + const widgetTemplate = this.widgetTypesService.getWidgetType( + "proportional", + 1 + ); + this.widgetTypesService.setNode( + widgetTemplate, + "configurator", + WellKnownPathKey.Formatters, + formatters + ); + } +} +\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\`, + "tutorials/customization/configurator-section/custom-configurator-section/custom-configurator-section.example.component.ts": \\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\`// © 2022 SolarWinds Worldwide, LLC. All rights reserved. +// +// Permission is hereby granted, free of charge, to any person obtaining a copy +// of this software and associated documentation files (the "Software"), to +// deal in the Software without restriction, including without limitation the +// rights to use, copy, modify, merge, publish, distribute, sublicense, and/or +// sell copies of the Software, and to permit persons to whom the Software is +// furnished to do so, subject to the following conditions: +// +// The above copyright notice and this permission notice shall be included in +// all copies or substantial portions of the Software. +// +// THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR +// IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, +// FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE +// AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER +// LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, +// OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN +// THE SOFTWARE. + +import { HttpClient, HttpErrorResponse } from "@angular/common/http"; +import { + ChangeDetectionStrategy, + ChangeDetectorRef, + Component, + EventEmitter, + Injectable, + Input, + OnChanges, + OnDestroy, + OnInit, + Output, + SimpleChanges, +} from "@angular/core"; +import { FormBuilder, FormGroup, Validators } from "@angular/forms"; +import { GridsterConfig, GridsterItem } from "angular-gridster2"; +// eslint-disable-next-line import/no-deprecated +import { BehaviorSubject, combineLatest, Observable } from "rxjs"; +// eslint-disable-next-line import/no-deprecated +import { finalize, map, startWith } from "rxjs/operators"; + +import { DataSourceService, IFilteringOutputs } from "@nova-ui/bits"; +import { + ComponentRegistryService, + DATA_SOURCE, + DEFAULT_PIZZAGNA_ROOT, + IDashboard, + IHasChangeDetector, + IHasForm, + IKpiData, + IProviderConfiguration, + IRefresherProperties, + IWidget, + IWidgets, + KpiComponent, + NOVA_KPI_DATASOURCE_ADAPTER, + PizzagnaLayer, + ProviderRegistryService, + WellKnownPathKey, + WellKnownProviders, + WidgetTypesService, +} from "@nova-ui/dashboards"; + +/** + * A custom version of the KpiDescriptionConfigurationComponent provided by the dashboards framework. + * --- + * For this example, the existing background color selection functionality has been replaced by custom + * template content. + */ +@Component({ + selector: "custom-kpi-description-configuration", + template: \\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\` + + +
+
+ + + +
+ + +
+
+ Custom Content +
+
+ The default version of this configurator section + displays a background color selector here. +
+
+ + +
+ + + +
+
+
+ \\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\`, + styleUrls: ["./custom-configurator-section.example.component.less"], + changeDetection: ChangeDetectionStrategy.OnPush, + standalone: false, +}) +// Remember to declare this class in the parent module +export class CustomKpiDescriptionConfigurationComponent + implements OnInit, OnChanges, IHasChangeDetector, IHasForm +{ + // Ensure that the lateLoadKey value matches class name + public static lateLoadKey = "CustomKpiDescriptionConfigurationComponent"; + + @Input() componentId: string; + @Input() configurableUnits: boolean; + + @Input() label: string = ""; + @Input() units: string = ""; + + @Output() formReady = new EventEmitter(); + + public form: FormGroup; + public subtitle$: Observable; + + constructor( + public changeDetector: ChangeDetectorRef, + private formBuilder: FormBuilder + ) {} + + public ngOnInit(): void { + this.form = this.formBuilder.group({ + label: [this.label, [Validators.required]], + }); + + if (this.configurableUnits) { + this.form.addControl("units", this.formBuilder.control(this.units)); + } + + const label = this.form.get("label"); + // eslint-disable-next-line import/no-deprecated + const labelValue = label?.valueChanges.pipe(startWith(label?.value)); + + // eslint-disable-next-line import/no-deprecated + this.subtitle$ = combineLatest([ + labelValue?.pipe(map((t) => t || $localize\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\`no label\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\`)), + ]).pipe(map((labels) => labels.join(", "))); + + this.formReady.emit(this.form); + } + + public ngOnChanges(changes: SimpleChanges): void { + if (changes.label) { + this.form.patchValue({ label: changes.label.currentValue }); + } + if (changes.units) { + this.form.patchValue({ units: changes.units.currentValue }); + } + } +} + +/** + * A simple KPI data source to retrieve the average rating of Harry Potter and the Sorcerer's Stone (book) via googleapis + */ +@Injectable() +export class AverageRatingKpiDataSource + extends DataSourceService + implements OnDestroy +{ + // This is the ID we'll use to identify the provider + public static providerId = "AverageRatingKpiDataSource"; + + // Use this subject to communicate the data source's busy state + public busy = new BehaviorSubject(false); + + constructor(private http: HttpClient) { + super(); + } + + // In this example, getFilteredData is invoked every 10 minutes (Take a look at the refresher + // provider definition in the widget configuration below to see how the interval is set) + public async getFilteredData(): Promise { + this.busy.next(true); + return new Promise((resolve) => { + // *** Make a resource request to an external API (if needed) + this.http + .get("https://www.googleapis.com/books/v1/volumes/5MQFrgEACAAJ") + .pipe(finalize(() => this.busy.next(false))) + .subscribe({ + next: (data: any) => { + resolve({ + result: { + value: data.volumeInfo.averageRating, + }, + }); + }, + error: (error: HttpErrorResponse) => { + resolve({ + result: null, + error: { + type: error.status, + }, + }); + }, + }); + }); + } + + public ngOnDestroy(): void { + this.outputsSubject.complete(); + } +} + +/** + * A simple KPI data source to retrieve the ratings count of Harry Potter and the Sorcerer's Stone (book) via googleapis + */ +@Injectable() +export class RatingsCountKpiDataSource + extends DataSourceService + implements OnDestroy +{ + public static providerId = "RatingsCountKpiDataSource"; + + // Use this subject to communicate the data source's busy state + public busy = new BehaviorSubject(false); + + constructor(private http: HttpClient) { + super(); + } + + public async getFilteredData(): Promise { + this.busy.next(true); + return new Promise((resolve) => { + this.http + .get("https://www.googleapis.com/books/v1/volumes/5MQFrgEACAAJ") + .pipe(finalize(() => this.busy.next(false))) + .subscribe({ + next: (data: any) => { + resolve({ + result: { + value: data.volumeInfo.ratingsCount, + }, + }); + }, + error: (error: HttpErrorResponse) => { + resolve({ + result: null, + error: { + type: error.status, + }, + }); + }, + }); + }); + } + + public ngOnDestroy(): void { + this.outputsSubject.complete(); + } +} + +/** + * A component that instantiates the dashboard + */ +@Component({ + selector: "custom-configurator-section-example", + templateUrl: "./custom-configurator-section.example.component.html", + styleUrls: ["./custom-configurator-section.example.component.less"], + standalone: false, +}) +export class CustomConfiguratorSectionExampleComponent implements OnInit { + // This variable will hold all the data needed to define the layout and behavior of the widgets. + // Pass this to the dashboard component's dashboard input in the template. + public dashboard: IDashboard | undefined; + + // Angular gridster requires a configuration object even if it's empty. + // Pass this to the dashboard component's gridsterConfig input in the template. + public gridsterConfig: GridsterConfig = {}; + + // Boolean which dashboard takes in as an input if its true it allows you to move widgets around. + public editMode: boolean = false; + + constructor( + // WidgetTypesService provides the widget's necessary structure information + private widgetTypesService: WidgetTypesService, + + // In general, the ProviderRegistryService is used for making entities available for injection into dynamically loaded components. + private providerRegistry: ProviderRegistryService, + + // Inject the ComponentRegistryService to make our custom component available for late loading by the dashboards framework + private componentRegistry: ComponentRegistryService, + + private changeDetectorRef: ChangeDetectorRef + ) {} + + public ngOnInit(): void { + // Grab the widget's default template which will be needed as a parameter for setNode. + const widgetTemplate = this.widgetTypesService.getWidgetType("kpi", 1); + + // Replace the default KPI description configuration component with our custom one. + // Note: This could also be done in the parent module's constructor to give + // multiple dashboards access to the same custom configurator section. + this.widgetTypesService.setNode( + widgetTemplate, + "configurator", + WellKnownPathKey.TileDescriptionConfigComponentType, + CustomKpiDescriptionConfigurationComponent.lateLoadKey + ); + + // Register the custom configurator section with the component registry to make it available + // for late loading by the dashboards framework. + this.componentRegistry.registerByLateLoadKey( + CustomKpiDescriptionConfigurationComponent + ); + + // Register our data sources as dropdown options in the widget editor/configurator + // Note: This could also be done in the parent module's constructor so that + // multiple dashboards could have access to the same widget template modification. + this.widgetTypesService.setNode( + // This is the template we grabbed above with getWidgetType + widgetTemplate, + // We are setting the editor/configurator part of the widget template + "configurator", + // This indicates which node you are changing and we want to change + // the data source providers available for selection in the editor. + WellKnownPathKey.DataSourceProviders, + // We are setting the data sources available for selection in the editor + [ + AverageRatingKpiDataSource.providerId, + RatingsCountKpiDataSource.providerId, + ] + ); + + // Register the data sources available for injection into the KPI tiles. + // Note: Each tile of a KPI widget is assigned its own instance of a data source + this.providerRegistry.setProviders({ + [AverageRatingKpiDataSource.providerId]: { + provide: DATA_SOURCE, + useClass: AverageRatingKpiDataSource, + // Any dependencies that need to be injected into the provider must be listed here + deps: [HttpClient], + }, + [RatingsCountKpiDataSource.providerId]: { + provide: DATA_SOURCE, + useClass: RatingsCountKpiDataSource, + deps: [HttpClient], + }, + }); + + this.initializeDashboard(); + } + + public initializeDashboard(): void { + // We're using a static configuration object for this example (see widgetConfig at the bottom of the file), + // but this is where the widget's configuration could potentially be populated from a database + const kpiWidget = widgetConfig; + const widgetIndex: IWidgets = { + // Complete the KPI widget with information coming from its type definition + [kpiWidget.id]: + this.widgetTypesService.mergeWithWidgetType(kpiWidget), + }; + + // Setting the widget dimensions and position (this is for gridster) + const positions: Record = { + [kpiWidget.id]: { + cols: 4, + rows: 6, + y: 0, + x: 0, + }, + }; + + // Finally, assigning the variables we created above to the dashboard + this.dashboard = { + positions, + widgets: widgetIndex, + }; + } + + /** Used for restoring widgets state */ + public reInitializeDashboard(): void { + // destroys the components and their providers so the dashboard can re init data + this.dashboard = undefined; + this.changeDetectorRef.detectChanges(); + + this.initializeDashboard(); + } +} + +const widgetConfig: IWidget = { + id: "widget1", + type: "kpi", + pizzagna: { + [PizzagnaLayer.Configuration]: { + [DEFAULT_PIZZAGNA_ROOT]: { + providers: { + [WellKnownProviders.Refresher]: { + properties: { + // Configuring the refresher interval so that our data source is invoked every ten minutes + interval: 60 * 10, + enabled: true, + } as IRefresherProperties, + } as Partial, + }, + }, + header: { + properties: { + title: "Harry Potter and the Sorcerer's Stone", + subtitle: "By J. K. Rowling", + }, + }, + tiles: { + properties: { + nodes: ["kpi1"], + }, + }, + kpi1: { + id: "kpi1", + componentType: KpiComponent.lateLoadKey, + properties: { + widgetData: { + units: "out of 5 Stars", + label: "Average Rating", + }, + }, + providers: { + [WellKnownProviders.DataSource]: { + // Setting the data source providerId for the tile with id "kpi1" + providerId: AverageRatingKpiDataSource.providerId, + } as IProviderConfiguration, + [WellKnownProviders.Adapter]: { + providerId: NOVA_KPI_DATASOURCE_ADAPTER, + properties: { + componentId: "kpi1", + propertyPath: "widgetData", + }, + } as IProviderConfiguration, + }, + }, + }, + }, +}; +\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\`, + "tutorials/customization/configurator-section/custom-configurator-section-docs.component.ts": \\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\`// © 2022 SolarWinds Worldwide, LLC. All rights reserved. +// +// Permission is hereby granted, free of charge, to any person obtaining a copy +// of this software and associated documentation files (the "Software"), to +// deal in the Software without restriction, including without limitation the +// rights to use, copy, modify, merge, publish, distribute, sublicense, and/or +// sell copies of the Software, and to permit persons to whom the Software is +// furnished to do so, subject to the following conditions: +// +// The above copyright notice and this permission notice shall be included in +// all copies or substantial portions of the Software. +// +// THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR +// IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, +// FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE +// AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER +// LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, +// OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN +// THE SOFTWARE. + +import { Component } from "@angular/core"; + +@Component({ + selector: "custom-configurator-section-docs", + templateUrl: "./custom-configurator-section-docs.component.html", + standalone: false, +}) +export class CustomConfiguratorSectionDocsComponent {} +\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\`, + "tutorials/customization/configurator-section/custom-configurator-section.module.ts": \\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\`// © 2022 SolarWinds Worldwide, LLC. All rights reserved. +// +// Permission is hereby granted, free of charge, to any person obtaining a copy +// of this software and associated documentation files (the "Software"), to +// deal in the Software without restriction, including without limitation the +// rights to use, copy, modify, merge, publish, distribute, sublicense, and/or +// sell copies of the Software, and to permit persons to whom the Software is +// furnished to do so, subject to the following conditions: +// +// The above copyright notice and this permission notice shall be included in +// all copies or substantial portions of the Software. +// +// THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR +// IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, +// FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE +// AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER +// LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, +// OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN +// THE SOFTWARE. + +import { CommonModule } from "@angular/common"; +import { HttpClientModule } from "@angular/common/http"; +import { NgModule } from "@angular/core"; +import { ReactiveFormsModule } from "@angular/forms"; +import { RouterModule } from "@angular/router"; + +import { + NuiButtonModule, + NuiDocsModule, + NuiFormFieldModule, + NuiIconModule, + NuiMessageModule, + NuiSwitchModule, + NuiTextboxModule, + DEMO_PATH_TOKEN, +} from "@nova-ui/bits"; +import { + NuiDashboardConfiguratorModule, + NuiDashboardsModule, +} from "@nova-ui/dashboards"; + +import { + CustomConfiguratorSectionExampleComponent, + CustomKpiDescriptionConfigurationComponent, +} from "./custom-configurator-section/custom-configurator-section.example.component"; +import { CustomConfiguratorSectionDocsComponent } from "./custom-configurator-section-docs.component"; +import { getDemoFiles } from "../../../../../demo-files-factory"; + +const routes = [ + { + path: "", + component: CustomConfiguratorSectionDocsComponent, + data: { + srlc: { + hideIndicator: true, + }, + showThemeSwitcher: true, + }, + }, + { + path: "example", + component: CustomConfiguratorSectionExampleComponent, + data: { + srlc: { + hideIndicator: true, + }, + }, + }, +]; + +@NgModule({ + imports: [ + CommonModule, + ReactiveFormsModule, + HttpClientModule, + NuiDashboardsModule, + NuiDashboardConfiguratorModule, + NuiDocsModule, + NuiFormFieldModule, + NuiIconModule, + NuiMessageModule, + NuiSwitchModule, + NuiTextboxModule, + NuiButtonModule, + RouterModule.forChild(routes), + ], + declarations: [ + CustomConfiguratorSectionDocsComponent, + CustomKpiDescriptionConfigurationComponent, + CustomConfiguratorSectionExampleComponent, + ], + providers: [ + { + provide: DEMO_PATH_TOKEN, + useValue: getDemoFiles("configurator-section"), + }, + ], +}) +export default class CustomConfiguratorSectionModule {} +\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\`, + "tutorials/customization/customization.module.ts": \\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\`// © 2022 SolarWinds Worldwide, LLC. All rights reserved. +// +// Permission is hereby granted, free of charge, to any person obtaining a copy +// of this software and associated documentation files (the "Software"), to +// deal in the Software without restriction, including without limitation the +// rights to use, copy, modify, merge, publish, distribute, sublicense, and/or +// sell copies of the Software, and to permit persons to whom the Software is +// furnished to do so, subject to the following conditions: +// +// The above copyright notice and this permission notice shall be included in +// all copies or substantial portions of the Software. +// +// THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR +// IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, +// FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE +// AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER +// LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, +// OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN +// THE SOFTWARE. + +import { NgModule, Type } from "@angular/core"; +import { RouterModule, Routes } from "@angular/router"; + +import { ConfiguratorHeadingService } from "@nova-ui/dashboards"; + +enum CustomizationModuleRoute { + ConfiguratorSection = "configurator-section", + Widget = "widget", + Formatter = "formatter", + DataSourceConfigurator = "data-source-configurator", +} + +const routes: Routes = [ + { + path: CustomizationModuleRoute.ConfiguratorSection, + loadChildren: async () => + import( + "./configurator-section/custom-configurator-section.module" + ) as object as Promise>, + }, + { + path: CustomizationModuleRoute.Widget, + loadChildren: async () => + import("./widget/custom-widget.module") as object as Promise< + Type + >, + }, + { + path: CustomizationModuleRoute.Formatter, + loadChildren: async () => + import("./formatter/custom-formatter.module") as object as Promise< + Type + >, + }, + { + path: CustomizationModuleRoute.DataSourceConfigurator, + loadChildren: async () => + import( + "./data-source-configurator/custom-data-source-configurator.module" + ) as object as Promise>, + }, +]; + +@NgModule({ + imports: [RouterModule.forChild(routes)], + providers: [ConfiguratorHeadingService], +}) +export default class CustomizationModule {} +\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\`, + "tutorials/customization/data-source-configurator/custom-data-source-configurator-docs.component.ts": \\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\`// © 2022 SolarWinds Worldwide, LLC. All rights reserved. +// +// Permission is hereby granted, free of charge, to any person obtaining a copy +// of this software and associated documentation files (the "Software"), to +// deal in the Software without restriction, including without limitation the +// rights to use, copy, modify, merge, publish, distribute, sublicense, and/or +// sell copies of the Software, and to permit persons to whom the Software is +// furnished to do so, subject to the following conditions: +// +// The above copyright notice and this permission notice shall be included in +// all copies or substantial portions of the Software. +// +// THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR +// IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, +// FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE +// AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER +// LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, +// OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN +// THE SOFTWARE. + +import { Component } from "@angular/core"; + +@Component({ + selector: "nui-custom-data-source-configurator-docs", + templateUrl: "./custom-data-source-configurator-docs.component.html", + standalone: false, +}) +export class CustomDataSourceConfiguratorDocComponent {} +\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\`, + "tutorials/customization/data-source-configurator/custom-data-source-configurator.module.ts": \\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\`// © 2022 SolarWinds Worldwide, LLC. All rights reserved. +// +// Permission is hereby granted, free of charge, to any person obtaining a copy +// of this software and associated documentation files (the "Software"), to +// deal in the Software without restriction, including without limitation the +// rights to use, copy, modify, merge, publish, distribute, sublicense, and/or +// sell copies of the Software, and to permit persons to whom the Software is +// furnished to do so, subject to the following conditions: +// +// The above copyright notice and this permission notice shall be included in +// all copies or substantial portions of the Software. +// +// THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR +// IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, +// FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE +// AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER +// LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, +// OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN +// THE SOFTWARE. + +import { NgModule } from "@angular/core"; +import { ReactiveFormsModule } from "@angular/forms"; +import { RouterModule, Routes } from "@angular/router"; + +// eslint-disable-next-line max-len +import { + NuiButtonModule, + NuiDocsModule, + NuiFormFieldModule, + NuiIconModule, + NuiMessageModule, + NuiSelectV2Module, + NuiSwitchModule, + NuiTextboxModule, + NuiValidationMessageModule, + DEMO_PATH_TOKEN, +} from "@nova-ui/bits"; +import { + NuiDashboardConfiguratorModule, + NuiDashboardsModule, +} from "@nova-ui/dashboards"; + +import { CustomDataSourceConfiguratorDocComponent } from "./custom-data-source-configurator-docs.component"; +import { + CustomDataSourceConfiguratorExampleComponent, + HarryPotterDataSourceConfiguratorComponent, +} from "./example/custom-data-source-configurator-example.component"; +import { getDemoFiles } from "../../../../../demo-files-factory"; + +const routes: Routes = [ + { + path: "", + component: CustomDataSourceConfiguratorDocComponent, + data: { + srlc: { + hideIndicator: true, + }, + showThemeSwitcher: true, + }, + }, +]; + +@NgModule({ + imports: [ + RouterModule.forChild(routes), + NuiDocsModule, + NuiButtonModule, + NuiMessageModule, + NuiDashboardConfiguratorModule, + NuiDashboardsModule, + NuiFormFieldModule, + NuiTextboxModule, + NuiSwitchModule, + NuiSelectV2Module, + NuiValidationMessageModule, + NuiIconModule, + ReactiveFormsModule, + ], + declarations: [ + CustomDataSourceConfiguratorDocComponent, + CustomDataSourceConfiguratorExampleComponent, + HarryPotterDataSourceConfiguratorComponent, + ], + providers: [ + { + provide: DEMO_PATH_TOKEN, + useValue: getDemoFiles("data-source-configurator"), + }, + ], +}) +export default class CustomDataSourceConfiguratorModuleRoute {} +\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\`, + "tutorials/customization/data-source-configurator/example/custom-data-source-configurator-example.component.ts": \\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\`// © 2022 SolarWinds Worldwide, LLC. All rights reserved. +// +// Permission is hereby granted, free of charge, to any person obtaining a copy +// of this software and associated documentation files (the "Software"), to +// deal in the Software without restriction, including without limitation the +// rights to use, copy, modify, merge, publish, distribute, sublicense, and/or +// sell copies of the Software, and to permit persons to whom the Software is +// furnished to do so, subject to the following conditions: +// +// The above copyright notice and this permission notice shall be included in +// all copies or substantial portions of the Software. +// +// THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR +// IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, +// FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE +// AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER +// LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, +// OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN +// THE SOFTWARE. + +import { HttpClient, HttpErrorResponse } from "@angular/common/http"; +import { + ChangeDetectorRef, + Component, + Inject, + Injectable, + Injector, + OnDestroy, + OnInit, +} from "@angular/core"; +import { FormBuilder, Validators } from "@angular/forms"; +import { GridsterConfig, GridsterItem } from "angular-gridster2"; +import { BehaviorSubject } from "rxjs"; +import { finalize } from "rxjs/operators"; + +import { + DataSourceService, + EventBus, + IEvent, + IFilteringOutputs, + LoggerService, +} from "@nova-ui/bits"; +import { + ComponentRegistryService, + ConfiguratorHeadingService, + DataSourceConfigurationV2Component, + DATA_SOURCE, + DEFAULT_PIZZAGNA_ROOT, + IConfigurable, + IDashboard, + IKpiData, + IProperties, + IProviderConfiguration, + IRefresherProperties, + IWidget, + IWidgets, + KpiComponent, + NOVA_KPI_DATASOURCE_ADAPTER, + PizzagnaLayer, + PIZZAGNA_EVENT_BUS, + ProviderRegistryService, + WellKnownPathKey, + WellKnownProviders, + WidgetTypesService, +} from "@nova-ui/dashboards"; + +/** + * This component will serve as the data source accordion in the configurator. + */ +@Component({ + selector: "harry-potter-data-source-configurator", + styleUrls: ["./custom-data-source-configurator-example.component.less"], + template: \\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\` + +
+ +
+ Data Source +
+ Harry Potter Books +
+
+
+
+ + + + {{ book.title }} + + + +
+
+ + + + {{ metric.label }} + + + +
+
+ \\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\`, + standalone: false, +}) +@Injectable() +export class HarryPotterDataSourceConfiguratorComponent + extends DataSourceConfigurationV2Component + implements OnInit +{ + // This lateLoadKey allows the component to be able to be registered by the componentRegistry + public static lateLoadKey = "HarryPotterDataSourceConfiguratorComponent"; + + // Array of books that will populate the book select + public books = [ + { + id: "5MQFrgEACAAJ", + title: $localize\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\`Harry Potter and the Sorcerer's Stone\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\`, + }, + { + id: "5iTebBW-w7QC", + title: $localize\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\`Harry Potter and the Chamber of Secrets\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\`, + }, + ]; + + // Array of metrics that will populate the metric select + public metrics = [ + { + id: "averageRating", + label: $localize\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\`Average Rating\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\`, + }, + { + id: "ratingsCount", + label: $localize\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\`Ratings Count\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\`, + }, + ]; + + // These need to be injected because DataSourceConfigurationV2Component uses them + constructor( + changeDetector: ChangeDetectorRef, + configuratorHeading: ConfiguratorHeadingService, + formBuilder: FormBuilder, + providerRegistryService: ProviderRegistryService, + @Inject(PIZZAGNA_EVENT_BUS) eventBus: EventBus, + injector: Injector, + logger: LoggerService + ) { + super( + changeDetector, + configuratorHeading, + formBuilder, + providerRegistryService, + eventBus, + injector, + logger + ); + } + + // Overriding 'ngOnInit' to add custom controls to the 'properties' form group + public ngOnInit(): void { + super.ngOnInit(); + + // Overriding the 'properties' control on the form to create a form group that accommodates our custom properties + this.form.setControl( + "properties", + this.formBuilder.group({ + bookId: [this.properties?.bookId ?? "", Validators.required], + metric: [this.properties?.metric ?? "", Validators.required], + }) + ); + // The default data source control has a required validator we're removing that validator here since we aren't using it. + this.form.setControl("dataSource", this.formBuilder.control(null)); + // Here we set the providerId to our only data source so when a new tile gets created it will default to it. + this.form.get("providerId")?.setValue(AcmeKpiDataSource.providerId); + // Here we subscribe to the form and if there are any changes we invoke the data source + this.form.valueChanges.subscribe((value) => { + if (!value.providerId) { + return; + } + this.invokeDataSource(value); + }); + } +} + +/** + * A simple KPI data source to retrieve the average rating of Harry Potter and the Sorcerer's Stone (book) via googleapis + */ +@Injectable() +export class AcmeKpiDataSource + extends DataSourceService + implements OnDestroy, IConfigurable +{ + // This is the ID we'll use to identify the provider + public static providerId = "AcmeKpiDataSource"; + + // Use this subject to communicate the data source's busy state + public busy = new BehaviorSubject(false); + + public properties: IProperties; + + constructor(private http: HttpClient) { + super(); + } + + // This function MUST be implemented in order to receive property updates from our configurator + public updateConfiguration(properties: IProperties): void { + // Saving the properties because we will need it for this data source. + this.properties = properties; + } + + // In this example, getFilteredData is invoked every 10 minutes (Take a look at the refresher + // provider definition in the widget configuration below to see how the interval is set) + public async getFilteredData(): Promise { + // For loading indicator to show + this.busy.next(true); + return new Promise((resolve) => { + // *** Make a resource request to an external API (if needed) + this.http + .get( + \\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\`https://www.googleapis.com/books/v1/volumes/\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\${this.properties?.bookId}\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\` + ) + // For loading indicator to be hidden + .pipe(finalize(() => this.busy.next(false))) + .subscribe({ + next: (data: any) => { + resolve({ + result: { + value: data.volumeInfo[this.properties?.metric], + }, + }); + }, + error: (error: HttpErrorResponse) => { + resolve({ + result: null, + error: { + type: error.status, + }, + }); + }, + }); + }); + } + + public ngOnDestroy(): void { + this.outputsSubject.complete(); + } +} + +/** + * A component that instantiates the dashboard + */ +@Component({ + selector: "custom-data-source-configurator-example", + templateUrl: "./custom-data-source-configurator-example.component.html", + styleUrls: ["./custom-data-source-configurator-example.component.less"], + standalone: false, +}) +export class CustomDataSourceConfiguratorExampleComponent implements OnInit { + // This variable will hold all the data needed to define the layout and behavior of the widgets. + // Pass this to the dashboard component's dashboard input in the template. + public dashboard: IDashboard; + + // Angular gridster requires a configuration object even if it's empty. + // Pass this to the dashboard component's gridsterConfig input in the template. + public gridsterConfig: GridsterConfig = {}; + + // Boolean which dashboard takes in as an input if its true it allows you to move widgets around. + public editMode: boolean = false; + + constructor( + // WidgetTypesService provides the widget's necessary structure information + private widgetTypesService: WidgetTypesService, + + // In general, the ProviderRegistryService is used for making entities available for injection into dynamically loaded components. + private providerRegistry: ProviderRegistryService, + + // Inject the ComponentRegistryService to make our custom component available for late loading by the dashboards framework + private componentRegistry: ComponentRegistryService + ) {} + + public ngOnInit(): void { + // Registering the new data source configurator so it can be used. + this.componentRegistry.registerByLateLoadKey( + HarryPotterDataSourceConfiguratorComponent + ); + // Registering the data source for injection into the KPI tile. + // Note: Each tile of a KPI widget is assigned its own instance of the data source + this.providerRegistry.setProviders({ + [AcmeKpiDataSource.providerId]: { + provide: DATA_SOURCE, + useClass: AcmeKpiDataSource, + // Any dependencies that need to be injected into the provider must be listed here + deps: [HttpClient], + }, + }); + + const kpiWidgetTemplate = this.widgetTypesService.getWidgetType( + "kpi", + 1 + ); + + this.widgetTypesService.setNode( + // This is the template we grabbed above with getWidgetType + kpiWidgetTemplate, + // We are setting the editor/configurator part of the widget template + "configurator", + // This is the path to go to the data source config component type. + WellKnownPathKey.DataSourceConfigComponentType, + // We are changing it to use the component we just created above instead of the default. + HarryPotterDataSourceConfiguratorComponent.lateLoadKey + ); + + // We're using a static configuration object for this example, but this is where + // the widget's configuration could potentially be populated from a database + const kpiWidget = widgetConfig; + const widgetIndex: IWidgets = { + // Complete the KPI widget with information coming from its type definition + [kpiWidget.id]: + this.widgetTypesService.mergeWithWidgetType(kpiWidget), + }; + + // Setting the widget dimensions and position (this is for gridster) + const positions: Record = { + [kpiWidget.id]: { + cols: 4, + rows: 6, + y: 0, + x: 0, + }, + }; + + // Finally, assigning the variables we created above to the dashboard + this.dashboard = { + positions, + widgets: widgetIndex, + }; + } +} + +const widgetConfig: IWidget = { + id: "widget1", + type: "kpi", + pizzagna: { + [PizzagnaLayer.Configuration]: { + [DEFAULT_PIZZAGNA_ROOT]: { + providers: { + [WellKnownProviders.Refresher]: { + properties: { + // Configuring the refresher interval so that our data source is invoked every ten minutes + interval: 60 * 10, + enabled: true, + } as IRefresherProperties, + } as Partial, + }, + }, + header: { + properties: { + title: "Harry Potter and the Sorcerer's Stone", + subtitle: "By J. K. Rowling", + }, + }, + tiles: { + properties: { + nodes: ["kpi1"], + }, + }, + kpi1: { + id: "kpi1", + componentType: KpiComponent.lateLoadKey, + properties: { + widgetData: { + units: "out of 5 Stars", + label: "Average Rating", + }, + }, + providers: { + [WellKnownProviders.DataSource]: { + // Setting the data source providerId for the tile with id "kpi1" + providerId: AcmeKpiDataSource.providerId, + properties: { + bookId: "5MQFrgEACAAJ", + metric: "averageRating", + }, + } as IProviderConfiguration, + [WellKnownProviders.Adapter]: { + providerId: NOVA_KPI_DATASOURCE_ADAPTER, + properties: { + componentId: "kpi1", + propertyPath: "widgetData", + }, + } as IProviderConfiguration, + }, + }, + }, + }, +}; +\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\`, + "tutorials/customization/formatter/custom-formatter.module.ts": \\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\`// © 2022 SolarWinds Worldwide, LLC. All rights reserved. +// +// Permission is hereby granted, free of charge, to any person obtaining a copy +// of this software and associated documentation files (the "Software"), to +// deal in the Software without restriction, including without limitation the +// rights to use, copy, modify, merge, publish, distribute, sublicense, and/or +// sell copies of the Software, and to permit persons to whom the Software is +// furnished to do so, subject to the following conditions: +// +// The above copyright notice and this permission notice shall be included in +// all copies or substantial portions of the Software. +// +// THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR +// IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, +// FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE +// AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER +// LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, +// OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN +// THE SOFTWARE. + +import { NgModule } from "@angular/core"; +import { ReactiveFormsModule } from "@angular/forms"; +import { RouterModule, Routes } from "@angular/router"; + +// eslint-disable-next-line max-len +import { + NuiButtonModule, + NuiDocsModule, + NuiFormFieldModule, + NuiIconModule, + NuiMessageModule, + NuiSelectV2Module, + NuiSwitchModule, + NuiTextboxModule, + NuiValidationMessageModule, + DEMO_PATH_TOKEN, +} from "@nova-ui/bits"; +import { NuiDashboardsModule } from "@nova-ui/dashboards"; + +import { CustomDonutContentFormatterDocComponent } from "./donut-content-formatter-example/custom-donut-content-formatter-docs.component"; +import { + CustomDonutContentFormatterComponent, + CustomDonutContentFormatterConfiguratorComponent, + CustomDonutContentFormatterExampleComponent, +} from "./donut-content-formatter-example/custom-donut-content-formatter-example.component"; +import { CustomFormatterDocComponent } from "./formatter-example/custom-formatter-docs.component"; +import { + CustomFormatterComponent, + CustomFormatterConfiguratorComponent, + CustomFormatterExampleComponent, +} from "./formatter-example/custom-formatter-example.component"; +import { getDemoFiles } from "../../../../../demo-files-factory"; + +const routes: Routes = [ + { + path: "table-formatter", + component: CustomFormatterDocComponent, + data: { + srlc: { + hideIndicator: true, + }, + showThemeSwitcher: true, + }, + }, + { + path: "donut-content-formatter", + component: CustomDonutContentFormatterDocComponent, + data: { + srlc: { + hideIndicator: true, + }, + showThemeSwitcher: true, + }, + }, +]; + +@NgModule({ + imports: [ + RouterModule.forChild(routes), + NuiDocsModule, + NuiButtonModule, + NuiMessageModule, + NuiDashboardsModule, + NuiFormFieldModule, + NuiTextboxModule, + NuiSwitchModule, + NuiSelectV2Module, + NuiValidationMessageModule, + NuiIconModule, + ReactiveFormsModule, + ], + declarations: [ + CustomDonutContentFormatterComponent, + CustomDonutContentFormatterExampleComponent, + CustomDonutContentFormatterConfiguratorComponent, + CustomDonutContentFormatterDocComponent, + CustomFormatterDocComponent, + CustomFormatterExampleComponent, + CustomFormatterConfiguratorComponent, + CustomFormatterComponent, + ], + providers: [ + { + provide: DEMO_PATH_TOKEN, + useValue: getDemoFiles("formatter"), + }, + ], +}) +export default class CustomFormatterModuleRoute {} +\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\`, + "tutorials/customization/formatter/donut-content-formatter-example/custom-donut-content-formatter-docs.component.ts": \\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\`// © 2022 SolarWinds Worldwide, LLC. All rights reserved. +// +// Permission is hereby granted, free of charge, to any person obtaining a copy +// of this software and associated documentation files (the "Software"), to +// deal in the Software without restriction, including without limitation the +// rights to use, copy, modify, merge, publish, distribute, sublicense, and/or +// sell copies of the Software, and to permit persons to whom the Software is +// furnished to do so, subject to the following conditions: +// +// The above copyright notice and this permission notice shall be included in +// all copies or substantial portions of the Software. +// +// THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR +// IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, +// FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE +// AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER +// LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, +// OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN +// THE SOFTWARE. + +import { Component } from "@angular/core"; + +@Component({ + selector: "nui-custom-donut-content-formatter-docs", + templateUrl: "./custom-donut-content-formatter-docs.component.html", + standalone: false, +}) +export class CustomDonutContentFormatterDocComponent {} +\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\`, + "tutorials/customization/formatter/donut-content-formatter-example/custom-donut-content-formatter-example.component.ts": \\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\`// © 2022 SolarWinds Worldwide, LLC. All rights reserved. +// +// Permission is hereby granted, free of charge, to any person obtaining a copy +// of this software and associated documentation files (the "Software"), to +// deal in the Software without restriction, including without limitation the +// rights to use, copy, modify, merge, publish, distribute, sublicense, and/or +// sell copies of the Software, and to permit persons to whom the Software is +// furnished to do so, subject to the following conditions: +// +// The above copyright notice and this permission notice shall be included in +// all copies or substantial portions of the Software. +// +// THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR +// IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, +// FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE +// AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER +// LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, +// OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN +// THE SOFTWARE. + +import { HttpClient } from "@angular/common/http"; +import { + ChangeDetectorRef, + Component, + Injectable, + Input, + OnChanges, + OnDestroy, + OnInit, + SimpleChanges, +} from "@angular/core"; +import { FormBuilder, FormGroup, Validators } from "@angular/forms"; +import { GridsterConfig, GridsterItem } from "angular-gridster2"; +import { Subject } from "rxjs"; +import { takeUntil, tap } from "rxjs/operators"; + +import { + DataSourceService, + IconService, + IDataSource, + IFilteringOutputs, + LoggerService, +} from "@nova-ui/bits"; +import { + ChartAssist, + IAccessors, + IChartAssistEvent, + IChartAssistSeries, +} from "@nova-ui/charts"; +import { + ComponentRegistryService, + ConfiguratorHeadingService, + DATA_SOURCE, + DonutChartFormatterConfiguratorComponent, + DonutContentPercentageConfigurationComponent, + DonutContentPercentageFormatterComponent, + DonutContentSumFormatterComponent, + IDashboard, + IFormatterDefinition, + IHasChangeDetector, + IProperties, + IProportionalWidgetChartOptions, + IProportionalWidgetConfig, + IProviderConfiguration, + IWidget, + IWidgets, + LegendPlacement, + PizzagnaLayer, + ProportionalWidgetChartTypes, + ProviderRegistryService, + WellKnownPathKey, + WellKnownProviders, + WidgetTypesService, +} from "@nova-ui/dashboards"; + +export enum Units { + Days = "Day(s)", + Weeks = "Week(s)", + Hours = "Hour(s)", +} + +@Component({ + selector: "custom-donut-content-formatter", + host: { class: "d-flex flex-column align-items-center" }, + template: \\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\` +
+ + {{ chartMetric || properties?.currentMetric || data[0].id }} +
+
+ {{ chartContent }} +
+
+ {{ units }} +
+
\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\`, + styleUrls: ["./custom-donut-content-formatter-example.component.less"], + standalone: false, +}) +export class CustomDonutContentFormatterComponent + implements IHasChangeDetector, OnInit, OnChanges +{ + public static lateLoadKey = "CustomDonutContentFormatterComponent"; + + // Used to emphasize the chart series when user interacts either with the chart legend, or chart segments. + public emphasizedSeriesData: IChartAssistSeries | undefined; + + // Current raw value of the metric to display + public currentMetricData: number; + + // Metric value rendered inside the template, when user selects a metric, and gets automatically recalculated depending on selected units + public chartContent: number; + + // Metric value rendered inside the template, when user interacts with either chart legend, or chart segments + public chartMetric: number; + + // Units which user can select from the configuration + public units: Units = Units.Days; + + private readonly destroy$ = new Subject(); + + constructor(public changeDetector: ChangeDetectorRef) {} + + // The data we receive from the chart, including metrics names and their values + @Input() data: IChartAssistSeries[]; + + // We use this chart assist instance to subscribe to the events triggered when an interaction with the chart occurs + @Input() chartAssist: ChartAssist; + + // These are the current properties from pizzagna. Used to use data set at the configuration layer + @Input() properties: IProperties; + + public ngOnChanges(changes: SimpleChanges): void { + if (changes.properties || !this.properties) { + // If current metric is not in the list of metrics any more we fall back to the very first one from the list we get from the datasource + this.currentMetricData = + this.data.find( + (item) => item.id === this.properties?.currentMetric + )?.data[0] || this.data[0].data[0]; + + // We either take the selected value, or fall back to the preselected default one + this.units = this.properties?.units || this.units; + } + + this.setContentValue(); + } + + public ngOnInit(): void { + // Here 'chartAssistSubject' is the entity that emits events every time user interacts with either chart legend, or chart segments. + // Subscribing to properly react on these kind of events + this.chartAssist.chartAssistSubject + .pipe( + tap( + (data: IChartAssistEvent) => + (this.emphasizedSeriesData = this.data.find( + (item) => item.id === data.payload.seriesId + )) + ), + tap(() => this.setContentValue()), + tap(() => this.setMetricValue()), + takeUntil(this.destroy$) + ) + .subscribe(); + } + + public getConvertedData(emphData: number): number { + // Recalculating data depending on the units user selected from the configuration view + switch (this.units) { + case Units.Weeks: + return this.emphasizedSeriesData + ? this.convertToWeeks(emphData) + : this.convertToWeeks(this.currentMetricData); + + case Units.Hours: + return this.emphasizedSeriesData + ? this.convertToHours(emphData) + : this.convertToHours(this.currentMetricData); + + default: + return this.emphasizedSeriesData + ? emphData + : this.currentMetricData; + } + } + + public setContentValue(): void { + this.chartContent = this.getConvertedData( + this.emphasizedSeriesData?.data[0] + ); + } + + public setMetricValue(): void { + this.chartMetric = this.emphasizedSeriesData + ? this.data.find( + (item) => + this.getConvertedData(item.data[0]) === + this.getConvertedData(this.emphasizedSeriesData?.data[0]) + )?.id + : // if metric was not initially selected we fall back to the very first one + this.properties?.currentMetric || this.data[0].id; + } + + private convertToWeeks(days: number | undefined): number { + return days ? Number((days / 7).toFixed(2)) : 0; + } + + private convertToHours(days: number | undefined): number { + return days ? Number((days * 24).toFixed(2)) : 0; + } +} + +@Component({ + selector: "custom-donut-content-formatter-configurator", + styleUrls: ["./custom-donut-content-formatter-example.component.less"], + template: \\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\` +
+
+ + + + {{ itemValue?.name }} + + + + This field is required + + +
+
+ + + + {{ itemValue }} + + + + This field is required + + +
+
+ \\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\`, + standalone: false, +}) +export class CustomDonutContentFormatterConfiguratorComponent + extends DonutChartFormatterConfiguratorComponent + implements OnChanges, OnInit, IHasChangeDetector +{ + public static lateLoadKey = "CustomFormatterConfiguratorComponent"; + + constructor( + changeDetector: ChangeDetectorRef, + formBuilder: FormBuilder, + logger: LoggerService, + public iconService: IconService, + public configuratorHeading: ConfiguratorHeadingService + ) { + super(changeDetector, formBuilder, logger); + } + + public availableUnits: Units[] = [Units.Days, Units.Hours, Units.Weeks]; + + protected addCustomFormControls(form: FormGroup): void { + form.addControl( + "units", + this.formBuilder.control(Units.Days, Validators.required) + ); + } +} + +/** + * A component that instantiates the dashboard + */ +@Component({ + selector: "custom-donut-content-formatter-example", + templateUrl: "./custom-donut-content-formatter-example.component.html", + styleUrls: ["./custom-donut-content-formatter-example.component.less"], + standalone: false, +}) +export class CustomDonutContentFormatterExampleComponent implements OnInit { + public editMode: boolean = false; + // This variable will hold all the data needed to define the layout and behavior of the widgets. + // Pass this to the dashboard component's dashboard input in the template. + public dashboard: IDashboard | undefined; + + // Angular gridster requires a configuration object even if it's empty. + // Pass this to the dashboard component's gridsterConfig input in the template. + public gridsterConfig: GridsterConfig = {}; + + constructor( + // WidgetTypesService provides the widget's necessary structure information + private widgetTypesService: WidgetTypesService, + // In general, the ProviderRegistryService is used for making entities available for injection into dynamically loaded components. + private providerRegistry: ProviderRegistryService, + // Inject the ComponentRegistryService to make our custom component available for late loading by the dashboards framework + private componentRegistry: ComponentRegistryService, + private changeDetectorRef: ChangeDetectorRef + ) { + // Register the custom configurator component with the component registry to make it available + // for late loading by the dashboard framework. + this.componentRegistry.registerByLateLoadKey( + CustomDonutContentFormatterConfiguratorComponent + ); + // Register the custom formatter component with the component registry to make it available + // for late loading by the dashboard framework. + this.componentRegistry.registerByLateLoadKey( + CustomDonutContentFormatterComponent + ); + + // Grab the widget's default template which will be needed as a parameter for setNode below. + const proportional = this.widgetTypesService.getWidgetType( + "proportional", + 1 + ); + + const donutFormatters: IFormatterDefinition[] = [ + { + componentType: DonutContentSumFormatterComponent.lateLoadKey, + label: $localize\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\`Sum\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\`, + } as IFormatterDefinition, + { + componentType: + DonutContentPercentageFormatterComponent.lateLoadKey, + label: $localize\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\`Percentage\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\`, + configurationComponent: + DonutContentPercentageConfigurationComponent.lateLoadKey, + } as IFormatterDefinition, + { + componentType: CustomDonutContentFormatterComponent.lateLoadKey, + label: $localize\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\`Custom\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\`, + // This is a custom configurator that will pop up below the formatter once it gets selected + configurationComponent: + CustomDonutContentFormatterConfiguratorComponent.lateLoadKey, + } as IFormatterDefinition, + ]; + + this.widgetTypesService.setNode( + // This is the template we grabbed above with getWidgetType + proportional, + // We are setting the editor/configurator part of the widget template + "configurator", + // This indicates which node you are changing and we want to change the formatters available for selection in the editor. + WellKnownPathKey.Formatters, + // We are setting the available formatters with the array we created above. + donutFormatters + ); + + // This sets the donut chart's datasource to have the StatusesExampleDatasource so the drop down is filled similar to the line above. + this.widgetTypesService.setNode( + proportional, + "configurator", + WellKnownPathKey.DataSourceProviders, + [StatusesExampleDatasource.providerId] + ); + + // Registering the data source for injection into the widget. + this.providerRegistry.setProviders({ + [StatusesExampleDatasource.providerId]: { + provide: DATA_SOURCE, + useClass: StatusesExampleDatasource, + // Any dependencies that need to be injected into the provider must be listed here + deps: [], + }, + }); + } + + public ngOnInit(): void { + this.initializeDashboard(); + } + + /** Used for restoring widgets state */ + public reInitializeDashboard(): void { + // destroys the components and their providers so the dashboard can re init data + this.dashboard = undefined; + this.changeDetectorRef.detectChanges(); + + this.initializeDashboard(); + } + + public initializeDashboard(): void { + // We're using a static configuration object for this example, but this is where + // the widget's configuration could potentially be populated from a database + const proportionalWidget = widgetConfig; + const widgetIndex: IWidgets = { + // Enhance the widget with information coming from it's type definition + [proportionalWidget.id]: + this.widgetTypesService.mergeWithWidgetType(proportionalWidget), + }; + + // Setting the widget dimensions and position (this is for gridster) + const positions: Record = { + [proportionalWidget.id]: { + cols: 12, + rows: 6, + y: 0, + x: 0, + }, + }; + + // Finally, assigning the variables we created above to the dashboard + this.dashboard = { + positions, + widgets: widgetIndex, + }; + } +} + +export interface IStatusesWidgetData { + id: string; + name: string; + data: number[]; +} + +export const randomStatusesWidgetData: IStatusesWidgetData[] = [ + { + id: "Down", + name: "Down", + data: [Math.round(Math.random() * 100)], + }, + { + id: "Critical", + name: "Critical", + data: [Math.round(Math.random() * 100)], + }, + { + id: "Warning", + name: "Warning", + data: [Math.round(Math.random() * 100)], + }, + { + id: "Unknown", + name: "Unknown", + data: [Math.round(Math.random() * 100)], + }, + { + id: "Up", + name: "Up", + data: [Math.round(Math.random() * 100)], + }, + { + id: "Unmanaged", + name: "Unmanaged", + data: [Math.round(Math.random() * 100)], + }, +]; + +@Injectable() +export class StatusesExampleDatasource + extends DataSourceService + implements IDataSource, OnDestroy +{ + public static providerId = "StatusesExampleDatasource"; + + public busy = new Subject(); + + constructor(private http: HttpClient) { + super(); + } + + public async getFilteredData(): Promise { + this.busy.next(true); + + return new Promise((resolve) => { + setTimeout(() => { + resolve({ + result: randomStatusesWidgetData, + }); + this.busy.next(false); + }, 1000); + }); + } + + public ngOnDestroy(): void { + this.outputsSubject.complete(); + } +} + +export const widgetConfig: IWidget = { + id: "proportionalWidgetId", + type: "proportional", + pizzagna: { + [PizzagnaLayer.Configuration]: { + header: { + properties: { + title: "Proportional Widget!", + subtitle: "Proportional widget with legend formatters", + }, + }, + chart: { + providers: { + [WellKnownProviders.DataSource]: { + providerId: StatusesExampleDatasource.providerId, + } as IProviderConfiguration, + }, + properties: { + configuration: { + interactive: true, + chartOptions: { + type: ProportionalWidgetChartTypes.DonutChart, + legendPlacement: LegendPlacement.Right, + contentFormatter: { + componentType: + CustomDonutContentFormatterComponent.lateLoadKey, + properties: { + // here you can set the default value for the metric you receive. If not set the first one from the list will be taken + currentMetric: "Down", + // here you set the default value for your custom controls. If not set the first one from the list will be taken + units: Units.Weeks, + }, + }, + } as IProportionalWidgetChartOptions, + chartColors: [ + "var(--nui-color-chart-eight)", + "var(--nui-color-chart-nine)", + "var(--nui-color-chart-ten)", + ], + } as IProportionalWidgetConfig, + }, + }, + }, + }, +}; +\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\`, + "tutorials/customization/formatter/formatter-example/custom-formatter-docs.component.ts": \\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\`// © 2022 SolarWinds Worldwide, LLC. All rights reserved. +// +// Permission is hereby granted, free of charge, to any person obtaining a copy +// of this software and associated documentation files (the "Software"), to +// deal in the Software without restriction, including without limitation the +// rights to use, copy, modify, merge, publish, distribute, sublicense, and/or +// sell copies of the Software, and to permit persons to whom the Software is +// furnished to do so, subject to the following conditions: +// +// The above copyright notice and this permission notice shall be included in +// all copies or substantial portions of the Software. +// +// THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR +// IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, +// FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE +// AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER +// LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, +// OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN +// THE SOFTWARE. + +import { Component } from "@angular/core"; + +@Component({ + selector: "nui-custom-formatter-docs", + templateUrl: "./custom-formatter-docs.component.html", + standalone: false, +}) +export class CustomFormatterDocComponent {} +\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\`, + "tutorials/customization/formatter/formatter-example/custom-formatter-example.component.ts": \\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\`// © 2022 SolarWinds Worldwide, LLC. All rights reserved. +// +// Permission is hereby granted, free of charge, to any person obtaining a copy +// of this software and associated documentation files (the "Software"), to +// deal in the Software without restriction, including without limitation the +// rights to use, copy, modify, merge, publish, distribute, sublicense, and/or +// sell copies of the Software, and to permit persons to whom the Software is +// furnished to do so, subject to the following conditions: +// +// The above copyright notice and this permission notice shall be included in +// all copies or substantial portions of the Software. +// +// THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR +// IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, +// FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE +// AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER +// LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, +// OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN +// THE SOFTWARE. + +import { ListRange } from "@angular/cdk/collections"; +import { ChangeDetectorRef, Component, Input, OnInit } from "@angular/core"; +import { FormBuilder, FormGroup, Validators } from "@angular/forms"; +import { GridsterConfig, GridsterItem } from "angular-gridster2"; +import isEqual from "lodash/isEqual"; +import orderBy from "lodash/orderBy"; +import { BehaviorSubject } from "rxjs"; + +import { + DataSourceService, + IconService, + IDataField, + INovaFilteringOutputs, + INovaFilters, + ISorterFilter, + LoggerService, +} from "@nova-ui/bits"; +import { + ComponentRegistryService, + ConfiguratorHeadingService, + DATA_SOURCE, + FormatterConfiguratorComponent, + IDashboard, + IDataSourceOutput, + IFormatterDefinition, + IHasChangeDetector, + ITableWidgetColumnConfig, + ITableWidgetSorterConfig, + IWidget, + IWidgets, + PizzagnaLayer, + ProviderRegistryService, + RawFormatterComponent, + TableFormatterRegistryService, + WellKnownPathKey, + WellKnownProviders, + WidgetTypesService, +} from "@nova-ui/dashboards"; + +export const BREW_API_URL = "https://api.punkapi.com/v2/beers"; + +@Component({ + selector: "custom-formatter", + host: { class: "d-flex" }, + template: \\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\` +
+
+ +
+
+ {{ data.value }} +
+
+ \\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\`, + styleUrls: ["./custom-formatter-example.component.less"], + standalone: false, +}) +export class CustomFormatterComponent implements IHasChangeDetector { + public static lateLoadKey = "CustomFormatterComponent"; + + constructor(public changeDetector: ChangeDetectorRef) {} + + @Input() public data: any; + @Input() public icon: string; + @Input() public threshold: string; + + public isAboveThreshold(): boolean { + return parseFloat(this.threshold) <= this.data.value; + } +} + +@Component({ + selector: "custom-formatter-configurator", + styleUrls: ["./custom-formatter-example.component.less"], + template: \\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\` +
+
+ + + + {{ item.label }} + + + + This field is required + + +
+
+ + + + + + + + This field is required + + +
+
+ + + + + This field is required + + +
+
+ +
+
+ +
+ + +
+ + + Select Item + +
+ \\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\`, + standalone: false, +}) +export class CustomFormatterConfiguratorComponent + extends FormatterConfiguratorComponent + implements OnInit, IHasChangeDetector +{ + public static lateLoadKey = "CustomFormatterConfiguratorComponent"; + + constructor( + changeDetector: ChangeDetectorRef, + configuratorHeading: ConfiguratorHeadingService, + formBuilder: FormBuilder, + logger: LoggerService, + public iconService: IconService + ) { + super(changeDetector, configuratorHeading, formBuilder, logger); + } + + public formatterFormGroup: FormGroup; + // This array is where the icon names will be stored + public options: string[] = []; + + public ngOnInit(): void { + for (const icon of this.iconService.icons) { + if (icon.category === "severity") { + this.options.push(icon.name); + } + } + } + + protected addCustomFormControls(form: FormGroup): void { + form.addControl( + "icon", + this.formBuilder.control("", Validators.required) + ); + form.addControl( + "threshold", + this.formBuilder.control(null, Validators.required) + ); + } +} + +/** + * A component that instantiates the dashboard + */ +@Component({ + selector: "custom-formatter-example", + templateUrl: "./custom-formatter-example.component.html", + styleUrls: ["./custom-formatter-example.component.less"], + standalone: false, +}) +export class CustomFormatterExampleComponent implements OnInit { + public editMode: boolean = false; + // This variable will hold all the data needed to define the layout and behavior of the widgets. + // Pass this to the dashboard component's dashboard input in the template. + public dashboard: IDashboard | undefined; + + // Angular gridster requires a configuration object even if it's empty. + // Pass this to the dashboard component's gridsterConfig input in the template. + public gridsterConfig: GridsterConfig = {}; + + constructor( + // WidgetTypesService provides the widget's necessary structure information + private widgetTypesService: WidgetTypesService, + // In general, the ProviderRegistryService is used for making entities available for injection into dynamically loaded components. + private providerRegistry: ProviderRegistryService, + // Inject the ComponentRegistryService to make our custom component available for late loading by the dashboards framework + private componentRegistry: ComponentRegistryService, + private tableFormatterRegistryService: TableFormatterRegistryService, + private changeDetectorRef: ChangeDetectorRef + ) { + // Register the custom configurator component with the component registry to make it available + // for late loading by the dashboard framework. + this.componentRegistry.registerByLateLoadKey( + CustomFormatterConfiguratorComponent + ); + // Register the custom formatter component with the component registry to make it available + // for late loading by the dashboard framework. + this.componentRegistry.registerByLateLoadKey(CustomFormatterComponent); + + // Grab the widget's default template which will be needed as a parameter for setNode below. + const table = this.widgetTypesService.getWidgetType("table", 1); + + const tableFormatters: IFormatterDefinition[] = [ + { + // This will be the component that will format the data + componentType: RawFormatterComponent.lateLoadKey, + // This is the label for what the formatter is selected in the drop down + label: $localize\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\`:table formatter|:No formatter\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\`, + // This says what datatype the formatter supports. If the value node is null, it accepts any data type. + dataTypes: { + // @ts-ignore: Ignoring compiler error to keep the same flow + value: null, + }, + }, + { + componentType: CustomFormatterComponent.lateLoadKey, + label: $localize\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\`:table formatter|:Custom formatter\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\`, + // This is a custom configurator that will pop up below the formatter once it gets selected + configurationComponent: + CustomFormatterConfiguratorComponent.lateLoadKey, + // This says what data types the formatter supports. + // In this case, it supports abv values only. + // If you look below in the table data source you'll see where we define our column's data types. + dataTypes: { + value: ["abv"], + }, + }, + ]; + + // Registering the formatters + this.tableFormatterRegistryService.addItems(tableFormatters); + + // This sets the table's datasource to have the BeerDataSource so the drop down is filled similar to the line above. + this.widgetTypesService.setNode( + table, + "configurator", + WellKnownPathKey.DataSourceProviders, + [BeerDataSource.providerId] + ); + + // Registering the data source for injection into the widget. + this.providerRegistry.setProviders({ + [BeerDataSource.providerId]: { + provide: DATA_SOURCE, + useClass: BeerDataSource, + // Any dependencies that need to be injected into the provider must be listed here + deps: [], + }, + }); + } + + public ngOnInit(): void { + this.initializeDashboard(); + } + + /** Used for restoring widgets state */ + public reInitializeDashboard(): void { + // destroys the components and their providers so the dashboard can re init data + this.dashboard = undefined; + this.changeDetectorRef.detectChanges(); + + this.initializeDashboard(); + } + + public initializeDashboard(): void { + // We're using a static configuration object for this example, but this is where + // the widget's configuration could potentially be populated from a database + const tableWidget = widgetConfig; + const widgetIndex: IWidgets = { + // Enhance the widget with information coming from it's type definition + [tableWidget.id]: + this.widgetTypesService.mergeWithWidgetType(tableWidget), + }; + + // Setting the widget dimensions and position (this is for gridster) + const positions: Record = { + [tableWidget.id]: { + cols: 12, + rows: 6, + y: 0, + x: 0, + }, + }; + + // Finally, assigning the variables we created above to the dashboard + this.dashboard = { + positions, + widgets: widgetIndex, + }; + } +} + +export interface IBrewInfo { + id: number; + name: string; + tagline: string; + first_brewed: string; + description: string; + brewers_tips: string; + abv: number; +} + +export interface IBrewDatasourceResponse { + brewInfo: IBrewInfo[]; + total: number; +} + +export class BeerDataSource extends DataSourceService { + public static providerId = "BeerDataSource"; + + private cache = Array.from({ length: 0 }); + private lastSortValue?: ISorterFilter; + private lastVirtualScroll?: ListRange; + // For simplicity, the totalItems value is hard-coded here, but in a real-world scenario the value would likely be retrieved via an async backend call + private totalItems: number = 325; + + public page: number = 1; + public busy = new BehaviorSubject(false); + + public dataFields: Array = [ + { id: "id", label: "No", dataType: "number" }, + { id: "name", label: "Name", dataType: "string" }, + { id: "tagline", label: "Tagline", dataType: "string" }, + { id: "first_brewed", label: "First Brewed", dataType: "string" }, + { id: "description", label: "Description", dataType: "string" }, + { id: "brewers_tips", label: "Brewer's Tips", dataType: "string" }, + // We are giving this field a custom data type of 'abv' so the dropdown in the custom formatter configurator can use it to filter out other data types + { id: "abv", label: "Alcohol By Volume", dataType: "abv" }, + ]; + + constructor(private logger: LoggerService) { + super(); + } + + public async getFilteredData( + filters: INovaFilters + ): Promise> { + const start = filters.virtualScroll?.value?.start ?? 0; + const end = filters.virtualScroll?.value?.end ?? 0; + const delta = end - start; + + // Note: We should start with a clean cache every time first page is requested + if (start === 0) { + this.cache = []; + } + + // This condition handles sorting. We want to sort columns without fetching another chunk of data. + // Since the data is being fetched when scrolled, we compare virtual scroll indexes here in the condition as well. + if (filters.sorter?.value) { + if ( + !isEqual(this.lastSortValue, filters.sorter.value) && + filters.virtualScroll?.value.start === 0 && + !!this.lastVirtualScroll + ) { + const totalPages = Math.ceil( + delta ? this.totalItems / delta : 1 + ); + const itemsPerPage: number = Math.max( + delta < 80 ? delta : 80, + 1 + ); + let response: Array | null = null; + let map: IBrewDatasourceResponse; + + if (filters.sorter?.value?.direction === "desc") { + this.cache = []; + for (let i = 0; i < this.page; ++i) { + response = await ( + await fetch( + \\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\`\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\${BREW_API_URL}/?page=\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\${ + totalPages - i || 1 + }&per_page=\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\${itemsPerPage}\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\` + ) + ).json(); + + // since the last page contains only 5 items we need to fetch another page to give virtual scroll enough space to work + if (response && response.length < itemsPerPage) { + this.page++; + } + map = { + brewInfo: response?.map((result: IBrewInfo) => ({ + id: result.id, + name: result.name, + tagline: result.tagline, + first_brewed: result.first_brewed, + description: result.description, + brewers_tips: result.brewers_tips, + })), + total: response?.length, + } as IBrewDatasourceResponse; + this.cache = + totalPages - i !== 0 + ? this.cache.concat(map.brewInfo) + : this.cache; + } + } + + if (filters.sorter?.value?.direction === "asc") { + this.cache = []; + for (let i = 0; i < this.page; i++) { + response = await ( + await fetch( + \\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\`\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\${BREW_API_URL}/?page=\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\${ + i + 1 + }&per_page=\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\${itemsPerPage}\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\` + ) + ).json(); + map = { + brewInfo: response?.map((result: IBrewInfo) => ({ + id: result.id, + name: result.name, + tagline: result.tagline, + first_brewed: result.first_brewed, + description: result.description, + brewers_tips: result.brewers_tips, + })), + total: response?.length, + } as IBrewDatasourceResponse; + this.cache = this.cache.concat(map.brewInfo); + } + } + + this.lastSortValue = filters.sorter?.value; + this.lastVirtualScroll = filters.virtualScroll?.value; + + return { + result: { + repeat: { + itemsSource: this.sortData(this.cache, filters), + }, + paginator: { total: this.totalItems }, + dataFields: this.dataFields, + }, + }; + } + } + + this.busy.next(true); + return new Promise((resolve) => { + setTimeout(() => { + this.getData(start, end, filters).then( + (response: INovaFilteringOutputs) => { + if (!response) { + return; + } + + this.cache = this.cache.concat(response.brewInfo); + + this.dataSubject.next(this.cache); + resolve({ + result: { + repeat: { + itemsSource: this.sortData( + this.cache, + filters + ), + }, + paginator: { total: this.totalItems }, + dataFields: this.dataFields, + }, + }); + + this.lastSortValue = filters.sorter?.value; + this.lastVirtualScroll = filters.virtualScroll?.value; + this.busy.next(false); + } + ); + }, 500); + }); + } + + public async getData( + start: number = 0, + end: number = 20, + filters: INovaFilters + ): Promise { + const delta = end - start; + const totalPages = Math.ceil(delta ? this.totalItems / delta : 1); + let response: Array | null = null; + // The api.punk.com is able to return only 80 items per page + const itemsPerPage: number = Math.max(delta < 80 ? delta : 80, 1); + + if (filters.sorter?.value?.direction === "asc") { + response = await ( + await fetch( + \\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\`\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\${BREW_API_URL}/?page=\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\${this.page}&per_page=\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\${itemsPerPage}\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\` + ) + ).json(); + } + + if (filters.sorter?.value?.direction === "desc") { + response = await ( + await fetch( + \\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\`\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\${BREW_API_URL}/?page=\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\${ + totalPages - this.page + }&per_page=\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\${itemsPerPage}\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\` + ) + ).json(); + } + + if (!filters.sorter) { + response = await ( + await fetch( + \\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\`\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\${BREW_API_URL}/?page=\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\${this.page}&per_page=\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\${itemsPerPage}\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\` + ) + ).json(); + } + return { + brewInfo: response?.map((result: IBrewInfo, i: number) => ({ + id: result.id, + abv: result.abv, + name: result.name, + tagline: result.tagline, + first_brewed: result.first_brewed, + description: result.description, + brewers_tips: result.brewers_tips, + })), + total: response?.length, + } as IBrewDatasourceResponse; + } + + private sortData(data: IBrewInfo[], filters: INovaFilters) { + return orderBy( + data, + filters.sorter?.value?.sortBy, + filters.sorter?.value?.direction as "desc" | "asc" + ); + } +} + +export const widgetConfig: IWidget = { + id: "tableWidgetId", + type: "table", + pizzagna: { + [PizzagnaLayer.Configuration]: { + header: { + properties: { + title: "Stupendous Suds", + subtitle: "Try These Brilliant Brews", + }, + }, + table: { + providers: { + [WellKnownProviders.DataSource]: { + providerId: BeerDataSource.providerId, + }, + }, + properties: { + configuration: { + columns: [ + { + id: "column1", + label: "Beer Name", + isActive: true, + width: 185, + formatter: { + componentType: + RawFormatterComponent.lateLoadKey, + properties: { + dataFieldIds: { + value: "name", + }, + }, + }, + }, + { + id: "column2", + label: "Tagline", + isActive: true, + width: 250, + formatter: { + componentType: + RawFormatterComponent.lateLoadKey, + properties: { + dataFieldIds: { + value: "tagline", + }, + }, + }, + }, + { + id: "column3", + label: "Alcohol By Volume", + isActive: true, + width: 150, + formatter: { + componentType: + CustomFormatterComponent.lateLoadKey, + properties: { + dataFieldIds: { + value: "abv", + }, + icon: "severity_error", + threshold: "5", + }, + }, + }, + { + id: "column4", + label: "Description", + isActive: true, + formatter: { + componentType: + RawFormatterComponent.lateLoadKey, + properties: { + dataFieldIds: { + value: "description", + }, + }, + }, + }, + ] as ITableWidgetColumnConfig[], + sorterConfiguration: { + descendantSorting: false, + sortBy: "", + } as ITableWidgetSorterConfig, + hasVirtualScroll: true, + }, + }, + }, + }, + }, +}; +\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\`, + "tutorials/customization/widget/custom-widget-docs.component.ts": \\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\`// © 2022 SolarWinds Worldwide, LLC. All rights reserved. +// +// Permission is hereby granted, free of charge, to any person obtaining a copy +// of this software and associated documentation files (the "Software"), to +// deal in the Software without restriction, including without limitation the +// rights to use, copy, modify, merge, publish, distribute, sublicense, and/or +// sell copies of the Software, and to permit persons to whom the Software is +// furnished to do so, subject to the following conditions: +// +// The above copyright notice and this permission notice shall be included in +// all copies or substantial portions of the Software. +// +// THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR +// IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, +// FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE +// AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER +// LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, +// OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN +// THE SOFTWARE. + +import { Component } from "@angular/core"; + +@Component({ + selector: "custom-widget-docs", + templateUrl: "./custom-widget-docs.component.html", + standalone: false, +}) +export class CustomWidgetDocsComponent {} +\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\`, + "tutorials/customization/widget/custom-widget.component.ts": \\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\`// © 2022 SolarWinds Worldwide, LLC. All rights reserved. +// +// Permission is hereby granted, free of charge, to any person obtaining a copy +// of this software and associated documentation files (the "Software"), to +// deal in the Software without restriction, including without limitation the +// rights to use, copy, modify, merge, publish, distribute, sublicense, and/or +// sell copies of the Software, and to permit persons to whom the Software is +// furnished to do so, subject to the following conditions: +// +// The above copyright notice and this permission notice shall be included in +// all copies or substantial portions of the Software. +// +// THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR +// IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, +// FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE +// AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER +// LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, +// OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN +// THE SOFTWARE. + +import { + ChangeDetectionStrategy, + ChangeDetectorRef, + Component, + EventEmitter, + HostBinding, + Input, + OnChanges, + OnInit, + Output, + SimpleChanges, +} from "@angular/core"; +import { FormBuilder, FormGroup, Validators } from "@angular/forms"; +import { GridsterConfig, GridsterItem } from "angular-gridster2"; + +import { IMenuItem } from "@nova-ui/bits"; +import { + ComponentRegistryService, + ConfiguratorHeadingService, + DEFAULT_PIZZAGNA_ROOT, + EVENT_PROXY, + FormStackComponent, + IConverterFormPartsProperties, + IDashboard, + IHasChangeDetector, + IHasForm, + IProviderConfiguration, + IWidget, + IWidgets, + IWidgetTypeDefinition, + NOVA_GENERIC_CONVERTER, + NOVA_TITLE_AND_DESCRIPTION_CONVERTER, + PizzagnaLayer, + refresher, + StackComponent, + TitleAndDescriptionConfigurationComponent, + WellKnownPathKey, + WellKnownProviders, + widgetBodyContentNodes, + WidgetConfiguratorSectionComponent, + WidgetTypesService, + WIDGET_BODY, + WIDGET_HEADER, + WIDGET_LOADING, +} from "@nova-ui/dashboards"; + +// The custom widget type name we'll use +const CUSTOM_WIDGET_TYPENAME = "example-custom-widget"; +// The path key we'll use for image selection in the configurator definition +const IMAGE_SELECTION_CONFIGURATOR_PATH_KEY = "imageSelection"; + +@Component({ + selector: "custom-widget-body", + // A simple template for our custom widget + template: \\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\`\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\`, + styleUrls: ["./custom-widget.component.less"], + changeDetection: ChangeDetectionStrategy.OnPush, + standalone: false, +}) +// Remember to declare this class in the parent module +export class CustomWidgetBodyContentComponent implements IHasChangeDetector { + // Ensure that the lateLoadKey value matches class name + public static lateLoadKey = "CustomWidgetBodyContentComponent"; + + // Optionally, providing an input for styling of the host element + @Input() @HostBinding("class") public elementClass = ""; + + // We'll map this input with the configurator form using the NOVA_GENERIC_CONVERTER. + // See the customWidget definition at the bottom of the file. + @Input() public imageSource: string; + + // Injecting the ChangeDetectorRef to implement IHasChangeDetector. + // This allows the dashboard framework to reliably propagate component property changes to the DOM. + constructor(public changeDetector: ChangeDetectorRef) {} +} + +/** + * A custom configurator section component for selecting the image source for the custom widget + */ +@Component({ + selector: "custom-configurator-section", + template: \\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\` + + + + +
+ + +
+ Image Selection +
+ {{ imageDisplayValue }} +
+
+
+
+ + + + + {{ item.title }} + + + +
+
+ \\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\`, + styleUrls: ["./custom-widget.component.less"], + changeDetection: ChangeDetectionStrategy.OnPush, + standalone: false, +}) +// Remember to declare this class in the parent module +export class CustomConfiguratorSectionComponent + implements OnInit, OnChanges, IHasChangeDetector, IHasForm +{ + // Ensure that the lateLoadKey value matches the class name + public static lateLoadKey = "CustomConfiguratorSectionComponent"; + + /** + * This input serves as the itemsSource a user can select an image from. + */ + @Input() imageItems: IMenuItem[] = []; + /** + * This property holds the currently selected image source string. + */ + @Input() imageSource: string; + + /** + * An output for emitting formReady to allow the immediate parent formGroup component to register us as a form control + * in the larger form. In this case, the immediate parent would be the WidgetConfiguratorSectionComponent as specified + * in the customWidget configurator definition at the bottom of this file. + */ + @Output() formReady = new EventEmitter(); + + public form: FormGroup; + public imageDisplayValue: string; + + constructor( + public changeDetector: ChangeDetectorRef, + private formBuilder: FormBuilder, + public configuratorHeading: ConfiguratorHeadingService + ) {} + + public ngOnInit(): void { + // Initializing the form + this.form = this.formBuilder.group({ + // Note: When using the NOVA_GENERIC_CONVERTER, the form control name, in this case 'imageSource', must match the input name on + // this component as well as that of the corresponding property on the custom widget body component. + imageSource: [{}, [Validators.required]], + }); + + // Emitting the formReady as described above. + this.formReady.emit(this.form); + } + + public ngOnChanges(changes: SimpleChanges): void { + if (changes.imageSource && !changes.imageSource.isFirstChange()) { + const previousValue: string = changes.imageSource.previousValue; + if (previousValue !== this.imageSource) { + // Setting the display value according to the current imageSource value + this.imageDisplayValue = this.imageItems.find( + (item: IMenuItem) => item.url === this.imageSource + )?.title; + + // Updating the form when the imageSource input gets updated + this.form.get("imageSource")?.setValue(this.imageSource); + } + } + } + + public onChanged(newValue: string): void { + // Keeping the display value updated as the user changes the dropdown selection + this.imageDisplayValue = this.imageItems.find( + (item: IMenuItem) => item.url === newValue + )?.title; + } +} + +/** + * The component that instantiates the dashboard + */ +@Component({ + selector: "custom-widget", + templateUrl: "./custom-widget.component.html", + styleUrls: ["./custom-widget.component.less"], + standalone: false, +}) +export class CustomWidgetComponent implements OnInit { + // This variable will hold all the data needed to define the layout and behavior of the widgets. + // Pass this to the dashboard component's dashboard input in the template. + public dashboard: IDashboard | undefined; + + // Angular gridster requires a configuration object even if it's empty. + // Pass this to the dashboard component's gridsterConfig input in the template. + public gridsterConfig: GridsterConfig = {}; + + // Boolean which dashboard takes in as an input if its true it allows you to move widgets around. + public editMode: boolean = false; + + constructor( + // WidgetTypesService provides the widget's necessary structure information + private widgetTypesService: WidgetTypesService, + + // Inject the ComponentRegistryService to make our custom component available for late loading by the dashboards framework + private componentRegistry: ComponentRegistryService, + private changeDetectorRef: ChangeDetectorRef + ) {} + + public ngOnInit(): void { + // Register the custom widget type and custom components + // Note: This could also be done in the parent module's constructor so that + // multiple dashboards could have access to the same registrations. + this.prepareNovaDashboards(); + + // Register some image items as dropdown options in the widget editor/configurator + // Note: This could also be done in the parent module's constructor so that + // multiple dashboards could have access to the same dropdown options. + this.registerImageOptions(); + + // Initialize our current instance of a dashboard with an instance of our custom widget + this.initializeDashboard(); + } + + /** Used for restoring widgets state */ + public reInitializeDashboard(): void { + // destroys the components and their providers so the dashboard can re init data + this.dashboard = undefined; + this.changeDetectorRef.detectChanges(); + + this.initializeDashboard(); + } + + public initializeDashboard(): void { + // We're using a static configuration object for this example (see widgetConfig at the bottom of the file), + // but this is where the widget's configuration could potentially be populated from a database + const widget = widgetConfig; + + // Create an index of widgets complete with structure and configuration to assign to the dashboard + const widgets: IWidgets = { + // Complete the custom widget with structure information coming from its type definition + [widget.id]: this.widgetTypesService.mergeWithWidgetType(widget), + }; + + // Setting the widget dimensions and position (this is for gridster) + const positions: Record = { + [widget.id]: { + cols: 4, + rows: 11, + y: 0, + x: 0, + }, + }; + + // Finally, assigning the variables we created above to the dashboard + this.dashboard = { positions, widgets }; + } + + private prepareNovaDashboards() { + // Register the custom widget type + this.widgetTypesService.registerWidgetType( + CUSTOM_WIDGET_TYPENAME, + 1, + customWidget + ); + + // Register the custom widget body component with the component registry to make it available + // for late loading by the dashboard framework. + this.componentRegistry.registerByLateLoadKey( + CustomWidgetBodyContentComponent + ); + + // Register the custom configurator section with the component registry to make it available + // for late loading by the dashboard framework. + this.componentRegistry.registerByLateLoadKey( + CustomConfiguratorSectionComponent + ); + } + + private registerImageOptions() { + // Grab the widget's default template which will be needed as a parameter for setNode below. + const widgetTemplate = this.widgetTypesService.getWidgetType( + CUSTOM_WIDGET_TYPENAME, + 1 + ); + + // Register some image items as dropdown options in the widget editor/configurator + this.widgetTypesService.setNode( + // This is the template we grabbed above with getWidgetType + widgetTemplate, + // We are setting the editor/configurator part of the widget template + "configurator", + // This indicates which node you are changing and we want to change the image items available for selection in the editor. + // For reference, see the 'paths' property of the custom widget's IWidgetTypeDefinition at the bottom of this file. + IMAGE_SELECTION_CONFIGURATOR_PATH_KEY, + // We are setting the image items available for selection in the editor. 'imageItems' is defined + // at the bottom of this file. + imageItems + ); + } +} + +/*************************************************************************************************** + * This is the type definition of our custom widget + ***************************************************************************************************/ +const customWidget: IWidgetTypeDefinition = { + /*************************************************************************************************** + * Paths to important settings in this type definition + ***************************************************************************************************/ + paths: { + widget: { + [WellKnownPathKey.Root]: DEFAULT_PIZZAGNA_ROOT, + }, + configurator: { + [WellKnownPathKey.Root]: DEFAULT_PIZZAGNA_ROOT, + // for the custom configuration component, this is the path for the list of image items available for selection + [IMAGE_SELECTION_CONFIGURATOR_PATH_KEY]: + "imageSelection.properties.imageItems", + }, + }, + /*************************************************************************************************** + * Widget section describes the structural part of the custom widget + ***************************************************************************************************/ + widget: { + [PizzagnaLayer.Structure]: { + [DEFAULT_PIZZAGNA_ROOT]: { + id: DEFAULT_PIZZAGNA_ROOT, + // base layout of the widget - all components referenced herein will be stacked in a column + componentType: StackComponent.lateLoadKey, + providers: { + // When enabled, this provider emits the REFRESH event on the pizzagna event bus every X seconds + [WellKnownProviders.Refresher]: refresher(), + // event proxy manages the transmission of events between widget and dashboard such as the WIDGET_EDIT and WIDGET_REMOVE events + [WellKnownProviders.EventProxy]: EVENT_PROXY, + }, + properties: { + // these values reference child components in the widget structure defined below + nodes: ["header", "loading", "body"], + }, + }, + // standard widget header + header: WIDGET_HEADER, + // this is the loading bar below the header + loading: WIDGET_LOADING, + // the body node + body: WIDGET_BODY, + + // retrieving the definitions for the body content nodes. the argument corresponds to the main content node key + ...widgetBodyContentNodes("mainContent"), + + // the component that supplies the content of our custom widget + mainContent: { + id: "mainContent", + componentType: CustomWidgetBodyContentComponent.lateLoadKey, + properties: { + elementClass: "d-flex w-100 justify-content-center", + }, + }, + }, + [PizzagnaLayer.Configuration]: { + [DEFAULT_PIZZAGNA_ROOT]: { + id: DEFAULT_PIZZAGNA_ROOT, + providers: { + // default refresher configuration + [WellKnownProviders.Refresher]: refresher(false, 60), + }, + }, + // default header configuration + header: { + properties: { + title: $localize\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\`Empty Custom Widget\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\`, + }, + }, + }, + }, + /*************************************************************************************************** + * Configurator section describes the form that's used to configure the widget + ***************************************************************************************************/ + configurator: { + [PizzagnaLayer.Structure]: { + [DEFAULT_PIZZAGNA_ROOT]: { + id: DEFAULT_PIZZAGNA_ROOT, + // base layout of the configurator - all form components referenced herein will be stacked in a column + componentType: FormStackComponent.lateLoadKey, + properties: { + elementClass: + "flex-grow-1 overflow-auto nui-scroll-shadows", + // these values reference child components laid out in this form (defined below) + nodes: ["presentation", "customConfig"], + }, + }, + // /presentation + presentation: { + id: "presentation", + componentType: WidgetConfiguratorSectionComponent.lateLoadKey, + properties: { + headerText: $localize\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\`Presentation\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\`, + nodes: ["titleAndDescription"], + }, + }, + // /presentation/titleAndDescription + titleAndDescription: { + id: "titleAndDescription", + componentType: + TitleAndDescriptionConfigurationComponent.lateLoadKey, + providers: { + converter: { + providerId: NOVA_TITLE_AND_DESCRIPTION_CONVERTER, + } as IProviderConfiguration, + }, + }, + // /customConfig + customConfig: { + id: "customConfig", + componentType: WidgetConfiguratorSectionComponent.lateLoadKey, + properties: { + headerText: $localize\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\`Custom Widget Configuration\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\`, + nodes: ["imageSelection"], + }, + }, + // /customConfig/imageSelection + imageSelection: { + id: "imageSelection", + // Here's where we set the configurator to use our custom configurator section + componentType: CustomConfiguratorSectionComponent.lateLoadKey, + properties: { + // This corresponds to the 'imageItems' input on the custom configurator section component + // which defines the list of image items to pick from. The empty value shown here is overridden + // in the 'registerImageOptions' method above. + imageItems: [] as IMenuItem[], + }, + providers: { + // Using the generic converter to map the selected image source between the widget and the form + [WellKnownProviders.Converter]: { + providerId: NOVA_GENERIC_CONVERTER, + properties: { + formParts: [ + { + // Setting up the generic converter to update the 'imageSource' property of the custom widget 'mainContent' component + previewPath: "mainContent.properties", + // Note: To use the NOVA_GENERIC_CONVERTER, the linked properties must have the same name between the configurator + // section component and the widget 'mainContent' component. Additionally, the property name must match the formControl + // name used in the configurator section component. In this case, the common name among all three is 'imageSource'. + keys: ["imageSource"], + }, + ] as IConverterFormPartsProperties[], + }, + } as IProviderConfiguration, + }, + }, + }, + }, +}; + +// For this example, we're using static items for the image selection dropdown. In a more realistic scenario, +// the items available for selection might come from a backend database. +const imageItems = [ + { + title: "Harry Potter Book Cover", + url: "https://imgc.allpostersimages.com/img/print/u-g-F8PQ9I0.jpg?w=550&h=550&p=0", + }, + { + title: "Harry Potter Movie Poster", + url: "https://images-na.ssl-images-amazon.com/images/I/81gpmMdKOHL._AC_SY741_.jpg", + }, +] as IMenuItem[]; + +// We're using a static configuration object for this example. In a more realistic scenario, +// a widget's configuration would likely be stored in a database. +const widgetConfig: IWidget = { + id: "widget1", + // This custom type is registered in the 'prepareNovaDashboards' method above. + type: CUSTOM_WIDGET_TYPENAME, + pizzagna: { + [PizzagnaLayer.Configuration]: { + header: { + // Setting the initial property values for the WidgetHeaderComponent + properties: { + title: "Harry Potter and the Sorcerer's Stone", + subtitle: "By J. K. Rowling", + }, + }, + mainContent: { + properties: { + // Setting the initial value for the 'imageSource' property on our custom widget body + imageSource: imageItems[0].url, + }, + }, + }, + }, +}; +\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\`, + "tutorials/customization/widget/custom-widget.module.ts": \\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\`// © 2022 SolarWinds Worldwide, LLC. All rights reserved. +// +// Permission is hereby granted, free of charge, to any person obtaining a copy +// of this software and associated documentation files (the "Software"), to +// deal in the Software without restriction, including without limitation the +// rights to use, copy, modify, merge, publish, distribute, sublicense, and/or +// sell copies of the Software, and to permit persons to whom the Software is +// furnished to do so, subject to the following conditions: +// +// The above copyright notice and this permission notice shall be included in +// all copies or substantial portions of the Software. +// +// THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR +// IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, +// FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE +// AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER +// LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, +// OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN +// THE SOFTWARE. + +import { CommonModule } from "@angular/common"; +import { HttpClientModule } from "@angular/common/http"; +import { NgModule } from "@angular/core"; +import { ReactiveFormsModule } from "@angular/forms"; +import { RouterModule } from "@angular/router"; + +import { + NuiButtonModule, + NuiDocsModule, + NuiFormFieldModule, + NuiIconModule, + NuiImageModule, + NuiMessageModule, + NuiSelectV2Module, + NuiSwitchModule, + DEMO_PATH_TOKEN, +} from "@nova-ui/bits"; +import { + NuiDashboardConfiguratorModule, + NuiDashboardsModule, +} from "@nova-ui/dashboards"; + +import { CustomWidgetDocsComponent } from "./custom-widget-docs.component"; +import { + CustomConfiguratorSectionComponent, + CustomWidgetBodyContentComponent, + CustomWidgetComponent, +} from "./custom-widget.component"; +import { getDemoFiles } from "../../../../../demo-files-factory"; + +const routes = [ + { + path: "", + component: CustomWidgetDocsComponent, + data: { + srlc: { + hideIndicator: true, + }, + showThemeSwitcher: true, + }, + }, + { + path: "example", + component: CustomWidgetComponent, + data: { + srlc: { + hideIndicator: true, + }, + }, + }, +]; + +@NgModule({ + imports: [ + CommonModule, + ReactiveFormsModule, + HttpClientModule, + NuiDashboardsModule, + NuiDashboardConfiguratorModule, + NuiDocsModule, + NuiFormFieldModule, + NuiIconModule, + NuiImageModule, + NuiMessageModule, + NuiSelectV2Module, + NuiSwitchModule, + NuiButtonModule, + RouterModule.forChild(routes), + ], + declarations: [ + CustomWidgetDocsComponent, + CustomConfiguratorSectionComponent, + CustomWidgetBodyContentComponent, + CustomWidgetComponent, + ], + providers: [ + { + provide: DEMO_PATH_TOKEN, + useValue: getDemoFiles("widget"), + }, + ], +}) +export default class CustomWidgetModule {} +\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\`, + "tutorials/data-source-setup/data-source-setup-docs.component.ts": \\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\`// © 2022 SolarWinds Worldwide, LLC. All rights reserved. +// +// Permission is hereby granted, free of charge, to any person obtaining a copy +// of this software and associated documentation files (the "Software"), to +// deal in the Software without restriction, including without limitation the +// rights to use, copy, modify, merge, publish, distribute, sublicense, and/or +// sell copies of the Software, and to permit persons to whom the Software is +// furnished to do so, subject to the following conditions: +// +// The above copyright notice and this permission notice shall be included in +// all copies or substantial portions of the Software. +// +// THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR +// IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, +// FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE +// AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER +// LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, +// OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN +// THE SOFTWARE. + +import { Component } from "@angular/core"; + +@Component({ + selector: "nui-dashboard-data-source-docs", + templateUrl: "./data-source-setup-docs.component.html", + standalone: false, +}) +export class DataSourceDocsComponent {} +\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\`, + "tutorials/data-source-setup/data-source-setup.component.ts": \\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\`// © 2022 SolarWinds Worldwide, LLC. All rights reserved. +// +// Permission is hereby granted, free of charge, to any person obtaining a copy +// of this software and associated documentation files (the "Software"), to +// deal in the Software without restriction, including without limitation the +// rights to use, copy, modify, merge, publish, distribute, sublicense, and/or +// sell copies of the Software, and to permit persons to whom the Software is +// furnished to do so, subject to the following conditions: +// +// The above copyright notice and this permission notice shall be included in +// all copies or substantial portions of the Software. +// +// THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR +// IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, +// FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE +// AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER +// LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, +// OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN +// THE SOFTWARE. + +import { HttpClient, HttpErrorResponse } from "@angular/common/http"; +import { Component, Injectable, OnDestroy, OnInit } from "@angular/core"; +import { GridsterConfig, GridsterItem } from "angular-gridster2"; +import { BehaviorSubject } from "rxjs"; +import { finalize } from "rxjs/operators"; + +import { DataSourceService, IFilteringOutputs } from "@nova-ui/bits"; +import { + DATA_SOURCE, + DEFAULT_PIZZAGNA_ROOT, + IDashboard, + IKpiData, + IProviderConfiguration, + IRefresherProperties, + IWidget, + IWidgets, + KpiComponent, + NOVA_KPI_DATASOURCE_ADAPTER, + PizzagnaLayer, + ProviderRegistryService, + WellKnownProviders, + WidgetTypesService, +} from "@nova-ui/dashboards"; + +/** + * A simple KPI data source to retrieve the average rating of Harry Potter and the Sorcerer's Stone (book) via googleapis + */ +@Injectable() +export class AverageRatingKpiDataSource + extends DataSourceService + implements OnDestroy +{ + // This is the ID we'll use to identify the provider + public static providerId = "AverageRatingKpiDataSource"; + + // Use this subject to communicate the data source's busy state + public busy = new BehaviorSubject(false); + + constructor(private http: HttpClient) { + super(); + } + + // In this example, getFilteredData is invoked every 10 minutes (Take a look at the refresher + // provider definition in the widget configuration below to see how the interval is set) + public async getFilteredData(): Promise { + this.busy.next(true); + return new Promise((resolve) => { + // *** Make a resource request to an external API (if needed) + this.http + .get("https://www.googleapis.com/books/v1/volumes/5MQFrgEACAAJ") + .pipe(finalize(() => this.busy.next(false))) + .subscribe({ + next: (data: any) => { + resolve({ + result: { + value: data.volumeInfo.averageRating, + }, + }); + }, + error: (error: HttpErrorResponse) => { + resolve({ + result: null, + error: { + type: error.status, + }, + }); + }, + }); + }); + } + + public ngOnDestroy(): void { + this.outputsSubject.complete(); + } +} + +/** + * A component that instantiates the dashboard + */ +@Component({ + selector: "data-source-setup", + templateUrl: "./data-source-setup.component.html", + styleUrls: ["./data-source-setup.component.less"], + standalone: false, +}) +export class DataSourceSetupComponent implements OnInit { + // This variable will hold all the data needed to define the layout and behavior of the widgets. + // Pass this to the dashboard component's dashboard input in the template. + public dashboard: IDashboard; + + // Angular gridster requires a configuration object even if it's empty. + // Pass this to the dashboard component's gridsterConfig input in the template. + public gridsterConfig: GridsterConfig = {}; + + constructor( + // WidgetTypesService provides the widget's necessary structure information + private widgetTypesService: WidgetTypesService, + + // In general, the ProviderRegistryService is used for making entities available for injection into dynamically loaded components. + private providerRegistry: ProviderRegistryService + ) {} + + public ngOnInit(): void { + // Registering the data source for injection into the KPI tile. + // Note: Each tile of a KPI widget is assigned its own instance of the data source + this.providerRegistry.setProviders({ + [AverageRatingKpiDataSource.providerId]: { + provide: DATA_SOURCE, + useClass: AverageRatingKpiDataSource, + // Any dependencies that need to be injected into the provider must be listed here + deps: [HttpClient], + }, + }); + // We're using a static configuration object for this example, but this is where + // the widget's configuration could potentially be populated from a database + const kpiWidget = widgetConfig; + const widgetIndex: IWidgets = { + // Complete the KPI widget with information coming from its type definition + [kpiWidget.id]: + this.widgetTypesService.mergeWithWidgetType(kpiWidget), + }; + + // Setting the widget dimensions and position (this is for gridster) + const positions: Record = { + [kpiWidget.id]: { + cols: 4, + rows: 6, + y: 0, + x: 0, + }, + }; + + // Finally, assigning the variables we created above to the dashboard + this.dashboard = { + positions, + widgets: widgetIndex, + }; + } +} + +const widgetConfig: IWidget = { + id: "widget1", + type: "kpi", + pizzagna: { + [PizzagnaLayer.Configuration]: { + [DEFAULT_PIZZAGNA_ROOT]: { + providers: { + [WellKnownProviders.Refresher]: { + properties: { + // Configuring the refresher interval so that our data source is invoked every ten minutes + interval: 60 * 10, + enabled: true, + } as IRefresherProperties, + } as Partial, + }, + }, + header: { + properties: { + title: "Harry Potter and the Sorcerer's Stone", + subtitle: "By J. K. Rowling", + }, + }, + tiles: { + properties: { + nodes: ["kpi1"], + }, + }, + kpi1: { + id: "kpi1", + componentType: KpiComponent.lateLoadKey, + properties: { + widgetData: { + units: "out of 5 Stars", + label: "Average Rating", + }, + }, + providers: { + [WellKnownProviders.DataSource]: { + // Setting the data source providerId for the tile with id "kpi1" + providerId: AverageRatingKpiDataSource.providerId, + } as IProviderConfiguration, + [WellKnownProviders.Adapter]: { + providerId: NOVA_KPI_DATASOURCE_ADAPTER, + properties: { + componentId: "kpi1", + propertyPath: "widgetData", + }, + } as IProviderConfiguration, + }, + }, + }, + }, +}; +\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\`, + "tutorials/data-source-setup/data-source-setup.module.ts": \\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\`// © 2022 SolarWinds Worldwide, LLC. All rights reserved. +// +// Permission is hereby granted, free of charge, to any person obtaining a copy +// of this software and associated documentation files (the "Software"), to +// deal in the Software without restriction, including without limitation the +// rights to use, copy, modify, merge, publish, distribute, sublicense, and/or +// sell copies of the Software, and to permit persons to whom the Software is +// furnished to do so, subject to the following conditions: +// +// The above copyright notice and this permission notice shall be included in +// all copies or substantial portions of the Software. +// +// THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR +// IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, +// FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE +// AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER +// LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, +// OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN +// THE SOFTWARE. + +import { CommonModule } from "@angular/common"; +import { HttpClientModule } from "@angular/common/http"; +import { NgModule } from "@angular/core"; +import { RouterModule } from "@angular/router"; + +import { + NuiDocsModule, + NuiMessageModule, + DEMO_PATH_TOKEN, +} from "@nova-ui/bits"; +import { NuiDashboardsModule } from "@nova-ui/dashboards"; + +import { DataSourceDocsComponent } from "./data-source-setup-docs.component"; +import { DataSourceSetupComponent } from "./data-source-setup.component"; +import { getDemoFiles } from "../../../../demo-files-factory"; + +const routes = [ + { + path: "", + component: DataSourceDocsComponent, + data: { + srlc: { + hideIndicator: true, + }, + showThemeSwitcher: true, + }, + }, + { + path: "example", + component: DataSourceSetupComponent, + data: { + srlc: { + hideIndicator: true, + }, + }, + }, +]; + +@NgModule({ + imports: [ + CommonModule, + HttpClientModule, + NuiDashboardsModule, + NuiDocsModule, + NuiMessageModule, + RouterModule.forChild(routes), + ], + declarations: [DataSourceDocsComponent, DataSourceSetupComponent], + providers: [ + { + provide: DEMO_PATH_TOKEN, + useValue: getDemoFiles("data-source-setup"), + }, + ], +}) +export default class DataSourceSetupModule {} +\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\`, + "tutorials/dynamic-header-links/dynamic-header-links-docs.component.ts": \\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\`// © 2022 SolarWinds Worldwide, LLC. All rights reserved. +// +// Permission is hereby granted, free of charge, to any person obtaining a copy +// of this software and associated documentation files (the "Software"), to +// deal in the Software without restriction, including without limitation the +// rights to use, copy, modify, merge, publish, distribute, sublicense, and/or +// sell copies of the Software, and to permit persons to whom the Software is +// furnished to do so, subject to the following conditions: +// +// The above copyright notice and this permission notice shall be included in +// all copies or substantial portions of the Software. +// +// THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR +// IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, +// FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE +// AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER +// LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, +// OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN +// THE SOFTWARE. + +import { Component } from "@angular/core"; + +@Component({ + selector: "nui-dynamic-header-links-docs", + templateUrl: "./dynamic-header-links-docs.component.html", + standalone: false, +}) +export class DynamicHeaderLinksDocsComponent {} +\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\`, + "tutorials/dynamic-header-links/dynamic-header-links-docs.module.ts": \\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\`// © 2022 SolarWinds Worldwide, LLC. All rights reserved. +// +// Permission is hereby granted, free of charge, to any person obtaining a copy +// of this software and associated documentation files (the "Software"), to +// deal in the Software without restriction, including without limitation the +// rights to use, copy, modify, merge, publish, distribute, sublicense, and/or +// sell copies of the Software, and to permit persons to whom the Software is +// furnished to do so, subject to the following conditions: +// +// The above copyright notice and this permission notice shall be included in +// all copies or substantial portions of the Software. +// +// THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR +// IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, +// FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE +// AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER +// LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, +// OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN +// THE SOFTWARE. + +import { NgModule } from "@angular/core"; +import { RouterModule } from "@angular/router"; + +import { DynamicHeaderLinksDocsComponent } from "./dynamic-header-links-docs.component"; + +const routes = [ + { + path: "", + component: DynamicHeaderLinksDocsComponent, + data: { + srlc: { + hideIndicator: true, + }, + showThemeSwitcher: true, + }, + }, +]; + +@NgModule({ + imports: [RouterModule.forChild(routes)], + declarations: [DynamicHeaderLinksDocsComponent], +}) +export default class DynamicHeaderLinksDocsModule {} +\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\`, + "tutorials/hello-dashboards/hello-dashboards-docs.component.ts": \\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\`// © 2022 SolarWinds Worldwide, LLC. All rights reserved. +// +// Permission is hereby granted, free of charge, to any person obtaining a copy +// of this software and associated documentation files (the "Software"), to +// deal in the Software without restriction, including without limitation the +// rights to use, copy, modify, merge, publish, distribute, sublicense, and/or +// sell copies of the Software, and to permit persons to whom the Software is +// furnished to do so, subject to the following conditions: +// +// The above copyright notice and this permission notice shall be included in +// all copies or substantial portions of the Software. +// +// THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR +// IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, +// FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE +// AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER +// LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, +// OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN +// THE SOFTWARE. + +import { Component } from "@angular/core"; + +@Component({ + selector: "nui-dashboard-hello-dashboards-docs", + templateUrl: "./hello-dashboards-docs.component.html", + standalone: false, +}) +export class HelloDashboardsDocsComponent {} +\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\`, + "tutorials/hello-dashboards/hello-dashboards-example/hello-dashboards-example.component.ts": \\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\`// © 2022 SolarWinds Worldwide, LLC. All rights reserved. +// +// Permission is hereby granted, free of charge, to any person obtaining a copy +// of this software and associated documentation files (the "Software"), to +// deal in the Software without restriction, including without limitation the +// rights to use, copy, modify, merge, publish, distribute, sublicense, and/or +// sell copies of the Software, and to permit persons to whom the Software is +// furnished to do so, subject to the following conditions: +// +// The above copyright notice and this permission notice shall be included in +// all copies or substantial portions of the Software. +// +// THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR +// IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, +// FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE +// AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER +// LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, +// OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN +// THE SOFTWARE. + +import { Component, OnInit } from "@angular/core"; +import { GridsterConfig, GridsterItem } from "angular-gridster2"; + +import { + IDashboard, + IWidget, + IWidgets, + KpiComponent, + PizzagnaLayer, + WidgetTypesService, +} from "@nova-ui/dashboards"; + +/** + * A component that instantiates the dashboard + */ +@Component({ + selector: "hello-dashboards-example", + templateUrl: "./hello-dashboards-example.component.html", + styleUrls: ["./hello-dashboards-example.component.less"], + standalone: false, +}) +export class HelloDashboardsExampleComponent implements OnInit { + // This variable will have all the data needed to render the widgets widgets. + // Pass this to the dashboard component's dashboard input. + public dashboard: IDashboard; + // Angular gridster requires a configuration object even if its empty. + // Pass this to the dashboard component's gridsterConfig input. + public gridsterConfig: GridsterConfig = {}; + + // WidgetTypesService provides the widget's necessary structure information + constructor(private widgetTypesService: WidgetTypesService) {} + + public ngOnInit(): void { + // Here we are hard-coding the widget config for this example, but this is where you + // could potentially populate the widget's configuration from a database + const kpiWidget = widgetConfig; + const widgetIndex: IWidgets = { + // Complete the KPI widget with information coming from its type definition + [kpiWidget.id]: + this.widgetTypesService.mergeWithWidgetType(kpiWidget), + }; + // Setting widget position and dimensions (this is for gridster) + const positions: Record = { + [kpiWidget.id]: { + cols: 4, + rows: 6, + y: 0, + x: 0, + }, + }; + // Finally, assigning the variables we created above to the dashboard + this.dashboard = { + positions, + widgets: widgetIndex, + }; + } +} + +// In a real-world scenario, this configuration would typically be fetched from a database or at least live in another file +const widgetConfig: IWidget = { + id: "widget1", + type: "kpi", + pizzagna: { + [PizzagnaLayer.Configuration]: { + header: { + properties: { + title: "Hello, KPI Widget!", + subtitle: "A Venue for Meaningful Values", + }, + }, + tiles: { + properties: { + nodes: ["kpi1"], + }, + }, + kpi1: { + id: "kpi1", + componentType: KpiComponent.lateLoadKey, + properties: { + widgetData: { + id: "totalStorage", + value: 1, + label: "Total storage", + units: "TB", + }, + }, + }, + }, + }, +}; +\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\`, + "tutorials/hello-dashboards/hello-dashboards.module.ts": \\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\`// © 2022 SolarWinds Worldwide, LLC. All rights reserved. +// +// Permission is hereby granted, free of charge, to any person obtaining a copy +// of this software and associated documentation files (the "Software"), to +// deal in the Software without restriction, including without limitation the +// rights to use, copy, modify, merge, publish, distribute, sublicense, and/or +// sell copies of the Software, and to permit persons to whom the Software is +// furnished to do so, subject to the following conditions: +// +// The above copyright notice and this permission notice shall be included in +// all copies or substantial portions of the Software. +// +// THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR +// IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, +// FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE +// AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER +// LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, +// OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN +// THE SOFTWARE. + +import { CommonModule } from "@angular/common"; +import { NgModule } from "@angular/core"; +import { RouterModule } from "@angular/router"; + +import { + NuiDocsModule, + NuiMessageModule, + DEMO_PATH_TOKEN, +} from "@nova-ui/bits"; +import { NuiDashboardsModule } from "@nova-ui/dashboards"; + +import { HelloDashboardsDocsComponent } from "./hello-dashboards-docs.component"; +import { HelloDashboardsExampleComponent } from "./hello-dashboards-example/hello-dashboards-example.component"; +import { getDemoFiles } from "../../../../demo-files-factory"; + +const routes = [ + { + path: "", + component: HelloDashboardsDocsComponent, + data: { + srlc: { + hideIndicator: true, + }, + showThemeSwitcher: true, + }, + }, + { + path: "example", + component: HelloDashboardsExampleComponent, + data: { + srlc: { + hideIndicator: true, + }, + }, + }, +]; + +@NgModule({ + imports: [ + CommonModule, + NuiDashboardsModule, + NuiDocsModule, + NuiMessageModule, + RouterModule.forChild(routes), + ], + declarations: [ + HelloDashboardsDocsComponent, + HelloDashboardsExampleComponent, + ], + providers: [ + { + provide: DEMO_PATH_TOKEN, + useValue: getDemoFiles("hello-dashboards"), + }, + ], +}) +export default class HelloDashboardsModule {} +\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\`, + "tutorials/persistence-handler-setup/persistence-handler-setup-docs.component.ts": \\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\`// © 2022 SolarWinds Worldwide, LLC. All rights reserved. +// +// Permission is hereby granted, free of charge, to any person obtaining a copy +// of this software and associated documentation files (the "Software"), to +// deal in the Software without restriction, including without limitation the +// rights to use, copy, modify, merge, publish, distribute, sublicense, and/or +// sell copies of the Software, and to permit persons to whom the Software is +// furnished to do so, subject to the following conditions: +// +// The above copyright notice and this permission notice shall be included in +// all copies or substantial portions of the Software. +// +// THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR +// IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, +// FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE +// AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER +// LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, +// OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN +// THE SOFTWARE. + +import { Component } from "@angular/core"; + +@Component({ + selector: "nui-dashboard-persistence-handler-setup-docs", + templateUrl: "./persistence-handler-setup-docs.component.html", + standalone: false, +}) +export class PersistenceHandlerSetupDocsComponent {} +\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\`, + "tutorials/persistence-handler-setup/persistence-handler-setup.component.ts": \\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\`// © 2022 SolarWinds Worldwide, LLC. All rights reserved. +// +// Permission is hereby granted, free of charge, to any person obtaining a copy +// of this software and associated documentation files (the "Software"), to +// deal in the Software without restriction, including without limitation the +// rights to use, copy, modify, merge, publish, distribute, sublicense, and/or +// sell copies of the Software, and to permit persons to whom the Software is +// furnished to do so, subject to the following conditions: +// +// The above copyright notice and this permission notice shall be included in +// all copies or substantial portions of the Software. +// +// THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR +// IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, +// FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE +// AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER +// LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, +// OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN +// THE SOFTWARE. + +import { HttpClient, HttpErrorResponse } from "@angular/common/http"; +import { + ChangeDetectorRef, + Component, + Injectable, + OnDestroy, + OnInit, +} from "@angular/core"; +import { GridsterConfig, GridsterItem } from "angular-gridster2"; +import { BehaviorSubject, Observable, Subject } from "rxjs"; +import { finalize } from "rxjs/operators"; + +import { + DataSourceService, + IFilteringOutputs, + ToastService, + uuid, +} from "@nova-ui/bits"; +import { + DATA_SOURCE, + DEFAULT_PIZZAGNA_ROOT, + IDashboard, + IDashboardPersistenceHandler, + IKpiData, + IProviderConfiguration, + IRefresherProperties, + IWidget, + IWidgets, + KpiComponent, + NOVA_KPI_DATASOURCE_ADAPTER, + PizzagnaLayer, + ProviderRegistryService, + WellKnownPathKey, + WellKnownProviders, + WidgetTypesService, +} from "@nova-ui/dashboards"; + +/** + * A simple persistence handler that is tied to the widget editor directive + */ +@Injectable() +// The realizer of IDashboardPersistenceHandler may implement a trySubmit and/or a tryRemove method. +export class PersistenceHandler implements IDashboardPersistenceHandler { + // This variable is just to show how to handle error handling. + private persistenceSucceeded: boolean = true; + + // The example uses the toast service to demonstrate the + // invocation of each of the persistence handler callbacks + constructor(private toastService: ToastService) { + // toastService options to let it sit on the page for 2 seconds. + this.toastService.setConfig({ + timeOut: 2000, + }); + } + + // This method will be invoked anytime the widget editor form gets submitted. + public trySubmit = (widget: IWidget): Observable => { + // Since we are working asynchronously, we'll return a subject. So, after the submit attempt + // succeeds or fails, we can let the subscriber know the result. + const subject = new Subject(); + + if (!widget.id) { + // Creates an id if the widget has no id. + // (This step will make more sense in the context of the widget cloning tutorial + // in which we handle the persistence of a newly created widget.) + widget.id = uuid(); + } + + // For this example, we're using a setTimeout to mock an asynchronous persistence request to a backend + setTimeout(() => { + if (this.persistenceSucceeded) { + // Passes along the new widget after one second. + subject.next(widget); + // Toast on the page on success. + this.toastService.success({ + title: $localize\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\`Submit succeeded.\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\`, + }); + } else { + const errorText = $localize\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\`Submit failed.\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\`; + // Toast on the page on failure. + this.toastService.error({ title: errorText }); + // Makes the subject say there is an error. + subject.error(errorText); + } + // Completes the subject so whoever subscribes to it knows its finished. + subject.complete(); + }, 1000); + + // Returns the subject as an observable. + return subject.asObservable(); + }; + + // This method will be invoked anytime there's a widget removal attempt. + public tryRemove = (widgetId: string): Observable => { + const subject = new Subject(); + + setTimeout(() => { + if (this.persistenceSucceeded) { + // Pass through the id of the widget that was removed. + subject.next(widgetId); + this.toastService.success({ + title: $localize\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\`Removal succeeded.\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\`, + }); + } else { + const errorText = $localize\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\`Removal failed.\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\`; + this.toastService.error({ title: errorText }); + subject.error(errorText); + } + subject.complete(); + }, 1000); + + return subject.asObservable(); + }; +} + +/** + * A component that instantiates the dashboard + */ +@Component({ + selector: "persistence-handler-setup", + templateUrl: "./persistence-handler-setup.component.html", + styleUrls: ["./persistence-handler-setup.component.less"], + // Here we provide our persistence handler at the component level; this can also be done in the module. + providers: [PersistenceHandler], + standalone: false, +}) +export class PersistenceHandlerSetupComponent implements OnInit { + // This variable will hold all the data needed to define the layout and behavior of the widgets. + // Pass this to the dashboard component's dashboard input in the template. + public dashboard: IDashboard | undefined; + + // Angular gridster requires a configuration object even if it's empty. + // Pass this to the dashboard component's gridsterConfig input in the template. + public gridsterConfig: GridsterConfig = {}; + + // Boolean which dashboard takes in as an input if its true it allows you to move widgets around. + public editMode: boolean = false; + + constructor( + // WidgetTypesService provides the widget's necessary structure information + private widgetTypesService: WidgetTypesService, + + // In general, the ProviderRegistryService is used for making entities available for injection into dynamically loaded components. + private providerRegistry: ProviderRegistryService, + + // We are injecting the PersistenceHandler we created and assigning it to a property we use in the template. + public persistenceHandler: PersistenceHandler, + private changeDetectorRef: ChangeDetectorRef + ) {} + + public ngOnInit(): void { + // Grabbing the widget's default template which will be needed as a parameter for setNode + const widgetTemplate = this.widgetTypesService.getWidgetType("kpi", 1); + // Registering our data sources as dropdown options in the widget editor/configurator + // Note: This could also be done in the parent module's constructor so that + // multiple dashboards could have access to the same widget template modification. + this.widgetTypesService.setNode( + // This is the template we grabbed above with getWidgetType + widgetTemplate, + // We are setting the editor/configurator part of the widget template + "configurator", + // This indicates which node you are changing and we want to change + // the data source providers available for selection in the editor. + WellKnownPathKey.DataSourceProviders, + // We are setting the data sources available for selection in the editor + [ + AverageRatingKpiDataSource.providerId, + RatingsCountKpiDataSource.providerId, + ] + ); + + // Registering the data sources available for injection into the KPI tiles. + // Note: Each tile of a KPI widget is assigned its own instance of a data source + this.providerRegistry.setProviders({ + [AverageRatingKpiDataSource.providerId]: { + provide: DATA_SOURCE, + useClass: AverageRatingKpiDataSource, + // Any dependencies that need to be injected into the provider must be listed here + deps: [HttpClient], + }, + [RatingsCountKpiDataSource.providerId]: { + provide: DATA_SOURCE, + useClass: RatingsCountKpiDataSource, + deps: [HttpClient], + }, + }); + + this.initializeDashboard(); + } + + /** Used for restoring widgets state */ + public reInitializeDashboard(): void { + // destroys the components and their providers so the dashboard can re init data + this.dashboard = undefined; + this.changeDetectorRef.detectChanges(); + + this.initializeDashboard(); + } + + public initializeDashboard(): void { + // We're using a static configuration object for this example (see widgetConfig at the bottom of the file), + // but this is where the widget's configuration could potentially be populated from a database + const kpiWidget = widgetConfig; + const widgetIndex: IWidgets = { + // Complete the KPI widget with information coming from its type definition + [kpiWidget.id]: + this.widgetTypesService.mergeWithWidgetType(kpiWidget), + }; + + // Setting the widget dimensions and position (this is for gridster) + const positions: Record = { + [kpiWidget.id]: { + cols: 4, + rows: 6, + y: 0, + x: 0, + }, + }; + + // Finally, assigning the variables we created above to the dashboard + this.dashboard = { + positions, + widgets: widgetIndex, + }; + } +} + +/** + * A simple KPI data source to retrieve the average rating of Harry Potter and the Sorcerer's Stone (book) via googleapis + */ +@Injectable() +export class AverageRatingKpiDataSource + extends DataSourceService + implements OnDestroy +{ + // This is the ID we'll use to identify the provider + public static providerId = "AverageRatingKpiDataSource"; + + // Use this subject to communicate the data source's busy state + public busy = new BehaviorSubject(false); + + constructor(private http: HttpClient) { + super(); + } + + // In this example, getFilteredData is invoked every 10 minutes (Take a look at the refresher + // provider definition in the widget configuration below to see how the interval is set) + public async getFilteredData(): Promise { + this.busy.next(true); + return new Promise((resolve) => { + // *** Make a resource request to an external API (if needed) + this.http + .get("https://www.googleapis.com/books/v1/volumes/5MQFrgEACAAJ") + .pipe(finalize(() => this.busy.next(false))) + .subscribe({ + next: (data: any) => { + resolve({ + result: { + value: data.volumeInfo.averageRating, + }, + }); + }, + error: (error: HttpErrorResponse) => { + resolve({ + result: null, + error: { + type: error.status, + }, + }); + }, + }); + }); + } + + public ngOnDestroy(): void { + this.outputsSubject.complete(); + } +} + +/** + * A simple KPI data source to retrieve the ratings count of Harry Potter and the Sorcerer's Stone (book) via googleapis + */ +@Injectable() +export class RatingsCountKpiDataSource + extends DataSourceService + implements OnDestroy +{ + public static providerId = "RatingsCountKpiDataSource"; + + // Use this subject to communicate the data source's busy state + public busy = new BehaviorSubject(false); + + constructor(private http: HttpClient) { + super(); + } + + public async getFilteredData(): Promise { + this.busy.next(true); + return new Promise((resolve) => { + this.http + .get("https://www.googleapis.com/books/v1/volumes/5MQFrgEACAAJ") + .pipe(finalize(() => this.busy.next(false))) + .subscribe({ + next: (data: any) => { + resolve({ + result: { + value: data.volumeInfo.ratingsCount, + }, + }); + }, + error: (error: HttpErrorResponse) => { + resolve({ + result: null, + error: { + type: error.status, + }, + }); + }, + }); + }); + } + + public ngOnDestroy(): void { + this.outputsSubject.complete(); + } +} + +const widgetConfig: IWidget = { + id: "widget1", + type: "kpi", + pizzagna: { + [PizzagnaLayer.Configuration]: { + [DEFAULT_PIZZAGNA_ROOT]: { + providers: { + [WellKnownProviders.Refresher]: { + properties: { + // Configuring the refresher interval so that our data source is invoked every ten minutes + interval: 60 * 10, + enabled: true, + } as IRefresherProperties, + } as Partial, + }, + }, + header: { + properties: { + title: "Harry Potter and the Sorcerer's Stone", + subtitle: "By J. K. Rowling", + }, + }, + tiles: { + properties: { + nodes: ["kpi1"], + }, + }, + kpi1: { + id: "kpi1", + componentType: KpiComponent.lateLoadKey, + properties: { + widgetData: { + units: "out of 5 Stars", + label: "Average Rating", + }, + }, + providers: { + [WellKnownProviders.DataSource]: { + // Setting the data source providerId for the tile with id "kpi1" + providerId: AverageRatingKpiDataSource.providerId, + } as IProviderConfiguration, + [WellKnownProviders.Adapter]: { + providerId: NOVA_KPI_DATASOURCE_ADAPTER, + properties: { + componentId: "kpi1", + propertyPath: "widgetData", + }, + } as IProviderConfiguration, + }, + }, + }, + }, +}; +\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\`, + "tutorials/persistence-handler-setup/persistence-handler-setup.module.ts": \\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\`// © 2022 SolarWinds Worldwide, LLC. All rights reserved. +// +// Permission is hereby granted, free of charge, to any person obtaining a copy +// of this software and associated documentation files (the "Software"), to +// deal in the Software without restriction, including without limitation the +// rights to use, copy, modify, merge, publish, distribute, sublicense, and/or +// sell copies of the Software, and to permit persons to whom the Software is +// furnished to do so, subject to the following conditions: +// +// The above copyright notice and this permission notice shall be included in +// all copies or substantial portions of the Software. +// +// THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR +// IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, +// FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE +// AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER +// LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, +// OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN +// THE SOFTWARE. + +import { CommonModule } from "@angular/common"; +import { HttpClientModule } from "@angular/common/http"; +import { NgModule } from "@angular/core"; +import { RouterModule } from "@angular/router"; + +import { + NuiButtonModule, + NuiDocsModule, + NuiMessageModule, + NuiSwitchModule, + NuiToastModule, + DEMO_PATH_TOKEN, +} from "@nova-ui/bits"; +import { NuiDashboardsModule } from "@nova-ui/dashboards"; + +import { PersistenceHandlerSetupDocsComponent } from "./persistence-handler-setup-docs.component"; +import { PersistenceHandlerSetupComponent } from "./persistence-handler-setup.component"; +import { getDemoFiles } from "../../../../demo-files-factory"; + +const routes = [ + { + path: "", + component: PersistenceHandlerSetupDocsComponent, + data: { + srlc: { + hideIndicator: true, + }, + showThemeSwitcher: true, + }, + }, + { + path: "example", + component: PersistenceHandlerSetupComponent, + data: { + srlc: { + hideIndicator: true, + }, + }, + }, +]; + +@NgModule({ + imports: [ + CommonModule, + HttpClientModule, + NuiDashboardsModule, + NuiDocsModule, + NuiMessageModule, + NuiSwitchModule, + NuiToastModule, + NuiButtonModule, + RouterModule.forChild(routes), + ], + declarations: [ + PersistenceHandlerSetupDocsComponent, + PersistenceHandlerSetupComponent, + ], + providers: [ + { + provide: DEMO_PATH_TOKEN, + useValue: getDemoFiles("persistence-handler-setup"), + }, + ], +}) +export default class PersistenceHandlerSetupModule {} +\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\`, + "tutorials/tutorials.module.ts": \\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\`// © 2022 SolarWinds Worldwide, LLC. All rights reserved. +// +// Permission is hereby granted, free of charge, to any person obtaining a copy +// of this software and associated documentation files (the "Software"), to +// deal in the Software without restriction, including without limitation the +// rights to use, copy, modify, merge, publish, distribute, sublicense, and/or +// sell copies of the Software, and to permit persons to whom the Software is +// furnished to do so, subject to the following conditions: +// +// The above copyright notice and this permission notice shall be included in +// all copies or substantial portions of the Software. +// +// THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR +// IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, +// FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE +// AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER +// LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, +// OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN +// THE SOFTWARE. + +import { NgModule, Type } from "@angular/core"; +import { RouterModule, Routes } from "@angular/router"; + +import { ConfiguratorHeadingService } from "@nova-ui/dashboards"; + +export enum TutorialsModuleRoute { + HelloDashboards = "hello-dashboards", + DataSource = "data-source-setup", + WidgetEditor = "widget-editor-setup", + SubmitHandler = "persistence-handler-setup", + WidgetCreation = "widget-creation", + Customization = "customization", + WidgetErrorHandling = "widget-error-handling", + DynamicHeaderLinks = "dynamic-header-links", +} + +const routes: Routes = [ + { + path: TutorialsModuleRoute.HelloDashboards, + loadChildren: async () => + import( + "./hello-dashboards/hello-dashboards.module" + ) as object as Promise>, + }, + { + path: TutorialsModuleRoute.DataSource, + loadChildren: async () => + import( + "./data-source-setup/data-source-setup.module" + ) as object as Promise>, + }, + { + path: TutorialsModuleRoute.WidgetEditor, + loadChildren: async () => + import( + "./widget-editor-setup/widget-editor-setup.module" + ) as object as Promise>, + }, + { + path: TutorialsModuleRoute.SubmitHandler, + loadChildren: async () => + import( + "./persistence-handler-setup/persistence-handler-setup.module" + ) as object as Promise>, + }, + { + path: TutorialsModuleRoute.WidgetCreation, + loadChildren: async () => + import( + "./widget-creation/widget-creation.module" + ) as object as Promise>, + }, + { + path: TutorialsModuleRoute.Customization, + loadChildren: async () => + import("./customization/customization.module") as object as Promise< + Type + >, + }, + { + path: TutorialsModuleRoute.WidgetErrorHandling, + loadChildren: async () => + import( + "./widget-error-handling/widget-error-handling.module" + ) as object as Promise>, + }, + { + path: TutorialsModuleRoute.DynamicHeaderLinks, + loadChildren: async () => + import( + "./dynamic-header-links/dynamic-header-links-docs.module" + ) as object as Promise>, + }, +]; + +@NgModule({ + imports: [RouterModule.forChild(routes)], + providers: [ConfiguratorHeadingService], +}) +export default class TutorialsModule {} +\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\`, + "tutorials/widget-creation/widget-creation-docs.component.ts": \\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\`// © 2022 SolarWinds Worldwide, LLC. All rights reserved. +// +// Permission is hereby granted, free of charge, to any person obtaining a copy +// of this software and associated documentation files (the "Software"), to +// deal in the Software without restriction, including without limitation the +// rights to use, copy, modify, merge, publish, distribute, sublicense, and/or +// sell copies of the Software, and to permit persons to whom the Software is +// furnished to do so, subject to the following conditions: +// +// The above copyright notice and this permission notice shall be included in +// all copies or substantial portions of the Software. +// +// THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR +// IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, +// FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE +// AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER +// LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, +// OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN +// THE SOFTWARE. + +import { Component } from "@angular/core"; + +@Component({ + selector: "nui-dashboard-widget-creation-docs", + templateUrl: "./widget-creation-docs.component.html", + standalone: false, +}) +export class WidgetCreationDocsComponent {} +\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\`, + "tutorials/widget-creation/widget-creation.component.ts": \\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\`// © 2022 SolarWinds Worldwide, LLC. All rights reserved. +// +// Permission is hereby granted, free of charge, to any person obtaining a copy +// of this software and associated documentation files (the "Software"), to +// deal in the Software without restriction, including without limitation the +// rights to use, copy, modify, merge, publish, distribute, sublicense, and/or +// sell copies of the Software, and to permit persons to whom the Software is +// furnished to do so, subject to the following conditions: +// +// The above copyright notice and this permission notice shall be included in +// all copies or substantial portions of the Software. +// +// THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR +// IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, +// FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE +// AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER +// LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, +// OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN +// THE SOFTWARE. + +import { HttpClient, HttpErrorResponse } from "@angular/common/http"; +import { + Component, + EventEmitter, + Injectable, + OnDestroy, + OnInit, + Output, + ViewChild, +} from "@angular/core"; +import { GridsterConfig, GridsterItem } from "angular-gridster2"; +import { BehaviorSubject, Observable, Subject } from "rxjs"; +import { finalize, take, takeUntil } from "rxjs/operators"; + +import { + DataSourceService, + IFilteringOutputs, + ToastService, + uuid, +} from "@nova-ui/bits"; +import { + DashboardComponent, + DATA_SOURCE, + DEFAULT_PIZZAGNA_ROOT, + IDashboard, + IDashboardPersistenceHandler, + IDataSourceOutput, + IKpiData, + IProviderConfiguration, + IRefresherProperties, + IWidget, + IWidgets, + IWidgetSelector, + IWidgetTemplateSelector, + KpiComponent, + NOVA_KPI_DATASOURCE_ADAPTER, + PizzagnaLayer, + ProviderRegistryService, + WellKnownPathKey, + WellKnownProviders, + WidgetClonerService, + WidgetTypesService, +} from "@nova-ui/dashboards"; + +// Interface of a widget item +interface IWidgetItem { + name: string; + widget: IWidget; +} + +// This component acts as the first step, or page, in the wizard where the user selects a wizard type to create. +// It's recommended to have this component in a different file. For this tutorial, it's included in the same +// file for simplicity. +@Component({ + selector: "widget-template-selection", + styleUrls: ["./widget-creation.component.less"], + template: \\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\` +
+ + +
+ + +
+
{{ item.name }}
+
+
+ \\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\`, + standalone: false, +}) +export class WidgetTemplateSelectionComponent + implements IWidgetTemplateSelector, OnInit +{ + // This output will notify the wizard that a widget has been selected. + @Output() public widgetSelected = new EventEmitter(); + + public widgetItems: IWidgetItem[] = []; + public widgetSelection: IWidgetItem[]; + + constructor(private widgetTypesService: WidgetTypesService) {} + + public ngOnInit(): void { + // Here we combine the widget structure from the WidgetTypesService with the corresponding widget + // configuration to create an array of widget objects for the itemSource on the repeat component. + this.widgetItems = [ + { + name: "Fully Configured KPI Widget", + widget: this.widgetTypesService.mergeWithWidgetType( + fullKpiWidgetConfig + ), + }, + { + name: "Unconfigured Proportional Widget", + // Note that 'partialPropWidgetConfig' sets 'metadata.needsConfiguration' to true. + // When this widget is selected in the wizard, the 'Create Widget' button will be hidden + // to guide the user to the second step where they can complete the configuration. + widget: this.widgetTypesService.mergeWithWidgetType( + partialPropWidgetConfig + ), + }, + ]; + + // You can optionally auto-select a widget by doing the following + // this.onSelect([this.widgetItems[0]]); + } + + public onSelect(selectedItems: any[]): void { + // We emit the selected widget to communicate the selection to the configurator + this.widgetSelected.emit(selectedItems[0].widget); + this.widgetSelection = selectedItems; + } +} + +/** + * A simple persistence handler that is tied to the widget editor directive + */ +@Injectable() +// The realizer of IDashboardPersistenceHandler may implement a trySubmit and/or a tryRemove method. +export class PersistenceHandler implements IDashboardPersistenceHandler { + // This variable is just to show how to handle error handling. + private persistenceSucceeded: boolean = true; + + // The example uses the toast service to demonstrate the + // invocation of each of the persistence handler callbacks + constructor(private toastService: ToastService) { + // toastService options to let it sit on the page for 2 seconds. + this.toastService.setConfig({ + timeOut: 2000, + }); + } + + // This method will be invoked anytime the widget editor form gets submitted. + public trySubmit = (widget: IWidget): Observable => { + // Since we are working asynchronously, we'll return a subject. So, after the submit attempt + // succeeds or fails, we can let the subscriber know the result. + const subject = new Subject(); + + if (!widget.id) { + // Creates an id if the widget has no id. + // (This step will make more sense in the context of the widget cloning tutorial + // in which we handle the persistence of a newly created widget.) + widget.id = uuid(); + } + + // For this example, we're using a setTimeout to mock an asynchronous persistence request to a backend + setTimeout(() => { + if (this.persistenceSucceeded) { + // Passes along the new widget after one second. + subject.next(widget); + // Toast on the page on success. + this.toastService.success({ + title: $localize\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\`Submit succeeded.\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\`, + }); + } else { + const errorText = $localize\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\`Submit failed.\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\`; + // Toast on the page on failure. + this.toastService.error({ title: errorText }); + // Makes the subject say there is an error. + subject.error(errorText); + } + // Completes the subject so whoever subscribes to it knows its finished. + subject.complete(); + }, 1000); + + // Returns the subject as an observable. + return subject.asObservable(); + }; + + // This method will be invoked anytime there's a widget removal attempt. + public tryRemove = (widgetId: string): Observable => { + const subject = new Subject(); + + setTimeout(() => { + if (this.persistenceSucceeded) { + // Pass through the id of the widget that was removed. + subject.next(widgetId); + this.toastService.success({ + title: $localize\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\`Removal success\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\`, + }); + } else { + const errorText = $localize\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\`Removal failed.\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\`; + this.toastService.error({ title: errorText }); + subject.error(errorText); + } + subject.complete(); + }, 1000); + + return subject.asObservable(); + }; +} + +/** + * A component that instantiates the dashboard + */ +@Component({ + selector: "widget-creation", + templateUrl: "./widget-creation.component.html", + styleUrls: ["./widget-creation.component.less"], + // Here we provide our persistence handler at the component level; this can also be done in the module. + providers: [PersistenceHandler], + standalone: false, +}) +export class WidgetCreationComponent implements OnInit { + // The WidgetClonerService will need this for updating the dashboard + @ViewChild(DashboardComponent, { static: true }) + dashboardComponent: DashboardComponent; + // This variable will hold all the data needed to define the layout and behavior of the widgets. + // Pass this to the dashboard component's dashboard input in the template. + public dashboard: IDashboard; + + // Angular gridster requires a configuration object even if it's empty. + // Pass this to the dashboard component's gridsterConfig input in the template. + public gridsterConfig: GridsterConfig = { + // These values will be used to set the initial widget dimensions on creation. + // If not set, they each default to 6. + defaultItemCols: 3, + defaultItemRows: 5, + }; + + // Boolean the dashboard takes in as an input; if it's set to true + // the dashboard allows you to resize widgets and move them around. + public editMode: boolean = false; + + // Subject used for auto-unsubscribing from subscriptions on component destruction + private readonly destroy$ = new Subject(); + + constructor( + // WidgetTypesService provides the widget's necessary structure information + private widgetTypesService: WidgetTypesService, + + // In general, the ProviderRegistryService is used for making entities available for injection into dynamically loaded components. + private providerRegistry: ProviderRegistryService, + + // Injecting the PersistenceHandler we created and assigning it to a property we use in the template. + public persistenceHandler: PersistenceHandler, + + // Injecting the cloner service which is needed for opening up the cloner wizard. + private widgetClonerService: WidgetClonerService + ) {} + + public ngOnInit(): void { + // Grabbing the widget's default template which will be needed as a parameter for setNode + const kpiTemplate = this.widgetTypesService.getWidgetType("kpi", 1); + const proportionalTemplate = this.widgetTypesService.getWidgetType( + "proportional", + 1 + ); + + // Registering our data sources as dropdown options in the widget editor/configurator + // Note: This could also be done in the parent module's constructor so that + // multiple dashboards could have access to the same widget template modification. + this.widgetTypesService.setNode( + // This is the template we grabbed above with getWidgetType + proportionalTemplate, + // Setting the editor/configurator part of the widget template + "configurator", + // This indicates which node you are changing and we want to change + // the data source providers available for selection in the editor. + WellKnownPathKey.DataSourceProviders, + // Setting the data sources available for selection in the editor + [RandomCitiesProportionalDataSource.providerId] + ); + + // Same as above, but for the KPI data sources + this.widgetTypesService.setNode( + kpiTemplate, + "configurator", + WellKnownPathKey.DataSourceProviders, + [ + AverageRatingKpiDataSource.providerId, + RatingsCountKpiDataSource.providerId, + ] + ); + + // Registering the data sources available for injection into the KPI tiles and proportional widget. + // Note: Each tile of a KPI widget is assigned its own instance of a data source. + this.providerRegistry.setProviders({ + [AverageRatingKpiDataSource.providerId]: { + provide: DATA_SOURCE, + useClass: AverageRatingKpiDataSource, + // Any dependencies that need to be injected into the provider must be listed here + deps: [HttpClient], + }, + [RatingsCountKpiDataSource.providerId]: { + provide: DATA_SOURCE, + useClass: RatingsCountKpiDataSource, + deps: [HttpClient], + }, + [RandomCitiesProportionalDataSource.providerId]: { + provide: DATA_SOURCE, + useClass: RandomCitiesProportionalDataSource, + deps: [], + }, + }); + + this.initializeDashboard(); + } + + public onCreateWidget(): void { + const widgetSelector: IWidgetSelector = { + // Template ref of the dashboard component. + dashboardComponent: this.dashboardComponent, + // A trySubmit function; in this case, we use the trySubmit from the PersistenceHandler created in the previous tutorial. + trySubmit: this.persistenceHandler.trySubmit, + // WidgetTemplateSelectionComponent will act as step one of the wizard to allow the user to select which widget will be cloned. + widgetSelectionComponentType: WidgetTemplateSelectionComponent, + }; + this.widgetClonerService + .open(widgetSelector) + .pipe( + // Auto-unsubscribe after one emission or on component destruction + take(1), + takeUntil(this.destroy$) + ) + .subscribe(); + } + + public initializeDashboard(): void { + // We're using a static configuration object for this example (see widgetConfig at the bottom of the file), + // but this is where the widget's configuration could potentially be populated from a database + const kpiWidget = fullKpiWidgetConfig; + const widgetIndex: IWidgets = { + // Complete the KPI widget with information coming from its type definition + [kpiWidget.id]: + this.widgetTypesService.mergeWithWidgetType(kpiWidget), + }; + + // Setting the widget dimensions and position (this is for gridster) + // Note: If no position is given for a widget the 'defaultItemCols' and 'defaultItemRows' properties + // from the gridsterConfig will be used for the dimensions + const positions: Record = { + [kpiWidget.id]: { + cols: 3, + rows: 5, + y: 0, + x: 0, + }, + }; + + // Finally, assigning the variables we created above to the dashboard + this.dashboard = { + positions, + widgets: widgetIndex, + }; + } +} + +// Interface for each data point in a proportional widget. +interface IProportionalWidgetData { + id: string; + name: string; + data: number[]; + icon: string; + link: string; + value: string; +} + +@Injectable() +export class RandomCitiesProportionalDataSource implements OnDestroy { + public static providerId = "RandomCitiesProportionalDataSource"; + + public outputsSubject = new Subject< + IDataSourceOutput + >(); + + // Every time applyFilters gets ran we are changing the data source. + public applyFilters(): void { + setTimeout(() => { + this.outputsSubject.next({ + result: this.getRandomProportionalWidgetData(), + }); + }, 1000); + } + + public ngOnDestroy(): void { + this.outputsSubject.complete(); + } + + private getRandomProportionalWidgetData(): IProportionalWidgetData[] { + return [ + { + id: "Down", + name: "Down", + data: [Math.round(Math.random() * 100)], + icon: "status_down", + link: "https://en.wikipedia.org/wiki/Brno", + value: "Brno", + }, + { + id: "Critical", + name: "Critical", + data: [Math.round(Math.random() * 100)], + icon: "status_critical", + link: "https://en.wikipedia.org/wiki/Kyiv", + value: "Kyiv", + }, + { + id: "Warning", + name: "Warning", + data: [Math.round(Math.random() * 100)], + icon: "status_warning", + link: "https://en.wikipedia.org/wiki/Austin", + value: "Austin", + }, + { + id: "Unknown", + name: "Unknown", + data: [Math.round(Math.random() * 100)], + icon: "status_unknown", + link: "https://en.wikipedia.org/wiki/Lisbon", + value: "Lisbon", + }, + { + id: "Up", + name: "Up", + data: [Math.round(Math.random() * 100)], + icon: "status_up", + link: "https://en.wikipedia.org/wiki/Sydney", + value: "Sydney", + }, + { + id: "Unmanaged", + name: "Unmanaged", + data: [Math.round(Math.random() * 100)], + icon: "status_unmanaged", + link: "https://en.wikipedia.org/wiki/Nur-Sultan", + value: "Nur-Sultan", + }, + ]; + } +} + +/** + * A simple KPI data source to retrieve the average rating of Harry Potter and the Sorcerer's Stone (book) via googleapis + */ +@Injectable() +export class AverageRatingKpiDataSource + extends DataSourceService + implements OnDestroy +{ + // This is the ID we'll use to identify the provider + public static providerId = "AverageRatingKpiDataSource"; + + // Use this subject to communicate the data source's busy state + public busy = new BehaviorSubject(false); + + constructor(private http: HttpClient) { + super(); + } + + // In this example, getFilteredData is invoked every 10 minutes (Take a look at the refresher + // provider definition in the widget configuration below to see how the interval is set) + public async getFilteredData(): Promise { + this.busy.next(true); + return new Promise((resolve) => { + // *** Make a resource request to an external API (if needed) + this.http + .get("https://www.googleapis.com/books/v1/volumes/5MQFrgEACAAJ") + .pipe(finalize(() => this.busy.next(false))) + .subscribe({ + next: (data: any) => { + resolve({ + result: { + value: data.volumeInfo.averageRating, + }, + }); + }, + error: (error: HttpErrorResponse) => { + resolve({ + result: null, + error: { + type: error.status, + }, + }); + }, + }); + }); + } + + public ngOnDestroy(): void { + this.outputsSubject.complete(); + } +} + +/** + * A simple KPI data source to retrieve the ratings count of Harry Potter and the Sorcerer's Stone (book) via googleapis + */ +@Injectable() +export class RatingsCountKpiDataSource + extends DataSourceService + implements OnDestroy +{ + public static providerId = "RatingsCountKpiDataSource"; + + // Use this subject to communicate the data source's busy state + public busy = new BehaviorSubject(false); + + constructor(private http: HttpClient) { + super(); + } + + public async getFilteredData(): Promise { + this.busy.next(true); + return new Promise((resolve) => { + this.http + .get("https://www.googleapis.com/books/v1/volumes/5MQFrgEACAAJ") + .pipe(finalize(() => this.busy.next(false))) + .subscribe({ + next: (data: any) => { + resolve({ + result: { + value: data.volumeInfo.ratingsCount, + }, + }); + }, + error: (error: HttpErrorResponse) => { + resolve({ + result: null, + error: { + type: error.status, + }, + }); + }, + }); + }); + } + + public ngOnDestroy(): void { + this.outputsSubject.complete(); + } +} + +const fullKpiWidgetConfig: IWidget = { + id: "widget1", + type: "kpi", + pizzagna: { + [PizzagnaLayer.Configuration]: { + [DEFAULT_PIZZAGNA_ROOT]: { + providers: { + [WellKnownProviders.Refresher]: { + properties: { + // Configuring the refresher interval so that our data source is invoked every ten minutes + interval: 60 * 10, + enabled: true, + } as IRefresherProperties, + } as Partial, + }, + }, + header: { + properties: { + title: "Harry Potter and the Sorcerer's Stone", + subtitle: "By J. K. Rowling", + }, + }, + tiles: { + properties: { + nodes: ["kpi1"], + }, + }, + kpi1: { + id: "kpi1", + componentType: KpiComponent.lateLoadKey, + properties: { + widgetData: { + units: \\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\`out of 5 Stars\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\`, + label: \\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\`Average Rating\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\`, + }, + }, + providers: { + [WellKnownProviders.DataSource]: { + // Setting the data source providerId for the tile with id "kpi1" + providerId: AverageRatingKpiDataSource.providerId, + } as IProviderConfiguration, + [WellKnownProviders.Adapter]: { + providerId: NOVA_KPI_DATASOURCE_ADAPTER, + properties: { + componentId: "kpi1", + propertyPath: "widgetData", + }, + } as IProviderConfiguration, + }, + }, + }, + }, +}; + +const partialPropWidgetConfig: IWidget = { + id: "widget2", + type: "proportional", + metadata: { + // Set 'needsConfiguration' to true if the widget needs further configuration before it can be + // placed on the dashboard. The "Create Widget" button will be hidden in the wizard when this + // widget is selected. + needsConfiguration: true, + }, + pizzagna: { + [PizzagnaLayer.Configuration]: { + header: { + properties: { + title: "*New Proportional Widget*", + }, + }, + }, + }, +}; +\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\`, + "tutorials/widget-creation/widget-creation.module.ts": \\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\`// © 2022 SolarWinds Worldwide, LLC. All rights reserved. +// +// Permission is hereby granted, free of charge, to any person obtaining a copy +// of this software and associated documentation files (the "Software"), to +// deal in the Software without restriction, including without limitation the +// rights to use, copy, modify, merge, publish, distribute, sublicense, and/or +// sell copies of the Software, and to permit persons to whom the Software is +// furnished to do so, subject to the following conditions: +// +// The above copyright notice and this permission notice shall be included in +// all copies or substantial portions of the Software. +// +// THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR +// IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, +// FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE +// AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER +// LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, +// OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN +// THE SOFTWARE. + +import { CommonModule } from "@angular/common"; +import { HttpClientModule } from "@angular/common/http"; +import { NgModule } from "@angular/core"; +import { RouterModule } from "@angular/router"; + +import { + NuiButtonModule, + NuiDocsModule, + NuiImageModule, + NuiMessageModule, + NuiRepeatModule, + NuiSwitchModule, + NuiToastModule, + DEMO_PATH_TOKEN, +} from "@nova-ui/bits"; +import { NuiDashboardsModule } from "@nova-ui/dashboards"; + +import { WidgetCreationDocsComponent } from "./widget-creation-docs.component"; +import { + WidgetCreationComponent, + WidgetTemplateSelectionComponent, +} from "./widget-creation.component"; +import { getDemoFiles } from "../../../../demo-files-factory"; + +const routes = [ + { + path: "", + component: WidgetCreationDocsComponent, + data: { + srlc: { + hideIndicator: true, + }, + showThemeSwitcher: true, + }, + }, + { + path: "example", + component: WidgetCreationComponent, + data: { + srlc: { + hideIndicator: true, + }, + }, + }, +]; + +@NgModule({ + imports: [ + CommonModule, + HttpClientModule, + NuiDashboardsModule, + NuiDocsModule, + NuiMessageModule, + NuiSwitchModule, + NuiToastModule, + NuiButtonModule, + NuiRepeatModule, + NuiImageModule, + RouterModule.forChild(routes), + ], + declarations: [ + WidgetCreationDocsComponent, + WidgetCreationComponent, + WidgetTemplateSelectionComponent, + ], + providers: [ + { + provide: DEMO_PATH_TOKEN, + useValue: getDemoFiles("widget-creation"), + }, + ], +}) +export default class WidgetCreationModule {} +\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\`, + "tutorials/widget-editor-setup/widget-editor-setup-docs.component.ts": \\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\`// © 2022 SolarWinds Worldwide, LLC. All rights reserved. +// +// Permission is hereby granted, free of charge, to any person obtaining a copy +// of this software and associated documentation files (the "Software"), to +// deal in the Software without restriction, including without limitation the +// rights to use, copy, modify, merge, publish, distribute, sublicense, and/or +// sell copies of the Software, and to permit persons to whom the Software is +// furnished to do so, subject to the following conditions: +// +// The above copyright notice and this permission notice shall be included in +// all copies or substantial portions of the Software. +// +// THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR +// IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, +// FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE +// AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER +// LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, +// OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN +// THE SOFTWARE. + +import { Component } from "@angular/core"; + +@Component({ + selector: "nui-dashboard-widget-editor-docs", + templateUrl: "./widget-editor-setup-docs.component.html", + standalone: false, +}) +export class WidgetEditorDocsComponent {} +\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\`, + "tutorials/widget-editor-setup/widget-editor-setup.component.ts": \\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\`// © 2022 SolarWinds Worldwide, LLC. All rights reserved. +// +// Permission is hereby granted, free of charge, to any person obtaining a copy +// of this software and associated documentation files (the "Software"), to +// deal in the Software without restriction, including without limitation the +// rights to use, copy, modify, merge, publish, distribute, sublicense, and/or +// sell copies of the Software, and to permit persons to whom the Software is +// furnished to do so, subject to the following conditions: +// +// The above copyright notice and this permission notice shall be included in +// all copies or substantial portions of the Software. +// +// THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR +// IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, +// FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE +// AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER +// LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, +// OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN +// THE SOFTWARE. + +import { HttpClient, HttpErrorResponse } from "@angular/common/http"; +import { + ChangeDetectorRef, + Component, + Injectable, + OnDestroy, + OnInit, +} from "@angular/core"; +import { GridsterConfig, GridsterItem } from "angular-gridster2"; +import { BehaviorSubject } from "rxjs"; +import { finalize } from "rxjs/operators"; + +import { DataSourceService, IFilteringOutputs } from "@nova-ui/bits"; +import { + DATA_SOURCE, + DEFAULT_PIZZAGNA_ROOT, + IDashboard, + IKpiData, + IProviderConfiguration, + IRefresherProperties, + IWidget, + IWidgets, + KpiComponent, + NOVA_KPI_DATASOURCE_ADAPTER, + PizzagnaLayer, + ProviderRegistryService, + WellKnownPathKey, + WellKnownProviders, + WidgetTypesService, +} from "@nova-ui/dashboards"; + +/** + * A simple KPI data source to retrieve the average rating of Harry Potter and the Sorcerer's Stone (book) via googleapis + */ +@Injectable() +export class AverageRatingKpiDataSource + extends DataSourceService + implements OnDestroy +{ + // This is the ID we'll use to identify the provider + public static providerId = "AverageRatingKpiDataSource"; + + // Use this subject to communicate the data source's busy state + public busy = new BehaviorSubject(false); + + constructor(private http: HttpClient) { + super(); + } + + // In this example, getFilteredData is invoked every 10 minutes (Take a look at the refresher + // provider definition in the widget configuration below to see how the interval is set) + public async getFilteredData(): Promise { + this.busy.next(true); + return new Promise((resolve) => { + // *** Make a resource request to an external API (if needed) + this.http + .get("https://www.googleapis.com/books/v1/volumes/5MQFrgEACAAJ") + .pipe(finalize(() => this.busy.next(false))) + .subscribe({ + next: (data: any) => { + resolve({ + result: { + value: data.volumeInfo.averageRating, + }, + }); + }, + error: (error: HttpErrorResponse) => { + resolve({ + result: null, + error: { + type: error.status, + }, + }); + }, + }); + }); + } + + public ngOnDestroy(): void { + this.outputsSubject.complete(); + } +} + +/** + * A simple KPI data source to retrieve the ratings count of Harry Potter and the Sorcerer's Stone (book) via googleapis + */ +@Injectable() +export class RatingsCountKpiDataSource + extends DataSourceService + implements OnDestroy +{ + public static providerId = "RatingsCountKpiDataSource"; + + // Use this subject to communicate the data source's busy state + public busy = new BehaviorSubject(false); + + constructor(private http: HttpClient) { + super(); + } + + public async getFilteredData(): Promise { + this.busy.next(true); + return new Promise((resolve) => { + this.http + .get("https://www.googleapis.com/books/v1/volumes/5MQFrgEACAAJ") + .pipe(finalize(() => this.busy.next(false))) + .subscribe({ + next: (data: any) => { + resolve({ + result: { + value: data.volumeInfo.ratingsCount, + }, + }); + }, + error: (error: HttpErrorResponse) => { + resolve({ + result: null, + error: { + type: error.status, + }, + }); + }, + }); + }); + } + + public ngOnDestroy(): void { + this.outputsSubject.complete(); + } +} + +/** + * A component that instantiates the dashboard + */ +@Component({ + selector: "widget-editor-setup", + templateUrl: "./widget-editor-setup.component.html", + styleUrls: ["./widget-editor-setup.component.less"], + standalone: false, +}) +export class WidgetEditorSetupComponent implements OnInit { + // This variable will hold all the data needed to define the layout and behavior of the widgets. + // Pass this to the dashboard component's dashboard input in the template. + public dashboard: IDashboard | undefined; + + // Angular gridster requires a configuration object even if it's empty. + // Pass this to the dashboard component's gridsterConfig input in the template. + public gridsterConfig: GridsterConfig = {}; + + // Boolean which dashboard takes in as an input if its true it allows you to move widgets around. + public editMode: boolean = false; + + constructor( + // WidgetTypesService provides the widget's necessary structure information + private widgetTypesService: WidgetTypesService, + + // In general, the ProviderRegistryService is used for making entities available for injection into dynamically loaded components. + private providerRegistry: ProviderRegistryService, + private changeDetectorRef: ChangeDetectorRef + ) {} + + public ngOnInit(): void { + // Grabbing the widget's default template which will be needed as a parameter for setNode + const widgetTemplate = this.widgetTypesService.getWidgetType("kpi", 1); + // Registering our data sources as dropdown options in the widget editor/configurator + // Note: This could also be done in the parent module's constructor so that + // multiple dashboards could have access to the same widget template modification. + this.widgetTypesService.setNode( + // This is the template we grabbed above with getWidgetType + widgetTemplate, + // We are setting the editor/configurator part of the widget template + "configurator", + // This indicates which node you are changing and we want to change + // the data source providers available for selection in the editor. + WellKnownPathKey.DataSourceProviders, + // We are setting the data sources available for selection in the editor + [ + AverageRatingKpiDataSource.providerId, + RatingsCountKpiDataSource.providerId, + ] + ); + + // Registering the data sources available for injection into the KPI tiles. + // Note: Each tile of a KPI widget is assigned its own instance of a data source + this.providerRegistry.setProviders({ + [AverageRatingKpiDataSource.providerId]: { + provide: DATA_SOURCE, + useClass: AverageRatingKpiDataSource, + // Any dependencies that need to be injected into the provider must be listed here + deps: [HttpClient], + }, + [RatingsCountKpiDataSource.providerId]: { + provide: DATA_SOURCE, + useClass: RatingsCountKpiDataSource, + deps: [HttpClient], + }, + }); + + this.initializeDashboard(); + } + + /** Used for restoring widgets state */ + public reInitializeDashboard(): void { + // destroys the components and their providers so the dashboard can re init data + this.dashboard = undefined; + this.changeDetectorRef.detectChanges(); + + this.initializeDashboard(); + } + + public initializeDashboard(): void { + // We're using a static configuration object for this example (see widgetConfig at the bottom of the file), + // but this is where the widget's configuration could potentially be populated from a database + const kpiWidget = widgetConfig; + const widgetIndex: IWidgets = { + // Complete the KPI widget with information coming from its type definition + [kpiWidget.id]: + this.widgetTypesService.mergeWithWidgetType(kpiWidget), + }; + + // Setting the widget dimensions and position (this is for gridster) + const positions: Record = { + [kpiWidget.id]: { + cols: 4, + rows: 6, + y: 0, + x: 0, + }, + }; + + // Finally, assigning the variables we created above to the dashboard + this.dashboard = { + positions, + widgets: widgetIndex, + }; + } +} + +const widgetConfig: IWidget = { + id: "widget1", + type: "kpi", + pizzagna: { + [PizzagnaLayer.Configuration]: { + [DEFAULT_PIZZAGNA_ROOT]: { + providers: { + [WellKnownProviders.Refresher]: { + properties: { + // Configuring the refresher interval so that our data source is invoked every ten minutes + interval: 60 * 10, + enabled: true, + } as IRefresherProperties, + } as Partial, + }, + }, + header: { + properties: { + title: "Harry Potter and the Sorcerer's Stone", + subtitle: "By J. K. Rowling", + }, + }, + tiles: { + properties: { + nodes: ["kpi1"], + }, + }, + kpi1: { + id: "kpi1", + componentType: KpiComponent.lateLoadKey, + properties: { + widgetData: { + units: "out of 5 Stars", + label: "Average Rating", + }, + }, + providers: { + [WellKnownProviders.DataSource]: { + // Setting the data source providerId for the tile with id "kpi1" + providerId: AverageRatingKpiDataSource.providerId, + } as IProviderConfiguration, + [WellKnownProviders.Adapter]: { + providerId: NOVA_KPI_DATASOURCE_ADAPTER, + properties: { + componentId: "kpi1", + propertyPath: "widgetData", + }, + } as IProviderConfiguration, + }, + }, + }, + }, +}; +\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\`, + "tutorials/widget-editor-setup/widget-editor-setup.module.ts": \\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\`// © 2022 SolarWinds Worldwide, LLC. All rights reserved. +// +// Permission is hereby granted, free of charge, to any person obtaining a copy +// of this software and associated documentation files (the "Software"), to +// deal in the Software without restriction, including without limitation the +// rights to use, copy, modify, merge, publish, distribute, sublicense, and/or +// sell copies of the Software, and to permit persons to whom the Software is +// furnished to do so, subject to the following conditions: +// +// The above copyright notice and this permission notice shall be included in +// all copies or substantial portions of the Software. +// +// THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR +// IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, +// FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE +// AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER +// LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, +// OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN +// THE SOFTWARE. + +import { CommonModule } from "@angular/common"; +import { HttpClientModule } from "@angular/common/http"; +import { NgModule } from "@angular/core"; +import { RouterModule } from "@angular/router"; + +import { + NuiButtonModule, + NuiDocsModule, + NuiMessageModule, + NuiSwitchModule, + DEMO_PATH_TOKEN, +} from "@nova-ui/bits"; +import { NuiDashboardsModule } from "@nova-ui/dashboards"; + +import { WidgetEditorDocsComponent } from "./widget-editor-setup-docs.component"; +import { WidgetEditorSetupComponent } from "./widget-editor-setup.component"; +import { getDemoFiles } from "../../../../demo-files-factory"; + +const routes = [ + { + path: "", + component: WidgetEditorDocsComponent, + data: { + srlc: { + hideIndicator: true, + }, + showThemeSwitcher: true, + }, + }, + { + path: "example", + component: WidgetEditorSetupComponent, + data: { + srlc: { + hideIndicator: true, + }, + }, + }, +]; + +@NgModule({ + imports: [ + CommonModule, + HttpClientModule, + NuiDashboardsModule, + NuiDocsModule, + NuiMessageModule, + NuiSwitchModule, + NuiButtonModule, + RouterModule.forChild(routes), + ], + declarations: [WidgetEditorDocsComponent, WidgetEditorSetupComponent], + providers: [ + { + provide: DEMO_PATH_TOKEN, + useValue: getDemoFiles("widget-editor-setup"), + }, + ], +}) +export default class WidgetEditorSetupModule {} +\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\`, + "tutorials/widget-error-handling/widget-error-handling-docs.component.ts": \\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\`// © 2022 SolarWinds Worldwide, LLC. All rights reserved. +// +// Permission is hereby granted, free of charge, to any person obtaining a copy +// of this software and associated documentation files (the "Software"), to +// deal in the Software without restriction, including without limitation the +// rights to use, copy, modify, merge, publish, distribute, sublicense, and/or +// sell copies of the Software, and to permit persons to whom the Software is +// furnished to do so, subject to the following conditions: +// +// The above copyright notice and this permission notice shall be included in +// all copies or substantial portions of the Software. +// +// THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR +// IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, +// FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE +// AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER +// LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, +// OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN +// THE SOFTWARE. + +import { Component } from "@angular/core"; + +@Component({ + selector: "nui-widget-error-handling-docs", + templateUrl: "./widget-error-handling-docs.component.html", + standalone: false, +}) +export class WidgetErrorHandlingDocsComponent { + public fallbackAdapter = \\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\` +@Injectable() +export class StatusContentFallbackAdapter implements OnDestroy, IHasComponent { + + protected readonly destroy$ = new Subject(); + protected componentId: string; + + constructor(@Inject(PIZZAGNA_EVENT_BUS) protected eventBus: EventBus, + protected pizzagnaService: PizzagnaService) { + this.eventBus.getStream(DATA_SOURCE_OUTPUT) + .pipe(takeUntil(this.destroy$)).subscribe((event: IEvent>) => { + this.handleDataSourceOutput(event); + }); + } + + public ngOnDestroy(): void { + this.destroy$.next(); + this.destroy$.complete(); + } + + public setComponent(component: any, componentId: string) { + this.componentId = componentId; + } + + protected handleDataSourceOutput(event: IEvent>) { + this.pizzagnaService.setProperty({ + componentId: this.componentId, + propertyPath: ["fallbackKey"], + pizzagnaKey: PizzagnaLayer.Data, + }, typeof event.payload?.error?.type !== "undefined" ? event.payload?.error?.type.toString() : undefined); + } +}\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\`; + public errorsMap = \\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\` +export const ERROR_FALLBACK_MAP: Record = { + [HttpStatusCode.Unknown]: ErrorNodeKey.ErrorUnknown, + [HttpStatusCode.Forbidden]: ErrorNodeKey.ErrorForbidden, + [HttpStatusCode.NotFound]: ErrorNodeKey.ErrorNotFound, +}; +\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\`; + public errorNodes = \\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\` +export const ERROR_NODES: Record = { + [ErrorNodeKey.ErrorUnknown]: { + id: ErrorNodeKey.ErrorUnknown, + componentType: WidgetErrorComponent.lateLoadKey, + properties: { + image: "no-data-to-show", + title: $localize\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\`Whoops, something went wrong\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\`, + description: $localize\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\`There was an unexpected error.\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\`, + } as IWidgetErrorDisplayProperties, + }, + [ErrorNodeKey.ErrorForbidden]: { + id: ErrorNodeKey.ErrorForbidden, + componentType: WidgetErrorComponent.lateLoadKey, + properties: { + image: "no-data-to-show", + title: $localize\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\`403 - Forbidden\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\`, + description: $localize\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\`The requested action was forbidden.\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\`, + } as IWidgetErrorDisplayProperties, + }, + [ErrorNodeKey.ErrorNotFound]: { + id: ErrorNodeKey.ErrorNotFound, + componentType: WidgetErrorComponent.lateLoadKey, + properties: { + image: "no-data-to-show", + title: $localize\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\`404 - Not Found\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\`, + description: $localize\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\`The requested resource could not be found.\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\`, + } as IWidgetErrorDisplayProperties, + }, +};\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\`; + public widgetBodyContentNodesSignature = \\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\` +/** + * Retrieves an index of the basic widget body content nodes including fallback nodes + * + * @param mainContentNodeKey The key corresponding to the main body content node + * @param fallbackAdapterId The id for the adapter responsible for activating fallback content in case of an error + * @param fallbackMap A map of node keys to fallback content definitions + * @param fallbackNodes An index of fallback content definitions + * + * @returns An index of component configurations + */ +export function widgetBodyContentNodes( + mainContentNodeKey: string, + fallbackAdapterId = NOVA_STATUS_CONTENT_FALLBACK_ADAPTER, + fallbackMap: Record = ERROR_FALLBACK_MAP, + fallbackNodes: Record = ERROR_NODES +): Record { ... } +\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\`; +} +\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\`, + "tutorials/widget-error-handling/widget-error-handling.component.ts": \\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\`// © 2022 SolarWinds Worldwide, LLC. All rights reserved. +// +// Permission is hereby granted, free of charge, to any person obtaining a copy +// of this software and associated documentation files (the "Software"), to +// deal in the Software without restriction, including without limitation the +// rights to use, copy, modify, merge, publish, distribute, sublicense, and/or +// sell copies of the Software, and to permit persons to whom the Software is +// furnished to do so, subject to the following conditions: +// +// The above copyright notice and this permission notice shall be included in +// all copies or substantial portions of the Software. +// +// THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR +// IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, +// FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE +// AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER +// LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, +// OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN +// THE SOFTWARE. + +import { HttpClient, HttpErrorResponse } from "@angular/common/http"; +import { + ChangeDetectorRef, + Component, + Injectable, + OnDestroy, + OnInit, +} from "@angular/core"; +import { GridsterConfig, GridsterItem } from "angular-gridster2"; +import { BehaviorSubject } from "rxjs"; +import { finalize } from "rxjs/operators"; + +import { DataSourceService, IFilteringOutputs } from "@nova-ui/bits"; +import { + DATA_SOURCE, + DEFAULT_PIZZAGNA_ROOT, + HttpStatusCode, + IDashboard, + IKpiData, + IProviderConfiguration, + IRefresherProperties, + IWidget, + IWidgets, + KpiComponent, + NOVA_KPI_DATASOURCE_ADAPTER, + PizzagnaLayer, + ProviderRegistryService, + WellKnownPathKey, + WellKnownProviders, + WidgetTypesService, +} from "@nova-ui/dashboards"; + +/** + * A simple KPI data source to retrieve the average rating of Harry Potter and the Sorcerer's Stone (book) via googleapis + */ +@Injectable() +export class AverageRatingKpiDataSource + extends DataSourceService + implements OnDestroy +{ + // This is the ID we'll use to identify the provider + public static providerId = "AverageRatingKpiDataSource"; + + // Use this subject to communicate the data source's busy state + public busy = new BehaviorSubject(false); + + constructor(private http: HttpClient) { + super(); + } + + // In this example, getFilteredData is invoked every 10 minutes (Take a look at the refresher + // provider definition in the widget configuration below to see how the interval is set) + public async getFilteredData(): Promise { + this.busy.next(true); + return new Promise((resolve) => { + // *** Make a resource request to an external API (if needed) + this.http + .get("https://www.googleapis.com/books/v1/volumes/5MQFrgEACAAJ") + .pipe(finalize(() => this.busy.next(false))) + .subscribe({ + next: (data: any) => { + resolve({ + result: { + value: data.volumeInfo.averageRating, + }, + }); + }, + error: (error: HttpErrorResponse) => { + resolve({ + result: null, + error: { + type: error.status, + }, + }); + }, + }); + }); + } + + public ngOnDestroy(): void { + this.outputsSubject.complete(); + } +} + +/** + * A simple KPI data source to retrieve the average rating of Harry Potter and the Sorcerer's Stone (book) via googleapis + */ +@Injectable() +export class ErrorUnknownDataSource + extends DataSourceService + implements OnDestroy +{ + // This is the ID we'll use to identify the provider + public static providerId = "ErrorUnknownDataSource"; + + // Use this subject to communicate the data source's busy state + public busy = new BehaviorSubject(false); + + // In this example, getFilteredData is invoked every 10 minutes (Take a look at the refresher + // provider definition in the widget configuration below to see how the interval is set) + public async getFilteredData(): Promise { + this.busy.next(true); + const mockError = { + result: null, + error: { type: HttpStatusCode.Unknown }, + }; + this.busy.next(false); + return mockError; + } + + public ngOnDestroy(): void { + this.outputsSubject.complete(); + } +} + +/** + * A simple KPI data source to retrieve the ratings count of Harry Potter and the Sorcerer's Stone (book) via googleapis + */ +@Injectable() +export class ErrorForbiddenDataSource + extends DataSourceService + implements OnDestroy +{ + public static providerId = "ErrorForbiddenDataSource"; + + // Use this subject to communicate the data source's busy state + public busy = new BehaviorSubject(false); + + constructor(private http: HttpClient) { + super(); + } + + public async getFilteredData(): Promise { + this.busy.next(true); + // generate a 403 + return new Promise((resolve) => { + this.http + .get( + "http://www.mocky.io/v2/5ecc724a3200000f0023614a?mocky-delay=4000ms" + ) + .pipe(finalize(() => this.busy.next(false))) + .subscribe({ + error: (error: HttpErrorResponse) => { + resolve({ + result: null, + error: { + type: error.status, + }, + }); + }, + }); + }); + } + + public ngOnDestroy(): void { + this.outputsSubject.complete(); + } +} + +/** + * A simple KPI data source to retrieve the ratings count of Harry Potter and the Sorcerer's Stone (book) via googleapis + */ +@Injectable() +export class ErrorNotFoundDataSource + extends DataSourceService + implements OnDestroy +{ + public static providerId = "ErrorNotFoundDataSource"; + + // Use this subject to communicate the data source's busy state + public busy = new BehaviorSubject(false); + + constructor(private http: HttpClient) { + super(); + } + + public async getFilteredData(): Promise { + this.busy.next(true); + // generate a 404 + return new Promise((resolve) => { + this.http + .get( + "http://www.mocky.io/v2/5ec6bfd93200007800d75100?mocky-delay=1000ms" + ) + .pipe(finalize(() => this.busy.next(false))) + .subscribe({ + error: (error: HttpErrorResponse) => { + resolve({ + result: null, + error: { + type: error.status, + }, + }); + }, + }); + }); + } + + public ngOnDestroy(): void { + this.outputsSubject.complete(); + } +} + +/** + * A component that instantiates the dashboard + */ +@Component({ + selector: "widget-error-handling", + templateUrl: "./widget-error-handling.component.html", + styleUrls: ["./widget-error-handling.component.less"], + standalone: false, +}) +export class WidgetErrorHandlingComponent implements OnInit { + // This variable will hold all the data needed to define the layout and behavior of the widgets. + // Pass this to the dashboard component's dashboard input in the template. + public dashboard: IDashboard | undefined; + + // Angular gridster requires a configuration object even if it's empty. + // Pass this to the dashboard component's gridsterConfig input in the template. + public gridsterConfig: GridsterConfig = {}; + + // Boolean which dashboard takes in as an input if its true it allows you to move widgets around. + public editMode: boolean = false; + + constructor( + // WidgetTypesService provides the widget's necessary structure information + private widgetTypesService: WidgetTypesService, + + // In general, the ProviderRegistryService is used for making entities available for injection into dynamically loaded components. + private providerRegistry: ProviderRegistryService, + private changeDetectorRef: ChangeDetectorRef + ) {} + + public ngOnInit(): void { + // Grab the widget's default template which will be needed as a parameter for setNode. + const widgetTemplate = this.widgetTypesService.getWidgetType("kpi", 1); + // Register our data sources as dropdown options in the widget editor/configurator + // Note: This could also be done in the parent module's constructor so that + // multiple dashboards could have access to the same widget template modification. + this.widgetTypesService.setNode( + // This is the template we grabbed above with getWidgetType + widgetTemplate, + // We are setting the editor/configurator part of the widget template + "configurator", + // This indicates which node you are changing and we want to change + // the data source providers available for selection in the editor. + WellKnownPathKey.DataSourceProviders, + // We are setting the data sources available for selection in the editor + [ + ErrorUnknownDataSource.providerId, + ErrorForbiddenDataSource.providerId, + ErrorNotFoundDataSource.providerId, + AverageRatingKpiDataSource.providerId, + ] + ); + + // Register the data sources available for injection into the KPI tiles. + // Note: Each tile of a KPI widget is assigned its own instance of a data source + this.providerRegistry.setProviders({ + [ErrorUnknownDataSource.providerId]: { + provide: DATA_SOURCE, + useClass: ErrorUnknownDataSource, + // Any dependencies that need to be injected into the provider must be listed here + deps: [HttpClient], + }, + [ErrorForbiddenDataSource.providerId]: { + provide: DATA_SOURCE, + useClass: ErrorForbiddenDataSource, + deps: [HttpClient], + }, + [ErrorNotFoundDataSource.providerId]: { + provide: DATA_SOURCE, + useClass: ErrorNotFoundDataSource, + deps: [HttpClient], + }, + [AverageRatingKpiDataSource.providerId]: { + provide: DATA_SOURCE, + useClass: AverageRatingKpiDataSource, + // Any dependencies that need to be injected into the provider must be listed here + deps: [HttpClient], + }, + }); + + this.initializeDashboard(); + } + + /** Used for restoring widgets state */ + public reInitializeDashboard(): void { + // destroys the components and their providers so the dashboard can re init data + this.dashboard = undefined; + this.changeDetectorRef.detectChanges(); + + this.initializeDashboard(); + } + + public initializeDashboard(): void { + // We're using a static configuration object for this example (see widgetConfig at the bottom of the file), + // but this is where the widget's configuration could potentially be populated from a database + const kpiWidget = widgetConfig; + const widgetIndex: IWidgets = { + // Complete the KPI widget with information coming from its type definition + [kpiWidget.id]: + this.widgetTypesService.mergeWithWidgetType(kpiWidget), + }; + + // Setting the widget dimensions and position (this is for gridster) + const positions: Record = { + [kpiWidget.id]: { + cols: 4, + rows: 6, + y: 0, + x: 0, + }, + }; + + // Finally, assigning the variables we created above to the dashboard + this.dashboard = { + positions, + widgets: widgetIndex, + }; + } +} + +const widgetConfig: IWidget = { + id: "widget1", + type: "kpi", + pizzagna: { + [PizzagnaLayer.Configuration]: { + [DEFAULT_PIZZAGNA_ROOT]: { + providers: { + [WellKnownProviders.Refresher]: { + properties: { + // Configuring the refresher interval so that our data source is invoked every ten minutes + interval: 60 * 10, + enabled: true, + } as IRefresherProperties, + } as Partial, + }, + }, + header: { + properties: { + title: "Harry Potter and the Sorcerer's Stone", + subtitle: "By J. K. Rowling", + }, + }, + tiles: { + properties: { + nodes: ["kpi1"], + }, + }, + kpi1: { + id: "kpi1", + componentType: KpiComponent.lateLoadKey, + properties: { + widgetData: { + units: "out of 5 Stars", + label: "Average Rating", + }, + }, + providers: { + [WellKnownProviders.DataSource]: { + // Setting the data source providerId for the tile with id "kpi1" + providerId: ErrorUnknownDataSource.providerId, + } as IProviderConfiguration, + [WellKnownProviders.Adapter]: { + providerId: NOVA_KPI_DATASOURCE_ADAPTER, + properties: { + componentId: "kpi1", + propertyPath: "widgetData", + }, + } as IProviderConfiguration, + }, + }, + }, + }, +}; +\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\`, + "tutorials/widget-error-handling/widget-error-handling.module.ts": \\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\`// © 2022 SolarWinds Worldwide, LLC. All rights reserved. +// +// Permission is hereby granted, free of charge, to any person obtaining a copy +// of this software and associated documentation files (the "Software"), to +// deal in the Software without restriction, including without limitation the +// rights to use, copy, modify, merge, publish, distribute, sublicense, and/or +// sell copies of the Software, and to permit persons to whom the Software is +// furnished to do so, subject to the following conditions: +// +// The above copyright notice and this permission notice shall be included in +// all copies or substantial portions of the Software. +// +// THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR +// IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, +// FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE +// AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER +// LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, +// OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN +// THE SOFTWARE. + +import { CommonModule } from "@angular/common"; +import { HttpClientModule } from "@angular/common/http"; +import { NgModule } from "@angular/core"; +import { ReactiveFormsModule } from "@angular/forms"; +import { RouterModule } from "@angular/router"; + +import { + NuiButtonModule, + NuiDocsModule, + NuiFormFieldModule, + NuiIconModule, + NuiMessageModule, + NuiSwitchModule, + NuiTextboxModule, + DEMO_PATH_TOKEN, +} from "@nova-ui/bits"; +import { + NuiDashboardConfiguratorModule, + NuiDashboardsModule, +} from "@nova-ui/dashboards"; + +import { WidgetErrorHandlingDocsComponent } from "./widget-error-handling-docs.component"; +import { WidgetErrorHandlingComponent } from "./widget-error-handling.component"; +import { getDemoFiles } from "../../../../demo-files-factory"; + +const routes = [ + { + path: "", + component: WidgetErrorHandlingDocsComponent, + data: { + srlc: { + hideIndicator: true, + }, + showThemeSwitcher: true, + }, + }, + { + path: "example", + component: WidgetErrorHandlingComponent, + data: { + srlc: { + hideIndicator: true, + }, + }, + }, +]; + +@NgModule({ + imports: [ + CommonModule, + ReactiveFormsModule, + HttpClientModule, + NuiButtonModule, + NuiDashboardsModule, + NuiDashboardConfiguratorModule, + NuiDocsModule, + NuiFormFieldModule, + NuiIconModule, + NuiMessageModule, + NuiIconModule, + NuiTextboxModule, + NuiIconModule, + NuiSwitchModule, + RouterModule.forChild(routes), + ], + declarations: [ + WidgetErrorHandlingDocsComponent, + WidgetErrorHandlingComponent, + ], + providers: [ + { + provide: DEMO_PATH_TOKEN, + useValue: getDemoFiles("widget-error-handling"), + }, + ], +}) +export default class WidgetErrorHandlingModule {} +\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\`, + "types.ts": \\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\`// © 2022 SolarWinds Worldwide, LLC. All rights reserved. +// +// Permission is hereby granted, free of charge, to any person obtaining a copy +// of this software and associated documentation files (the "Software"), to +// deal in the Software without restriction, including without limitation the +// rights to use, copy, modify, merge, publish, distribute, sublicense, and/or +// sell copies of the Software, and to permit persons to whom the Software is +// furnished to do so, subject to the following conditions: +// +// The above copyright notice and this permission notice shall be included in +// all copies or substantial portions of the Software. +// +// THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR +// IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, +// FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE +// AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER +// LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, +// OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN +// THE SOFTWARE. + +export enum APOLLO_API_NAMESPACE { + COUNTRIES = "countries", +} +\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\`, + "widget-types/drilldown/drilldown-multi-request-widget/drilldown-multi-request-widget-example.component.ts": \\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\`// © 2022 SolarWinds Worldwide, LLC. All rights reserved. +// +// Permission is hereby granted, free of charge, to any person obtaining a copy +// of this software and associated documentation files (the "Software"), to +// deal in the Software without restriction, including without limitation the +// rights to use, copy, modify, merge, publish, distribute, sublicense, and/or +// sell copies of the Software, and to permit persons to whom the Software is +// furnished to do so, subject to the following conditions: +// +// The above copyright notice and this permission notice shall be included in +// all copies or substantial portions of the Software. +// +// THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR +// IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, +// FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE +// AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER +// LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, +// OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN +// THE SOFTWARE. + +import { HttpClient } from "@angular/common/http"; +import { + ChangeDetectorRef, + Component, + Injectable, + OnDestroy, + OnInit, +} from "@angular/core"; +import { GridsterConfig, GridsterItem } from "angular-gridster2"; +import { Apollo, gql } from "apollo-angular"; +import { BehaviorSubject, Observable, of, Subject } from "rxjs"; +// eslint-disable-next-line import/no-deprecated +import { finalize, map, switchMap, tap } from "rxjs/operators"; + +import { + DataSourceService, + IconStatus, + IDataField, + IFilters, + INovaFilters, +} from "@nova-ui/bits"; +import { + DATA_SOURCE, + DEFAULT_PIZZAGNA_ROOT, + IDashboard, + IDrilldownComponentsConfiguration, + IListWidgetConfiguration, + IProviderConfiguration, + IWidget, + IWidgets, + ListGroupItemComponent, + ListLeafItemComponent, + NOVA_DRILLDOWN_DATASOURCE_ADAPTER, + PizzagnaLayer, + ProviderRegistryService, + WellKnownPathKey, + WellKnownProviders, + WidgetTypesService, +} from "@nova-ui/dashboards"; + +import { APOLLO_API_NAMESPACE } from "../../../types"; + +/** + * A simple KPI data source to retrieve the average rating of Harry Potter and the Sorcerer's Stone (book) via googleapis + */ +@Injectable() +export class DrilldownDataSource + extends DataSourceService + implements OnDestroy +{ + // This is the ID we'll use to identify the provider + public static providerId = "DrilldownDataSource"; + + // Use this subject to communicate the data source's busy state + public busy = new BehaviorSubject(false); + + public dataFields: Partial[] = [ + { id: "Region", label: "Region name" }, + { id: "Subregion", label: "Subregion name" }, + ]; + + private drillState: string[] = []; + private groupBy: string[]; + private cache: any; + private lastDrillState: string[] = []; + private leafGroup: string = "country"; + private applyFilters$ = new Subject(); + + constructor(private http: HttpClient, private apollo: Apollo) { + super(); + + // TODO: remove Partial in vNext after marking dataType field as optional - NUI-5838 + ( + this.dataFieldsConfig.dataFields$ as BehaviorSubject< + Partial[] + > + ).next(this.dataFields); + + this.applyFilters$ + // eslint-disable-next-line import/no-deprecated + .pipe(switchMap((filters) => this.getData(filters))) + .subscribe(async (res) => { + this.outputsSubject.next(await this.getFilteredData(res)); + }); + } + + private groupedDataHistory: any[] = []; + + // In this example, getFilteredData is invoked every 10 minutes (Take a look at the refresher + // provider definition in the widget configuration below to see how the interval is set) + public async getFilteredData(data: any): Promise { + return of(data) + .pipe( + map((entries) => { + if (this.isDrillDown()) { + const activeDrillLvl = this.drillState.length; + const group = this.groupBy[activeDrillLvl]; + const lastGroupedValue = + this.getTransformedDataForGroup( + entries, + group, + getLast(this.drillState) + ); + + this.groupedDataHistory.push(lastGroupedValue); + + return lastGroupedValue; + } + + const mapIconsToEntries = entries.map((item: any) => ({ + ...item, + icon: "virtual-host", + icon_status: IconStatus.Up, + })); + this.groupedDataHistory.push(mapIconsToEntries); + const widgetInput = this.getOutput(entries); + + return widgetInput; + }) + ) + .toPromise(); + } + + public ngOnDestroy(): void { + this.outputsSubject.complete(); + } + + // redefine parent method + public async applyFilters(): Promise { + this.applyFilters$.next(this.getFilters()); + } + + private getQuery(key: string, value: string) { + const groupToRequestMap: Record = { + Region: \\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\`{ Region { name } }\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\`, + Subregion: \\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\`{ Subregion(filter: { region: { name: "\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\${value}" } } ) { name } }\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\`, + Country: \\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\`{ Country(filter: { subregion: { name: "\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\${value}" } } ) { name capital } }\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\`, + }; + + return gql\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\` + \\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\${groupToRequestMap[key]} + \\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\`; + } + + private getData(filters: INovaFilters): Observable { + this.drillState = filters.drillstate?.value; + this.groupBy = filters.group?.value; + const group = this.groupBy[this.drillState.length]; + const isDrillUp = this.drillState.length < this.lastDrillState.length; + + this.lastDrillState = [...this.drillState]; + + if (!this.drillState.length) { + this.groupedDataHistory.length = 0; + } + + this.busy.next(true); + + if (this.cache && (isDrillUp || this.isHome())) { + return of(this.cache).pipe( + map((data) => data.data[group]), + finalize(() => this.busy.next(false)) + ); + } else { + return this.apollo + .use(APOLLO_API_NAMESPACE.COUNTRIES) + .query({ + query: this.getQuery( + group || this.leafGroup, + getLast(this.drillState) + ), + }) + .pipe( + tap( + (data) => + (this.cache = { + data: { ...this.cache?.data, ...data?.data }, + }) + ), + map((data) => data.data[group || this.leafGroup]), + finalize(() => this.busy.next(false)) + ); + } + } + + private getTransformedDataForGroup( + data: any, + group: string, + drillStateValue: string + ) { + const fallback: string = \\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\`No \\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\${group} for \\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\${drillStateValue}\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\`; + const dataArr = Object.values(data).map((val: any) => ({ + id: val.name || fallback, + label: val.name || fallback, + statuses: [ + { key: "state_ok", value: val.name?.length }, + { + key: "status_unreachable", + value: generateNumberUpTo(100000), + }, + { key: "status_warning", value: generateNumberUpTo(10000) }, + { key: "status_unknown", value: generateNumberUpTo(1000) }, + ], + })); + + return dataArr; + } + + private isHome(): boolean { + return this.drillState.length === 0; + } + + private isDrillDown(): boolean { + return this.drillState.length !== this.groupBy.length; + } + + private getOutput(data: any) { + if (this.isHome()) { + this.groupedDataHistory.length = 0; + } + + const lastHistoryValue = getLast(this.groupedDataHistory); + + if (!lastHistoryValue) { + return data; + } + + return lastHistoryValue[getLast(this.drillState)] || lastHistoryValue; + } +} + +@Component({ + selector: "drilldown-multi-request-widget-example", + templateUrl: "./drilldown-multi-request-widget-example.component.html", + styleUrls: ["./drilldown-multi-request-widget-example.component.less"], + standalone: false, +}) +export class DrilldownMultiRequestWidgetExampleComponent implements OnInit { + // This variable will hold all the data needed to define the layout and behavior of the widgets. + // Pass this to the dashboard component's dashboard input in the template. + public dashboard: IDashboard | undefined; + + // Angular gridster requires a configuration object even if it's empty. + // Pass this to the dashboard component's gridsterConfig input in the template. + public gridsterConfig: GridsterConfig = {}; + + // Boolean passed as an input to the dashboard. When true, widgets can be moved, resized, removed, or edited + public editMode: boolean = false; + + constructor( + // WidgetTypesService provides the widget's necessary structure information + private widgetTypesService: WidgetTypesService, + private providerRegistry: ProviderRegistryService, + private changeDetectorRef: ChangeDetectorRef + ) {} + + public ngOnInit(): void { + // this.prepareNovaDashboards(); + this.initializeDashboard(); + const widgetTemplate = this.widgetTypesService.getWidgetType( + "drilldown", + 1 + ); + this.widgetTypesService.setNode( + widgetTemplate, + "configurator", + WellKnownPathKey.DataSourceProviders, + [DrilldownDataSource.providerId] + ); + + // Registering the data source for injection into the KPI tile. + // Note: Each tile of a KPI widget is assigned its own instance of the data source + this.providerRegistry.setProviders({ + [DrilldownDataSource.providerId]: { + provide: DATA_SOURCE, + useClass: DrilldownDataSource, + // Any dependencies that need to be injected into the provider must be listed here + deps: [HttpClient, Apollo], + }, + }); + } + + /** Used for restoring widgets state */ + public reInitializeDashboard(): void { + // destroys the components and their providers so the dashboard can re init data + this.dashboard = undefined; + this.changeDetectorRef.detectChanges(); + + this.initializeDashboard(); + } + + public initializeDashboard(): void { + // We're using a static configuration object for this example, but this is where + // the widget's configuration could potentially be populated from a database + const drilldownWidget = widgetConfig; + const widgets: IWidgets = { + // Complete the widget with information coming from its type definition + [drilldownWidget.id]: + this.widgetTypesService.mergeWithWidgetType(drilldownWidget), + }; + + // Setting the widget dimensions and position (this is for gridster) + const positions: Record = { + [drilldownWidget.id]: { + cols: 10, + rows: 10, + y: 0, + x: 0, + }, + }; + + // Finally, assigning the variables we created above to the dashboard + this.dashboard = { positions, widgets }; + } +} + +const widgetConfig: IWidget = { + id: "drilldown", + type: "drilldown", + pizzagna: { + [PizzagnaLayer.Configuration]: { + header: { + properties: { + title: "Drilldown Widget", + subtitle: "Countries BY continent THEN currency", + }, + }, + [DEFAULT_PIZZAGNA_ROOT]: { + providers: { + [WellKnownProviders.DataSource]: { + // Setting the data source providerId for the tile with id "kpi1" + providerId: DrilldownDataSource.providerId, + properties: {}, + } as IProviderConfiguration, + }, + }, + listWidget: { + providers: { + [WellKnownProviders.Adapter]: { + providerId: NOVA_DRILLDOWN_DATASOURCE_ADAPTER, + properties: { + // widget + navigationBarId: "navigationBar", + componentId: "listWidget", + dataPath: "data", + + // adapter props + drillstate: [], + groups: ["Region", "Subregion"], + groupBy: ["Region", "Subregion"], + + // components + componentsConfig: { + group: { + componentType: + ListGroupItemComponent.lateLoadKey, + properties: { + dataFieldIds: { + id: "id", + label: "label", + statuses: "statuses", + }, + }, + itemProperties: { + canNavigate: true, + }, + }, + leaf: { + componentType: + ListLeafItemComponent.lateLoadKey, + properties: { + dataFieldIds: { + icon: "icon", + status: "icon_status", + detailedUrl: "capital", + label: "name", + }, + }, + itemProperties: { + canNavigate: false, + }, + }, + } as IDrilldownComponentsConfiguration, + }, + }, + }, + properties: { + configuration: { + // FORMAT: + // componentType: ListLeafItemComponent.lateLoadKey, + // properties: { + // dataFieldIds: { + // icon: "", + // status: "code", + // detailedUrl: "capital", + // label: "name", + // }, + // }, + // + } as IListWidgetConfiguration, + }, + }, + }, + }, +}; + +const getLast = (arr: any[]) => arr[arr.length - 1]; + +const generateNumberUpTo = (upperLimit: number): number => + Math.floor(Math.random() * upperLimit + 1); +\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\`, + "widget-types/drilldown/drilldown-widget/data-mock.ts": \\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\`// © 2022 SolarWinds Worldwide, LLC. All rights reserved. +// +// Permission is hereby granted, free of charge, to any person obtaining a copy +// of this software and associated documentation files (the "Software"), to +// deal in the Software without restriction, including without limitation the +// rights to use, copy, modify, merge, publish, distribute, sublicense, and/or +// sell copies of the Software, and to permit persons to whom the Software is +// furnished to do so, subject to the following conditions: +// +// The above copyright notice and this permission notice shall be included in +// all copies or substantial portions of the Software. +// +// THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR +// IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, +// FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE +// AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER +// LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, +// OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN +// THE SOFTWARE. + +import { IconStatus } from "@nova-ui/bits"; + +export const GRAPH_DATA_MOCK = { + data: { + countries: [ + { + name: "Andorra", + code: "AD", + icon: "virtual-host", + icon_status: IconStatus.Up, + capital: "Andorra la Vella", + continent: { + name: "Europe", + }, + currency: "EUR", + languages: [ + { + name: "Catalan", + }, + ], + url: "https://en.wikipedia.org/wiki/Andorra", + }, + { + name: "United Arab Emirates", + code: "AE", + icon: "virtual-host", + icon_status: IconStatus.Up, + capital: "Abu Dhabi", + continent: { + name: "Asia", + }, + currency: "AED", + languages: [ + { + name: "Arabic", + }, + ], + url: "https://en.wikipedia.org/wiki/United_Arab_Emirates", + }, + { + name: "Afghanistan", + code: "AF", + icon: "virtual-host", + icon_status: IconStatus.Up, + capital: "Kabul", + continent: { + name: "Asia", + }, + currency: "AFN", + languages: [ + { + name: "Pashto", + }, + { + name: "Uzbek", + }, + { + name: "Turkmen", + }, + ], + url: "https://en.wikipedia.org/wiki/Afghanistan", + }, + { + name: "Antigua and Barbuda", + code: "AG", + icon: "virtual-host", + icon_status: IconStatus.Up, + capital: "Saint John's", + continent: { + name: "North America", + }, + currency: "XCD", + languages: [ + { + name: "English", + }, + ], + url: "https://en.wikipedia.org/wiki/Antigua_and_Barbuda", + }, + { + name: "Anguilla", + code: "AI", + icon: "virtual-host", + icon_status: IconStatus.Up, + capital: "The Valley", + continent: { + name: "North America", + }, + currency: "XCD", + languages: [ + { + name: "English", + }, + ], + url: "https://en.wikipedia.org/wiki/Anguilla", + }, + { + name: "Albania", + code: "AL", + icon: "virtual-host", + icon_status: IconStatus.Up, + capital: "Tirana", + continent: { + name: "Europe", + }, + currency: "ALL", + languages: [ + { + name: "Albanian", + }, + ], + url: "https://en.wikipedia.org/wiki/Albania", + }, + { + name: "Armenia", + code: "AM", + icon: "virtual-host", + icon_status: IconStatus.Up, + capital: "Yerevan", + continent: { + name: "Asia", + }, + currency: "AMD", + languages: [ + { + name: "Armenian", + }, + { + name: "Russian", + }, + ], + url: "https://en.wikipedia.org/wiki/Armenia", + }, + { + name: "Angola", + code: "AO", + icon: "virtual-host", + icon_status: IconStatus.Up, + capital: "Luanda", + continent: { + name: "Africa", + }, + currency: "AOA", + languages: [ + { + name: "Portuguese", + }, + ], + }, + { + name: "Antarctica", + code: "AQ", + icon: "virtual-host", + icon_status: IconStatus.Up, + capital: null, + continent: { + name: "Antarctica", + }, + currency: null, + languages: [], + }, + { + name: "Argentina", + code: "AR", + icon: "virtual-host", + icon_status: IconStatus.Up, + capital: "Buenos Aires", + continent: { + name: "South America", + }, + currency: "ARS", + languages: [ + { + name: "Spanish", + }, + { + name: "Guarani", + }, + ], + }, + { + name: "American Samoa", + code: "AS", + icon: "virtual-host", + icon_status: IconStatus.Up, + capital: "Pago Pago", + continent: { + name: "Oceania", + }, + currency: "USD", + languages: [ + { + name: "English", + }, + { + name: "Samoan", + }, + ], + }, + { + name: "Austria", + code: "AT", + icon: "virtual-host", + icon_status: IconStatus.Up, + capital: "Vienna", + continent: { + name: "Europe", + }, + currency: "EUR", + languages: [ + { + name: "German", + }, + ], + }, + { + name: "Australia", + code: "AU", + icon: "virtual-host", + icon_status: IconStatus.Up, + capital: "Canberra", + continent: { + name: "Oceania", + }, + currency: "AUD", + languages: [ + { + name: "English", + }, + ], + }, + { + name: "Aruba", + code: "AW", + icon: "virtual-host", + icon_status: IconStatus.Up, + capital: "Oranjestad", + continent: { + name: "North America", + }, + currency: "AWG", + languages: [ + { + name: "Dutch", + }, + { + name: "Panjabi / Punjabi", + }, + ], + }, + { + name: "Åland", + code: "AX", + icon: "virtual-host", + icon_status: IconStatus.Up, + capital: "Mariehamn", + continent: { + name: "Europe", + }, + currency: "EUR", + languages: [ + { + name: "Swedish", + }, + ], + }, + { + name: "Azerbaijan", + code: "AZ", + icon: "virtual-host", + icon_status: IconStatus.Up, + capital: "Baku", + continent: { + name: "Asia", + }, + currency: "AZN", + languages: [ + { + name: "Azerbaijani", + }, + ], + }, + { + name: "Bosnia and Herzegovina", + code: "BA", + icon: "virtual-host", + icon_status: IconStatus.Up, + capital: "Sarajevo", + continent: { + name: "Europe", + }, + currency: "BAM", + languages: [ + { + name: "Bosnian", + }, + { + name: "Croatian", + }, + { + name: "Serbian", + }, + ], + }, + { + name: "Barbados", + code: "BB", + icon: "virtual-host", + icon_status: IconStatus.Up, + capital: "Bridgetown", + continent: { + name: "North America", + }, + currency: "BBD", + languages: [ + { + name: "English", + }, + ], + }, + { + name: "Bangladesh", + code: "BD", + icon: "virtual-host", + icon_status: IconStatus.Up, + capital: "Dhaka", + continent: { + name: "Asia", + }, + currency: "BDT", + languages: [ + { + name: "Bengali", + }, + ], + }, + { + name: "Belgium", + code: "BE", + icon: "virtual-host", + icon_status: IconStatus.Up, + capital: "Brussels", + continent: { + name: "Europe", + }, + currency: "EUR", + languages: [ + { + name: "Dutch", + }, + { + name: "French", + }, + { + name: "German", + }, + ], + }, + { + name: "Burkina Faso", + code: "BF", + icon: "virtual-host", + icon_status: IconStatus.Up, + capital: "Ouagadougou", + continent: { + name: "Africa", + }, + currency: "XOF", + languages: [ + { + name: "French", + }, + { + name: "Peul", + }, + ], + }, + { + name: "Bulgaria", + code: "BG", + icon: "virtual-host", + icon_status: IconStatus.Up, + capital: "Sofia", + continent: { + name: "Europe", + }, + currency: "BGN", + languages: [ + { + name: "Bulgarian", + }, + ], + }, + { + name: "Bahrain", + code: "BH", + icon: "virtual-host", + icon_status: IconStatus.Up, + capital: "Manama", + continent: { + name: "Asia", + }, + currency: "BHD", + languages: [ + { + name: "Arabic", + }, + ], + }, + { + name: "Burundi", + code: "BI", + icon: "virtual-host", + icon_status: IconStatus.Up, + capital: "Bujumbura", + continent: { + name: "Africa", + }, + currency: "BIF", + languages: [ + { + name: "French", + }, + { + name: "Kirundi", + }, + ], + }, + { + name: "Benin", + code: "BJ", + icon: "virtual-host", + icon_status: IconStatus.Up, + capital: "Porto-Novo", + continent: { + name: "Africa", + }, + currency: "XOF", + languages: [ + { + name: "French", + }, + ], + }, + { + name: "Saint Barthélemy", + code: "BL", + icon: "virtual-host", + icon_status: IconStatus.Up, + capital: "Gustavia", + continent: { + name: "North America", + }, + currency: "EUR", + languages: [ + { + name: "French", + }, + ], + }, + { + name: "Bermuda", + code: "BM", + icon: "virtual-host", + icon_status: IconStatus.Up, + capital: "Hamilton", + continent: { + name: "North America", + }, + currency: "BMD", + languages: [ + { + name: "English", + }, + ], + }, + { + name: "Brunei", + code: "BN", + icon: "virtual-host", + icon_status: IconStatus.Up, + capital: "Bandar Seri Begawan", + continent: { + name: "Asia", + }, + currency: "BND", + languages: [ + { + name: "Malay", + }, + ], + }, + { + name: "Bolivia", + code: "BO", + icon: "virtual-host", + icon_status: IconStatus.Up, + capital: "Sucre", + continent: { + name: "South America", + }, + currency: "BOB,BOV", + languages: [ + { + name: "Spanish", + }, + { + name: "Aymara", + }, + { + name: "Quechua", + }, + ], + }, + { + name: "Bonaire", + code: "BQ", + icon: "virtual-host", + icon_status: IconStatus.Up, + capital: "Kralendijk", + continent: { + name: "North America", + }, + currency: "USD", + languages: [ + { + name: "Dutch", + }, + ], + }, + { + name: "Brazil", + code: "BR", + icon: "virtual-host", + icon_status: IconStatus.Up, + capital: "Brasília", + continent: { + name: "South America", + }, + currency: "BRL", + languages: [ + { + name: "Portuguese", + }, + ], + }, + { + name: "Bahamas", + code: "BS", + icon: "virtual-host", + icon_status: IconStatus.Up, + capital: "Nassau", + continent: { + name: "North America", + }, + currency: "BSD", + languages: [ + { + name: "English", + }, + ], + }, + { + name: "Bhutan", + code: "BT", + icon: "virtual-host", + icon_status: IconStatus.Up, + capital: "Thimphu", + continent: { + name: "Asia", + }, + currency: "BTN,INR", + languages: [ + { + name: "Dzongkha", + }, + ], + }, + { + name: "Bouvet Island", + code: "BV", + icon: "virtual-host", + icon_status: IconStatus.Up, + capital: null, + continent: { + name: "Antarctica", + }, + currency: "NOK", + languages: [ + { + name: "Norwegian", + }, + { + name: "Norwegian Bokmål", + }, + { + name: "Norwegian Nynorsk", + }, + ], + }, + { + name: "Botswana", + code: "BW", + icon: "virtual-host", + icon_status: IconStatus.Up, + capital: "Gaborone", + continent: { + name: "Africa", + }, + currency: "BWP", + languages: [ + { + name: "English", + }, + { + name: "Tswana", + }, + ], + }, + { + name: "Belarus", + code: "BY", + icon: "virtual-host", + icon_status: IconStatus.Up, + capital: "Minsk", + continent: { + name: "Europe", + }, + currency: "BYN", + languages: [ + { + name: "Belarusian", + }, + { + name: "Russian", + }, + ], + }, + { + name: "Belize", + code: "BZ", + icon: "virtual-host", + icon_status: IconStatus.Up, + capital: "Belmopan", + continent: { + name: "North America", + }, + currency: "BZD", + languages: [ + { + name: "English", + }, + { + name: "Spanish", + }, + ], + }, + { + name: "Canada", + code: "CA", + icon: "virtual-host", + icon_status: IconStatus.Up, + capital: "Ottawa", + continent: { + name: "North America", + }, + currency: "CAD", + languages: [ + { + name: "English", + }, + { + name: "French", + }, + ], + }, + { + name: "Cocos [Keeling] Islands", + code: "CC", + icon: "virtual-host", + icon_status: IconStatus.Up, + capital: "West Island", + continent: { + name: "Asia", + }, + currency: "AUD", + languages: [ + { + name: "English", + }, + ], + }, + { + name: "Democratic Republic of the Congo", + code: "CD", + icon: "virtual-host", + icon_status: IconStatus.Up, + capital: "Kinshasa", + continent: { + name: "Africa", + }, + currency: "CDF", + languages: [ + { + name: "French", + }, + { + name: "Lingala", + }, + { + name: "Kongo", + }, + { + name: "Swahili", + }, + { + name: "Luba-Katanga", + }, + ], + }, + { + name: "Central African Republic", + code: "CF", + icon: "virtual-host", + icon_status: IconStatus.Up, + capital: "Bangui", + continent: { + name: "Africa", + }, + currency: "XAF", + languages: [ + { + name: "French", + }, + { + name: "Sango", + }, + ], + }, + { + name: "Republic of the Congo", + code: "CG", + icon: "virtual-host", + icon_status: IconStatus.Up, + capital: "Brazzaville", + continent: { + name: "Africa", + }, + currency: "XAF", + languages: [ + { + name: "French", + }, + { + name: "Lingala", + }, + ], + }, + { + name: "Switzerland", + code: "CH", + icon: "virtual-host", + icon_status: IconStatus.Up, + capital: "Bern", + continent: { + name: "Europe", + }, + currency: "CHE,CHF,CHW", + languages: [ + { + name: "German", + }, + { + name: "French", + }, + { + name: "Italian", + }, + ], + }, + { + name: "Ivory Coast", + code: "CI", + icon: "virtual-host", + icon_status: IconStatus.Up, + capital: "Yamoussoukro", + continent: { + name: "Africa", + }, + currency: "XOF", + languages: [ + { + name: "French", + }, + ], + }, + { + name: "Cook Islands", + code: "CK", + icon: "virtual-host", + icon_status: IconStatus.Up, + capital: "Avarua", + continent: { + name: "Oceania", + }, + currency: "NZD", + languages: [ + { + name: "English", + }, + ], + }, + { + name: "Chile", + code: "CL", + icon: "virtual-host", + icon_status: IconStatus.Up, + capital: "Santiago", + continent: { + name: "South America", + }, + currency: "CLF,CLP", + languages: [ + { + name: "Spanish", + }, + ], + }, + { + name: "Cameroon", + code: "CM", + icon: "virtual-host", + icon_status: IconStatus.Up, + capital: "Yaoundé", + continent: { + name: "Africa", + }, + currency: "XAF", + languages: [ + { + name: "English", + }, + { + name: "French", + }, + ], + }, + { + name: "China", + code: "CN", + icon: "virtual-host", + icon_status: IconStatus.Up, + capital: "Beijing", + continent: { + name: "Asia", + }, + currency: "CNY", + languages: [ + { + name: "Chinese", + }, + ], + }, + { + name: "Colombia", + code: "CO", + icon: "virtual-host", + icon_status: IconStatus.Up, + capital: "Bogotá", + continent: { + name: "South America", + }, + currency: "COP", + languages: [ + { + name: "Spanish", + }, + ], + }, + { + name: "Costa Rica", + code: "CR", + icon: "virtual-host", + icon_status: IconStatus.Up, + capital: "San José", + continent: { + name: "North America", + }, + currency: "CRC", + languages: [ + { + name: "Spanish", + }, + ], + }, + { + name: "Cuba", + code: "CU", + icon: "virtual-host", + icon_status: IconStatus.Up, + capital: "Havana", + continent: { + name: "North America", + }, + currency: "CUC,CUP", + languages: [ + { + name: "Spanish", + }, + ], + }, + { + name: "Cape Verde", + code: "CV", + icon: "virtual-host", + icon_status: IconStatus.Up, + capital: "Praia", + continent: { + name: "Africa", + }, + currency: "CVE", + languages: [ + { + name: "Portuguese", + }, + ], + }, + { + name: "Curacao", + code: "CW", + icon: "virtual-host", + icon_status: IconStatus.Up, + capital: "Willemstad", + continent: { + name: "North America", + }, + currency: "ANG", + languages: [ + { + name: "Dutch", + }, + { + name: "Panjabi / Punjabi", + }, + { + name: "English", + }, + ], + }, + { + name: "Christmas Island", + code: "CX", + icon: "virtual-host", + icon_status: IconStatus.Up, + capital: "Flying Fish Cove", + continent: { + name: "Asia", + }, + currency: "AUD", + languages: [ + { + name: "English", + }, + ], + }, + { + name: "Cyprus", + code: "CY", + icon: "virtual-host", + icon_status: IconStatus.Up, + capital: "Nicosia", + continent: { + name: "Europe", + }, + currency: "EUR", + languages: [ + { + name: "Greek", + }, + { + name: "Turkish", + }, + { + name: "Armenian", + }, + ], + }, + { + name: "Czech Republic", + code: "CZ", + icon: "virtual-host", + icon_status: IconStatus.Up, + capital: "Prague", + continent: { + name: "Europe", + }, + currency: "CZK", + languages: [ + { + name: "Czech", + }, + { + name: "Slovak", + }, + ], + }, + { + name: "Germany", + code: "DE", + icon: "virtual-host", + icon_status: IconStatus.Up, + capital: "Berlin", + continent: { + name: "Europe", + }, + currency: "EUR", + languages: [ + { + name: "German", + }, + ], + }, + { + name: "Djibouti", + code: "DJ", + icon: "virtual-host", + icon_status: IconStatus.Up, + capital: "Djibouti", + continent: { + name: "Africa", + }, + currency: "DJF", + languages: [ + { + name: "French", + }, + { + name: "Arabic", + }, + ], + }, + { + name: "Denmark", + code: "DK", + icon: "virtual-host", + icon_status: IconStatus.Up, + capital: "Copenhagen", + continent: { + name: "Europe", + }, + currency: "DKK", + languages: [ + { + name: "Danish", + }, + ], + }, + { + name: "Dominica", + code: "DM", + icon: "virtual-host", + icon_status: IconStatus.Up, + capital: "Roseau", + continent: { + name: "North America", + }, + currency: "XCD", + languages: [ + { + name: "English", + }, + ], + }, + { + name: "Dominican Republic", + code: "DO", + icon: "virtual-host", + icon_status: IconStatus.Up, + capital: "Santo Domingo", + continent: { + name: "North America", + }, + currency: "DOP", + languages: [ + { + name: "Spanish", + }, + ], + }, + { + name: "Algeria", + code: "DZ", + icon: "virtual-host", + icon_status: IconStatus.Up, + capital: "Algiers", + continent: { + name: "Africa", + }, + currency: "DZD", + languages: [ + { + name: "Arabic", + }, + ], + }, + { + name: "Ecuador", + code: "EC", + icon: "virtual-host", + icon_status: IconStatus.Up, + capital: "Quito", + continent: { + name: "South America", + }, + currency: "USD", + languages: [ + { + name: "Spanish", + }, + ], + }, + { + name: "Estonia", + code: "EE", + icon: "virtual-host", + icon_status: IconStatus.Up, + capital: "Tallinn", + continent: { + name: "Europe", + }, + currency: "EUR", + languages: [ + { + name: "Estonian", + }, + ], + }, + { + name: "Egypt", + code: "EG", + icon: "virtual-host", + icon_status: IconStatus.Up, + capital: "Cairo", + continent: { + name: "Africa", + }, + currency: "EGP", + languages: [ + { + name: "Arabic", + }, + ], + }, + { + name: "Western Sahara", + code: "EH", + icon: "virtual-host", + icon_status: IconStatus.Up, + capital: "El Aaiún", + continent: { + name: "Africa", + }, + currency: "MAD,DZD,MRU", + languages: [ + { + name: "Spanish", + }, + ], + }, + { + name: "Eritrea", + code: "ER", + icon: "virtual-host", + icon_status: IconStatus.Up, + capital: "Asmara", + continent: { + name: "Africa", + }, + currency: "ERN", + languages: [ + { + name: "Tigrinya", + }, + { + name: "Arabic", + }, + { + name: "English", + }, + ], + }, + { + name: "Spain", + code: "ES", + icon: "virtual-host", + icon_status: IconStatus.Up, + capital: "Madrid", + continent: { + name: "Europe", + }, + currency: "EUR", + languages: [ + { + name: "Spanish", + }, + { + name: "Basque", + }, + { + name: "Catalan", + }, + { + name: "Galician", + }, + { + name: "Occitan", + }, + ], + }, + { + name: "Ethiopia", + code: "ET", + icon: "virtual-host", + icon_status: IconStatus.Up, + capital: "Addis Ababa", + continent: { + name: "Africa", + }, + currency: "ETB", + languages: [ + { + name: "Amharic", + }, + ], + }, + { + name: "Finland", + code: "FI", + icon: "virtual-host", + icon_status: IconStatus.Up, + capital: "Helsinki", + continent: { + name: "Europe", + }, + currency: "EUR", + languages: [ + { + name: "Finnish", + }, + { + name: "Swedish", + }, + ], + }, + { + name: "Fiji", + code: "FJ", + icon: "virtual-host", + icon_status: IconStatus.Up, + capital: "Suva", + continent: { + name: "Oceania", + }, + currency: "FJD", + languages: [ + { + name: "English", + }, + { + name: "Fijian", + }, + { + name: "Hindi", + }, + { + name: "Urdu", + }, + ], + }, + { + name: "Falkland Islands", + code: "FK", + icon: "virtual-host", + icon_status: IconStatus.Up, + capital: "Stanley", + continent: { + name: "South America", + }, + currency: "FKP", + languages: [ + { + name: "English", + }, + ], + }, + { + name: "Micronesia", + code: "FM", + icon: "virtual-host", + icon_status: IconStatus.Up, + capital: "Palikir", + continent: { + name: "Oceania", + }, + currency: "USD", + languages: [ + { + name: "English", + }, + ], + }, + { + name: "Faroe Islands", + code: "FO", + icon: "virtual-host", + icon_status: IconStatus.Up, + capital: "Tórshavn", + continent: { + name: "Europe", + }, + currency: "DKK", + languages: [ + { + name: "Faroese", + }, + ], + }, + { + name: "France", + code: "FR", + icon: "virtual-host", + icon_status: IconStatus.Up, + capital: "Paris", + continent: { + name: "Europe", + }, + currency: "EUR", + languages: [ + { + name: "French", + }, + ], + }, + { + name: "Gabon", + code: "GA", + icon: "virtual-host", + icon_status: IconStatus.Up, + capital: "Libreville", + continent: { + name: "Africa", + }, + currency: "XAF", + languages: [ + { + name: "French", + }, + ], + }, + { + name: "United Kingdom", + code: "GB", + icon: "virtual-host", + icon_status: IconStatus.Up, + capital: "London", + continent: { + name: "Europe", + }, + currency: "GBP", + languages: [ + { + name: "English", + }, + ], + }, + { + name: "Grenada", + code: "GD", + icon: "virtual-host", + icon_status: IconStatus.Up, + capital: "St. George's", + continent: { + name: "North America", + }, + currency: "XCD", + languages: [ + { + name: "English", + }, + ], + }, + { + name: "Georgia", + code: "GE", + icon: "virtual-host", + icon_status: IconStatus.Up, + capital: "Tbilisi", + continent: { + name: "Asia", + }, + currency: "GEL", + languages: [ + { + name: "Georgian", + }, + ], + }, + { + name: "French Guiana", + code: "GF", + icon: "virtual-host", + icon_status: IconStatus.Up, + capital: "Cayenne", + continent: { + name: "South America", + }, + currency: "EUR", + languages: [ + { + name: "French", + }, + ], + }, + { + name: "Guernsey", + code: "GG", + icon: "virtual-host", + icon_status: IconStatus.Up, + capital: "St. Peter Port", + continent: { + name: "Europe", + }, + currency: "GBP", + languages: [ + { + name: "English", + }, + { + name: "French", + }, + ], + }, + { + name: "Ghana", + code: "GH", + icon: "virtual-host", + icon_status: IconStatus.Up, + capital: "Accra", + continent: { + name: "Africa", + }, + currency: "GHS", + languages: [ + { + name: "English", + }, + ], + }, + { + name: "Gibraltar", + code: "GI", + icon: "virtual-host", + icon_status: IconStatus.Up, + capital: "Gibraltar", + continent: { + name: "Europe", + }, + currency: "GIP", + languages: [ + { + name: "English", + }, + ], + }, + { + name: "Greenland", + code: "GL", + icon: "virtual-host", + icon_status: IconStatus.Up, + capital: "Nuuk", + continent: { + name: "North America", + }, + currency: "DKK", + languages: [ + { + name: "Greenlandic", + }, + ], + }, + { + name: "Gambia", + code: "GM", + icon: "virtual-host", + icon_status: IconStatus.Up, + capital: "Banjul", + continent: { + name: "Africa", + }, + currency: "GMD", + languages: [ + { + name: "English", + }, + ], + }, + { + name: "Guinea", + code: "GN", + icon: "virtual-host", + icon_status: IconStatus.Up, + capital: "Conakry", + continent: { + name: "Africa", + }, + currency: "GNF", + languages: [ + { + name: "French", + }, + { + name: "Peul", + }, + ], + }, + { + name: "Guadeloupe", + code: "GP", + icon: "virtual-host", + icon_status: IconStatus.Up, + capital: "Basse-Terre", + continent: { + name: "North America", + }, + currency: "EUR", + languages: [ + { + name: "French", + }, + ], + }, + { + name: "Equatorial Guinea", + code: "GQ", + icon: "virtual-host", + icon_status: IconStatus.Up, + capital: "Malabo", + continent: { + name: "Africa", + }, + currency: "XAF", + languages: [ + { + name: "Spanish", + }, + { + name: "French", + }, + ], + }, + { + name: "Greece", + code: "GR", + icon: "virtual-host", + icon_status: IconStatus.Up, + capital: "Athens", + continent: { + name: "Europe", + }, + currency: "EUR", + languages: [ + { + name: "Greek", + }, + ], + }, + { + name: "South Georgia and the South Sandwich Islands", + code: "GS", + icon: "virtual-host", + icon_status: IconStatus.Up, + capital: "King Edward Point", + continent: { + name: "Antarctica", + }, + currency: "GBP", + languages: [ + { + name: "English", + }, + ], + }, + { + name: "Guatemala", + code: "GT", + icon: "virtual-host", + icon_status: IconStatus.Up, + capital: "Guatemala City", + continent: { + name: "North America", + }, + currency: "GTQ", + languages: [ + { + name: "Spanish", + }, + ], + }, + { + name: "Guam", + code: "GU", + icon: "virtual-host", + icon_status: IconStatus.Up, + capital: "Hagåtña", + continent: { + name: "Oceania", + }, + currency: "USD", + languages: [ + { + name: "English", + }, + { + name: "Chamorro", + }, + { + name: "Spanish", + }, + ], + }, + { + name: "Guinea-Bissau", + code: "GW", + icon: "virtual-host", + icon_status: IconStatus.Up, + capital: "Bissau", + continent: { + name: "Africa", + }, + currency: "XOF", + languages: [ + { + name: "Portuguese", + }, + ], + }, + { + name: "Guyana", + code: "GY", + icon: "virtual-host", + icon_status: IconStatus.Up, + capital: "Georgetown", + continent: { + name: "South America", + }, + currency: "GYD", + languages: [ + { + name: "English", + }, + ], + }, + { + name: "Hong Kong", + code: "HK", + icon: "virtual-host", + icon_status: IconStatus.Up, + capital: "City of Victoria", + continent: { + name: "Asia", + }, + currency: "HKD", + languages: [ + { + name: "Chinese", + }, + { + name: "English", + }, + ], + }, + { + name: "Heard Island and McDonald Islands", + code: "HM", + icon: "virtual-host", + icon_status: IconStatus.Up, + capital: null, + continent: { + name: "Antarctica", + }, + currency: "AUD", + languages: [ + { + name: "English", + }, + ], + }, + { + name: "Honduras", + code: "HN", + icon: "virtual-host", + icon_status: IconStatus.Up, + capital: "Tegucigalpa", + continent: { + name: "North America", + }, + currency: "HNL", + languages: [ + { + name: "Spanish", + }, + ], + }, + { + name: "Croatia", + code: "HR", + icon: "virtual-host", + icon_status: IconStatus.Up, + capital: "Zagreb", + continent: { + name: "Europe", + }, + currency: "HRK", + languages: [ + { + name: "Croatian", + }, + ], + }, + { + name: "Haiti", + code: "HT", + icon: "virtual-host", + icon_status: IconStatus.Up, + capital: "Port-au-Prince", + continent: { + name: "North America", + }, + currency: "HTG,USD", + languages: [ + { + name: "French", + }, + { + name: "Haitian", + }, + ], + }, + { + name: "Hungary", + code: "HU", + icon: "virtual-host", + icon_status: IconStatus.Up, + capital: "Budapest", + continent: { + name: "Europe", + }, + currency: "HUF", + languages: [ + { + name: "Hungarian", + }, + ], + }, + { + name: "Indonesia", + code: "ID", + icon: "virtual-host", + icon_status: IconStatus.Up, + capital: "Jakarta", + continent: { + name: "Asia", + }, + currency: "IDR", + languages: [ + { + name: "Indonesian", + }, + ], + }, + { + name: "Ireland", + code: "IE", + icon: "virtual-host", + icon_status: IconStatus.Up, + capital: "Dublin", + continent: { + name: "Europe", + }, + currency: "EUR", + languages: [ + { + name: "Irish", + }, + { + name: "English", + }, + ], + }, + { + name: "Israel", + code: "IL", + icon: "virtual-host", + icon_status: IconStatus.Up, + capital: "Jerusalem", + continent: { + name: "Asia", + }, + currency: "ILS", + languages: [ + { + name: "Hebrew", + }, + { + name: "Arabic", + }, + ], + }, + { + name: "Isle of Man", + code: "IM", + icon: "virtual-host", + icon_status: IconStatus.Up, + capital: "Douglas", + continent: { + name: "Europe", + }, + currency: "GBP", + languages: [ + { + name: "English", + }, + { + name: "Manx", + }, + ], + }, + { + name: "India", + code: "IN", + icon: "virtual-host", + icon_status: IconStatus.Up, + capital: "New Delhi", + continent: { + name: "Asia", + }, + currency: "INR", + languages: [ + { + name: "Hindi", + }, + { + name: "English", + }, + ], + }, + { + name: "British Indian Ocean Territory", + code: "IO", + icon: "virtual-host", + icon_status: IconStatus.Up, + capital: "Diego Garcia", + continent: { + name: "Asia", + }, + currency: "USD", + languages: [ + { + name: "English", + }, + ], + }, + { + name: "Iraq", + code: "IQ", + icon: "virtual-host", + icon_status: IconStatus.Up, + capital: "Baghdad", + continent: { + name: "Asia", + }, + currency: "IQD", + languages: [ + { + name: "Arabic", + }, + { + name: "Kurdish", + }, + ], + }, + { + name: "Iran", + code: "IR", + icon: "virtual-host", + icon_status: IconStatus.Up, + capital: "Tehran", + continent: { + name: "Asia", + }, + currency: "IRR", + languages: [ + { + name: "Persian", + }, + ], + }, + { + name: "Iceland", + code: "IS", + icon: "virtual-host", + icon_status: IconStatus.Up, + capital: "Reykjavik", + continent: { + name: "Europe", + }, + currency: "ISK", + languages: [ + { + name: "Icelandic", + }, + ], + }, + { + name: "Italy", + code: "IT", + icon: "virtual-host", + icon_status: IconStatus.Up, + capital: "Rome", + continent: { + name: "Europe", + }, + currency: "EUR", + languages: [ + { + name: "Italian", + }, + ], + }, + { + name: "Jersey", + code: "JE", + icon: "virtual-host", + icon_status: IconStatus.Up, + capital: "Saint Helier", + continent: { + name: "Europe", + }, + currency: "GBP", + languages: [ + { + name: "English", + }, + { + name: "French", + }, + ], + }, + { + name: "Jamaica", + code: "JM", + icon: "virtual-host", + icon_status: IconStatus.Up, + capital: "Kingston", + continent: { + name: "North America", + }, + currency: "JMD", + languages: [ + { + name: "English", + }, + ], + }, + { + name: "Jordan", + code: "JO", + icon: "virtual-host", + icon_status: IconStatus.Up, + capital: "Amman", + continent: { + name: "Asia", + }, + currency: "JOD", + languages: [ + { + name: "Arabic", + }, + ], + }, + { + name: "Japan", + code: "JP", + icon: "virtual-host", + icon_status: IconStatus.Up, + capital: "Tokyo", + continent: { + name: "Asia", + }, + currency: "JPY", + languages: [ + { + name: "Japanese", + }, + ], + }, + { + name: "Kenya", + code: "KE", + icon: "virtual-host", + icon_status: IconStatus.Up, + capital: "Nairobi", + continent: { + name: "Africa", + }, + currency: "KES", + languages: [ + { + name: "English", + }, + { + name: "Swahili", + }, + ], + }, + { + name: "Kyrgyzstan", + code: "KG", + icon: "virtual-host", + icon_status: IconStatus.Up, + capital: "Bishkek", + continent: { + name: "Asia", + }, + currency: "KGS", + languages: [ + { + name: "Kirghiz", + }, + { + name: "Russian", + }, + ], + }, + { + name: "Cambodia", + code: "KH", + icon: "virtual-host", + icon_status: IconStatus.Up, + capital: "Phnom Penh", + continent: { + name: "Asia", + }, + currency: "KHR", + languages: [ + { + name: "Cambodian", + }, + ], + }, + { + name: "Kiribati", + code: "KI", + icon: "virtual-host", + icon_status: IconStatus.Up, + capital: "South Tarawa", + continent: { + name: "Oceania", + }, + currency: "AUD", + languages: [ + { + name: "English", + }, + ], + }, + { + name: "Comoros", + code: "KM", + icon: "virtual-host", + icon_status: IconStatus.Up, + capital: "Moroni", + continent: { + name: "Africa", + }, + currency: "KMF", + languages: [ + { + name: "Arabic", + }, + { + name: "French", + }, + ], + }, + { + name: "Saint Kitts and Nevis", + code: "KN", + icon: "virtual-host", + icon_status: IconStatus.Up, + capital: "Basseterre", + continent: { + name: "North America", + }, + currency: "XCD", + languages: [ + { + name: "English", + }, + ], + }, + { + name: "North Korea", + code: "KP", + icon: "virtual-host", + icon_status: IconStatus.Up, + capital: "Pyongyang", + continent: { + name: "Asia", + }, + currency: "KPW", + languages: [ + { + name: "Korean", + }, + ], + }, + { + name: "South Korea", + code: "KR", + icon: "virtual-host", + icon_status: IconStatus.Up, + capital: "Seoul", + continent: { + name: "Asia", + }, + currency: "KRW", + languages: [ + { + name: "Korean", + }, + ], + }, + { + name: "Kuwait", + code: "KW", + icon: "virtual-host", + icon_status: IconStatus.Up, + capital: "Kuwait City", + continent: { + name: "Asia", + }, + currency: "KWD", + languages: [ + { + name: "Arabic", + }, + ], + }, + { + name: "Cayman Islands", + code: "KY", + icon: "virtual-host", + icon_status: IconStatus.Up, + capital: "George Town", + continent: { + name: "North America", + }, + currency: "KYD", + languages: [ + { + name: "English", + }, + ], + }, + { + name: "Kazakhstan", + code: "KZ", + icon: "virtual-host", + icon_status: IconStatus.Up, + capital: "Astana", + continent: { + name: "Asia", + }, + currency: "KZT", + languages: [ + { + name: "Kazakh", + }, + { + name: "Russian", + }, + ], + }, + { + name: "Laos", + code: "LA", + icon: "virtual-host", + icon_status: IconStatus.Up, + capital: "Vientiane", + continent: { + name: "Asia", + }, + currency: "LAK", + languages: [ + { + name: "Laotian", + }, + ], + }, + { + name: "Lebanon", + code: "LB", + icon: "virtual-host", + icon_status: IconStatus.Up, + capital: "Beirut", + continent: { + name: "Asia", + }, + currency: "LBP", + languages: [ + { + name: "Arabic", + }, + { + name: "French", + }, + ], + }, + { + name: "Saint Lucia", + code: "LC", + icon: "virtual-host", + icon_status: IconStatus.Up, + capital: "Castries", + continent: { + name: "North America", + }, + currency: "XCD", + languages: [ + { + name: "English", + }, + ], + }, + { + name: "Liechtenstein", + code: "LI", + icon: "virtual-host", + icon_status: IconStatus.Up, + capital: "Vaduz", + continent: { + name: "Europe", + }, + currency: "CHF", + languages: [ + { + name: "German", + }, + ], + }, + { + name: "Sri Lanka", + code: "LK", + icon: "virtual-host", + icon_status: IconStatus.Up, + capital: "Colombo", + continent: { + name: "Asia", + }, + currency: "LKR", + languages: [ + { + name: "Sinhalese", + }, + { + name: "Tamil", + }, + ], + }, + { + name: "Liberia", + code: "LR", + icon: "virtual-host", + icon_status: IconStatus.Up, + capital: "Monrovia", + continent: { + name: "Africa", + }, + currency: "LRD", + languages: [ + { + name: "English", + }, + ], + }, + { + name: "Lesotho", + code: "LS", + icon: "virtual-host", + icon_status: IconStatus.Up, + capital: "Maseru", + continent: { + name: "Africa", + }, + currency: "LSL,ZAR", + languages: [ + { + name: "English", + }, + { + name: "Southern Sotho", + }, + ], + }, + { + name: "Lithuania", + code: "LT", + icon: "virtual-host", + icon_status: IconStatus.Up, + capital: "Vilnius", + continent: { + name: "Europe", + }, + currency: "EUR", + languages: [ + { + name: "Lithuanian", + }, + ], + }, + { + name: "Luxembourg", + code: "LU", + icon: "virtual-host", + icon_status: IconStatus.Up, + capital: "Luxembourg", + continent: { + name: "Europe", + }, + currency: "EUR", + languages: [ + { + name: "French", + }, + { + name: "German", + }, + { + name: "Luxembourgish", + }, + ], + }, + { + name: "Latvia", + code: "LV", + icon: "virtual-host", + icon_status: IconStatus.Up, + capital: "Riga", + continent: { + name: "Europe", + }, + currency: "EUR", + languages: [ + { + name: "Latvian", + }, + ], + }, + { + name: "Libya", + code: "LY", + icon: "virtual-host", + icon_status: IconStatus.Up, + capital: "Tripoli", + continent: { + name: "Africa", + }, + currency: "LYD", + languages: [ + { + name: "Arabic", + }, + ], + }, + { + name: "Morocco", + code: "MA", + icon: "virtual-host", + icon_status: IconStatus.Up, + capital: "Rabat", + continent: { + name: "Africa", + }, + currency: "MAD", + languages: [ + { + name: "Arabic", + }, + ], + }, + { + name: "Monaco", + code: "MC", + icon: "virtual-host", + icon_status: IconStatus.Up, + capital: "Monaco", + continent: { + name: "Europe", + }, + currency: "EUR", + languages: [ + { + name: "French", + }, + ], + }, + { + name: "Moldova", + code: "MD", + icon: "virtual-host", + icon_status: IconStatus.Up, + capital: "Chișinău", + continent: { + name: "Europe", + }, + currency: "MDL", + languages: [ + { + name: "Romanian", + }, + ], + }, + { + name: "Montenegro", + code: "ME", + icon: "virtual-host", + icon_status: IconStatus.Up, + capital: "Podgorica", + continent: { + name: "Europe", + }, + currency: "EUR", + languages: [ + { + name: "Serbian", + }, + { + name: "Bosnian", + }, + { + name: "Albanian", + }, + { + name: "Croatian", + }, + ], + }, + { + name: "Saint Martin", + code: "MF", + icon: "virtual-host", + icon_status: IconStatus.Up, + capital: "Marigot", + continent: { + name: "North America", + }, + currency: "EUR", + languages: [ + { + name: "English", + }, + { + name: "French", + }, + { + name: "Dutch", + }, + ], + }, + { + name: "Madagascar", + code: "MG", + icon: "virtual-host", + icon_status: IconStatus.Up, + capital: "Antananarivo", + continent: { + name: "Africa", + }, + currency: "MGA", + languages: [ + { + name: "French", + }, + { + name: "Malagasy", + }, + ], + }, + { + name: "Marshall Islands", + code: "MH", + icon: "virtual-host", + icon_status: IconStatus.Up, + capital: "Majuro", + continent: { + name: "Oceania", + }, + currency: "USD", + languages: [ + { + name: "English", + }, + { + name: "Marshallese", + }, + ], + }, + { + name: "North Macedonia", + code: "MK", + icon: "virtual-host", + icon_status: IconStatus.Up, + capital: "Skopje", + continent: { + name: "Europe", + }, + currency: "MKD", + languages: [ + { + name: "Macedonian", + }, + ], + }, + { + name: "Mali", + code: "ML", + icon: "virtual-host", + icon_status: IconStatus.Up, + capital: "Bamako", + continent: { + name: "Africa", + }, + currency: "XOF", + languages: [ + { + name: "French", + }, + ], + }, + { + name: "Myanmar [Burma]", + code: "MM", + icon: "virtual-host", + icon_status: IconStatus.Up, + capital: "Naypyidaw", + continent: { + name: "Asia", + }, + currency: "MMK", + languages: [ + { + name: "Burmese", + }, + ], + }, + { + name: "Mongolia", + code: "MN", + icon: "virtual-host", + icon_status: IconStatus.Up, + capital: "Ulan Bator", + continent: { + name: "Asia", + }, + currency: "MNT", + languages: [ + { + name: "Mongolian", + }, + ], + }, + { + name: "Macao", + code: "MO", + icon: "virtual-host", + icon_status: IconStatus.Up, + capital: null, + continent: { + name: "Asia", + }, + currency: "MOP", + languages: [ + { + name: "Chinese", + }, + { + name: "Portuguese", + }, + ], + }, + { + name: "Northern Mariana Islands", + code: "MP", + icon: "virtual-host", + icon_status: IconStatus.Up, + capital: "Saipan", + continent: { + name: "Oceania", + }, + currency: "USD", + languages: [ + { + name: "English", + }, + { + name: "Chamorro", + }, + ], + }, + { + name: "Martinique", + code: "MQ", + icon: "virtual-host", + icon_status: IconStatus.Up, + capital: "Fort-de-France", + continent: { + name: "North America", + }, + currency: "EUR", + languages: [ + { + name: "French", + }, + ], + }, + { + name: "Mauritania", + code: "MR", + icon: "virtual-host", + icon_status: IconStatus.Up, + capital: "Nouakchott", + continent: { + name: "Africa", + }, + currency: "MRU", + languages: [ + { + name: "Arabic", + }, + ], + }, + { + name: "Montserrat", + code: "MS", + icon: "virtual-host", + icon_status: IconStatus.Up, + capital: "Plymouth", + continent: { + name: "North America", + }, + currency: "XCD", + languages: [ + { + name: "English", + }, + ], + }, + { + name: "Malta", + code: "MT", + icon: "virtual-host", + icon_status: IconStatus.Up, + capital: "Valletta", + continent: { + name: "Europe", + }, + currency: "EUR", + languages: [ + { + name: "Maltese", + }, + { + name: "English", + }, + ], + }, + { + name: "Mauritius", + code: "MU", + icon: "virtual-host", + icon_status: IconStatus.Up, + capital: "Port Louis", + continent: { + name: "Africa", + }, + currency: "MUR", + languages: [ + { + name: "English", + }, + ], + }, + { + name: "Maldives", + code: "MV", + icon: "virtual-host", + icon_status: IconStatus.Up, + capital: "Malé", + continent: { + name: "Asia", + }, + currency: "MVR", + languages: [ + { + name: "Divehi", + }, + ], + }, + { + name: "Malawi", + code: "MW", + icon: "virtual-host", + icon_status: IconStatus.Up, + capital: "Lilongwe", + continent: { + name: "Africa", + }, + currency: "MWK", + languages: [ + { + name: "English", + }, + { + name: "Chichewa", + }, + ], + }, + { + name: "Mexico", + code: "MX", + icon: "virtual-host", + icon_status: IconStatus.Up, + capital: "Mexico City", + continent: { + name: "North America", + }, + currency: "MXN", + languages: [ + { + name: "Spanish", + }, + ], + }, + { + name: "Malaysia", + code: "MY", + icon: "virtual-host", + icon_status: IconStatus.Up, + capital: "Kuala Lumpur", + continent: { + name: "Asia", + }, + currency: "MYR", + languages: [ + { + name: "Malay", + }, + ], + }, + { + name: "Mozambique", + code: "MZ", + icon: "virtual-host", + icon_status: IconStatus.Up, + capital: "Maputo", + continent: { + name: "Africa", + }, + currency: "MZN", + languages: [ + { + name: "Portuguese", + }, + ], + }, + { + name: "Namibia", + code: "NA", + icon: "virtual-host", + icon_status: IconStatus.Up, + capital: "Windhoek", + continent: { + name: "Africa", + }, + currency: "NAD,ZAR", + languages: [ + { + name: "English", + }, + { + name: "Afrikaans", + }, + ], + }, + { + name: "New Caledonia", + code: "NC", + icon: "virtual-host", + icon_status: IconStatus.Up, + capital: "Nouméa", + continent: { + name: "Oceania", + }, + currency: "XPF", + languages: [ + { + name: "French", + }, + ], + }, + { + name: "Niger", + code: "NE", + icon: "virtual-host", + icon_status: IconStatus.Up, + capital: "Niamey", + continent: { + name: "Africa", + }, + currency: "XOF", + languages: [ + { + name: "French", + }, + ], + }, + { + name: "Norfolk Island", + code: "NF", + icon: "virtual-host", + icon_status: IconStatus.Up, + capital: "Kingston", + continent: { + name: "Oceania", + }, + currency: "AUD", + languages: [ + { + name: "English", + }, + ], + }, + { + name: "Nigeria", + code: "NG", + icon: "virtual-host", + icon_status: IconStatus.Up, + capital: "Abuja", + continent: { + name: "Africa", + }, + currency: "NGN", + languages: [ + { + name: "English", + }, + ], + }, + { + name: "Nicaragua", + code: "NI", + icon: "virtual-host", + icon_status: IconStatus.Up, + capital: "Managua", + continent: { + name: "North America", + }, + currency: "NIO", + languages: [ + { + name: "Spanish", + }, + ], + }, + { + name: "Netherlands", + code: "NL", + icon: "virtual-host", + icon_status: IconStatus.Up, + capital: "Amsterdam", + continent: { + name: "Europe", + }, + currency: "EUR", + languages: [ + { + name: "Dutch", + }, + ], + }, + { + name: "Norway", + code: "NO", + icon: "virtual-host", + icon_status: IconStatus.Up, + capital: "Oslo", + continent: { + name: "Europe", + }, + currency: "NOK", + languages: [ + { + name: "Norwegian", + }, + { + name: "Norwegian Bokmål", + }, + { + name: "Norwegian Nynorsk", + }, + ], + }, + { + name: "Nepal", + code: "NP", + icon: "virtual-host", + icon_status: IconStatus.Up, + capital: "Kathmandu", + continent: { + name: "Asia", + }, + currency: "NPR", + languages: [ + { + name: "Nepali", + }, + ], + }, + { + name: "Nauru", + code: "NR", + icon: "virtual-host", + icon_status: IconStatus.Up, + capital: "Yaren", + continent: { + name: "Oceania", + }, + currency: "AUD", + languages: [ + { + name: "English", + }, + { + name: "Nauruan", + }, + ], + }, + { + name: "Niue", + code: "NU", + icon: "virtual-host", + icon_status: IconStatus.Up, + capital: "Alofi", + continent: { + name: "Oceania", + }, + currency: "NZD", + languages: [ + { + name: "English", + }, + ], + }, + { + name: "New Zealand", + code: "NZ", + icon: "virtual-host", + icon_status: IconStatus.Up, + capital: "Wellington", + continent: { + name: "Oceania", + }, + currency: "NZD", + languages: [ + { + name: "English", + }, + { + name: "Maori", + }, + ], + }, + { + name: "Oman", + code: "OM", + icon: "virtual-host", + icon_status: IconStatus.Up, + capital: "Muscat", + continent: { + name: "Asia", + }, + currency: "OMR", + languages: [ + { + name: "Arabic", + }, + ], + }, + { + name: "Panama", + code: "PA", + icon: "virtual-host", + icon_status: IconStatus.Up, + capital: "Panama City", + continent: { + name: "North America", + }, + currency: "PAB,USD", + languages: [ + { + name: "Spanish", + }, + ], + }, + { + name: "Peru", + code: "PE", + icon: "virtual-host", + icon_status: IconStatus.Up, + capital: "Lima", + continent: { + name: "South America", + }, + currency: "PEN", + languages: [ + { + name: "Spanish", + }, + ], + }, + { + name: "French Polynesia", + code: "PF", + icon: "virtual-host", + icon_status: IconStatus.Up, + capital: "Papeetē", + continent: { + name: "Oceania", + }, + currency: "XPF", + languages: [ + { + name: "French", + }, + ], + }, + { + name: "Papua New Guinea", + code: "PG", + icon: "virtual-host", + icon_status: IconStatus.Up, + capital: "Port Moresby", + continent: { + name: "Oceania", + }, + currency: "PGK", + languages: [ + { + name: "English", + }, + ], + }, + { + name: "Philippines", + code: "PH", + icon: "virtual-host", + icon_status: IconStatus.Up, + capital: "Manila", + continent: { + name: "Asia", + }, + currency: "PHP", + languages: [ + { + name: "English", + }, + ], + }, + { + name: "Pakistan", + code: "PK", + icon: "virtual-host", + icon_status: IconStatus.Up, + capital: "Islamabad", + continent: { + name: "Asia", + }, + currency: "PKR", + languages: [ + { + name: "English", + }, + { + name: "Urdu", + }, + ], + }, + { + name: "Poland", + code: "PL", + icon: "virtual-host", + icon_status: IconStatus.Up, + capital: "Warsaw", + continent: { + name: "Europe", + }, + currency: "PLN", + languages: [ + { + name: "Polish", + }, + ], + }, + { + name: "Saint Pierre and Miquelon", + code: "PM", + icon: "virtual-host", + icon_status: IconStatus.Up, + capital: "Saint-Pierre", + continent: { + name: "North America", + }, + currency: "EUR", + languages: [ + { + name: "French", + }, + ], + }, + { + name: "Pitcairn Islands", + code: "PN", + icon: "virtual-host", + icon_status: IconStatus.Up, + capital: "Adamstown", + continent: { + name: "Oceania", + }, + currency: "NZD", + languages: [ + { + name: "English", + }, + ], + }, + { + name: "Puerto Rico", + code: "PR", + icon: "virtual-host", + icon_status: IconStatus.Up, + capital: "San Juan", + continent: { + name: "North America", + }, + currency: "USD", + languages: [ + { + name: "Spanish", + }, + { + name: "English", + }, + ], + }, + { + name: "Palestine", + code: "PS", + icon: "virtual-host", + icon_status: IconStatus.Up, + capital: "Ramallah", + continent: { + name: "Asia", + }, + currency: "ILS", + languages: [ + { + name: "Arabic", + }, + ], + }, + { + name: "Portugal", + code: "PT", + icon: "virtual-host", + icon_status: IconStatus.Up, + capital: "Lisbon", + continent: { + name: "Europe", + }, + currency: "EUR", + languages: [ + { + name: "Portuguese", + }, + ], + }, + { + name: "Palau", + code: "PW", + icon: "virtual-host", + icon_status: IconStatus.Up, + capital: "Ngerulmud", + continent: { + name: "Oceania", + }, + currency: "USD", + languages: [ + { + name: "English", + }, + ], + }, + { + name: "Paraguay", + code: "PY", + icon: "virtual-host", + icon_status: IconStatus.Up, + capital: "Asunción", + continent: { + name: "South America", + }, + currency: "PYG", + languages: [ + { + name: "Spanish", + }, + { + name: "Guarani", + }, + ], + }, + { + name: "Qatar", + code: "QA", + icon: "virtual-host", + icon_status: IconStatus.Up, + capital: "Doha", + continent: { + name: "Asia", + }, + currency: "QAR", + languages: [ + { + name: "Arabic", + }, + ], + }, + { + name: "Réunion", + code: "RE", + icon: "virtual-host", + icon_status: IconStatus.Up, + capital: "Saint-Denis", + continent: { + name: "Africa", + }, + currency: "EUR", + languages: [ + { + name: "French", + }, + ], + }, + { + name: "Romania", + code: "RO", + icon: "virtual-host", + icon_status: IconStatus.Up, + capital: "Bucharest", + continent: { + name: "Europe", + }, + currency: "RON", + languages: [ + { + name: "Romanian", + }, + ], + }, + { + name: "Serbia", + code: "RS", + icon: "virtual-host", + icon_status: IconStatus.Up, + capital: "Belgrade", + continent: { + name: "Europe", + }, + currency: "RSD", + languages: [ + { + name: "Serbian", + }, + ], + }, + { + name: "Russia", + code: "RU", + icon: "virtual-host", + icon_status: IconStatus.Up, + capital: "Moscow", + continent: { + name: "Europe", + }, + currency: "RUB", + languages: [ + { + name: "Russian", + }, + ], + }, + { + name: "Rwanda", + code: "RW", + icon: "virtual-host", + icon_status: IconStatus.Up, + capital: "Kigali", + continent: { + name: "Africa", + }, + currency: "RWF", + languages: [ + { + name: "Rwandi", + }, + { + name: "English", + }, + { + name: "French", + }, + ], + }, + { + name: "Saudi Arabia", + code: "SA", + icon: "virtual-host", + icon_status: IconStatus.Up, + capital: "Riyadh", + continent: { + name: "Asia", + }, + currency: "SAR", + languages: [ + { + name: "Arabic", + }, + ], + }, + { + name: "Solomon Islands", + code: "SB", + icon: "virtual-host", + icon_status: IconStatus.Up, + capital: "Honiara", + continent: { + name: "Oceania", + }, + currency: "SBD", + languages: [ + { + name: "English", + }, + ], + }, + { + name: "Seychelles", + code: "SC", + icon: "virtual-host", + icon_status: IconStatus.Up, + capital: "Victoria", + continent: { + name: "Africa", + }, + currency: "SCR", + languages: [ + { + name: "French", + }, + { + name: "English", + }, + ], + }, + { + name: "Sudan", + code: "SD", + icon: "virtual-host", + icon_status: IconStatus.Up, + capital: "Khartoum", + continent: { + name: "Africa", + }, + currency: "SDG", + languages: [ + { + name: "Arabic", + }, + { + name: "English", + }, + ], + }, + { + name: "Sweden", + code: "SE", + icon: "virtual-host", + icon_status: IconStatus.Up, + capital: "Stockholm", + continent: { + name: "Europe", + }, + currency: "SEK", + languages: [ + { + name: "Swedish", + }, + ], + }, + { + name: "Singapore", + code: "SG", + icon: "virtual-host", + icon_status: IconStatus.Up, + capital: "Singapore", + continent: { + name: "Asia", + }, + currency: "SGD", + languages: [ + { + name: "English", + }, + { + name: "Malay", + }, + { + name: "Tamil", + }, + { + name: "Chinese", + }, + ], + }, + { + name: "Saint Helena", + code: "SH", + icon: "virtual-host", + icon_status: IconStatus.Up, + capital: "Jamestown", + continent: { + name: "Africa", + }, + currency: "SHP", + languages: [ + { + name: "English", + }, + ], + }, + { + name: "Slovenia", + code: "SI", + icon: "virtual-host", + icon_status: IconStatus.Up, + capital: "Ljubljana", + continent: { + name: "Europe", + }, + currency: "EUR", + languages: [ + { + name: "Slovenian", + }, + ], + }, + { + name: "Svalbard and Jan Mayen", + code: "SJ", + icon: "virtual-host", + icon_status: IconStatus.Up, + capital: "Longyearbyen", + continent: { + name: "Europe", + }, + currency: "NOK", + languages: [ + { + name: "Norwegian", + }, + ], + }, + { + name: "Slovakia", + code: "SK", + icon: "virtual-host", + icon_status: IconStatus.Up, + capital: "Bratislava", + continent: { + name: "Europe", + }, + currency: "EUR", + languages: [ + { + name: "Slovak", + }, + ], + }, + { + name: "Sierra Leone", + code: "SL", + icon: "virtual-host", + icon_status: IconStatus.Up, + capital: "Freetown", + continent: { + name: "Africa", + }, + currency: "SLL", + languages: [ + { + name: "English", + }, + ], + }, + { + name: "San Marino", + code: "SM", + icon: "virtual-host", + icon_status: IconStatus.Up, + capital: "City of San Marino", + continent: { + name: "Europe", + }, + currency: "EUR", + languages: [ + { + name: "Italian", + }, + ], + }, + { + name: "Senegal", + code: "SN", + icon: "virtual-host", + icon_status: IconStatus.Up, + capital: "Dakar", + continent: { + name: "Africa", + }, + currency: "XOF", + languages: [ + { + name: "French", + }, + ], + }, + { + name: "Somalia", + code: "SO", + icon: "virtual-host", + icon_status: IconStatus.Up, + capital: "Mogadishu", + continent: { + name: "Africa", + }, + currency: "SOS", + languages: [ + { + name: "Somalia", + }, + { + name: "Arabic", + }, + ], + }, + { + name: "Suriname", + code: "SR", + icon: "virtual-host", + icon_status: IconStatus.Up, + capital: "Paramaribo", + continent: { + name: "South America", + }, + currency: "SRD", + languages: [ + { + name: "Dutch", + }, + ], + }, + { + name: "South Sudan", + code: "SS", + icon: "virtual-host", + icon_status: IconStatus.Up, + capital: "Juba", + continent: { + name: "Africa", + }, + currency: "SSP", + languages: [ + { + name: "English", + }, + ], + }, + { + name: "São Tomé and Príncipe", + code: "ST", + icon: "virtual-host", + icon_status: IconStatus.Up, + capital: "São Tomé", + continent: { + name: "Africa", + }, + currency: "STN", + languages: [ + { + name: "Portuguese", + }, + ], + }, + { + name: "El Salvador", + code: "SV", + icon: "virtual-host", + icon_status: IconStatus.Up, + capital: "San Salvador", + continent: { + name: "North America", + }, + currency: "SVC,USD", + languages: [ + { + name: "Spanish", + }, + ], + }, + { + name: "Sint Maarten", + code: "SX", + icon: "virtual-host", + icon_status: IconStatus.Up, + capital: "Philipsburg", + continent: { + name: "North America", + }, + currency: "ANG", + languages: [ + { + name: "Dutch", + }, + { + name: "English", + }, + ], + }, + { + name: "Syria", + code: "SY", + icon: "virtual-host", + icon_status: IconStatus.Up, + capital: "Damascus", + continent: { + name: "Asia", + }, + currency: "SYP", + languages: [ + { + name: "Arabic", + }, + ], + }, + { + name: "Swaziland", + code: "SZ", + icon: "virtual-host", + icon_status: IconStatus.Up, + capital: "Lobamba", + continent: { + name: "Africa", + }, + currency: "SZL", + languages: [ + { + name: "English", + }, + { + name: "Swati", + }, + ], + }, + { + name: "Turks and Caicos Islands", + code: "TC", + icon: "virtual-host", + icon_status: IconStatus.Up, + capital: "Cockburn Town", + continent: { + name: "North America", + }, + currency: "USD", + languages: [ + { + name: "English", + }, + ], + }, + { + name: "Chad", + code: "TD", + icon: "virtual-host", + icon_status: IconStatus.Up, + capital: "N'Djamena", + continent: { + name: "Africa", + }, + currency: "XAF", + languages: [ + { + name: "French", + }, + { + name: "Arabic", + }, + ], + }, + { + name: "French Southern Territories", + code: "TF", + icon: "virtual-host", + icon_status: IconStatus.Up, + capital: "Port-aux-Français", + continent: { + name: "Antarctica", + }, + currency: "EUR", + languages: [ + { + name: "French", + }, + ], + }, + { + name: "Togo", + code: "TG", + icon: "virtual-host", + icon_status: IconStatus.Up, + capital: "Lomé", + continent: { + name: "Africa", + }, + currency: "XOF", + languages: [ + { + name: "French", + }, + ], + }, + { + name: "Thailand", + code: "TH", + icon: "virtual-host", + icon_status: IconStatus.Up, + capital: "Bangkok", + continent: { + name: "Asia", + }, + currency: "THB", + languages: [ + { + name: "Thai", + }, + ], + }, + { + name: "Tajikistan", + code: "TJ", + icon: "virtual-host", + icon_status: IconStatus.Up, + capital: "Dushanbe", + continent: { + name: "Asia", + }, + currency: "TJS", + languages: [ + { + name: "Tajik", + }, + { + name: "Russian", + }, + ], + }, + { + name: "Tokelau", + code: "TK", + icon: "virtual-host", + icon_status: IconStatus.Up, + capital: "Fakaofo", + continent: { + name: "Oceania", + }, + currency: "NZD", + languages: [ + { + name: "English", + }, + ], + }, + { + name: "East Timor", + code: "TL", + icon: "virtual-host", + icon_status: IconStatus.Up, + capital: "Dili", + continent: { + name: "Oceania", + }, + currency: "USD", + languages: [ + { + name: "Portuguese", + }, + ], + }, + { + name: "Turkmenistan", + code: "TM", + icon: "virtual-host", + icon_status: IconStatus.Up, + capital: "Ashgabat", + continent: { + name: "Asia", + }, + currency: "TMT", + languages: [ + { + name: "Turkmen", + }, + { + name: "Russian", + }, + ], + }, + { + name: "Tunisia", + code: "TN", + icon: "virtual-host", + icon_status: IconStatus.Up, + capital: "Tunis", + continent: { + name: "Africa", + }, + currency: "TND", + languages: [ + { + name: "Arabic", + }, + ], + }, + { + name: "Tonga", + code: "TO", + icon: "virtual-host", + icon_status: IconStatus.Up, + capital: "Nuku'alofa", + continent: { + name: "Oceania", + }, + currency: "TOP", + languages: [ + { + name: "English", + }, + { + name: "Tonga", + }, + ], + }, + { + name: "Turkey", + code: "TR", + icon: "virtual-host", + icon_status: IconStatus.Up, + capital: "Ankara", + continent: { + name: "Asia", + }, + currency: "TRY", + languages: [ + { + name: "Turkish", + }, + ], + }, + { + name: "Trinidad and Tobago", + code: "TT", + icon: "virtual-host", + icon_status: IconStatus.Up, + capital: "Port of Spain", + continent: { + name: "North America", + }, + currency: "TTD", + languages: [ + { + name: "English", + }, + ], + }, + { + name: "Tuvalu", + code: "TV", + icon: "virtual-host", + icon_status: IconStatus.Up, + capital: "Funafuti", + continent: { + name: "Oceania", + }, + currency: "AUD", + languages: [ + { + name: "English", + }, + ], + }, + { + name: "Taiwan", + code: "TW", + icon: "virtual-host", + icon_status: IconStatus.Up, + capital: "Taipei", + continent: { + name: "Asia", + }, + currency: "TWD", + languages: [ + { + name: "Chinese", + }, + ], + }, + { + name: "Tanzania", + code: "TZ", + icon: "virtual-host", + icon_status: IconStatus.Up, + capital: "Dodoma", + continent: { + name: "Africa", + }, + currency: "TZS", + languages: [ + { + name: "Swahili", + }, + { + name: "English", + }, + ], + }, + { + name: "Ukraine", + code: "UA", + icon: "virtual-host", + icon_status: IconStatus.Up, + capital: "Kyiv", + continent: { + name: "Europe", + }, + currency: "UAH", + languages: [ + { + name: "Ukrainian", + }, + ], + }, + { + name: "Uganda", + code: "UG", + icon: "virtual-host", + icon_status: IconStatus.Up, + capital: "Kampala", + continent: { + name: "Africa", + }, + currency: "UGX", + languages: [ + { + name: "English", + }, + { + name: "Swahili", + }, + ], + }, + { + name: "U.S. Minor Outlying Islands", + code: "UM", + icon: "virtual-host", + icon_status: IconStatus.Up, + capital: null, + continent: { + name: "Oceania", + }, + currency: "USD", + languages: [ + { + name: "English", + }, + ], + }, + { + name: "United States", + code: "US", + icon: "virtual-host", + icon_status: IconStatus.Up, + capital: "Washington D.C.", + continent: { + name: "North America", + }, + currency: "USD,USN,USS", + languages: [ + { + name: "English", + }, + ], + }, + { + name: "Uruguay", + code: "UY", + icon: "virtual-host", + icon_status: IconStatus.Up, + capital: "Montevideo", + continent: { + name: "South America", + }, + currency: "UYI,UYU", + languages: [ + { + name: "Spanish", + }, + ], + }, + { + name: "Uzbekistan", + code: "UZ", + icon: "virtual-host", + icon_status: IconStatus.Up, + capital: "Tashkent", + continent: { + name: "Asia", + }, + currency: "UZS", + languages: [ + { + name: "Uzbek", + }, + { + name: "Russian", + }, + ], + }, + { + name: "Vatican City", + code: "VA", + icon: "virtual-host", + icon_status: IconStatus.Up, + capital: "Vatican City", + continent: { + name: "Europe", + }, + currency: "EUR", + languages: [ + { + name: "Italian", + }, + { + name: "Latin", + }, + ], + }, + { + name: "Saint Vincent and the Grenadines", + code: "VC", + icon: "virtual-host", + icon_status: IconStatus.Up, + capital: "Kingstown", + continent: { + name: "North America", + }, + currency: "XCD", + languages: [ + { + name: "English", + }, + ], + }, + { + name: "Venezuela", + code: "VE", + icon: "virtual-host", + icon_status: IconStatus.Up, + capital: "Caracas", + continent: { + name: "South America", + }, + currency: "VES", + languages: [ + { + name: "Spanish", + }, + ], + }, + { + name: "British Virgin Islands", + code: "VG", + icon: "virtual-host", + icon_status: IconStatus.Up, + capital: "Road Town", + continent: { + name: "North America", + }, + currency: "USD", + languages: [ + { + name: "English", + }, + ], + }, + { + name: "U.S. Virgin Islands", + code: "VI", + icon: "virtual-host", + icon_status: IconStatus.Up, + capital: "Charlotte Amalie", + continent: { + name: "North America", + }, + currency: "USD", + languages: [ + { + name: "English", + }, + ], + }, + { + name: "Vietnam", + code: "VN", + icon: "virtual-host", + icon_status: IconStatus.Up, + capital: "Hanoi", + continent: { + name: "Asia", + }, + currency: "VND", + languages: [ + { + name: "Vietnamese", + }, + ], + }, + { + name: "Vanuatu", + code: "VU", + icon: "virtual-host", + icon_status: IconStatus.Up, + capital: "Port Vila", + continent: { + name: "Oceania", + }, + currency: "VUV", + languages: [ + { + name: "Bislama", + }, + { + name: "English", + }, + { + name: "French", + }, + ], + }, + { + name: "Wallis and Futuna", + code: "WF", + icon: "virtual-host", + icon_status: IconStatus.Up, + capital: "Mata-Utu", + continent: { + name: "Oceania", + }, + currency: "XPF", + languages: [ + { + name: "French", + }, + ], + }, + { + name: "Samoa", + code: "WS", + icon: "virtual-host", + icon_status: IconStatus.Up, + capital: "Apia", + continent: { + name: "Oceania", + }, + currency: "WST", + languages: [ + { + name: "Samoan", + }, + { + name: "English", + }, + ], + }, + { + name: "Kosovo", + code: "XK", + icon: "virtual-host", + icon_status: IconStatus.Up, + capital: "Pristina", + continent: { + name: "Europe", + }, + currency: "EUR", + languages: [ + { + name: "Albanian", + }, + { + name: "Serbian", + }, + ], + }, + { + name: "Yemen", + code: "YE", + icon: "virtual-host", + icon_status: IconStatus.Up, + capital: "Sana'a", + continent: { + name: "Asia", + }, + currency: "YER", + languages: [ + { + name: "Arabic", + }, + ], + }, + { + name: "Mayotte", + code: "YT", + icon: "virtual-host", + icon_status: IconStatus.Up, + capital: "Mamoudzou", + continent: { + name: "Africa", + }, + currency: "EUR", + languages: [ + { + name: "French", + }, + ], + }, + { + name: "South Africa", + code: "ZA", + icon: "virtual-host", + icon_status: IconStatus.Up, + capital: "Pretoria", + continent: { + name: "Africa", + }, + currency: "ZAR", + languages: [ + { + name: "Afrikaans", + }, + { + name: "English", + }, + { + name: "South Ndebele", + }, + { + name: "Southern Sotho", + }, + { + name: "Swati", + }, + { + name: "Tswana", + }, + { + name: "Tsonga", + }, + { + name: "Venda", + }, + { + name: "Xhosa", + }, + { + name: "Zulu", + }, + ], + }, + { + name: "Zambia", + code: "ZM", + icon: "virtual-host", + icon_status: IconStatus.Up, + capital: "Lusaka", + continent: { + name: "Africa", + }, + currency: "ZMW", + languages: [ + { + name: "English", + }, + ], + }, + { + name: "Zimbabwe", + code: "ZW", + icon: "virtual-host", + icon_status: IconStatus.Up, + capital: "Harare", + continent: { + name: "Africa", + }, + currency: "USD,ZAR,BWP,GBP,AUD,CNY,INR,JPY", + languages: [ + { + name: "English", + }, + { + name: "Shona", + }, + { + name: "North Ndebele", + }, + ], + }, + ], + }, +}; +\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\`, + "widget-types/drilldown/drilldown-widget/drilldown-widget-example.component.ts": \\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\`// © 2022 SolarWinds Worldwide, LLC. All rights reserved. +// +// Permission is hereby granted, free of charge, to any person obtaining a copy +// of this software and associated documentation files (the "Software"), to +// deal in the Software without restriction, including without limitation the +// rights to use, copy, modify, merge, publish, distribute, sublicense, and/or +// sell copies of the Software, and to permit persons to whom the Software is +// furnished to do so, subject to the following conditions: +// +// The above copyright notice and this permission notice shall be included in +// all copies or substantial portions of the Software. +// +// THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR +// IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, +// FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE +// AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER +// LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, +// OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN +// THE SOFTWARE. + +import { HttpClient } from "@angular/common/http"; +import { + ChangeDetectorRef, + Component, + Injectable, + OnDestroy, + OnInit, +} from "@angular/core"; +import { GridsterConfig, GridsterItem } from "angular-gridster2"; +import { Apollo, gql } from "apollo-angular"; +import groupBy from "lodash/groupBy"; +import { BehaviorSubject, Observable, of } from "rxjs"; +import { catchError, delay, filter, map } from "rxjs/operators"; + +import { + DataSourceFeatures, + IconStatus, + IDataField, + IDataSource, + IDataSourceFeatures, + IDataSourceFeaturesConfiguration, + INovaFilters, + LoggerService, + ServerSideDataSource, + IFilters, +} from "@nova-ui/bits"; +import { + DATA_SOURCE, + DEFAULT_PIZZAGNA_ROOT, + IDashboard, + IDrilldownComponentsConfiguration, + IListWidgetConfiguration, + IProviderConfiguration, + IWidget, + IWidgets, + ListGroupItemComponent, + ListLeafItemComponent, + NOVA_DRILLDOWN_DATASOURCE_ADAPTER, + PizzagnaLayer, + ProviderRegistryService, + WellKnownPathKey, + WellKnownProviders, + WidgetTypesService, +} from "@nova-ui/dashboards"; + +import { DrilldownDataSource } from "./mock-data-source"; + +/** + * A simple KPI data source to retrieve the average rating of Harry Potter and the Sorcerer's Stone (book) via googleapis + */ +@Injectable() +export class DrilldownDataSourceRealApi + extends ServerSideDataSource + implements OnDestroy, IDataSource +{ + // This is the ID we'll use to identify the provider + public static providerId = "DrilldownDataSourceRealApi"; + + // Use this subject to communicate the data source's busy state + public busy = new BehaviorSubject(false); + public dataFields: Partial[] = [ + { id: "regionName", label: "Region name" }, + { id: "subregionName", label: "Subregion name" }, + ]; + + public features: IDataSourceFeaturesConfiguration; + private supportedFeatures: IDataSourceFeatures = { + search: { enabled: true }, + }; + + private drillState: string[] = []; + private groupBy: string[]; + + constructor( + private logger: LoggerService, + private http: HttpClient, + private apollo: Apollo + ) { + super(); + this.features = new DataSourceFeatures(this.supportedFeatures); + // TODO: remove Partial in vNext after marking dataType field as optional - NUI-5838 + ( + this.dataFieldsConfig.dataFields$ as BehaviorSubject< + Partial[] + > + ).next(this.dataFields); + } + + private groupedDataHistory: Array> = []; + + // In this example, getFilteredData is invoked every 10 minutes (Take a look at the refresher + // provider definition in the widget configuration below to see how the interval is set) + public async getFilteredData(data: IFilters): Promise { + return of(data) + .pipe( + filter(() => !!this.drillState), + map((countries) => { + const lastHistory = () => getLast(this.groupedDataHistory); + + if (!this.drillState.length && !this.groupBy.length) { + return countries; + } + + // adding "ROOT" as a root level for drilling + const fullDrillState = ["ROOT", ...this.drillState]; + const activeDrillLvl = fullDrillState.length; + const historyLvl = this.groupedDataHistory.length; + + // checking how many lvls we have to group for drilling, in case some are missed + const drillLvlDiff = activeDrillLvl - historyLvl; + + if (!drillLvlDiff) { + return lastHistory() || countries; + } + + const drillToGroup = fullDrillState.slice( + fullDrillState.length - drillLvlDiff + ); + + for (const drill of drillToGroup) { + const drillIdx = fullDrillState.findIndex( + (v) => v === drill + ); + const group = this.groupBy[drillIdx]; + + if (group) { + const dataToGroup = lastHistory() + ? lastHistory()[drill] + : countries; + const lastGroupedValue = groupBy( + dataToGroup, + group + ); + + this.groupedDataHistory.push(lastGroupedValue); + } + } + + // take last if we have all data grouped + if (this.groupBy.length === this.drillState.length) { + return lastHistory()[getLast(this.drillState)]; + } + + // get groping and transform to raw data format + return this.getGroupsWidgetData(lastHistory()); + }) + ) + .toPromise(); + } + + public ngOnDestroy(): void { + this.outputsSubject.complete(); + } + + // This method is expected to return all data needed for repeat/paginator/filterGroups in order to work. + // In case of custom filtering participants feel free to extend INovaFilteringOutputs. + protected getBackendData(filters: INovaFilters): Observable { + const mainRequest = this.apollo.watchQuery<{ countries: any }>({ + query: this.generateQuery(filters), + }); + + return mainRequest.valueChanges.pipe( + // mock delay + delay(300), + // data mapping, !DS specific! + map((res) => res.data.countries), + // adds mock icons to be displayed on leaf nodes !DS specific! + map((res: any[]) => + res.map((v) => ({ + ...v, + icon: "virtual-host", + icon_status: IconStatus.Up, + subregionName: + v.subregion?.name || "No Subregion Specified", + regionName: + v.subregion?.region?.name || "No Region Specified", + })) + ), + catchError((e) => { + this.logger.error(e); + return of({} as any); + }) + ); + } + + private generateQuery(filters: INovaFilters) { + const { search } = filters; + const searchValue = search?.value ? \\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\`^[\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\${search.value}]*\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\` : ""; + + const queryString = \\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\` + query { + countries(filter: {name: {regex: "\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\${searchValue}"} }) { + name + native + capital + languages { + name + } + currencies + subdivisions { + name + } + } + } + \\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\`; + + return gql\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\` + \\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\${queryString} + \\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\`; + } + + // Overrides default ServerSideDataSource.beforeApplyFilters implementation + // to save some filters that are used internally + // -- !DS specific + protected beforeApplyFilters(filters: INovaFilters): void { + this.busy.next(true); + + this.drillState = filters.drillstate?.value; + this.groupBy = filters.group?.value; + + if (this.isHome()) { + this.groupedDataHistory.length = 0; + } + + if (this.isBack()) { + this.groupedDataHistory.length = this.groupedDataHistory.length - 1; + } + + if (this.getFilters()["search"] && this.filterChanged("search")) { + this.groupedDataHistory.length = 0; + } + } + + private getGroupsWidgetData(groupByObj: Record) { + return Object.keys(groupByObj).map((property) => ({ + id: property, + label: property, + // statuses that will be displayed on group item + statuses: [ + { key: "virtual-host", value: groupByObj[property].length }, + { + key: "acknowledge", + value: this.getPopulation(groupByObj[property]), + }, + ], + })); + } + + private isHome(): boolean { + return this.drillState?.length === 0; + } + + private isBack(): boolean { + return ( + this.groupedDataHistory?.length > this.drillState?.length && + !this.isHome() + ); + } + + /** + * Gets population for the country(ies) + */ + private getPopulation(countries: any[]) { + const totalPopulation = countries.reduce( + (acc, next) => (acc += next.population), + 0 + ); + return \\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\`\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\${totalPopulation * Math.pow(10, -3)} k\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\`; + } +} + +@Component({ + selector: "drilldown-widget-example", + templateUrl: "./drilldown-widget-example.component.html", + styleUrls: ["./drilldown-widget-example.component.less"], + standalone: false, +}) +export class DrilldownWidgetExampleComponent implements OnInit { + // This variable will hold all the data needed to define the layout and behavior of the widgets. + // Pass this to the dashboard component's dashboard input in the template. + public dashboard: IDashboard | undefined; + + // Angular gridster requires a configuration object even if it's empty. + // Pass this to the dashboard component's gridsterConfig input in the template. + public gridsterConfig: GridsterConfig = {}; + + // Boolean passed as an input to the dashboard. When true, widgets can be moved, resized, removed, or edited + public editMode: boolean = false; + + constructor( + // WidgetTypesService provides the widget's necessary structure information + private widgetTypesService: WidgetTypesService, + private providerRegistry: ProviderRegistryService, + private changeDetectorRef: ChangeDetectorRef + ) {} + + public ngOnInit(): void { + // Registering the data source for injection into the KPI tile. + // Note: Each tile of a KPI widget is assigned its own instance of the data source + this.providerRegistry.setProviders({ + [DrilldownDataSource.providerId]: { + provide: DATA_SOURCE, + useClass: DrilldownDataSource, + // Any dependencies that need to be injected into the provider must be listed here + deps: [HttpClient], + }, + [DrilldownDataSourceRealApi.providerId]: { + provide: DATA_SOURCE, + useClass: DrilldownDataSourceRealApi, + // Any dependencies that need to be injected into the provider must be listed here + deps: [LoggerService, HttpClient, Apollo], + }, + }); + + this.initializeDashboard(); + const widgetTemplate = this.widgetTypesService.getWidgetType( + "drilldown", + 1 + ); + this.widgetTypesService.setNode( + widgetTemplate, + "configurator", + WellKnownPathKey.DataSourceProviders, + [ + DrilldownDataSourceRealApi.providerId, + DrilldownDataSource.providerId, + ] + ); + } + + public initializeDashboard(): void { + // We're using a static configuration object for this example, but this is where + // the widget's configuration could potentially be populated from a database + const drilldownWidget = widgetConfig; + const widgets: IWidgets = { + // Complete the widget with information coming from its type definition + [drilldownWidget.id]: + this.widgetTypesService.mergeWithWidgetType(drilldownWidget), + }; + + // Setting the widget dimensions and position (this is for gridster) + const positions: Record = { + [drilldownWidget.id]: { + cols: 10, + rows: 10, + y: 0, + x: 0, + }, + }; + + // Finally, assigning the variables we created above to the dashboard + this.dashboard = { positions, widgets }; + } + + public reInitializeDashboard(): void { + // destroys the components and their providers so the dashboard can re init data + this.dashboard = undefined; + this.changeDetectorRef.detectChanges(); + + const adapterProperties = + widgetConfig.pizzagna[PizzagnaLayer.Configuration].listWidget + .providers?.adapter?.properties; + + if (adapterProperties) { + adapterProperties.drillstate = []; + } + + this.initializeDashboard(); + } +} + +const widgetConfig: IWidget = { + id: "drilldown", + type: "drilldown", + pizzagna: { + [PizzagnaLayer.Configuration]: { + [DEFAULT_PIZZAGNA_ROOT]: { + providers: { + [WellKnownProviders.DataSource]: { + // Setting the data source providerId for the tile with id "kpi1" + providerId: DrilldownDataSourceRealApi.providerId, + properties: {}, + } as IProviderConfiguration, + }, + }, + header: { + properties: { + title: "Drilldown Widget", + subtitle: "Search is case sensitive!", + }, + }, + listWidget: { + providers: { + [WellKnownProviders.Adapter]: { + providerId: NOVA_DRILLDOWN_DATASOURCE_ADAPTER, + properties: { + // widget + navigationBarId: "navigationBar", + componentId: "listWidget", + dataPath: "data", + + // adapter props + drillstate: [], + groupBy: ["regionName", "subregionName"], + groups: ["regionName", "subregionName"], + + // components + componentsConfig: { + group: { + componentType: + ListGroupItemComponent.lateLoadKey, + properties: { + dataFieldIds: { + id: "id", + label: "label", + statuses: "statuses", + }, + }, + itemProperties: { + canNavigate: true, + }, + }, + leaf: { + componentType: + ListLeafItemComponent.lateLoadKey, + properties: { + dataFieldIds: { + icon: "icon", + status: "icon_status", + detailedUrl: "capital", + label: "name", + }, + }, + itemProperties: { + canNavigate: false, + }, + }, + } as IDrilldownComponentsConfiguration, + }, + }, + }, + properties: { + configuration: { + // FORMAT: + // componentType: ListLeafItemComponent.lateLoadKey, + // properties: { + // dataFieldIds: { + // icon: "", + // status: "code", + // detailedUrl: "capital", + // label: "name", + // }, + // }, + // + } as IListWidgetConfiguration, + }, + }, + }, + }, +}; + +const getLast = (arr: any[]) => arr[arr.length - 1]; +\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\`, + "widget-types/drilldown/drilldown-widget/mock-data-source.ts": \\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\`// © 2022 SolarWinds Worldwide, LLC. All rights reserved. +// +// Permission is hereby granted, free of charge, to any person obtaining a copy +// of this software and associated documentation files (the "Software"), to +// deal in the Software without restriction, including without limitation the +// rights to use, copy, modify, merge, publish, distribute, sublicense, and/or +// sell copies of the Software, and to permit persons to whom the Software is +// furnished to do so, subject to the following conditions: +// +// The above copyright notice and this permission notice shall be included in +// all copies or substantial portions of the Software. +// +// THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR +// IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, +// FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE +// AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER +// LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, +// OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN +// THE SOFTWARE. + +import { Injectable, OnDestroy } from "@angular/core"; +import groupBy from "lodash/groupBy"; +import { BehaviorSubject, Observable, of, Subject } from "rxjs"; +import { + catchError, + delay, + finalize, + map, + // eslint-disable-next-line import/no-deprecated + switchMap, + tap, +} from "rxjs/operators"; + +import { + DataSourceService, + IDataField, + IDataSource, + IFilters, + INovaFilters, +} from "@nova-ui/bits"; + +import { GRAPH_DATA_MOCK } from "./data-mock"; + +/** + * A simple KPI data source to retrieve the average rating of Harry Potter and the Sorcerer's Stone (book) via googleapis + */ +@Injectable() +export class DrilldownDataSource + extends DataSourceService + implements IDataSource, OnDestroy +{ + // This is the ID we'll use to identify the provider + public static providerId = "DrilldownDataSource"; + + // Use this subject to communicate the data source's busy state + public busy = new BehaviorSubject(false); + public dataFields: Partial[] = [ + { id: "continent.name", label: "Continent name" }, + { id: "currency", label: "Currency" }, + ]; + + private drillState: string[] = []; + private groupBy: string[]; + private cache: any; + private applyFilters$ = new Subject(); + + constructor() { + super(); + + // TODO: remove Partial in vNext after marking dataType field as optional - NUI-5838 + ( + this.dataFieldsConfig.dataFields$ as BehaviorSubject< + Partial[] + > + ).next(this.dataFields); + + this.applyFilters$ + // eslint-disable-next-line import/no-deprecated + .pipe(switchMap((filters) => this.getData(filters))) + .subscribe(async (res) => { + this.outputsSubject.next(await this.getFilteredData(res)); + }); + } + + private groupedDataHistory: any[] = []; + + // In this example, getFilteredData is invoked every 10 minutes (Take a look at the refresher + // provider definition in the widget configuration below to see how the interval is set) + public async getFilteredData(data: any): Promise { + return of(data) + .pipe( + map((countries) => { + const widgetInput = this.getOutput(countries); + + if (this.isDrillDown()) { + const activeDrillLvl = this.drillState.length; + const group = this.groupBy[activeDrillLvl]; + const [lastGroupedValue, groupedData] = + this.getTransformedDataForGroup(widgetInput, group); + + this.groupedDataHistory.push(lastGroupedValue); + + return groupedData; + } + + return widgetInput; + }) + ) + .toPromise(); + } + + public ngOnDestroy(): void { + this.outputsSubject.complete(); + } + + // redefine parent method + public async applyFilters(): Promise { + this.applyFilters$.next(this.getFilters()); + } + + private getData(filters: INovaFilters): Observable { + this.drillState = filters.drillstate?.value; + this.groupBy = filters.group?.value; + + this.busy.next(true); + + return of(this.cache || GRAPH_DATA_MOCK).pipe( + delay(1000), + tap((data) => (this.cache = data)), + map((data) => data.data.countries), + catchError((e) => of([])), + finalize(() => this.busy.next(false)) + ); + } + + private getTransformedDataForGroup(data: any, groupName: string) { + const groupedDict = groupBy(data, groupName); + const dataArr = Object.keys(groupedDict).map((property) => ({ + id: property, + label: property, + // TODO: apply groups mapping here + statuses: [ + { key: "state_ok", value: groupedDict[property].length }, + { + key: "status_unreachable", + value: generateNumberUpTo(100000), + }, + { key: "status_warning", value: generateNumberUpTo(10000) }, + { key: "status_unknown", value: generateNumberUpTo(1000) }, + ], + })); + + return [groupedDict, dataArr]; + } + + private isHome(): boolean { + return !this.drillState || this.drillState.length === 0; + } + + private isBack(): boolean { + return ( + this.groupedDataHistory.length > this.drillState?.length && + !this.isHome() + ); + } + + private isDrillDown(): boolean { + return this.drillState?.length !== this.groupBy?.length; + } + + private getOutput(data: any) { + if (this.isHome()) { + this.groupedDataHistory.length = 0; + } + + if (this.isBack()) { + this.groupedDataHistory.length = this.groupedDataHistory.length - 1; + } + + const lastHistoryValue = getLast(this.groupedDataHistory); + + if (!lastHistoryValue) { + return data; + } + + return lastHistoryValue[getLast(this.drillState)] || lastHistoryValue; + } +} + +const getLast = (arr: any[]) => arr[arr.length - 1]; +const generateNumberUpTo = (upperLimit: number): number => + Math.floor(Math.random() * upperLimit + 1); +\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\`, + "widget-types/drilldown/drilldown-widget-docs.component.ts": \\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\`// © 2022 SolarWinds Worldwide, LLC. All rights reserved. +// +// Permission is hereby granted, free of charge, to any person obtaining a copy +// of this software and associated documentation files (the "Software"), to +// deal in the Software without restriction, including without limitation the +// rights to use, copy, modify, merge, publish, distribute, sublicense, and/or +// sell copies of the Software, and to permit persons to whom the Software is +// furnished to do so, subject to the following conditions: +// +// The above copyright notice and this permission notice shall be included in +// all copies or substantial portions of the Software. +// +// THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR +// IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, +// FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE +// AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER +// LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, +// OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN +// THE SOFTWARE. + +import { Component, OnInit } from "@angular/core"; + +import { mapContentFile } from "../../../../demo-files-factory"; + +@Component({ + selector: "nui-drilldown-docs", + templateUrl: "./drilldown-widget-docs.component.html", + standalone: false, +}) +export class DrilldownDocsComponent implements OnInit { + public widgetFileText = ""; + public configuratorFileText = ""; + + public predefinedGroping = \\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\` +listWidget: { + providers: { + [WellKnownProviders.Adapter]: { + providerId: NOVA_DRILLDOWN_DATASOURCE_ADAPTER, + properties: { + ... + // adapter props + drillstate: [], + groupBy: ["regionName", "subregionName"], + groups: ["regionName", "subregionName"], + ... + }, + }, + }, +}, +\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\`; + public featuredDeclaredText = \\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\` + private supportedFeatures: IDataSourceFeatures = { + search: { enabled: true }, + };\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\`; + public featuresUsedText = \\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\` + this.features = new DataSourceFeatures(this.supportedFeatures); + \\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\`; + + public async ngOnInit(): Promise { + this.widgetFileText = await import( + "./../../../../../../src/lib/widget-types/drilldown/drilldown-widget" + ).then(mapContentFile); + this.configuratorFileText = await import( + "./../../../../../../src/lib/widget-types/drilldown/drilldown-configurator" + ).then(mapContentFile); + } +} +\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\`, + "widget-types/drilldown/drilldown-widget-docs.module.ts": \\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\`// © 2022 SolarWinds Worldwide, LLC. All rights reserved. +// +// Permission is hereby granted, free of charge, to any person obtaining a copy +// of this software and associated documentation files (the "Software"), to +// deal in the Software without restriction, including without limitation the +// rights to use, copy, modify, merge, publish, distribute, sublicense, and/or +// sell copies of the Software, and to permit persons to whom the Software is +// furnished to do so, subject to the following conditions: +// +// The above copyright notice and this permission notice shall be included in +// all copies or substantial portions of the Software. +// +// THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR +// IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, +// FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE +// AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER +// LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, +// OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN +// THE SOFTWARE. + +import { NgModule } from "@angular/core"; +import { RouterModule, Routes } from "@angular/router"; + +// eslint-disable-next-line max-len +import { + NuiButtonModule, + NuiDocsModule, + NuiMessageModule, + NuiSwitchModule, + DEMO_PATH_TOKEN, +} from "@nova-ui/bits"; +import { NuiDashboardsModule } from "@nova-ui/dashboards"; + +import { DrilldownMultiRequestWidgetExampleComponent } from "./drilldown-multi-request-widget/drilldown-multi-request-widget-example.component"; +import { DrilldownWidgetExampleComponent } from "./drilldown-widget/drilldown-widget-example.component"; +import { DrilldownDocsComponent } from "./drilldown-widget-docs.component"; +import { getDemoFiles } from "../../../../demo-files-factory"; + +const routes: Routes = [ + { + path: "", + component: DrilldownDocsComponent, + data: { + srlc: { + hideIndicator: true, + }, + }, + }, + { + path: "example", + component: DrilldownWidgetExampleComponent, + data: { + srlc: { + hideIndicator: true, + }, + }, + }, + { + path: "multiple-requests", + component: DrilldownMultiRequestWidgetExampleComponent, + data: { + srlc: { + hideIndicator: true, + }, + }, + }, +]; + +@NgModule({ + imports: [ + RouterModule.forChild(routes), + NuiButtonModule, + NuiDocsModule, + NuiMessageModule, + NuiDashboardsModule, + NuiSwitchModule, + ], + declarations: [ + DrilldownDocsComponent, + DrilldownWidgetExampleComponent, + DrilldownMultiRequestWidgetExampleComponent, + ], + providers: [ + { + provide: DEMO_PATH_TOKEN, + useValue: getDemoFiles("drilldown"), + }, + ], +}) +export default class DrilldownDocsModule {} +\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\`, + "widget-types/embedded-content/embedded-content-docs.component.ts": \\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\`// © 2022 SolarWinds Worldwide, LLC. All rights reserved. +// +// Permission is hereby granted, free of charge, to any person obtaining a copy +// of this software and associated documentation files (the "Software"), to +// deal in the Software without restriction, including without limitation the +// rights to use, copy, modify, merge, publish, distribute, sublicense, and/or +// sell copies of the Software, and to permit persons to whom the Software is +// furnished to do so, subject to the following conditions: +// +// The above copyright notice and this permission notice shall be included in +// all copies or substantial portions of the Software. +// +// THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR +// IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, +// FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE +// AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER +// LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, +// OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN +// THE SOFTWARE. + +import { Component, OnInit } from "@angular/core"; + +import { mapContentFile } from "../../../../demo-files-factory"; + +@Component({ + selector: "nui-embedded-content-docs", + templateUrl: "./embedded-content-docs.component.html", + standalone: false, +}) +export class EmbeddedContentDocsComponent implements OnInit { + public embeddedContentWidgetFileText = ""; + public embeddedContentConfiguratorFileText = ""; + + public async ngOnInit(): Promise { + this.embeddedContentWidgetFileText = await import( + "./../../../../../../src/lib/widget-types/embedded-content/embedded-content-widget" + ).then(mapContentFile); + this.embeddedContentWidgetFileText = await import( + "./../../../../../../src/lib/widget-types/embedded-content/embedded-content-configurator" + ).then(mapContentFile); + } +} +\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\`, + "widget-types/embedded-content/embedded-content-docs.module.ts": \\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\`// © 2022 SolarWinds Worldwide, LLC. All rights reserved. +// +// Permission is hereby granted, free of charge, to any person obtaining a copy +// of this software and associated documentation files (the "Software"), to +// deal in the Software without restriction, including without limitation the +// rights to use, copy, modify, merge, publish, distribute, sublicense, and/or +// sell copies of the Software, and to permit persons to whom the Software is +// furnished to do so, subject to the following conditions: +// +// The above copyright notice and this permission notice shall be included in +// all copies or substantial portions of the Software. +// +// THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR +// IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, +// FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE +// AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER +// LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, +// OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN +// THE SOFTWARE. + +import { NgModule } from "@angular/core"; +import { RouterModule, Routes } from "@angular/router"; + +// eslint-disable-next-line max-len +import { + NuiButtonModule, + NuiDocsModule, + NuiMessageModule, + NuiSwitchModule, + DEMO_PATH_TOKEN, +} from "@nova-ui/bits"; +import { NuiDashboardsModule } from "@nova-ui/dashboards"; + +import { EmbeddedContentDocsComponent } from "./embedded-content-docs.component"; +import { EmbeddedContentWidgetExampleComponent } from "./embedded-content-widget-example/embedded-content-widget-example.component"; +import { getDemoFiles } from "../../../../demo-files-factory"; + +const routes: Routes = [ + { + path: "", + component: EmbeddedContentDocsComponent, + data: { + srlc: { + hideIndicator: true, + }, + }, + }, + { + path: "example", + component: EmbeddedContentWidgetExampleComponent, + data: { + srlc: { + hideIndicator: true, + }, + }, + }, +]; + +@NgModule({ + imports: [ + RouterModule.forChild(routes), + NuiButtonModule, + NuiDocsModule, + NuiMessageModule, + NuiDashboardsModule, + NuiSwitchModule, + ], + declarations: [ + EmbeddedContentDocsComponent, + EmbeddedContentWidgetExampleComponent, + ], + providers: [ + { + provide: DEMO_PATH_TOKEN, + useValue: getDemoFiles("embedded-content"), + }, + ], +}) +export default class EmbeddedContentDocsModule {} +\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\`, + "widget-types/embedded-content/embedded-content-widget-example/embedded-content-widget-example.component.ts": \\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\`// © 2022 SolarWinds Worldwide, LLC. All rights reserved. +// +// Permission is hereby granted, free of charge, to any person obtaining a copy +// of this software and associated documentation files (the "Software"), to +// deal in the Software without restriction, including without limitation the +// rights to use, copy, modify, merge, publish, distribute, sublicense, and/or +// sell copies of the Software, and to permit persons to whom the Software is +// furnished to do so, subject to the following conditions: +// +// The above copyright notice and this permission notice shall be included in +// all copies or substantial portions of the Software. +// +// THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR +// IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, +// FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE +// AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER +// LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, +// OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN +// THE SOFTWARE. + +import { ChangeDetectorRef, Component, OnInit } from "@angular/core"; +import { GridsterConfig, GridsterItem } from "angular-gridster2"; + +import { + ComponentRegistryService, + EmbeddedContentComponent, + EmbeddedContentConfigurationComponent, + EmbeddedContentMode, + IDashboard, + IWidget, + IWidgets, + PizzagnaLayer, + WidgetTypesService, +} from "@nova-ui/dashboards"; + +@Component({ + selector: "embedded-content-widget-example", + templateUrl: "./embedded-content-widget-example.component.html", + styleUrls: ["./embedded-content-widget-example.component.less"], + standalone: false, +}) +export class EmbeddedContentWidgetExampleComponent implements OnInit { + // This variable will hold all the data needed to define the layout and behavior of the widgets. + // Pass this to the dashboard component's dashboard input in the template. + public dashboard: IDashboard | undefined; + + // Angular gridster requires a configuration object even if it's empty. + // Pass this to the dashboard component's gridsterConfig input in the template. + public gridsterConfig: GridsterConfig = {}; + + // Boolean passed as an input to the dashboard. When true, widgets can be moved, resized, removed, or edited + public editMode: boolean = false; + + constructor( + // WidgetTypesService provides the widget's necessary structure information + private widgetTypesService: WidgetTypesService, + + private componentRegistry: ComponentRegistryService, + private changeDetectorRef: ChangeDetectorRef + ) {} + + public ngOnInit(): void { + this.prepareNovaDashboards(); + this.initializeDashboard(); + } + + /** Used for restoring widgets state */ + public reInitializeDashboard(): void { + // destroys the components and their providers so the dashboard can re init data + this.dashboard = undefined; + this.changeDetectorRef.detectChanges(); + + this.initializeDashboard(); + } + + public initializeDashboard(): void { + // We're using a static configuration object for this example, but this is where + // the widget's configuration could potentially be populated from a database + const embeddedContentWidget = widgetConfig; + const widgets: IWidgets = { + // Complete the widget with information coming from its type definition + [embeddedContentWidget.id]: + this.widgetTypesService.mergeWithWidgetType( + embeddedContentWidget + ), + }; + + // Setting the widget dimensions and position (this is for gridster) + const positions: Record = { + [embeddedContentWidget.id]: { + cols: 10, + rows: 10, + y: 0, + x: 0, + }, + }; + + // Finally, assigning the variables we created above to the dashboard + this.dashboard = { positions, widgets }; + } + + private prepareNovaDashboards() { + this.componentRegistry.registerByLateLoadKey(EmbeddedContentComponent); + this.componentRegistry.registerByLateLoadKey( + EmbeddedContentConfigurationComponent + ); + } +} + +const widgetConfig: IWidget = { + id: "embeddedContentWidgetId", + type: "embedded-content", + pizzagna: { + [PizzagnaLayer.Configuration]: { + header: { + properties: { + title: "Embedded Content Widget", + subtitle: "", + }, + }, + mainContent: { + properties: { + sanitized: true, + mode: EmbeddedContentMode.URL, + customEmbeddedContent: "https://www.ventusky.com/", + }, + }, + }, + }, +}; +\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\`, + "widget-types/kpi/kpi-docs.component.ts": \\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\`// © 2022 SolarWinds Worldwide, LLC. All rights reserved. +// +// Permission is hereby granted, free of charge, to any person obtaining a copy +// of this software and associated documentation files (the "Software"), to +// deal in the Software without restriction, including without limitation the +// rights to use, copy, modify, merge, publish, distribute, sublicense, and/or +// sell copies of the Software, and to permit persons to whom the Software is +// furnished to do so, subject to the following conditions: +// +// The above copyright notice and this permission notice shall be included in +// all copies or substantial portions of the Software. +// +// THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR +// IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, +// FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE +// AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER +// LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, +// OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN +// THE SOFTWARE. + +import { Component, OnInit } from "@angular/core"; + +import { mapContentFile } from "../../../../demo-files-factory"; + +@Component({ + selector: "nui-kpi-docs", + templateUrl: "./kpi-docs.component.html", + standalone: false, +}) +export class KpiDocsComponent implements OnInit { + public kpiWidgetFileText = ""; + public kpiConfiguratorFileText = ""; + + public async ngOnInit(): Promise { + this.kpiWidgetFileText = await import( + "./../../../../../../src/lib/widget-types/kpi/kpi-widget" + ).then(mapContentFile); + this.kpiConfiguratorFileText = await import( + "./../../../../../../src/lib/widget-types/kpi/kpi-configurator" + ).then(mapContentFile); + } +} +\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\`, + "widget-types/kpi/kpi-docs.module.ts": \\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\`// © 2022 SolarWinds Worldwide, LLC. All rights reserved. +// +// Permission is hereby granted, free of charge, to any person obtaining a copy +// of this software and associated documentation files (the "Software"), to +// deal in the Software without restriction, including without limitation the +// rights to use, copy, modify, merge, publish, distribute, sublicense, and/or +// sell copies of the Software, and to permit persons to whom the Software is +// furnished to do so, subject to the following conditions: +// +// The above copyright notice and this permission notice shall be included in +// all copies or substantial portions of the Software. +// +// THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR +// IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, +// FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE +// AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER +// LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, +// OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN +// THE SOFTWARE. + +import { NgModule } from "@angular/core"; +import { RouterModule, Routes } from "@angular/router"; + +import { + NuiButtonModule, + NuiDocsModule, + NuiMessageModule, + NuiSwitchModule, + DEMO_PATH_TOKEN, +} from "@nova-ui/bits"; +import { + KpiColorComparatorsRegistryService, + NuiDashboardsModule, +} from "@nova-ui/dashboards"; + +import { KpiDocsComponent } from "./kpi-docs.component"; +import { KpiSyncBrokerExampleComponent } from "./kpi-sync-broker/kpi-sync-broker-example.component"; +import { KpiSyncBrokerDocsComponent } from "./kpi-sync-broker-docs.component"; +import { KpiSyncBrokerForAllTilesExampleComponent } from "./kpi-sync-broker-for-all-tiles/kpi-sync-broker-for-all-tiles-example.component"; +import { KpiWidgetExampleComponent } from "./kpi-widget/kpi-widget-example.component"; +import { KpiWidgetBackgroundColorExampleComponent } from "./kpi-widget-background-color/kpi-widget-background-color-example.component"; +import { KpiWidgetBackgroundColorDocsComponent } from "./kpi-widget-background-color-docs.component"; +import { KpiWidgetInteractiveExampleComponent } from "./kpi-widget-interactive/kpi-widget-interactive-example.component"; +import { getDemoFiles } from "../../../../demo-files-factory"; + +const routes: Routes = [ + { + path: "", + component: KpiDocsComponent, + data: { + srlc: { + hideIndicator: true, + }, + showThemeSwitcher: true, + }, + }, + { + path: "example", + component: KpiWidgetExampleComponent, + data: { + srlc: { + hideIndicator: true, + }, + }, + }, + { + path: "background-color", + component: KpiWidgetBackgroundColorDocsComponent, + data: { + srlc: { + hideIndicator: true, + }, + }, + }, + { + path: "sync-broker", + component: KpiSyncBrokerDocsComponent, + data: { + srlc: { + hideIndicator: true, + }, + }, + }, +]; + +@NgModule({ + imports: [ + RouterModule.forChild(routes), + NuiButtonModule, + NuiDocsModule, + NuiMessageModule, + NuiDashboardsModule, + NuiSwitchModule, + ], + declarations: [ + KpiDocsComponent, + KpiWidgetExampleComponent, + KpiWidgetInteractiveExampleComponent, + KpiWidgetBackgroundColorDocsComponent, + KpiWidgetBackgroundColorExampleComponent, + KpiSyncBrokerDocsComponent, + KpiSyncBrokerExampleComponent, + KpiSyncBrokerForAllTilesExampleComponent, + ], + providers: [ + KpiColorComparatorsRegistryService, + { + provide: DEMO_PATH_TOKEN, + useValue: getDemoFiles("kpi"), + }, + ], +}) +export default class KpiDocsModule { + constructor( + private comparatorsRegistry: KpiColorComparatorsRegistryService + ) { + this.backgroundColorDocsSetup(); + } + + private backgroundColorDocsSetup() { + this.comparatorsRegistry.registerComparators({ + "!=": { + comparatorFn: (actual: any, reference: any) => + // eslint-disable-next-line eqeqeq + actual != reference, + label: "Not equal", + }, + }); + } +} +\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\`, + "widget-types/kpi/kpi-sync-broker/kpi-sync-broker-example.component.ts": \\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\`// © 2022 SolarWinds Worldwide, LLC. All rights reserved. +// +// Permission is hereby granted, free of charge, to any person obtaining a copy +// of this software and associated documentation files (the "Software"), to +// deal in the Software without restriction, including without limitation the +// rights to use, copy, modify, merge, publish, distribute, sublicense, and/or +// sell copies of the Software, and to permit persons to whom the Software is +// furnished to do so, subject to the following conditions: +// +// The above copyright notice and this permission notice shall be included in +// all copies or substantial portions of the Software. +// +// THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR +// IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, +// FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE +// AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER +// LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, +// OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN +// THE SOFTWARE. + +import { HttpClient, HttpErrorResponse } from "@angular/common/http"; +import { + ChangeDetectorRef, + Component, + Injectable, + OnDestroy, + OnInit, +} from "@angular/core"; +import { GridsterConfig, GridsterItem } from "angular-gridster2"; +import keyBy from "lodash/keyBy"; +import { BehaviorSubject, of } from "rxjs"; +import { delay, finalize, take } from "rxjs/operators"; + +import { DataSourceService, IFilteringOutputs } from "@nova-ui/bits"; +import { + DATA_SOURCE, + IDashboard, + IKpiData, + IProviderConfiguration, + IWidget, + KpiComponent, + NOVA_KPI_DATASOURCE_ADAPTER, + NOVA_KPI_SCALE_SYNC_BROKER, + PizzagnaLayer, + ProviderRegistryService, + WellKnownPathKey, + WellKnownProviders, + WidgetTypesService, +} from "@nova-ui/dashboards"; + +/** + * A simple KPI data source to retrieve the average rating of Harry Potter and the Sorcerer's Stone (book) via googleapis + */ +@Injectable() +export class AverageRatingKpiDataSource + extends DataSourceService + implements OnDestroy +{ + public static providerId = "AverageRatingKpiDataSource"; + public busy = new BehaviorSubject(false); + + constructor(private http: HttpClient) { + super(); + } + + public async getFilteredData(): Promise { + this.busy.next(true); + return new Promise((resolve) => { + // *** Make a resource request to an external API (if needed) + this.http + .get("https://www.googleapis.com/books/v1/volumes/5MQFrgEACAAJ") + .pipe(finalize(() => this.busy.next(false))) + .subscribe({ + next: (data: any) => { + resolve({ + result: { + value: data.volumeInfo.averageRating, + }, + }); + }, + error: (error: HttpErrorResponse) => { + resolve({ + result: null, + error: { + type: error.status, + }, + }); + }, + }); + }); + } + + public ngOnDestroy(): void { + this.outputsSubject.complete(); + } +} + +/** + * A simple KPI data source to retrieve the ratings count of Harry Potter and the Sorcerer's Stone (book) via googleapis + */ +@Injectable() +export class RatingsCountKpiDataSource + extends DataSourceService + implements OnDestroy +{ + public static providerId = "RatingsCountKpiDataSource"; + + // Use this subject to communicate the data source's busy state + public busy = new BehaviorSubject(false); + + constructor(private http: HttpClient) { + super(); + } + + public async getFilteredData(): Promise { + this.busy.next(true); + return new Promise((resolve) => { + this.http + .get("https://www.googleapis.com/books/v1/volumes/5MQFrgEACAAJ") + .pipe( + delay(2000), + finalize(() => this.busy.next(false)) + ) + .subscribe({ + next: (data: any) => { + resolve({ + result: { + value: data.volumeInfo.ratingsCount, + }, + }); + }, + error: (error: HttpErrorResponse) => { + resolve({ + result: null, + error: { + type: error.status, + }, + }); + }, + }); + }); + } + + public ngOnDestroy(): void { + this.outputsSubject.complete(); + } +} +/** + * A simple KPI data source to retrieve the ratings count of Harry Potter and the Sorcerer's Stone (book) via googleapis + */ +@Injectable() +export class MockKpiDataSource + extends DataSourceService + implements OnDestroy +{ + public static providerId = "MockKpiDataSource"; + + // Use this subject to communicate the data source's busy state + public busy = new BehaviorSubject(false); + + constructor() { + super(); + } + + public async getFilteredData(): Promise { + this.busy.next(true); + return new Promise((resolve) => { + of(3381342) + .pipe( + delay(5000), + take(1), + finalize(() => this.busy.next(false)) + ) + .subscribe({ + next: (data: any) => { + resolve({ + result: { + value: data, + }, + }); + }, + }); + }); + } + + public ngOnDestroy(): void { + this.outputsSubject.complete(); + } +} + +/** + * A component that instantiates the dashboard + */ +@Component({ + selector: "kpi-sync-broker-example", + templateUrl: "./kpi-sync-broker-example.component.html", + styleUrls: ["./kpi-sync-broker-example.component.less"], + standalone: false, +}) +export class KpiSyncBrokerExampleComponent implements OnInit { + public dashboard: IDashboard | undefined; + public gridsterConfig: GridsterConfig = {}; + public editMode: boolean = false; + + constructor( + private widgetTypesService: WidgetTypesService, + private providerRegistry: ProviderRegistryService, + private changeDetectorRef: ChangeDetectorRef + ) {} + + public ngOnInit(): void { + this.setupDashboard(); + + this.initializeDashboard(); + } + + private setupDashboard() { + const widgetTemplate = this.widgetTypesService.getWidgetType("kpi", 1); + + this.widgetTypesService.setNode( + widgetTemplate, + "configurator", + WellKnownPathKey.DataSourceProviders, + [ + AverageRatingKpiDataSource.providerId, + RatingsCountKpiDataSource.providerId, + MockKpiDataSource.providerId, + ] + ); + + this.providerRegistry.setProviders({ + [AverageRatingKpiDataSource.providerId]: { + provide: DATA_SOURCE, + useClass: AverageRatingKpiDataSource, + deps: [HttpClient], + }, + [RatingsCountKpiDataSource.providerId]: { + provide: DATA_SOURCE, + useClass: RatingsCountKpiDataSource, + deps: [HttpClient], + }, + [MockKpiDataSource.providerId]: { + provide: DATA_SOURCE, + useClass: MockKpiDataSource, + deps: [], + }, + }); + } + + /** Used for restoring widgets state */ + public reInitializeDashboard(): void { + // destroys the components and their providers so the dashboard can re init data + this.dashboard = undefined; + this.changeDetectorRef.detectChanges(); + + this.initializeDashboard(); + } + + private initializeDashboard(): void { + const widgetsWithStructure = widgetsConfig.map((w) => + this.widgetTypesService.mergeWithWidgetType(w) + ); + const widgetsIndex = keyBy(widgetsWithStructure, (w: IWidget) => w.id); + + const positions: Record = { + kpiWidgetId: { + cols: 3, + rows: 6, + y: 0, + x: 0, + }, + kpiWidgetId2: { + cols: 3, + rows: 6, + y: 0, + x: 0, + }, + }; + + this.dashboard = { + positions, + widgets: widgetsIndex, + }; + } +} + +const widgetsConfig: IWidget[] = [ + { + id: "kpiWidgetId", + type: "kpi", + pizzagna: { + [PizzagnaLayer.Configuration]: { + header: { + properties: { + title: "NO Sync Broker", + subtitle: "Values sizes are being not synced", + }, + }, + tiles: { + properties: { + nodes: ["kpi1", "kpi2", "kpi3"], + }, + }, + kpi1: { + id: "kpi1", + componentType: KpiComponent.lateLoadKey, + properties: { + widgetData: { + units: \\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\`out of 5 Stars\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\`, + label: \\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\`Average Rating\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\`, + backgroundColor: "lightpink", + }, + }, + providers: { + [WellKnownProviders.DataSource]: { + providerId: AverageRatingKpiDataSource.providerId, + } as IProviderConfiguration, + [WellKnownProviders.Adapter]: { + providerId: NOVA_KPI_DATASOURCE_ADAPTER, + properties: { + componentId: "kpi1", + propertyPath: "widgetData", + }, + } as IProviderConfiguration, + }, + }, + kpi2: { + id: "kpi2", + componentType: KpiComponent.lateLoadKey, + properties: { + widgetData: { + label: \\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\`Another label which might be a pretty long one\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\`, + units: \\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\`Which comes from somewhere\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\`, + backgroundColor: "skyblue", + }, + }, + providers: { + [WellKnownProviders.DataSource]: { + providerId: RatingsCountKpiDataSource.providerId, + } as IProviderConfiguration, + [WellKnownProviders.Adapter]: { + providerId: NOVA_KPI_DATASOURCE_ADAPTER, + properties: { + componentId: "kpi2", + propertyPath: "widgetData", + }, + } as IProviderConfiguration, + }, + }, + kpi3: { + id: "kpi3", + componentType: KpiComponent.lateLoadKey, + properties: { + widgetData: { + label: \\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\`Random\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\`, + units: \\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\`Data\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\`, + }, + }, + providers: { + [WellKnownProviders.DataSource]: { + providerId: MockKpiDataSource.providerId, + } as IProviderConfiguration, + [WellKnownProviders.Adapter]: { + providerId: NOVA_KPI_DATASOURCE_ADAPTER, + properties: { + componentId: "kpi3", + propertyPath: "widgetData", + }, + } as IProviderConfiguration, + }, + }, + }, + }, + }, + { + id: "kpiWidgetId2", + type: "kpi", + pizzagna: { + [PizzagnaLayer.Configuration]: { + header: { + properties: { + title: "WITH Sync Broker", + subtitle: + "Now the values of label, units, and value are being synced", + }, + }, + tiles: { + properties: { + nodes: ["kpi4", "kpi5", "kpi6"], + }, + providers: { + // This is where and how you set the sync broker provider + kpiScaleSyncBroker: { + providerId: NOVA_KPI_SCALE_SYNC_BROKER, + properties: { + scaleSyncConfig: [ + // You can decide which values to keep in sync. For instance, you can leave only 'label' id in the array below + { id: "value" }, + { id: "label" }, + { id: "units" }, + ], + }, + }, + }, + }, + kpi4: { + id: "kpi4", + componentType: KpiComponent.lateLoadKey, + properties: { + widgetData: { + units: \\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\`out of 5 Stars\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\`, + label: \\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\`Average Rating\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\`, + backgroundColor: "lightpink", + }, + }, + providers: { + [WellKnownProviders.DataSource]: { + providerId: AverageRatingKpiDataSource.providerId, + } as IProviderConfiguration, + [WellKnownProviders.Adapter]: { + providerId: NOVA_KPI_DATASOURCE_ADAPTER, + properties: { + componentId: "kpi4", + propertyPath: "widgetData", + }, + } as IProviderConfiguration, + }, + }, + kpi5: { + id: "kpi5", + componentType: KpiComponent.lateLoadKey, + properties: { + widgetData: { + label: \\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\`Another label which might be a pretty long one\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\`, + units: \\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\`Which comes from somewhere\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\`, + backgroundColor: "skyblue", + }, + }, + providers: { + [WellKnownProviders.DataSource]: { + providerId: RatingsCountKpiDataSource.providerId, + } as IProviderConfiguration, + [WellKnownProviders.Adapter]: { + providerId: NOVA_KPI_DATASOURCE_ADAPTER, + properties: { + componentId: "kpi5", + propertyPath: "widgetData", + }, + } as IProviderConfiguration, + }, + }, + kpi6: { + id: "kpi6", + componentType: KpiComponent.lateLoadKey, + properties: { + widgetData: { + label: \\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\`Random\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\`, + units: \\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\`Data\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\`, + }, + }, + providers: { + [WellKnownProviders.DataSource]: { + providerId: MockKpiDataSource.providerId, + } as IProviderConfiguration, + [WellKnownProviders.Adapter]: { + providerId: NOVA_KPI_DATASOURCE_ADAPTER, + properties: { + componentId: "kpi6", + propertyPath: "widgetData", + }, + } as IProviderConfiguration, + }, + }, + }, + }, + }, +]; +\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\`, + "widget-types/kpi/kpi-sync-broker-docs.component.ts": \\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\`// © 2022 SolarWinds Worldwide, LLC. All rights reserved. +// +// Permission is hereby granted, free of charge, to any person obtaining a copy +// of this software and associated documentation files (the "Software"), to +// deal in the Software without restriction, including without limitation the +// rights to use, copy, modify, merge, publish, distribute, sublicense, and/or +// sell copies of the Software, and to permit persons to whom the Software is +// furnished to do so, subject to the following conditions: +// +// The above copyright notice and this permission notice shall be included in +// all copies or substantial portions of the Software. +// +// THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR +// IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, +// FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE +// AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER +// LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, +// OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN +// THE SOFTWARE. + +import { Component } from "@angular/core"; + +@Component({ + selector: "kpi-sync-broker-docs", + templateUrl: "./kpi-sync-broker-docs.component.html", + standalone: false, +}) +export class KpiSyncBrokerDocsComponent { + public kpiScaleSyncBroker = \\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\` +"tiles": { + "providers": { + kpiScaleSyncBroker: { + providerId: NOVA_KPI_SCALE_SYNC_BROKER, + properties: { + scaleSyncConfig: [ + { id: "value" }, + { id: "label" }, + { id: "units" }, + ], + }, + }, + }, +}, +\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\`; + + public defineScaleBrokerOnDashboardSetup = \\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\` +// To add the sync broker globally to all the kpi tiles you may start with setting up the broker config +// Here you define which values to keep in sync +const brokerConfig = { + providerId: NOVA_KPI_SCALE_SYNC_BROKER, + properties: { + scaleSyncConfig: [ + { id: "value" }, + { id: "label" }, + { id: "units" }, + ], + }, + }; + +// And here is how you set the sync broker for every KPI widget in the dashboard. +// Later, you will be able to override this setting for each separate KPI widget in the configuration (just like it is shown in the third +// width of the example with the 'kpiWidgetId3') +this.widgetTypesService.setNode( + widgetTemplate, + "widget", + "tiles.providers.kpiScaleSyncBroker", + brokerConfig +); +\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\`; +} +\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\`, + "widget-types/kpi/kpi-sync-broker-for-all-tiles/kpi-sync-broker-for-all-tiles-example.component.ts": \\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\`// © 2022 SolarWinds Worldwide, LLC. All rights reserved. +// +// Permission is hereby granted, free of charge, to any person obtaining a copy +// of this software and associated documentation files (the "Software"), to +// deal in the Software without restriction, including without limitation the +// rights to use, copy, modify, merge, publish, distribute, sublicense, and/or +// sell copies of the Software, and to permit persons to whom the Software is +// furnished to do so, subject to the following conditions: +// +// The above copyright notice and this permission notice shall be included in +// all copies or substantial portions of the Software. +// +// THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR +// IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, +// FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE +// AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER +// LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, +// OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN +// THE SOFTWARE. + +import { HttpClient, HttpErrorResponse } from "@angular/common/http"; +import { + ChangeDetectorRef, + Component, + Injectable, + OnDestroy, + OnInit, +} from "@angular/core"; +import { GridsterConfig, GridsterItem } from "angular-gridster2"; +import keyBy from "lodash/keyBy"; +import { BehaviorSubject, of } from "rxjs"; +import { delay, finalize, take } from "rxjs/operators"; + +import { DataSourceService, IFilteringOutputs } from "@nova-ui/bits"; +import { + DATA_SOURCE, + IDashboard, + IKpiData, + IProviderConfiguration, + IWidget, + KpiComponent, + NOVA_KPI_DATASOURCE_ADAPTER, + NOVA_KPI_SCALE_SYNC_BROKER, + PizzagnaLayer, + ProviderRegistryService, + WellKnownPathKey, + WellKnownProviders, + WidgetTypesService, +} from "@nova-ui/dashboards"; + +/** + * A simple KPI data source to retrieve the average rating of Harry Potter and the Sorcerer's Stone (book) via googleapis + */ +@Injectable() +export class AverageRatingKpiDataSource + extends DataSourceService + implements OnDestroy +{ + public static providerId = "AverageRatingKpiDataSource"; + public busy = new BehaviorSubject(false); + + constructor(private http: HttpClient) { + super(); + } + + public async getFilteredData(): Promise { + this.busy.next(true); + return new Promise((resolve) => { + // *** Make a resource request to an external API (if needed) + this.http + .get("https://www.googleapis.com/books/v1/volumes/5MQFrgEACAAJ") + .pipe(finalize(() => this.busy.next(false))) + .subscribe({ + next: (data: any) => { + resolve({ + result: { + value: data.volumeInfo.averageRating, + }, + }); + }, + error: (error: HttpErrorResponse) => { + resolve({ + result: null, + error: { + type: error.status, + }, + }); + }, + }); + }); + } + + public ngOnDestroy(): void { + this.outputsSubject.complete(); + } +} + +/** + * A simple KPI data source to retrieve the ratings count of Harry Potter and the Sorcerer's Stone (book) via googleapis + */ +@Injectable() +export class RatingsCountKpiDataSource + extends DataSourceService + implements OnDestroy +{ + public static providerId = "RatingsCountKpiDataSource"; + + // Use this subject to communicate the data source's busy state + public busy = new BehaviorSubject(false); + + constructor(private http: HttpClient) { + super(); + } + + public async getFilteredData(): Promise { + this.busy.next(true); + return new Promise((resolve) => { + this.http + .get("https://www.googleapis.com/books/v1/volumes/5MQFrgEACAAJ") + .pipe( + delay(2000), + finalize(() => this.busy.next(false)) + ) + .subscribe({ + next: (data: any) => { + resolve({ + result: { + value: data.volumeInfo.ratingsCount, + }, + }); + }, + error: (error: HttpErrorResponse) => { + resolve({ + result: null, + error: { + type: error.status, + }, + }); + }, + }); + }); + } + + public ngOnDestroy(): void { + this.outputsSubject.complete(); + } +} +/** + * A simple KPI data source to retrieve the ratings count of Harry Potter and the Sorcerer's Stone (book) via googleapis + */ +@Injectable() +export class MockKpiDataSource + extends DataSourceService + implements OnDestroy +{ + public static providerId = "MockKpiDataSource"; + + // Use this subject to communicate the data source's busy state + public busy = new BehaviorSubject(false); + public value: number = 3381342; + + constructor() { + super(); + } + + public async getFilteredData(): Promise { + this.busy.next(true); + return new Promise((resolve) => { + of(this.value) + .pipe( + delay(5000), + take(1), + finalize(() => this.busy.next(false)) + ) + .subscribe({ + next: (data: any) => { + resolve({ + result: { + value: data, + }, + }); + }, + }); + }); + } + + public ngOnDestroy(): void { + this.outputsSubject.complete(); + } +} + +/** + * A component that instantiates the dashboard + */ +@Component({ + selector: "kpi-sync-broker-for-all-tiles-example", + templateUrl: "./kpi-sync-broker-for-all-tiles-example.component.html", + styleUrls: ["./kpi-sync-broker-for-all-tiles-example.component.less"], + standalone: false, +}) +export class KpiSyncBrokerForAllTilesExampleComponent implements OnInit { + public dashboard: IDashboard | undefined; + public gridsterConfig: GridsterConfig = {}; + public editMode: boolean = false; + + constructor( + private widgetTypesService: WidgetTypesService, + private providerRegistry: ProviderRegistryService, + private changeDetectorRef: ChangeDetectorRef + ) {} + + public ngOnInit(): void { + this.setupDashboard(); + + this.initializeDashboard(); + } + + /** Used for restoring widgets state */ + public reInitializeDashboard(): void { + // destroys the components and their providers so the dashboard can re init data + this.dashboard = undefined; + this.changeDetectorRef.detectChanges(); + + this.initializeDashboard(); + } + + private setupDashboard() { + // To add the sync broker globally to all the kpi tiles you may start with setting up the broker config + // Here you define which values to keep in sync + const brokerConfig = { + providerId: NOVA_KPI_SCALE_SYNC_BROKER, + properties: { + scaleSyncConfig: [ + { id: "value" }, + { id: "label" }, + { id: "units" }, + ], + }, + }; + const widgetTemplate = this.widgetTypesService.getWidgetType("kpi", 1); + + this.widgetTypesService.setNode( + widgetTemplate, + "configurator", + WellKnownPathKey.DataSourceProviders, + [ + AverageRatingKpiDataSource.providerId, + RatingsCountKpiDataSource.providerId, + MockKpiDataSource.providerId, + ] + ); + + // And here is how you set the sync broker for every KPI widget in the dashboard. + // Later, you will be able to override this setting for each separate KPI widget in the configuration (just like it is shown in the third + // width of the example with the 'kpiWidgetId3') + this.widgetTypesService.setNode( + widgetTemplate, + "widget", + "tiles.providers.kpiScaleSyncBroker", + brokerConfig + ); + + this.providerRegistry.setProviders({ + [AverageRatingKpiDataSource.providerId]: { + provide: DATA_SOURCE, + useClass: AverageRatingKpiDataSource, + deps: [HttpClient], + }, + [RatingsCountKpiDataSource.providerId]: { + provide: DATA_SOURCE, + useClass: RatingsCountKpiDataSource, + deps: [HttpClient], + }, + [MockKpiDataSource.providerId]: { + provide: DATA_SOURCE, + useClass: MockKpiDataSource, + deps: [], + }, + }); + } + + private initializeDashboard(): void { + const widgetsWithStructure = widgetsConfig.map((w) => + this.widgetTypesService.mergeWithWidgetType(w) + ); + const widgetsIndex = keyBy(widgetsWithStructure, (w: IWidget) => w.id); + + const positions: Record = { + kpiWidgetId: { + cols: 3, + rows: 6, + y: 0, + x: 0, + }, + kpiWidgetId2: { + cols: 3, + rows: 6, + y: 0, + x: 3, + }, + kpiWidgetId3: { + cols: 3, + rows: 6, + y: 0, + x: 6, + }, + }; + + this.dashboard = { + positions, + widgets: widgetsIndex, + }; + } +} + +const widgetsConfig: IWidget[] = [ + { + id: "kpiWidgetId", + type: "kpi", + pizzagna: { + [PizzagnaLayer.Configuration]: { + header: { + properties: { + title: "Sync Broker Applied for ALL Widgets", + subtitle: "Values are being synced", + }, + }, + tiles: { + properties: { + nodes: ["kpi1", "kpi2", "kpi3"], + }, + }, + kpi1: { + id: "kpi1", + componentType: KpiComponent.lateLoadKey, + properties: { + widgetData: { + units: \\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\`out of 5 Stars\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\`, + label: \\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\`Average Rating\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\`, + backgroundColor: "lightpink", + }, + }, + providers: { + [WellKnownProviders.DataSource]: { + providerId: AverageRatingKpiDataSource.providerId, + } as IProviderConfiguration, + [WellKnownProviders.Adapter]: { + providerId: NOVA_KPI_DATASOURCE_ADAPTER, + properties: { + componentId: "kpi1", + propertyPath: "widgetData", + }, + } as IProviderConfiguration, + }, + }, + kpi2: { + id: "kpi2", + componentType: KpiComponent.lateLoadKey, + properties: { + widgetData: { + label: \\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\`Another label which might be a pretty long one\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\`, + units: \\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\`Which comes from somewhere\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\`, + backgroundColor: "skyblue", + }, + }, + providers: { + [WellKnownProviders.DataSource]: { + providerId: RatingsCountKpiDataSource.providerId, + } as IProviderConfiguration, + [WellKnownProviders.Adapter]: { + providerId: NOVA_KPI_DATASOURCE_ADAPTER, + properties: { + componentId: "kpi2", + propertyPath: "widgetData", + }, + } as IProviderConfiguration, + }, + }, + kpi3: { + id: "kpi3", + componentType: KpiComponent.lateLoadKey, + properties: { + widgetData: { + label: \\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\`Random\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\`, + units: \\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\`Data\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\`, + }, + }, + providers: { + [WellKnownProviders.DataSource]: { + providerId: MockKpiDataSource.providerId, + } as IProviderConfiguration, + [WellKnownProviders.Adapter]: { + providerId: NOVA_KPI_DATASOURCE_ADAPTER, + properties: { + componentId: "kpi3", + propertyPath: "widgetData", + }, + } as IProviderConfiguration, + }, + }, + }, + }, + }, + { + id: "kpiWidgetId2", + type: "kpi", + pizzagna: { + [PizzagnaLayer.Configuration]: { + header: { + properties: { + title: "Sync Broker Applied for ALL Widgets", + subtitle: + "Now the values of label, units, and value are being synced", + }, + }, + tiles: { + properties: { + nodes: ["kpi1", "kpi2", "kpi3"], + }, + }, + kpi1: { + id: "kpi1", + componentType: KpiComponent.lateLoadKey, + properties: { + widgetData: { + units: \\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\`out of 5 Stars\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\`, + label: \\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\`Average Rating\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\`, + backgroundColor: "lightpink", + }, + }, + providers: { + [WellKnownProviders.DataSource]: { + providerId: AverageRatingKpiDataSource.providerId, + } as IProviderConfiguration, + [WellKnownProviders.Adapter]: { + providerId: NOVA_KPI_DATASOURCE_ADAPTER, + properties: { + componentId: "kpi1", + propertyPath: "widgetData", + }, + } as IProviderConfiguration, + }, + }, + kpi2: { + id: "kpi2", + componentType: KpiComponent.lateLoadKey, + properties: { + widgetData: { + label: \\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\`Another label which might be a pretty long one\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\`, + units: \\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\`Which comes from somewhere\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\`, + backgroundColor: "skyblue", + }, + }, + providers: { + [WellKnownProviders.DataSource]: { + providerId: RatingsCountKpiDataSource.providerId, + } as IProviderConfiguration, + [WellKnownProviders.Adapter]: { + providerId: NOVA_KPI_DATASOURCE_ADAPTER, + properties: { + componentId: "kpi2", + propertyPath: "widgetData", + }, + } as IProviderConfiguration, + }, + }, + kpi3: { + id: "kpi3", + componentType: KpiComponent.lateLoadKey, + properties: { + widgetData: { + label: \\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\`Random\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\`, + units: \\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\`Data\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\`, + }, + }, + providers: { + [WellKnownProviders.DataSource]: { + providerId: MockKpiDataSource.providerId, + } as IProviderConfiguration, + [WellKnownProviders.Adapter]: { + providerId: NOVA_KPI_DATASOURCE_ADAPTER, + properties: { + componentId: "kpi3", + propertyPath: "widgetData", + }, + } as IProviderConfiguration, + }, + }, + }, + }, + }, + { + id: "kpiWidgetId3", + type: "kpi", + pizzagna: { + [PizzagnaLayer.Configuration]: { + header: { + properties: { + title: "Here We Sync Only Labels and Units", + subtitle: + "Now only the label, and units are being synced", + }, + }, + tiles: { + properties: { + nodes: ["kpi1", "kpi2", "kpi3"], + }, + providers: { + // This is where and how you can override the globally set broker config + kpiScaleSyncBroker: { + providerId: NOVA_KPI_SCALE_SYNC_BROKER, + properties: { + scaleSyncConfig: [ + { id: "label" }, + { id: "units" }, + ], + }, + }, + }, + }, + kpi1: { + id: "kpi1", + componentType: KpiComponent.lateLoadKey, + properties: { + widgetData: { + units: \\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\`out of 5 Stars\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\`, + label: \\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\`Average Rating\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\`, + backgroundColor: "lightpink", + }, + }, + providers: { + [WellKnownProviders.DataSource]: { + providerId: AverageRatingKpiDataSource.providerId, + } as IProviderConfiguration, + [WellKnownProviders.Adapter]: { + providerId: NOVA_KPI_DATASOURCE_ADAPTER, + properties: { + componentId: "kpi1", + propertyPath: "widgetData", + }, + } as IProviderConfiguration, + }, + }, + kpi2: { + id: "kpi2", + componentType: KpiComponent.lateLoadKey, + properties: { + widgetData: { + label: \\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\`Another label which might be a pretty long one\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\`, + units: \\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\`Which comes from somewhere\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\`, + backgroundColor: "skyblue", + }, + }, + providers: { + [WellKnownProviders.DataSource]: { + providerId: RatingsCountKpiDataSource.providerId, + } as IProviderConfiguration, + [WellKnownProviders.Adapter]: { + providerId: NOVA_KPI_DATASOURCE_ADAPTER, + properties: { + componentId: "kpi2", + propertyPath: "widgetData", + }, + } as IProviderConfiguration, + }, + }, + kpi3: { + id: "kpi3", + componentType: KpiComponent.lateLoadKey, + properties: { + widgetData: { + label: \\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\`Random\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\`, + units: \\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\`Data\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\`, + }, + }, + providers: { + [WellKnownProviders.DataSource]: { + providerId: MockKpiDataSource.providerId, + } as IProviderConfiguration, + [WellKnownProviders.Adapter]: { + providerId: NOVA_KPI_DATASOURCE_ADAPTER, + properties: { + componentId: "kpi3", + propertyPath: "widgetData", + }, + } as IProviderConfiguration, + }, + }, + }, + }, + }, +]; +\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\`, + "widget-types/kpi/kpi-widget/kpi-widget-example.component.ts": \\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\`// © 2022 SolarWinds Worldwide, LLC. All rights reserved. +// +// Permission is hereby granted, free of charge, to any person obtaining a copy +// of this software and associated documentation files (the "Software"), to +// deal in the Software without restriction, including without limitation the +// rights to use, copy, modify, merge, publish, distribute, sublicense, and/or +// sell copies of the Software, and to permit persons to whom the Software is +// furnished to do so, subject to the following conditions: +// +// The above copyright notice and this permission notice shall be included in +// all copies or substantial portions of the Software. +// +// THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR +// IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, +// FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE +// AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER +// LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, +// OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN +// THE SOFTWARE. + +import { HttpClient, HttpErrorResponse } from "@angular/common/http"; +import { Component, Injectable, OnDestroy, OnInit } from "@angular/core"; +import { GridsterConfig, GridsterItem } from "angular-gridster2"; +import { BehaviorSubject } from "rxjs"; +import { finalize } from "rxjs/operators"; + +import { DataSourceService, IFilteringOutputs } from "@nova-ui/bits"; +import { + DATA_SOURCE, + DEFAULT_PIZZAGNA_ROOT, + IDashboard, + IKpiData, + IProviderConfiguration, + IRefresherProperties, + IWidget, + IWidgets, + KpiComponent, + NOVA_KPI_DATASOURCE_ADAPTER, + PizzagnaLayer, + ProviderRegistryService, + WellKnownPathKey, + WellKnownProviders, + WidgetTypesService, +} from "@nova-ui/dashboards"; + +/** + * A simple KPI data source to retrieve the average rating of Harry Potter and the Sorcerer's Stone (book) via googleapis + */ +@Injectable() +export class AverageRatingKpiDataSource + extends DataSourceService + implements OnDestroy +{ + // This is the ID we'll use to identify the provider + public static providerId = "AverageRatingKpiDataSource"; + + // Use this subject to communicate the data source's busy state + public busy = new BehaviorSubject(false); + + constructor(private http: HttpClient) { + super(); + } + + // In this example, getFilteredData is invoked every 10 minutes (Take a look at the refresher + // provider definition in the widget configuration below to see how the interval is set) + public async getFilteredData(): Promise { + this.busy.next(true); + return new Promise((resolve) => { + // *** Make a resource request to an external API (if needed) + this.http + .get("https://www.googleapis.com/books/v1/volumes/5MQFrgEACAAJ") + .pipe(finalize(() => this.busy.next(false))) + .subscribe({ + next: (data: any) => { + resolve({ + result: { + value: data.volumeInfo.averageRating, + }, + }); + }, + error: (error: HttpErrorResponse) => { + resolve({ + result: null, + error: { + type: error.status, + }, + }); + }, + }); + }); + } + + public ngOnDestroy(): void { + this.outputsSubject.complete(); + } +} + +/** + * A component that instantiates the dashboard + */ +@Component({ + selector: "kpi-widget-example", + templateUrl: "./kpi-widget-example.component.html", + styleUrls: ["./kpi-widget-example.component.less"], + standalone: false, +}) +export class KpiWidgetExampleComponent implements OnInit { + // This variable will hold all the data needed to define the layout and behavior of the widgets. + // Pass this to the dashboard component's dashboard input in the template. + public dashboard: IDashboard; + + // Angular gridster requires a configuration object even if it's empty. + // Pass this to the dashboard component's gridsterConfig input in the template. + public gridsterConfig: GridsterConfig = {}; + + // Boolean passed as an input to the dashboard. When true, widgets can be moved, resized, removed, or edited + public editMode: boolean = false; + + constructor( + // WidgetTypesService provides the widget's necessary structure information + private widgetTypesService: WidgetTypesService, + + // In general, the ProviderRegistryService is used for making entities available for injection into dynamically loaded components. + private providerRegistry: ProviderRegistryService + ) {} + + public ngOnInit(): void { + // Grabbing the widget's default template which will be needed as a parameter for setNode + const widgetTemplate = this.widgetTypesService.getWidgetType("kpi", 1); + // Registering our data sources as dropdown options in the widget editor/configurator + // Note: This could also be done in the parent module's constructor so that + // multiple dashboards could have access to the same widget template modification. + this.widgetTypesService.setNode( + // This is the template we grabbed above with getWidgetType + widgetTemplate, + // We are setting the editor/configurator part of the widget template + "configurator", + // This indicates which node you are changing and we want to change + // the data source providers available for selection in the editor. + WellKnownPathKey.DataSourceProviders, + // We are setting the data sources available for selection in the editor + [AverageRatingKpiDataSource.providerId] + ); + + // Registering the data source for injection into the KPI tile. + // Note: Each tile of a KPI widget is assigned its own instance of the data source + this.providerRegistry.setProviders({ + [AverageRatingKpiDataSource.providerId]: { + provide: DATA_SOURCE, + useClass: AverageRatingKpiDataSource, + // Any dependencies that need to be injected into the provider must be listed here + deps: [HttpClient], + }, + }); + + this.initializeDashboard(); + } + + public initializeDashboard(): void { + // We're using a static configuration object for this example, but this is where + // the widget's configuration could potentially be populated from a database + const kpiWidget = widgetConfig; + const widgetIndex: IWidgets = { + // Complete the KPI widget with information coming from its type definition + [kpiWidget.id]: + this.widgetTypesService.mergeWithWidgetType(kpiWidget), + }; + + // Setting the widget dimensions and position (this is for gridster) + const positions: Record = { + [kpiWidget.id]: { + cols: 4, + rows: 6, + y: 0, + x: 0, + }, + }; + + // Finally, assigning the variables we created above to the dashboard + this.dashboard = { + positions, + widgets: widgetIndex, + }; + } +} + +const widgetConfig: IWidget = { + id: "kpiWidgetId", + type: "kpi", + pizzagna: { + [PizzagnaLayer.Configuration]: { + [DEFAULT_PIZZAGNA_ROOT]: { + providers: { + [WellKnownProviders.Refresher]: { + properties: { + // Configuring the refresher interval so that our data source is invoked every ten minutes + interval: 60 * 10, + enabled: true, + } as IRefresherProperties, + } as Partial, + }, + }, + header: { + properties: { + title: "Harry Potter and the Sorcerer's Stone", + subtitle: "By J. K. Rowling", + }, + }, + tiles: { + properties: { + nodes: ["kpi1"], + }, + }, + kpi1: { + id: "kpi1", + componentType: KpiComponent.lateLoadKey, + properties: { + widgetData: { + units: \\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\`out of 5 Stars\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\`, + label: \\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\`Average Rating\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\`, + }, + }, + providers: { + [WellKnownProviders.DataSource]: { + // Setting the data source providerId for the tile with id "kpi1" + providerId: AverageRatingKpiDataSource.providerId, + } as IProviderConfiguration, + [WellKnownProviders.Adapter]: { + providerId: NOVA_KPI_DATASOURCE_ADAPTER, + properties: { + componentId: "kpi1", + propertyPath: "widgetData", + }, + } as IProviderConfiguration, + }, + }, + }, + }, +}; +\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\`, + "widget-types/kpi/kpi-widget-background-color/kpi-widget-background-color-example.component.ts": \\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\`// © 2022 SolarWinds Worldwide, LLC. All rights reserved. +// +// Permission is hereby granted, free of charge, to any person obtaining a copy +// of this software and associated documentation files (the "Software"), to +// deal in the Software without restriction, including without limitation the +// rights to use, copy, modify, merge, publish, distribute, sublicense, and/or +// sell copies of the Software, and to permit persons to whom the Software is +// furnished to do so, subject to the following conditions: +// +// The above copyright notice and this permission notice shall be included in +// all copies or substantial portions of the Software. +// +// THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR +// IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, +// FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE +// AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER +// LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, +// OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN +// THE SOFTWARE. + +import { HttpClient, HttpErrorResponse } from "@angular/common/http"; +import { + ChangeDetectorRef, + Component, + Injectable, + OnDestroy, + OnInit, +} from "@angular/core"; +import { GridsterConfig, GridsterItem } from "angular-gridster2"; +import { BehaviorSubject } from "rxjs"; +import { finalize } from "rxjs/operators"; + +import { DataSourceService, IFilteringOutputs } from "@nova-ui/bits"; +import { + DATA_SOURCE, + DEFAULT_KPI_BACKGROUND_COLORS, + IDashboard, + IKpiColorRules, + IKpiData, + IProviderConfiguration, + IWidget, + IWidgets, + KpiComponent, + NOVA_KPI_COLOR_PRIORITIZER, + NOVA_KPI_DATASOURCE_ADAPTER, + PizzagnaLayer, + ProviderRegistryService, + WellKnownPathKey, + WellKnownProviders, + WidgetTypesService, +} from "@nova-ui/dashboards"; + +/** + * A simple KPI data source to retrieve the average rating of Harry Potter and the Sorcerer's Stone (book) via googleapis + */ +@Injectable() +export class AverageRatingKpiDataSource + extends DataSourceService + implements OnDestroy +{ + public static providerId = "AverageRatingKpiDataSource"; + public busy = new BehaviorSubject(false); + + constructor(private http: HttpClient) { + super(); + } + + public async getFilteredData(): Promise { + this.busy.next(true); + return new Promise((resolve) => { + // *** Make a resource request to an external API (if needed) + this.http + .get("https://www.googleapis.com/books/v1/volumes/5MQFrgEACAAJ") + .pipe(finalize(() => this.busy.next(false))) + .subscribe({ + next: (data: any) => { + resolve({ + result: { + value: data.volumeInfo.averageRating, + // setting the color on the dataSource "Sea Green", + // uncomment to get the background color update from the "Data" layer + // backgroundColor: "var(--nui-color-chart-three)", + }, + }); + }, + error: (error: HttpErrorResponse) => { + resolve({ + result: null, + error: { + type: error.status, + }, + }); + }, + }); + }); + } + + public ngOnDestroy(): void { + this.outputsSubject.complete(); + } +} + +/** + * A component that instantiates the dashboard + */ +@Component({ + selector: "kpi-widget-background-color-example", + templateUrl: "./kpi-widget-background-color-example.component.html", + styleUrls: ["./kpi-widget-background-color-example.component.less"], + standalone: false, +}) +export class KpiWidgetBackgroundColorExampleComponent implements OnInit { + public dashboard: IDashboard | undefined; + public gridsterConfig: GridsterConfig = {}; + public editMode: boolean = false; + + constructor( + private widgetTypesService: WidgetTypesService, + private providerRegistry: ProviderRegistryService, + private changeDetectorRef: ChangeDetectorRef + ) {} + + public ngOnInit(): void { + this.setupDashboard(); + + // KPI tile default color setup + this.setupDefaultColorStructure(); + + // Sets the custom pallette to the 'Description' section + this.setupCustomPalletteDescription(); + + // Sets the custom pallette to the 'Background color rules' section + this.setupCustomPalletteRules(); + + this.initializeDashboard(); + } + + /** Used for restoring widgets state */ + public reInitializeDashboard(): void { + // destroys the components and their providers so the dashboard can re init data + this.dashboard = undefined; + this.changeDetectorRef.detectChanges(); + + this.initializeDashboard(); + } + + private setupCustomPalletteDescription() { + const kpiWidgetTemplate = this.widgetTypesService.getWidgetType( + "kpi", + 1 + ); + this.widgetTypesService.setNode( + kpiWidgetTemplate, + "configurator", + WellKnownPathKey.TileDescriptionBackgroundColors, + [ + { color: "var(--nui-color-chart-one)", label: "Blue" }, + { + color: "var(--nui-color-chart-one-light)", + label: "Blue Light", + }, + { + color: "var(--nui-color-chart-one-dark)", + label: "Blue Dark", + }, + ] + ); + } + + private setupCustomPalletteRules() { + const kpiWidgetTemplate = this.widgetTypesService.getWidgetType( + "kpi", + 1 + ); + this.widgetTypesService.setNode( + kpiWidgetTemplate, + "configurator", + WellKnownPathKey.TileBackgroundColorRulesBackgroundColors, + [ + { color: "red", label: "Native Red" }, + ...DEFAULT_KPI_BACKGROUND_COLORS, + ] + ); + } + + private setupDefaultColorStructure() { + const widgetTemplate = this.widgetTypesService.getWidgetType("kpi", 1); + this.widgetTypesService.setNode( + widgetTemplate, + "widget", + "tiles.properties.template.properties.widgetData.backgroundColor", + "red" + ); + } + + private setupDashboard() { + const widgetTemplate = this.widgetTypesService.getWidgetType("kpi", 1); + this.widgetTypesService.setNode( + widgetTemplate, + "configurator", + WellKnownPathKey.DataSourceProviders, + [AverageRatingKpiDataSource.providerId] + ); + + this.providerRegistry.setProviders({ + [AverageRatingKpiDataSource.providerId]: { + provide: DATA_SOURCE, + useClass: AverageRatingKpiDataSource, + deps: [HttpClient], + }, + }); + } + + private initializeDashboard(): void { + const kpiWidget = widgetConfig; + const widgetIndex: IWidgets = { + [kpiWidget.id]: + this.widgetTypesService.mergeWithWidgetType(kpiWidget), + }; + + const positions: Record = { + [kpiWidget.id]: { + cols: 4, + rows: 6, + y: 0, + x: 0, + }, + }; + + this.dashboard = { + positions, + widgets: widgetIndex, + }; + } +} + +const widgetConfig: IWidget = { + id: "kpiWidgetId", + type: "kpi", + pizzagna: { + [PizzagnaLayer.Configuration]: { + header: { + properties: { + title: "Harry Potter and the Sorcerer's Stone", + subtitle: "By J. K. Rowling", + }, + }, + tiles: { + properties: { + nodes: ["kpi1"], + }, + }, + kpi1: { + id: "kpi1", + componentType: KpiComponent.lateLoadKey, + properties: { + widgetData: { + units: \\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\`out of 5 Stars\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\`, + label: \\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\`Average Rating\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\`, + // Configuration color "Blue" + backgroundColor: "var(--nui-color-chart-one)", + }, + }, + providers: { + [WellKnownProviders.DataSource]: { + providerId: AverageRatingKpiDataSource.providerId, + } as IProviderConfiguration, + [WellKnownProviders.Adapter]: { + providerId: NOVA_KPI_DATASOURCE_ADAPTER, + properties: { + componentId: "kpi1", + propertyPath: "widgetData", + }, + } as IProviderConfiguration, + [WellKnownProviders.KpiColorPrioritizer]: { + providerId: NOVA_KPI_COLOR_PRIORITIZER, + properties: { + // Color Prioritizer Rules + // settings rules - if the value is more than "2" display "Violet" color + rules: [ + { + comparisonType: ">", + value: 2, + color: "var(--nui-color-chart-four)", + }, + ] as IKpiColorRules[], + }, + } as IProviderConfiguration, + }, + }, + }, + }, +}; +\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\`, + "widget-types/kpi/kpi-widget-background-color-docs.component.ts": \\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\`// © 2022 SolarWinds Worldwide, LLC. All rights reserved. +// +// Permission is hereby granted, free of charge, to any person obtaining a copy +// of this software and associated documentation files (the "Software"), to +// deal in the Software without restriction, including without limitation the +// rights to use, copy, modify, merge, publish, distribute, sublicense, and/or +// sell copies of the Software, and to permit persons to whom the Software is +// furnished to do so, subject to the following conditions: +// +// The above copyright notice and this permission notice shall be included in +// all copies or substantial portions of the Software. +// +// THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR +// IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, +// FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE +// AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER +// LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, +// OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN +// THE SOFTWARE. + +import { Component } from "@angular/core"; + +@Component({ + selector: "nui-kpi-background-color-docs", + templateUrl: "./kpi-widget-background-color-docs.component.html", + standalone: false, +}) +export class KpiWidgetBackgroundColorDocsComponent { + public comparatorsRegistryCode = \\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\` + this.comparatorsRegistry.registerComparators({ + "!=": { + comparatorFn: (actual: any, reference: any) => actual != reference, + label: "Not equal", + }, + }); + \\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\`; +} +\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\`, + "widget-types/kpi/kpi-widget-interactive/kpi-widget-interactive-example.component.ts": \\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\`// © 2022 SolarWinds Worldwide, LLC. All rights reserved. +// +// Permission is hereby granted, free of charge, to any person obtaining a copy +// of this software and associated documentation files (the "Software"), to +// deal in the Software without restriction, including without limitation the +// rights to use, copy, modify, merge, publish, distribute, sublicense, and/or +// sell copies of the Software, and to permit persons to whom the Software is +// furnished to do so, subject to the following conditions: +// +// The above copyright notice and this permission notice shall be included in +// all copies or substantial portions of the Software. +// +// THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR +// IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, +// FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE +// AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER +// LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, +// OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN +// THE SOFTWARE. + +import { HttpClient, HttpErrorResponse } from "@angular/common/http"; +import { Component, Injectable, OnDestroy, OnInit } from "@angular/core"; +import { GridsterConfig, GridsterItem } from "angular-gridster2"; +import { BehaviorSubject } from "rxjs"; +import { finalize } from "rxjs/operators"; + +import { DataSourceService, IFilteringOutputs } from "@nova-ui/bits"; +import { + DATA_SOURCE, + IDashboard, + IKpiData, + IProviderConfiguration, + IWidget, + IWidgets, + KpiComponent, + NOVA_KPI_DATASOURCE_ADAPTER, + NOVA_URL_INTERACTION_HANDLER, + PizzagnaLayer, + ProviderRegistryService, + WellKnownPathKey, + WellKnownProviders, + WidgetTypesService, +} from "@nova-ui/dashboards"; + +/** + * A simple KPI data source to retrieve the average rating of Harry Potter and the Sorcerer's Stone (book) via googleapis + */ +@Injectable() +export class BookRatingDataSource + extends DataSourceService + implements OnDestroy +{ + // This is the ID we'll use to identify the provider + public static providerId = "BookRatingDataSource"; + + // Use this subject to communicate the data source's busy state + public busy = new BehaviorSubject(false); + + constructor(private http: HttpClient) { + super(); + } + + // In this example, getFilteredData is invoked every 10 minutes (Take a look at the refresher + // provider definition in the widget configuration below to see how the interval is set) + public async getFilteredData(): Promise { + this.busy.next(true); + return new Promise((resolve) => { + // *** Make a resource request to an external API (if needed) + this.http + .get("https://www.googleapis.com/books/v1/volumes/zpvysRGsBlwC") + .pipe(finalize(() => this.busy.next(false))) + .subscribe({ + next: (data: any) => { + resolve({ + result: { + value: data.volumeInfo.averageRating, + link: data.volumeInfo.infoLink, + }, + }); + }, + error: (error: HttpErrorResponse) => { + resolve({ + result: null, + error: { + type: error.status, + }, + }); + }, + }); + }); + } + + public ngOnDestroy(): void { + this.outputsSubject.complete(); + } +} + +/** + * A component that instantiates the dashboard + */ +@Component({ + selector: "kpi-widget-interactive-example", + templateUrl: "./kpi-widget-interactive-example.component.html", + styleUrls: ["./kpi-widget-interactive-example.component.less"], + standalone: false, +}) +export class KpiWidgetInteractiveExampleComponent implements OnInit { + // This variable will hold all the data needed to define the layout and behavior of the widgets. + // Pass this to the dashboard component's dashboard input in the template. + public dashboard: IDashboard; + + // Angular gridster requires a configuration object even if it's empty. + // Pass this to the dashboard component's gridsterConfig input in the template. + public gridsterConfig: GridsterConfig = {}; + + // Boolean passed as an input to the dashboard. When true, widgets can be moved, resized, removed, or edited + public editMode: boolean = false; + + constructor( + // WidgetTypesService provides the widget's necessary structure information + private widgetTypesService: WidgetTypesService, + + // In general, the ProviderRegistryService is used for making entities available for injection into dynamically loaded components. + private providerRegistry: ProviderRegistryService + ) {} + + public ngOnInit(): void { + // Grabbing the widget's default template which will be needed as a parameter for setNode + const widgetTemplate = this.widgetTypesService.getWidgetType("kpi", 1); + // Registering our data sources as dropdown options in the widget editor/configurator + // Note: This could also be done in the parent module's constructor so that + // multiple dashboards could have access to the same widget template modification. + this.widgetTypesService.setNode( + // This is the template we grabbed above with getWidgetType + widgetTemplate, + // We are setting the editor/configurator part of the widget template + "configurator", + // This indicates which node you are changing and we want to change + // the data source providers available for selection in the editor. + WellKnownPathKey.DataSourceProviders, + // We are setting the data sources available for selection in the editor + [BookRatingDataSource.providerId] + ); + + // Registering the data source for injection into the KPI tile. + // Note: Each tile of a KPI widget is assigned its own instance of the data source + this.providerRegistry.setProviders({ + [BookRatingDataSource.providerId]: { + provide: DATA_SOURCE, + useClass: BookRatingDataSource, + // Any dependencies that need to be injected into the provider must be listed here + deps: [HttpClient], + }, + }); + + this.initializeDashboard(); + } + + public initializeDashboard(): void { + // We're using a static configuration object for this example, but this is where + // the widget's configuration could potentially be populated from a database + const kpiWidget = widgetConfig; + const widgetIndex: IWidgets = { + // Complete the KPI widget with information coming from its type definition + [kpiWidget.id]: + this.widgetTypesService.mergeWithWidgetType(kpiWidget), + }; + + // Setting the widget dimensions and position (this is for gridster) + const positions: Record = { + [kpiWidget.id]: { + cols: 4, + rows: 6, + y: 0, + x: 0, + }, + }; + + // Finally, assigning the variables we created above to the dashboard + this.dashboard = { + positions, + widgets: widgetIndex, + }; + } +} + +const widgetConfig: IWidget = { + id: "kpiWidgetId", + type: "kpi", + pizzagna: { + [PizzagnaLayer.Configuration]: { + header: { + properties: { + title: "Harry Potter and the Order of the Phoenix", + subtitle: "By: J. K. Rowling", + }, + }, + tiles: { + providers: { + interaction: { + // Configuring the UrlInteractionHandler for interactions on the tiles + providerId: NOVA_URL_INTERACTION_HANDLER, + properties: { + // the 'url' property tells the handler what link to use when interaction occurs on the series + url: "\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\${data.link}", + }, + }, + }, + properties: { + nodes: ["kpi1"], + }, + }, + kpi1: { + id: "kpi1", + componentType: KpiComponent.lateLoadKey, + properties: { + widgetData: { + units: \\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\`out of 5 stars\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\`, + label: \\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\`Average Rating\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\`, + value: 0, + // the link property that is passed to the UrlInteractionHandler when the title is clicked + // this will be updated in BookRatingDataSource's 'getFilteredData' call. + link: "http://www.google.com", + }, + }, + providers: { + [WellKnownProviders.DataSource]: { + // Setting the data source providerId for the tile with id "kpi1" + providerId: BookRatingDataSource.providerId, + } as IProviderConfiguration, + [WellKnownProviders.Adapter]: { + providerId: NOVA_KPI_DATASOURCE_ADAPTER, + properties: { + componentId: "kpi1", + propertyPath: "widgetData", + }, + } as IProviderConfiguration, + }, + }, + }, + }, +}; +\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\`, + "widget-types/proportional/models.ts": \\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\`export interface IMockBeerReview { + id: string; + name: string; + data: number[]; + icon: string; + link?: string; + value: string; + color?: string; +} +\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\`, + "widget-types/proportional/proportional-docs.component.ts": \\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\`// © 2022 SolarWinds Worldwide, LLC. All rights reserved. +// +// Permission is hereby granted, free of charge, to any person obtaining a copy +// of this software and associated documentation files (the "Software"), to +// deal in the Software without restriction, including without limitation the +// rights to use, copy, modify, merge, publish, distribute, sublicense, and/or +// sell copies of the Software, and to permit persons to whom the Software is +// furnished to do so, subject to the following conditions: +// +// The above copyright notice and this permission notice shall be included in +// all copies or substantial portions of the Software. +// +// THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR +// IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, +// FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE +// AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER +// LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, +// OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN +// THE SOFTWARE. + +import { Component, OnInit } from "@angular/core"; + +import { mapContentFile } from "../../../../demo-files-factory"; + +@Component({ + selector: "nui-proportional-docs", + templateUrl: "./proportional-docs.component.html", + standalone: false, +}) +export class ProportionalDocsComponent implements OnInit { + public proportionalWidgetFileText = ""; + public proportionalConfiguratorFileText = ""; + + public async ngOnInit(): Promise { + this.proportionalWidgetFileText = await import( + "./../../../../../../src/lib/widget-types/proportional/proportional-widget" + ).then(mapContentFile); + this.proportionalConfiguratorFileText = await import( + "./../../../../../../src/lib/widget-types/proportional/proportional-configurator" + ).then(mapContentFile); + } +} +\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\`, + "widget-types/proportional/proportional-docs.module.ts": \\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\`// © 2022 SolarWinds Worldwide, LLC. All rights reserved. +// +// Permission is hereby granted, free of charge, to any person obtaining a copy +// of this software and associated documentation files (the "Software"), to +// deal in the Software without restriction, including without limitation the +// rights to use, copy, modify, merge, publish, distribute, sublicense, and/or +// sell copies of the Software, and to permit persons to whom the Software is +// furnished to do so, subject to the following conditions: +// +// The above copyright notice and this permission notice shall be included in +// all copies or substantial portions of the Software. +// +// THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR +// IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, +// FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE +// AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER +// LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, +// OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN +// THE SOFTWARE. + +import { NgModule } from "@angular/core"; +import { RouterModule, Routes } from "@angular/router"; + +import { + NuiButtonModule, + NuiDocsModule, + NuiMessageModule, + NuiSwitchModule, + DEMO_PATH_TOKEN, +} from "@nova-ui/bits"; +import { NuiDashboardsModule } from "@nova-ui/dashboards"; + +import { ProportionalDocsComponent } from "./proportional-docs.component"; +import { ProportionalDonutContentDocsComponent } from "./proportional-donut-content-docs.component"; +import { ProportionalWidgetDonutContentFormattersExampleComponent } from "./proportional-donut-content-formatters/proportional-donut-content-formatters-example.component"; +import { ProportionalWidgetExampleComponent } from "./proportional-widget/proportional-widget-example.component"; +import { ProportionalWidgetInteractiveExampleComponent } from "./proportional-widget-interactive/proportional-widget-interactive-example.component"; +import { getDemoFiles } from "../../../../demo-files-factory"; + +const routes: Routes = [ + { + path: "", + component: ProportionalDocsComponent, + data: { + srlc: { + hideIndicator: true, + }, + showThemeSwitcher: true, + }, + }, + { + path: "example", + component: ProportionalWidgetExampleComponent, + data: { + srlc: { + hideIndicator: true, + }, + }, + }, + { + path: "donut-content-formatters", + component: ProportionalDonutContentDocsComponent, + data: { + srlc: { + hideIndicator: true, + }, + }, + }, + { + path: "donut-content-formatters-example", + component: ProportionalWidgetDonutContentFormattersExampleComponent, + data: { + srlc: { + hideIndicator: true, + }, + }, + }, + { + path: "proportional-widget-interactive-example", + component: ProportionalWidgetInteractiveExampleComponent, + data: { + srlc: { + hideIndicator: true, + }, + }, + }, +]; + +@NgModule({ + imports: [ + RouterModule.forChild(routes), + NuiButtonModule, + NuiDocsModule, + NuiDashboardsModule, + NuiMessageModule, + NuiSwitchModule, + ], + declarations: [ + ProportionalDocsComponent, + ProportionalWidgetExampleComponent, + ProportionalWidgetInteractiveExampleComponent, + ProportionalWidgetDonutContentFormattersExampleComponent, + ProportionalDonutContentDocsComponent, + ], + providers: [ + { + provide: DEMO_PATH_TOKEN, + useValue: getDemoFiles("proportional"), + }, + ], +}) +export default class ProportionalDocsModule {} +\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\`, + "widget-types/proportional/proportional-donut-content-docs.component.ts": \\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\`// © 2022 SolarWinds Worldwide, LLC. All rights reserved. +// +// Permission is hereby granted, free of charge, to any person obtaining a copy +// of this software and associated documentation files (the "Software"), to +// deal in the Software without restriction, including without limitation the +// rights to use, copy, modify, merge, publish, distribute, sublicense, and/or +// sell copies of the Software, and to permit persons to whom the Software is +// furnished to do so, subject to the following conditions: +// +// The above copyright notice and this permission notice shall be included in +// all copies or substantial portions of the Software. +// +// THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR +// IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, +// FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE +// AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER +// LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, +// OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN +// THE SOFTWARE. + +import { Component } from "@angular/core"; + +@Component({ + selector: "nui-proportional-donut-content-docs", + templateUrl: "./proportional-donut-content-docs.component.html", + standalone: false, +}) +export class ProportionalDonutContentDocsComponent { + public dataSourceDataFieldsConfig = \\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\` +public dataFieldsConfig: IProportionalDataFieldsConfig = { + dataFields$: new BehaviorSubject(this.dataFields), + chartSeriesDataFields$: new BehaviorSubject(this.chartSeriesDataFields), +}; + \\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\`; + + public widgetConfigSlice = \\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\` +"properties": { + "configuration": { + "chartOptions": { + donutContentConfig: { + formatter: { + componentType: SiUnitsFormatterComponent.lateLoadKey, + }, + aggregator: { + aggregatorType: sumAggregator.aggregatorType, + }, + }, + } + } +} + + \\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\`; +} +\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\`, + "widget-types/proportional/proportional-donut-content-formatters/proportional-donut-content-formatters-example.component.ts": \\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\`// © 2022 SolarWinds Worldwide, LLC. All rights reserved. +// +// Permission is hereby granted, free of charge, to any person obtaining a copy +// of this software and associated documentation files (the "Software"), to +// deal in the Software without restriction, including without limitation the +// rights to use, copy, modify, merge, publish, distribute, sublicense, and/or +// sell copies of the Software, and to permit persons to whom the Software is +// furnished to do so, subject to the following conditions: +// +// The above copyright notice and this permission notice shall be included in +// all copies or substantial portions of the Software. +// +// THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR +// IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, +// FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE +// AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER +// LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, +// OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN +// THE SOFTWARE. + +import { + ChangeDetectorRef, + Component, + Injectable, + OnDestroy, + OnInit, +} from "@angular/core"; +import { GridsterConfig, GridsterItem } from "angular-gridster2"; +import { BehaviorSubject } from "rxjs"; + +import { + DataSourceService, + IDataField, + IDataSource, + IFilteringOutputs, +} from "@nova-ui/bits"; +import { IAccessors, IChartAssistSeries } from "@nova-ui/charts"; +import { + DATA_SOURCE, + DEFAULT_LEGEND_FORMATTERS, + DEFAULT_PIZZAGNA_ROOT, + DEFAULT_PROPORTIONAL_CONTENT_AGGREGATORS, + DEFAULT_PROPORTIONAL_CONTENT_FORMATTERS, + DONUT_CONTENT_CONFIGURATION_SLICE, + IDashboard, + IDonutContentConfig, + IProportionalDataFieldsConfig, + IProportionalWidgetChartOptions, + IProportionalWidgetConfig, + IProviderConfiguration, + IWidget, + IWidgets, + LegendPlacement, + PizzagnaLayer, + ProportionalContentAggregatorsRegistryService, + ProportionalDonutContentFormattersRegistryService, + ProportionalLegendFormattersRegistryService, + ProportionalWidgetChartTypes, + ProviderRegistryService, + SiUnitsFormatterComponent, + sumAggregator, + WellKnownPathKey, + WellKnownProviders, + WidgetTypesService, +} from "@nova-ui/dashboards"; + +import { IMockBeerReview } from "../models"; + +/** + * A simple proportional data source to retrieve beer review counts by city + */ +@Injectable() +export class BeerReviewCountsByCityMockDataSource + extends DataSourceService> + implements IDataSource>, OnDestroy +{ + public static providerId = "BeerReviewCountsByCityMockDataSource"; + public busy = new BehaviorSubject(false); + + protected dataFields: IDataField[] = [ + { + id: "Brno", + label: "Brno", + // @ts-ignore + dataType: null, + }, + { + id: "kyiv", + label: "Kyiv", + // @ts-ignore + dataType: null, + }, + { + id: "austin", + label: "Austin", + // @ts-ignore + dataType: null, + }, + { + id: "lisbon", + label: "Lisbon", + // @ts-ignore + dataType: null, + }, + { + id: "sydney", + label: "Sydney", + // @ts-ignore + dataType: null, + }, + { + id: "nur-sultan", + label: "Nur-Sultan", + // @ts-ignore + dataType: null, + }, + ]; + protected chartSeriesDataFields: IDataField[] = [ + // default field in the chart series that is used for the aggregation + { + id: "data[0]", + label: "data", + // @ts-ignore + dataType: null, + }, + // any custom field in the chart series that is used for the aggregation + { + id: "customDonutContent", + label: "Custom Donut Content", + // @ts-ignore + dataType: null, + }, + ]; + + /** + * DataSource needs to implement the "IDataFieldsConfig" for this scenario. + * + * It's necessary to provide the "chartSeriesDataFields", + * that's why proportional widget dataSource has it's own interface for that - IProportionalDataFieldsConfig. + * + * dataFields$ - stands for possible series fields + * chartSeriesDataFields$ - stands for the fields IN the series + * + * see declaration of "dataFields" and "chartSeriesDataFields" for the example. + */ + public dataFieldsConfig: IProportionalDataFieldsConfig = { + dataFields$: new BehaviorSubject(this.dataFields), + chartSeriesDataFields$: new BehaviorSubject( + this.chartSeriesDataFields + ), + }; + + public async getFilteredData(): Promise { + this.busy.next(true); + return new Promise((resolve) => { + setTimeout(() => { + this.outputsSubject.next({ + result: getMockBeerReviewCountsByCity(), + }); + this.busy.next(false); + }, 300); + }); + } + + public ngOnDestroy(): void { + this.outputsSubject.complete(); + } +} + +/** + * A component that instantiates the dashboard + */ +@Component({ + selector: "proportional-widget-donut-content-formatters-example", + templateUrl: "./proportional-donut-content-formatters-example.component.html", + styleUrls: [ + "./proportional-donut-content-formatters-example.component.less", + ], + standalone: false, +}) +export class ProportionalWidgetDonutContentFormattersExampleComponent + implements OnInit +{ + public dashboard: IDashboard | undefined; + + // Angular gridster requires a configuration object even if it's empty. + // Pass this to the dashboard component's gridsterConfig input in the template. + public gridsterConfig: GridsterConfig = {}; + + // Boolean passed as an input to the dashboard. When true, widgets can be moved, resized, removed, or edited + public editMode: boolean = false; + + constructor( + // WidgetTypesService provides the widget's necessary structure information + private widgetTypesService: WidgetTypesService, + // In general, the ProviderRegistryService is used for making entities available for injection into dynamically loaded components. + private providerRegistry: ProviderRegistryService, + // registry for adding the formatter for donut content + contentFormattersRegistry: ProportionalDonutContentFormattersRegistryService, + // registry for adding the formatter for proportional legend + legendFormattersRegistry: ProportionalLegendFormattersRegistryService, + // registry for adding the aggregators for donut content + aggregatorRegistry: ProportionalContentAggregatorsRegistryService, + private changeDetectorRef: ChangeDetectorRef + ) { + // on the dashboard startup, it's necessary to add possible content formatters, legend formatters and content aggregators to the registry. + // using registry is a way for setting the available formatters. + legendFormattersRegistry.addItems(DEFAULT_LEGEND_FORMATTERS); + contentFormattersRegistry.addItems( + DEFAULT_PROPORTIONAL_CONTENT_FORMATTERS + ); + aggregatorRegistry.addItems(DEFAULT_PROPORTIONAL_CONTENT_AGGREGATORS); + } + + public ngOnInit(): void { + // Grabbing the widget's default template which will be needed as a parameter for setNode + const widgetTemplate = this.widgetTypesService.getWidgetType( + "proportional", + 1 + ); + + // Registering our data sources as dropdown options in the widget editor/configurator + // Note: This could also be done in the parent module's constructor so that + // multiple dashboards could have access to the same widget template modification. + this.widgetTypesService.setNode( + // This is the template we grabbed above with getWidgetType + widgetTemplate, + // We are setting the editor/configurator part of the widget template + "configurator", + // This indicates which node you are changing and we want to change + // the data source providers available for selection in the editor. + WellKnownPathKey.DataSourceProviders, + // We are setting the data sources available for selection in the editor + [BeerReviewCountsByCityMockDataSource.providerId] + ); + + // Setup of the configurator is done here + this.setupConfigurator(); + + // Registering the data source for injection into the Proportional widget. + this.providerRegistry.setProviders({ + [BeerReviewCountsByCityMockDataSource.providerId]: { + provide: DATA_SOURCE, + useClass: BeerReviewCountsByCityMockDataSource, + deps: [], + }, + }); + + this.initializeDashboard(); + } + + /** Used for restoring widgets state */ + public reInitializeDashboard(): void { + // destroys the components and their providers so the dashboard can re init data + this.dashboard = undefined; + this.changeDetectorRef.detectChanges(); + + this.initializeDashboard(); + } + + private initializeDashboard(): void { + // We're using a static configuration object for this example, but this is where + // the widget's configuration could potentially be populated from a database + const widgetIndex: IWidgets = { + // Complete the proportional widget with information coming from its type definition + [widgetConfig.id]: + this.widgetTypesService.mergeWithWidgetType(widgetConfig), + }; + + // Setting the widget dimensions and position (this is for gridster) + const positions: Record = { + [widgetConfig.id]: { + cols: 6, + rows: 6, + y: 0, + x: 0, + }, + }; + + // Finally, assigning the variables we created above to the dashboard + this.dashboard = { + positions, + widgets: widgetIndex, + }; + } + + /** + * Sets up the configurator sections for proportional donut + */ + private setupConfigurator() { + const widgetTemplate = this.widgetTypesService.getWidgetType( + "proportional", + 1 + ); + + // remove old "presentation", "chartOptionsEditor" and "donutContentConfiguration" sections from the configurator + delete widgetTemplate.configurator?.structure?.presentation; + delete widgetTemplate.configurator?.structure?.chartOptionsEditor; + delete widgetTemplate.configurator?.structure + ?.donutContentConfiguration; + + // add new "presentation" section + this.widgetTypesService.setNode( + widgetTemplate, + "configurator", + "presentation", + DONUT_CONTENT_CONFIGURATION_SLICE.presentation + ); + // add new "chartOptionsEditor" section + this.widgetTypesService.setNode( + widgetTemplate, + "configurator", + "chartOptionsEditor", + DONUT_CONTENT_CONFIGURATION_SLICE.chartOptionsEditor + ); + // add new "donutContentConfiguration" section + this.widgetTypesService.setNode( + widgetTemplate, + "configurator", + "donutContentConfiguration", + DONUT_CONTENT_CONFIGURATION_SLICE.donutContentConfiguration + ); + } +} + +const widgetConfig: IWidget = { + id: "proportionalWidgetId", + type: "proportional", + pizzagna: { + [PizzagnaLayer.Configuration]: { + [DEFAULT_PIZZAGNA_ROOT]: { + providers: {}, + }, + header: { + properties: { + title: "Beer Review Tally by City", + subtitle: "These People Love Beer", + }, + }, + chart: { + providers: { + [WellKnownProviders.DataSource]: { + // Setting the data source providerId for the chart + providerId: + BeerReviewCountsByCityMockDataSource.providerId, + } as IProviderConfiguration, + }, + properties: { + configuration: { + chartOptions: { + type: ProportionalWidgetChartTypes.DonutChart, + legendPlacement: LegendPlacement.Right, + // old configuration looks like this + // contentFormatter: { + // componentType: DonutContentSumFormatterComponent.lateLoadKey, + // }, + + // NEW configuration looks like this + donutContentConfig: { + formatter: { + componentType: + SiUnitsFormatterComponent.lateLoadKey, + }, + aggregator: { + aggregatorType: + sumAggregator.aggregatorType, + properties: { + // example of a default metric to be used for the percentage calculation + // activeMetricId: "austin", + }, + }, + } as IDonutContentConfig, + } as IProportionalWidgetChartOptions, + } as IProportionalWidgetConfig, + }, + }, + }, + }, +}; + +export function getMockBeerReviewCountsByCity(): IMockBeerReview[] { + return [ + { + id: "Brno", + name: "Brno", + data: [Math.round(Math.random() * 1000000)], + icon: "status_down", + link: "https://en.wikipedia.org/wiki/Brno", + value: "Brno", + customDonutContent: "Custom Brno", + }, + { + id: "kyiv", + name: "Kyiv", + data: [Math.round(Math.random() * 1000000)], + icon: "status_critical", + link: "https://en.wikipedia.org/wiki/Kyiv", + value: "Kyiv", + customDonutContent: "Custom Kyiv", + }, + { + id: "austin", + name: "Austin", + data: [Math.round(Math.random() * 1000000)], + icon: "status_warning", + link: "https://en.wikipedia.org/wiki/Austin", + value: "Austin", + customDonutContent: "Custom Austin", + }, + { + id: "lisbon", + name: "Lisbon", + data: [Math.round(Math.random() * 1000000)], + icon: "status_unknown", + link: "https://en.wikipedia.org/wiki/Lisbon", + value: "Lisbon", + customDonutContent: "Custom Lisbon", + }, + { + id: "sydney", + name: "Sydney", + data: [Math.round(Math.random() * 1000000)], + icon: "status_up", + link: "https://en.wikipedia.org/wiki/Sydney", + value: "Sydney", + customDonutContent: "Custom Sydney", + }, + { + id: "nur-sultan", + name: "Nur-Sultan", + data: [Math.round(Math.random() * 1000000)], + icon: "status_unmanaged", + link: "https://en.wikipedia.org/wiki/Nur-Sultan", + value: "Nur-Sultan", + customDonutContent: "Custom Nur-Sultan", + }, + ].sort((a, b) => a.data[0] - b.data[0]); +} +\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\`, + "widget-types/proportional/proportional-widget/proportional-widget-example.component.ts": \\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\`// © 2022 SolarWinds Worldwide, LLC. All rights reserved. +// +// Permission is hereby granted, free of charge, to any person obtaining a copy +// of this software and associated documentation files (the "Software"), to +// deal in the Software without restriction, including without limitation the +// rights to use, copy, modify, merge, publish, distribute, sublicense, and/or +// sell copies of the Software, and to permit persons to whom the Software is +// furnished to do so, subject to the following conditions: +// +// The above copyright notice and this permission notice shall be included in +// all copies or substantial portions of the Software. +// +// THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR +// IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, +// FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE +// AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER +// LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, +// OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN +// THE SOFTWARE. + +import { + ChangeDetectorRef, + Component, + Injectable, + OnDestroy, + OnInit, +} from "@angular/core"; +import { GridsterConfig, GridsterItem } from "angular-gridster2"; +import { BehaviorSubject } from "rxjs"; + +import { + DataSourceService, + IDataSource, + IFilteringOutputs, +} from "@nova-ui/bits"; +import { + DATA_SOURCE, + DEFAULT_PIZZAGNA_ROOT, + IDashboard, + IProportionalWidgetChartOptions, + IProportionalWidgetConfig, + IProportionalWidgetData, + IProviderConfiguration, + IRefresherProperties, + IWidget, + IWidgets, + LegendPlacement, + PizzagnaLayer, + ProportionalWidgetChartTypes, + ProviderRegistryService, + WellKnownPathKey, + WellKnownProviders, + WidgetTypesService, +} from "@nova-ui/dashboards"; + +import { IMockBeerReview } from "../models"; + +/** + * A simple proportional data source to retrieve beer review counts by city + */ +@Injectable() +export class BeerReviewCountsByCityMockDataSource + extends DataSourceService + implements IDataSource, OnDestroy +{ + // This is the ID we'll use to identify the provider + public static providerId = "BeerReviewCountsByCityMockDataSource"; + public busy = new BehaviorSubject(false); + + public async getFilteredData(): Promise { + this.busy.next(true); + return new Promise((resolve) => { + setTimeout(() => { + this.outputsSubject.next({ + result: getMockBeerReviewCountsByCity(), + }); + this.busy.next(false); + }, 300); + }); + } + + public ngOnDestroy(): void { + this.outputsSubject.complete(); + } +} + +/** + * A component that instantiates the dashboard + */ +@Component({ + selector: "proportional-widget-example", + templateUrl: "./proportional-widget-example.component.html", + styleUrls: ["./proportional-widget-example.component.less"], + standalone: false, +}) +export class ProportionalWidgetExampleComponent implements OnInit { + // This variable will hold all the data needed to define the layout and behavior of the widgets. + // Pass this to the dashboard component's dashboard input in the template. + public dashboard: IDashboard | undefined; + + // Angular gridster requires a configuration object even if it's empty. + // Pass this to the dashboard component's gridsterConfig input in the template. + public gridsterConfig: GridsterConfig = {}; + + // Boolean passed as an input to the dashboard. When true, widgets can be moved, resized, removed, or edited + public editMode: boolean = false; + + constructor( + // WidgetTypesService provides the widget's necessary structure information + private widgetTypesService: WidgetTypesService, + // In general, the ProviderRegistryService is used for making entities available for injection into dynamically loaded components. + private providerRegistry: ProviderRegistryService, + private changeDetectorRef: ChangeDetectorRef + ) {} + + public ngOnInit(): void { + // Grabbing the widget's default template which will be needed as a parameter for setNode + const widgetTemplate = this.widgetTypesService.getWidgetType( + "proportional", + 1 + ); + + // Registering our data sources as dropdown options in the widget editor/configurator + // Note: This could also be done in the parent module's constructor so that + // multiple dashboards could have access to the same widget template modification. + this.widgetTypesService.setNode( + // This is the template we grabbed above with getWidgetType + widgetTemplate, + // We are setting the editor/configurator part of the widget template + "configurator", + // This indicates which node you are changing and we want to change + // the data source providers available for selection in the editor. + WellKnownPathKey.DataSourceProviders, + // We are setting the data sources available for selection in the editor + [BeerReviewCountsByCityMockDataSource.providerId] + ); + + // Registering the data source for injection into the Proportional widget. + this.providerRegistry.setProviders({ + [BeerReviewCountsByCityMockDataSource.providerId]: { + provide: DATA_SOURCE, + useClass: BeerReviewCountsByCityMockDataSource, + deps: [], + }, + }); + + this.initializeDashboard(); + } + + /** Used for restoring widgets state */ + public reInitializeDashboard(): void { + // destroys the components and their providers so the dashboard can re init data + this.dashboard = undefined; + this.changeDetectorRef.detectChanges(); + + this.initializeDashboard(); + } + + public initializeDashboard(): void { + // We're using a static configuration object for this example, but this is where + // the widget's configuration could potentially be populated from a database + const widgetIndex: IWidgets = { + // Complete the proportional widget with information coming from its type definition + [widgetConfig.id]: + this.widgetTypesService.mergeWithWidgetType(widgetConfig), + }; + + // Setting the widget dimensions and position (this is for gridster) + const positions: Record = { + [widgetConfig.id]: { + cols: 5, + rows: 6, + y: 0, + x: 0, + }, + }; + + // Finally, assigning the variables we created above to the dashboard + this.dashboard = { + positions, + widgets: widgetIndex, + }; + } +} + +const widgetConfig: IWidget = { + id: "proportionalWidgetId", + type: "proportional", + pizzagna: { + [PizzagnaLayer.Configuration]: { + [DEFAULT_PIZZAGNA_ROOT]: { + providers: { + [WellKnownProviders.Refresher]: { + properties: { + // Configuring the refresher interval so that our data source is invoked every ten minutes + interval: 60 * 10, + enabled: true, + } as IRefresherProperties, + } as Partial, + }, + }, + header: { + properties: { + title: "Beer Review Tally by City", + subtitle: "These People Love Beer", + }, + }, + chart: { + providers: { + [WellKnownProviders.DataSource]: { + // Setting the data source providerId for the chart + providerId: + BeerReviewCountsByCityMockDataSource.providerId, + } as IProviderConfiguration, + }, + properties: { + configuration: { + chartOptions: { + type: ProportionalWidgetChartTypes.DonutChart, + legendPlacement: LegendPlacement.Right, + } as IProportionalWidgetChartOptions, + // You can optionally define custom colors for the chart by setting the 'chartColors' configuration property + // "chartColors": [ + // "var(--nui-color-chart-five)", + // "var(--nui-color-chart-six)", + // "var(--nui-color-chart-seven)", + // "var(--nui-color-chart-eight)", + // "var(--nui-color-chart-nine)", + // "var(--nui-color-chart-ten)", + // ], + // or use-mapped structure + chartColors: { + Brno: "var(--nui-color-chart-five)", + kyiv: "var(--nui-color-chart-six)", + austin: "var(--nui-color-chart-seven)", + lisbon: "var(--nui-color-chart-eight)", + sydney: "var(--nui-color-chart-nine)", + "nur-sultan": "var(--nui-color-chart-ten)", + }, + prioritizeWidgetColors: false, + } as IProportionalWidgetConfig, + }, + }, + }, + }, +}; + +export function getMockBeerReviewCountsByCity(): IMockBeerReview[] { + return [ + { + id: "Brno", + name: "Brno", + data: [Math.round(Math.random() * 100000)], + icon: "status_down", + link: "https://en.wikipedia.org/wiki/Brno", + value: "Brno", + color: "var(--nui-color-chart-one)", + }, + { + id: "kyiv", + name: "Kyiv", + data: [Math.round(Math.random() * 100000)], + icon: "status_critical", + link: "https://en.wikipedia.org/wiki/Kyiv", + value: "Kyiv", + color: "var(--nui-color-chart-two)", + }, + { + id: "austin", + name: "Austin", + data: [Math.round(Math.random() * 100000)], + icon: "status_warning", + link: "https://en.wikipedia.org/wiki/Austin", + value: "Austin", + color: "var(--nui-color-chart-three)", + }, + { + id: "lisbon", + name: "Lisbon", + data: [Math.round(Math.random() * 100000)], + icon: "status_unknown", + link: "https://en.wikipedia.org/wiki/Lisbon", + value: "Lisbon", + color: "var(--nui-color-chart-four)", + }, + { + id: "sydney", + name: "Sydney", + data: [Math.round(Math.random() * 100000)], + icon: "status_up", + link: "https://en.wikipedia.org/wiki/Sydney", + value: "Sydney", + color: "var(--nui-color-chart-five)", + }, + { + id: "nur-sultan", + name: "Nur-Sultan", + data: [Math.round(Math.random() * 100000)], + icon: "status_unmanaged", + link: "https://en.wikipedia.org/wiki/Nur-Sultan", + value: "Nur-Sultan", + color: "var(--nui-color-chart-six)", + }, + ].sort((a, b) => a.data[0] - b.data[0]); +} +\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\`, + "widget-types/proportional/proportional-widget-interactive/proportional-widget-interactive-example.component.ts": \\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\`// © 2022 SolarWinds Worldwide, LLC. All rights reserved. +// +// Permission is hereby granted, free of charge, to any person obtaining a copy +// of this software and associated documentation files (the "Software"), to +// deal in the Software without restriction, including without limitation the +// rights to use, copy, modify, merge, publish, distribute, sublicense, and/or +// sell copies of the Software, and to permit persons to whom the Software is +// furnished to do so, subject to the following conditions: +// +// The above copyright notice and this permission notice shall be included in +// all copies or substantial portions of the Software. +// +// THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR +// IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, +// FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE +// AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER +// LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, +// OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN +// THE SOFTWARE. + +import { + ChangeDetectorRef, + Component, + Injectable, + OnDestroy, + OnInit, +} from "@angular/core"; +import { GridsterConfig, GridsterItem } from "angular-gridster2"; +import keyBy from "lodash/keyBy"; +import { BehaviorSubject } from "rxjs"; + +import { + DataSourceService, + IDataSource, + IFilteringOutputs, +} from "@nova-ui/bits"; +import { + DATA_SOURCE, + DEFAULT_PIZZAGNA_ROOT, + IDashboard, + IProportionalWidgetChartOptions, + IProportionalWidgetConfig, + IProportionalWidgetData, + IProviderConfiguration, + IWidget, + LegendPlacement, + NOVA_URL_INTERACTION_HANDLER, + PizzagnaLayer, + ProportionalWidgetChartTypes, + ProviderRegistryService, + WellKnownPathKey, + WellKnownProviders, + WidgetTypesService, +} from "@nova-ui/dashboards"; + +import { IMockBeerReview } from "../models"; + +/** + * A simple proportional data source to retrieve beer review counts by city + */ +@Injectable() +export class ReviewCountsByCityMockDataSource + extends DataSourceService + implements IDataSource, OnDestroy +{ + // This is the ID we'll use to identify the provider + public static providerId = "ReviewCountsByCityMockDataSource"; + public busy = new BehaviorSubject(false); + + public async getFilteredData(): Promise { + this.busy.next(true); + return new Promise((resolve) => { + setTimeout(() => { + this.outputsSubject.next({ + result: getMockBeerReviewCountsByCity(), + }); + this.busy.next(false); + }, 300); + }); + } + + public ngOnDestroy(): void { + this.outputsSubject.complete(); + } +} + +/** + * A component that instantiates the dashboard + */ +@Component({ + selector: "proportional-widget-interactive-example", + templateUrl: "./proportional-widget-interactive-example.component.html", + styleUrls: ["./proportional-widget-interactive-example.component.less"], + standalone: false, +}) +export class ProportionalWidgetInteractiveExampleComponent implements OnInit { + // This variable will hold all the data needed to define the layout and behavior of the widgets. + // Pass this to the dashboard component's dashboard input in the template. + public dashboard: IDashboard | undefined; + + // Angular gridster requires a configuration object even if it's empty. + // Pass this to the dashboard component's gridsterConfig input in the template. + public gridsterConfig: GridsterConfig = {}; + + // Boolean passed as an input to the dashboard. When true, widgets can be moved, resized, removed, or edited + public editMode: boolean = false; + + constructor( + // WidgetTypesService provides the widget's necessary structure information + private widgetTypesService: WidgetTypesService, + // In general, the ProviderRegistryService is used for making entities available for injection into dynamically loaded components. + private providerRegistry: ProviderRegistryService, + private changeDetectorRef: ChangeDetectorRef + ) {} + + public ngOnInit(): void { + // Grabbing the widget's default template which will be needed as a parameter for setNode + const widgetTemplate = this.widgetTypesService.getWidgetType( + "proportional", + 1 + ); + + // Registering our data sources as dropdown options in the widget editor/configurator + // Note: This could also be done in the parent module's constructor so that + // multiple dashboards could have access to the same widget template modification. + this.widgetTypesService.setNode( + // This is the template we grabbed above with getWidgetType + widgetTemplate, + // We are setting the editor/configurator part of the widget template + "configurator", + // This indicates which node you are changing and we want to change + // the data source providers available for selection in the editor. + WellKnownPathKey.DataSourceProviders, + // We are setting the data sources available for selection in the editor + [ReviewCountsByCityMockDataSource.providerId] + ); + + // Registering the data source for injection into the Proportional widget. + this.providerRegistry.setProviders({ + [ReviewCountsByCityMockDataSource.providerId]: { + provide: DATA_SOURCE, + useClass: ReviewCountsByCityMockDataSource, + deps: [], + }, + }); + + this.initializeDashboard(); + } + + /** Used for restoring widgets state */ + public reInitializeDashboard(): void { + // destroys the components and their providers so the dashboard can re init data + this.dashboard = undefined; + this.changeDetectorRef.detectChanges(); + + this.initializeDashboard(); + } + + public initializeDashboard(): void { + // We're using a static configuration object for this example, but this is where + // the widget's configuration could potentially be populated from a database + const widgetsWithStructure = widgetConfigs.map((w) => + this.widgetTypesService.mergeWithWidgetType(w) + ); + const widgetsIndex = keyBy(widgetsWithStructure, (w: IWidget) => w.id); + + // Setting the widget dimensions and position (this is for gridster) + const positions: Record = { + [widgetConfigs[0].id]: { + cols: 6, + rows: 6, + y: 0, + x: 0, + }, + [widgetConfigs[1].id]: { + cols: 6, + rows: 6, + y: 0, + x: 6, + }, + }; + + // Finally, assigning the variables we created above to the dashboard + this.dashboard = { + positions, + widgets: widgetsIndex, + }; + } +} + +const widgetConfigs: IWidget[] = [ + { + id: "widget1", + type: "proportional", + pizzagna: { + [PizzagnaLayer.Configuration]: { + [DEFAULT_PIZZAGNA_ROOT]: { + providers: { + // Configuring the UrlInteractionHandler to handle interactions + [WellKnownProviders.InteractionHandler]: { + providerId: NOVA_URL_INTERACTION_HANDLER, + properties: { + // the 'url' property tells the handler what link to use when interaction occurs on the series + // if the series does not have a link we are passing one to the handler + url: "\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\${data.link || 'https://en.wikipedia.org/wiki/'+data.id}", + // by default the link is opened in the current window, set 'newWindow' to true to open in a new tab instead + // newWindow: true, + }, + }, + }, + }, + header: { + properties: { + title: "Proportional Widget", + subtitle: "With interaction handler", + }, + }, + chart: { + providers: { + [WellKnownProviders.DataSource]: { + // Setting the data source providerId for the chart + providerId: + ReviewCountsByCityMockDataSource.providerId, + } as IProviderConfiguration, + }, + properties: { + configuration: { + // Setting the interactive to true + interactive: true, + chartOptions: { + type: ProportionalWidgetChartTypes.VerticalBarChart, + legendPlacement: LegendPlacement.Bottom, + } as IProportionalWidgetChartOptions, + prioritizeWidgetColors: false, + } as IProportionalWidgetConfig, + }, + }, + }, + }, + }, + { + id: "widget2", + type: "proportional", + pizzagna: { + [PizzagnaLayer.Configuration]: { + header: { + properties: { + title: "Proportional Widget", + subtitle: "Without interaction handler", + }, + }, + chart: { + providers: { + [WellKnownProviders.DataSource]: { + // Setting the data source providerId for the chart + providerId: + ReviewCountsByCityMockDataSource.providerId, + } as IProviderConfiguration, + }, + properties: { + configuration: { + // interactive set to false so series without links are not styled like a link + interactive: false, + chartOptions: { + type: ProportionalWidgetChartTypes.HorizontalBarChart, + legendPlacement: LegendPlacement.Bottom, + } as IProportionalWidgetChartOptions, + prioritizeWidgetColors: false, + } as IProportionalWidgetConfig, + }, + }, + }, + }, + }, +]; + +export function getMockBeerReviewCountsByCity(): IMockBeerReview[] { + return [ + { + id: "Brno", + name: "Brno", + data: [Math.round(Math.random() * 100000)], + icon: "status_down", + link: "https://en.wikipedia.org/wiki/Brno", + value: "Brno", + color: "var(--nui-color-chart-one)", + }, + { + id: "kyiv", + name: "Kyiv", + data: [Math.round(Math.random() * 100000)], + icon: "status_critical", + link: "https://en.wikipedia.org/wiki/Kyiv", + value: "Kyiv", + color: "var(--nui-color-chart-two)", + }, + { + id: "austin", + name: "Austin", + data: [Math.round(Math.random() * 100000)], + icon: "status_warning", + value: "Austin", + color: "var(--nui-color-chart-three)", + }, + { + id: "lisbon", + name: "Lisbon", + data: [Math.round(Math.random() * 100000)], + icon: "status_unknown", + link: "https://en.wikipedia.org/wiki/Lisbon", + value: "Lisbon", + color: "var(--nui-color-chart-four)", + }, + { + id: "sydney", + name: "Sydney", + data: [Math.round(Math.random() * 100000)], + icon: "status_up", + value: "Sydney", + color: "var(--nui-color-chart-five)", + }, + { + id: "nur-sultan", + name: "Nur-Sultan", + data: [Math.round(Math.random() * 100000)], + icon: "status_unmanaged", + value: "Nur-Sultan", + color: "var(--nui-color-chart-six)", + }, + ].sort((a, b) => a.data[0] - b.data[0]); +} +\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\`, + "widget-types/risk-score/risk-score-docs.component.ts": \\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\`// © 2023 SolarWinds Worldwide, LLC. All rights reserved. +// +// Permission is hereby granted, free of charge, to any person obtaining a copy +// of this software and associated documentation files (the "Software"), to +// deal in the Software without restriction, including without limitation the +// rights to use, copy, modify, merge, publish, distribute, sublicense, and/or +// sell copies of the Software, and to permit persons to whom the Software is +// furnished to do so, subject to the following conditions: +// +// The above copyright notice and this permission notice shall be included in +// all copies or substantial portions of the Software. +// +// THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR +// IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, +// FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE +// AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER +// LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, +// OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN +// THE SOFTWARE. + +import { Component, OnInit } from "@angular/core"; + +import { mapContentFile } from "../../../../demo-files-factory"; + +@Component({ + selector: "nui-risk-score-docs", + templateUrl: "./risk-score-docs.component.html", + standalone: false, +}) +export class RiskScoreDocsComponent implements OnInit { + public riskScoreWidgetFileText = ""; + public riskScoreConfiguratorFileText = ""; + + public async ngOnInit(): Promise { + this.riskScoreWidgetFileText = await import( + "./../../../../../../src/lib/widget-types/risk-score/risk-score-widget" + ).then(mapContentFile); + this.riskScoreConfiguratorFileText = await import( + "./../../../../../../src/lib/widget-types/risk-score/risk-score-configurator" + ).then(mapContentFile); + } +} +\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\`, + "widget-types/risk-score/risk-score-docs.module.ts": \\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\`// © 2023 SolarWinds Worldwide, LLC. All rights reserved. +// +// Permission is hereby granted, free of charge, to any person obtaining a copy +// of this software and associated documentation files (the "Software"), to +// deal in the Software without restriction, including without limitation the +// rights to use, copy, modify, merge, publish, distribute, sublicense, and/or +// sell copies of the Software, and to permit persons to whom the Software is +// furnished to do so, subject to the following conditions: +// +// The above copyright notice and this permission notice shall be included in +// all copies or substantial portions of the Software. +// +// THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR +// IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, +// FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE +// AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER +// LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, +// OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN +// THE SOFTWARE. + +import { NgModule } from "@angular/core"; +import { RouterModule, Routes } from "@angular/router"; + +import { + DEMO_PATH_TOKEN, + NuiButtonModule, + NuiDocsModule, + NuiMessageModule, + NuiSwitchModule, +} from "@nova-ui/bits"; +import { NuiDashboardsModule } from "@nova-ui/dashboards"; + +import { RiskScoreDocsComponent } from "./risk-score-docs.component"; +import { RiskScoreWidgetExampleComponent } from "./risk-score-widget-example/risk-score-widget-example.component"; +import { getDemoFiles } from "../../../../demo-files-factory"; + +const routes: Routes = [ + { + path: "", + component: RiskScoreDocsComponent, + data: { + srlc: { + hideIndicator: true, + }, + showThemeSwitcher: true, + }, + }, + { + path: "example", + component: RiskScoreWidgetExampleComponent, + data: { + srlc: { + hideIndicator: true, + }, + }, + }, +]; + +@NgModule({ + imports: [ + RouterModule.forChild(routes), + NuiButtonModule, + NuiDocsModule, + NuiMessageModule, + NuiDashboardsModule, + NuiSwitchModule, + ], + declarations: [RiskScoreDocsComponent, RiskScoreWidgetExampleComponent], + providers: [ + { + provide: DEMO_PATH_TOKEN, + useValue: getDemoFiles("risk-score"), + }, + ], +}) +export default class RiskScoreDocsModule {} +\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\`, + "widget-types/risk-score/risk-score-widget-example/risk-score-widget-example.component.ts": \\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\`// © 2023 SolarWinds Worldwide, LLC. All rights reserved. +// +// Permission is hereby granted, free of charge, to any person obtaining a copy +// of this software and associated documentation files (the "Software"), to +// deal in the Software without restriction, including without limitation the +// rights to use, copy, modify, merge, publish, distribute, sublicense, and/or +// sell copies of the Software, and to permit persons to whom the Software is +// furnished to do so, subject to the following conditions: +// +// The above copyright notice and this permission notice shall be included in +// all copies or substantial portions of the Software. +// +// THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR +// IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, +// FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE +// AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER +// LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, +// OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN +// THE SOFTWARE. + +import { HttpClient, HttpErrorResponse } from "@angular/common/http"; +import { Component, Injectable, OnDestroy, OnInit } from "@angular/core"; +import { GridsterConfig, GridsterItem } from "angular-gridster2"; +import { BehaviorSubject } from "rxjs"; +import { finalize } from "rxjs/operators"; + +import { DataSourceService, IFilteringOutputs } from "@nova-ui/bits"; +import { + DATA_SOURCE, + DEFAULT_PIZZAGNA_ROOT, + IDashboard, + IRiskScoreData, + IProviderConfiguration, + IRefresherProperties, + IWidget, + IWidgets, + RiskScoreTileComponent, + NOVA_KPI_DATASOURCE_ADAPTER, + PizzagnaLayer, + ProviderRegistryService, + WellKnownPathKey, + WellKnownProviders, + WidgetTypesService, +} from "@nova-ui/dashboards"; + +/** + * A simple KPI data source to retrieve the average rating of Harry Potter and the Sorcerer's Stone (book) via googleapis + */ +@Injectable() +export class AverageRatingRiskScoreDataSource + extends DataSourceService + implements OnDestroy +{ + // This is the ID we'll use to identify the provider + public static providerId = "AverageRatingRiskScoreDataSource"; + + // Use this subject to communicate the data source's busy state + public busy = new BehaviorSubject(false); + + constructor(private http: HttpClient) { + super(); + } + + // In this example, getFilteredData is invoked every 10 minutes (Take a look at the refresher + // provider definition in the widget configuration below to see how the interval is set) + public async getFilteredData(): Promise { + this.busy.next(true); + return new Promise((resolve) => { + // *** Make a resource request to an external API (if needed) + this.http + .get("https://www.googleapis.com/books/v1/volumes/5MQFrgEACAAJ") + .pipe(finalize(() => this.busy.next(false))) + .subscribe({ + next: (data: any) => { + resolve({ + result: { + value: data.volumeInfo.averageRating, + }, + }); + }, + error: (error: HttpErrorResponse) => { + resolve({ + result: null, + error: { + type: error.status, + }, + }); + }, + }); + }); + } + + public ngOnDestroy(): void { + this.outputsSubject.complete(); + } +} + +/** + * A component that instantiates the dashboard + */ +@Component({ + selector: "risk-score-widget-example", + templateUrl: "./risk-score-widget-example.component.html", + styleUrls: ["./risk-score-widget-example.component.less"], + standalone: false, +}) +export class RiskScoreWidgetExampleComponent implements OnInit { + // This variable will hold all the data needed to define the layout and behavior of the widgets. + // Pass this to the dashboard component's dashboard input in the template. + public dashboard: IDashboard; + + // Angular gridster requires a configuration object even if it's empty. + // Pass this to the dashboard component's gridsterConfig input in the template. + public gridsterConfig: GridsterConfig = {}; + + // Boolean passed as an input to the dashboard. When true, widgets can be moved, resized, removed, or edited + public editMode: boolean = false; + + constructor( + // WidgetTypesService provides the widget's necessary structure information + private widgetTypesService: WidgetTypesService, + + // In general, the ProviderRegistryService is used for making entities available for injection into dynamically loaded components. + private providerRegistry: ProviderRegistryService + ) {} + + public ngOnInit(): void { + // Grabbing the widget's default template which will be needed as a parameter for setNode + const widgetTemplate = this.widgetTypesService.getWidgetType( + "risk-score", + 1 + ); + // Registering our data sources as dropdown options in the widget editor/configurator + // Note: This could also be done in the parent module's constructor so that + // multiple dashboards could have access to the same widget template modification. + this.widgetTypesService.setNode( + // This is the template we grabbed above with getWidgetType + widgetTemplate, + // We are setting the editor/configurator part of the widget template + "configurator", + // This indicates which node you are changing and we want to change + // the data source providers available for selection in the editor. + WellKnownPathKey.DataSourceProviders, + // We are setting the data sources available for selection in the editor + [AverageRatingRiskScoreDataSource.providerId] + ); + + // Registering the data source for injection into the KPI tile. + // Note: Each tile of a KPI widget is assigned its own instance of the data source + this.providerRegistry.setProviders({ + [AverageRatingRiskScoreDataSource.providerId]: { + provide: DATA_SOURCE, + useClass: AverageRatingRiskScoreDataSource, + // Any dependencies that need to be injected into the provider must be listed here + deps: [HttpClient], + }, + }); + + this.initializeDashboard(); + } + + public initializeDashboard(): void { + // We're using a static configuration object for this example, but this is where + // the widget's configuration could potentially be populated from a database + const riskScoreWidget = widgetConfig; + const widgetIndex: IWidgets = { + // Complete the KPI widget with information coming from its type definition + [riskScoreWidget.id]: + this.widgetTypesService.mergeWithWidgetType(riskScoreWidget), + }; + + // Setting the widget dimensions and position (this is for gridster) + const positions: Record = { + [riskScoreWidget.id]: { + cols: 4, + rows: 6, + y: 0, + x: 0, + }, + }; + + // Finally, assigning the variables we created above to the dashboard + this.dashboard = { + positions, + widgets: widgetIndex, + }; + } +} + +const widgetConfig: IWidget = { + id: "riskScoreWidgetId", + type: "risk-score", + pizzagna: { + [PizzagnaLayer.Configuration]: { + [DEFAULT_PIZZAGNA_ROOT]: { + providers: { + [WellKnownProviders.Refresher]: { + properties: { + // Configuring the refresher interval so that our data source is invoked every ten minutes + interval: 60 * 10, + enabled: true, + } as IRefresherProperties, + } as Partial, + }, + }, + header: { + properties: { + title: "Harry Potter and the Sorcerer's Stone", + subtitle: "By J. K. Rowling", + }, + }, + tiles: { + properties: { + nodes: ["riskScore1"], + }, + }, + riskScore1: { + id: "riskScore1", + componentType: RiskScoreTileComponent.lateLoadKey, + properties: { + widgetData: { + minValue: 0, + maxValue: 5, + useStaticLabel: false, + staticLabel: undefined, + label: \\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\`Average Rating\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\`, + description: \\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\`Harry Potter and the Sorcerer's Stone By J. K. Rowling Average Rating Risk Score\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\`, + }, + }, + providers: { + [WellKnownProviders.DataSource]: { + // Setting the data source providerId for the tile with id "kpi1" + providerId: AverageRatingRiskScoreDataSource.providerId, + } as IProviderConfiguration, + [WellKnownProviders.Adapter]: { + providerId: NOVA_KPI_DATASOURCE_ADAPTER, + properties: { + componentId: "riskScore1", + propertyPath: "widgetData", + }, + } as IProviderConfiguration, + }, + }, + }, + }, +}; +\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\`, + "widget-types/table/table-docs.component.ts": \\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\`// © 2022 SolarWinds Worldwide, LLC. All rights reserved. +// +// Permission is hereby granted, free of charge, to any person obtaining a copy +// of this software and associated documentation files (the "Software"), to +// deal in the Software without restriction, including without limitation the +// rights to use, copy, modify, merge, publish, distribute, sublicense, and/or +// sell copies of the Software, and to permit persons to whom the Software is +// furnished to do so, subject to the following conditions: +// +// The above copyright notice and this permission notice shall be included in +// all copies or substantial portions of the Software. +// +// THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR +// IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, +// FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE +// AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER +// LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, +// OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN +// THE SOFTWARE. + +import { Component, OnInit } from "@angular/core"; + +import { mapContentFile } from "../../../../demo-files-factory"; + +@Component({ + selector: "nui-table-docs", + templateUrl: "./table-docs.component.html", + standalone: false, +}) +export class TableDocsComponent implements OnInit { + public widgetFileText = ""; + public configuratorFileText = ""; + + public async ngOnInit(): Promise { + this.widgetFileText = await import( + "./../../../../../../src/lib/widget-types/table/table-widget" + ).then(mapContentFile); + this.configuratorFileText = await import( + "./../../../../../../src/lib/widget-types/table/table-configurator" + ).then(mapContentFile); + } +} +\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\`, + "widget-types/table/table-docs.module.ts": \\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\`// © 2022 SolarWinds Worldwide, LLC. All rights reserved. +// +// Permission is hereby granted, free of charge, to any person obtaining a copy +// of this software and associated documentation files (the "Software"), to +// deal in the Software without restriction, including without limitation the +// rights to use, copy, modify, merge, publish, distribute, sublicense, and/or +// sell copies of the Software, and to permit persons to whom the Software is +// furnished to do so, subject to the following conditions: +// +// The above copyright notice and this permission notice shall be included in +// all copies or substantial portions of the Software. +// +// THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR +// IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, +// FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE +// AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER +// LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, +// OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN +// THE SOFTWARE. + +import { NgModule } from "@angular/core"; +import { RouterModule, Routes } from "@angular/router"; + +import { DEMO_PATH_TOKEN } from "@nova-ui/bits"; +import { + NuiButtonModule, + NuiDocsModule, + NuiMessageModule, + NuiSwitchModule, +} from "@nova-ui/bits"; +import { + NuiDashboardsModule, + TableFormatterRegistryService, +} from "@nova-ui/dashboards"; + +import { TableDocsComponent } from "./table-docs.component"; +import { TablePaginatorDocsComponent } from "./table-paginator-docs.component"; +import { TableSelectableDocsComponent } from "./table-selectable-docs.component"; +import { TableWidgetExampleComponent } from "./table-widget/table-widget-example.component"; +import { TableWidgetInteractiveExampleComponent } from "./table-widget-interactive/table-widget-interactive-example.component"; +import { TableWidgetPaginatorExampleComponent } from "./table-widget-paginator/table-widget-paginator-example.component"; +import { TableWidgetSearchExampleComponent } from "./table-widget-search/table-widget-search-example.component"; +import { TableSearchDocsComponent } from "./table-widget-search-docs.component"; +import { TableWidgetSelectableMultiExampleComponent } from "./table-widget-selectable/table-widget-selectable-multi/table-widget-selectable-multi.example.component"; +import { TableWidgetSelectableRadioExampleComponent } from "./table-widget-selectable/table-widget-selectable-radio/table-widget-selectable-radio.example.component"; +import { TableWidgetSelectableSingleExampleComponent } from "./table-widget-selectable/table-widget-selectable-single/table-widget-selectable-single.example.component"; +import { TableWidgetSelectableExampleComponent } from "./table-widget-selectable/table-widget-selectable.example.component"; +import { DEFAULT_TABLE_FORMATTERS } from "../../../../../../src/lib/widget-types/table/default-table-formatters"; +import { getDemoFiles } from "../../../../demo-files-factory"; + +const routes: Routes = [ + { + path: "", + component: TableDocsComponent, + data: { + srlc: { + hideIndicator: true, + }, + showThemeSwitcher: true, + }, + }, + { + path: "example", + component: TableWidgetExampleComponent, + data: { + srlc: { + hideIndicator: true, + }, + }, + }, + { + path: "table-search", + component: TableSearchDocsComponent, + data: { + srlc: { + hideIndicator: true, + }, + showThemeSwitcher: true, + }, + }, + { + path: "table-paginator", + component: TablePaginatorDocsComponent, + data: { + srlc: { + hideIndicator: true, + }, + showThemeSwitcher: true, + }, + }, + { + path: "table-select", + component: TableSelectableDocsComponent, + data: { + srlc: { + hideIndicator: true, + }, + showThemeSwitcher: true, + }, + }, +]; + +@NgModule({ + imports: [ + RouterModule.forChild(routes), + NuiButtonModule, + NuiDocsModule, + NuiMessageModule, + NuiSwitchModule, + NuiDashboardsModule, + ], + declarations: [ + TableDocsComponent, + TableSearchDocsComponent, + TablePaginatorDocsComponent, + TableWidgetPaginatorExampleComponent, + TableSelectableDocsComponent, + TableWidgetInteractiveExampleComponent, + TableWidgetExampleComponent, + TableWidgetSearchExampleComponent, + TableWidgetSelectableExampleComponent, + TableWidgetSelectableMultiExampleComponent, + TableWidgetSelectableSingleExampleComponent, + TableWidgetSelectableRadioExampleComponent, + ], + providers: [ + { + provide: DEMO_PATH_TOKEN, + useValue: getDemoFiles("table"), + }, + ], +}) +export default class TableDocsModule { + constructor(tableFormattersRegistryService: TableFormatterRegistryService) { + tableFormattersRegistryService.addItems(DEFAULT_TABLE_FORMATTERS); + } +} +\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\`, + "widget-types/table/table-paginator-docs.component.ts": \\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\`// © 2022 SolarWinds Worldwide, LLC. All rights reserved. +// +// Permission is hereby granted, free of charge, to any person obtaining a copy +// of this software and associated documentation files (the "Software"), to +// deal in the Software without restriction, including without limitation the +// rights to use, copy, modify, merge, publish, distribute, sublicense, and/or +// sell copies of the Software, and to permit persons to whom the Software is +// furnished to do so, subject to the following conditions: +// +// The above copyright notice and this permission notice shall be included in +// all copies or substantial portions of the Software. +// +// THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR +// IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, +// FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE +// AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER +// LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, +// OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN +// THE SOFTWARE. + +import { Component } from "@angular/core"; + +@Component({ + selector: "nui-table-paginator-docs", + templateUrl: "./table-paginator-docs.component.html", + standalone: false, +}) +export class TablePaginatorDocsComponent { + public tableConfigurationText = \\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\` + "table": { + ... + properties: { + configuration: { + // define paginator configuration here + scrollType: ScrollType.paginator, + paginatorConfiguration: { + pageSize: 10, // Value have to be one of pageSizeSet values + pageSizeSet: [10, 20, 30], + }, + // If not specified, default is set to + // pageSize: 10, + // pageSizeSet: [10, 20, 50], + hasVirtualScroll: false, // Has to be speciefied because of backward compatibility + } as ITableWidgetConfig, + }, + }, + \\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\`; +} +\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\`, + "widget-types/table/table-selectable-docs.component.ts": \\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\`// © 2022 SolarWinds Worldwide, LLC. All rights reserved. +// +// Permission is hereby granted, free of charge, to any person obtaining a copy +// of this software and associated documentation files (the "Software"), to +// deal in the Software without restriction, including without limitation the +// rights to use, copy, modify, merge, publish, distribute, sublicense, and/or +// sell copies of the Software, and to permit persons to whom the Software is +// furnished to do so, subject to the following conditions: +// +// The above copyright notice and this permission notice shall be included in +// all copies or substantial portions of the Software. +// +// THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR +// IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, +// FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE +// AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER +// LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, +// OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN +// THE SOFTWARE. + +import { Component } from "@angular/core"; + +@Component({ + selector: "nui-table-selectable-docs", + templateUrl: "./table-selectable-docs.component.html", + standalone: false, +}) +export class TableSelectableDocsComponent { + public tableConfigurationText = \\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\` + "table": { + ... + properties: { + // enabling selection here + selectionConfiguration: { + // whether the selection is enabled or disabled + enabled: true, + // can be Multi | Radio | Single + selectionMode: TableSelectionMode.Multi, + // property that uniquely identifies row in a table + trackByProperty: "id", + // whether clicking on row should select it + clickableRow: true, + }, + }, + }, + \\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\`; + + public eventSubscriptionText = \\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\` +... +constructor(Inject(PIZZAGNA_EVENT_BUS) eventBus: EventBus) { + eventBus + .getStream(SELECTION) + // don't forget to unsubscribe! + .subscribe((selection: ISelection) => ...) +} +... + \\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\`; +} +\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\`, + "widget-types/table/table-widget/table-widget-example.component.ts": \\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\`// © 2022 SolarWinds Worldwide, LLC. All rights reserved. +// +// Permission is hereby granted, free of charge, to any person obtaining a copy +// of this software and associated documentation files (the "Software"), to +// deal in the Software without restriction, including without limitation the +// rights to use, copy, modify, merge, publish, distribute, sublicense, and/or +// sell copies of the Software, and to permit persons to whom the Software is +// furnished to do so, subject to the following conditions: +// +// The above copyright notice and this permission notice shall be included in +// all copies or substantial portions of the Software. +// +// THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR +// IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, +// FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE +// AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER +// LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, +// OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN +// THE SOFTWARE. + +import { ChangeDetectorRef, Component, OnInit } from "@angular/core"; +import { GridsterConfig, GridsterItem } from "angular-gridster2"; +import orderBy from "lodash/orderBy"; +import { BehaviorSubject, firstValueFrom, from } from "rxjs"; +import { map, tap } from "rxjs/operators"; + +import { + DataSourceService, + IDataField, + INovaFilteringOutputs, + INovaFilters, + nameof, +} from "@nova-ui/bits"; +import { + DATA_SOURCE, + IDashboard, + ITableWidgetColumnConfig, + IWidget, + IWidgets, + ProviderRegistryService, + WellKnownPathKey, + WellKnownProviders, + WidgetTypesService, +} from "@nova-ui/dashboards"; + +export const BREW_API_URL = "https://api.punkapi.com/v2/beers"; + +export interface IBrewInfo { + id: string; + name: string; + tagline: string; + first_brewed: string; + description: string; + brewers_tips: string; +} + +export interface IBrewDatasourceResponse { + brewInfo: IBrewInfo[]; + total: number; +} + +export class BeerDataSource extends DataSourceService { + public static providerId = "BeerDataSource"; + + private cache: IBrewInfo[] = []; + + public busy = new BehaviorSubject(false); + + public dataFields: Array = [ + { + id: nameof("id"), + label: "No", + dataType: "number", + sortable: true, + }, + // To indicate that a column should not be sortable, set the optional IDataField 'sortable' property to false + { + id: nameof("name"), + label: "Name", + dataType: "string", + sortable: true, + }, + { + id: nameof("tagline"), + label: "Tagline", + dataType: "string", + sortable: true, + }, + { + id: nameof("first_brewed"), + label: "First Brewed", + dataType: "string", + sortable: true, + }, + { + id: nameof("description"), + label: "Description", + dataType: "string", + sortable: false, + }, + { + id: nameof("brewers_tips"), + label: "Brewer's Tips", + dataType: "string", + sortable: false, + }, + ]; + + public async getFilteredData( + filters: INovaFilters + ): Promise { + const start = filters.virtualScroll?.value?.start ?? 0; + const end = filters.virtualScroll?.value?.end ?? 0; + + // Resetting cache on first page request + if (start === 0) { + this.cache = []; + } + + // extract sorter settings to send to the backend + // filters.sorterValue.sortBy; filters.sorterValue.direction + return firstValueFrom( + from(this.fetch(start, end)).pipe( + tap((response: IBrewDatasourceResponse | undefined) => { + if (!response) { + return; + } + this.cache = this.sortData( + this.cache.concat(response.brewInfo), + filters + ); + this.dataSubject.next(this.cache); + }), + map(() => ({ + repeat: { itemsSource: this.cache }, + dataFields: this.dataFields, + })) + ) + ); + } + + public async fetch( + start: number, + end: number + ): Promise { + const delta: number = end - start; + const currentPage: number = end / delta || 0; + const response: object | Array = await ( + await fetch( + \\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\`\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\${BREW_API_URL}/?page=\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\${currentPage}&per_page=\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\${delta}\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\` + ) + ).json(); + + // Note: In case request fails we should not proceed with mapping + if (!Array.isArray(response)) { + return undefined; + } + + return { + brewInfo: response.map((result: IBrewInfo) => ({ + id: result.id, + name: result.name, + tagline: result.tagline, + first_brewed: result.first_brewed, + description: result.description, + brewers_tips: result.brewers_tips, + })), + total: response.length, + }; + } + + private sortData(data: IBrewInfo[], filters: INovaFilters): IBrewInfo[] { + return orderBy( + data, + filters.sorter?.value?.sortBy, + filters.sorter?.value?.direction as "desc" | "asc" + ); + } +} + +/** + * A component that instantiates the dashboard + */ +@Component({ + selector: "table-widget-example", + templateUrl: "./table-widget-example.component.html", + styleUrls: ["./table-widget-example.component.less"], + standalone: false, +}) +export class TableWidgetExampleComponent implements OnInit { + // This variable will hold all the data needed to define the layout and behavior of the widgets. + // Pass this to the dashboard component's dashboard input in the template. + public dashboard: IDashboard | undefined; + + // Angular gridster requires a configuration object even if it's empty. + // Pass this to the dashboard component's gridsterConfig input in the template. + public gridsterConfig: GridsterConfig = {}; + + // Boolean passed as an input to the dashboard. When true, widgets can be moved, resized, removed, or edited + public editMode: boolean = false; + + constructor( + // WidgetTypesService provides the widget's necessary structure information + private widgetTypesService: WidgetTypesService, + // In general, the ProviderRegistryService is used for making entities available for injection into dynamically loaded components. + private providerRegistry: ProviderRegistryService, + private changeDetectorRef: ChangeDetectorRef + ) {} + + public ngOnInit(): void { + // Grabbing the widget's default template which will be needed as a parameter for setNode + const widgetTemplate = this.widgetTypesService.getWidgetType( + "table", + 1 + ); + + // Registering our data sources as dropdown options in the widget editor/configurator + // Note: This could also be done in the parent module's constructor so that + // multiple dashboards could have access to the same widget template modification. + this.widgetTypesService.setNode( + // This is the template we grabbed above with getWidgetType + widgetTemplate, + // We are setting the editor/configurator part of the widget template + "configurator", + // This indicates which node you are changing and we want to change + // the data source providers available for selection in the editor. + WellKnownPathKey.DataSourceProviders, + // We are setting the data sources available for selection in the editor + [BeerDataSource.providerId] + ); + + // Registering the data source for injection into the widget. + this.providerRegistry.setProviders({ + [BeerDataSource.providerId]: { + provide: DATA_SOURCE, + useClass: BeerDataSource, + // Any dependencies that need to be injected into the provider must be listed here + deps: [], + }, + }); + + this.initializeDashboard(); + } + + /** Used for restoring widgets state */ + public reInitializeDashboard(): void { + // destroys the components and their providers so the dashboard can re init data + this.dashboard = undefined; + this.changeDetectorRef.detectChanges(); + + this.initializeDashboard(); + } + + public initializeDashboard(): void { + // We're using a static configuration object for this example, but this is where + // the widget's configuration could potentially be populated from a database + const tableWidget = widgetConfig; + const widgetIndex: IWidgets = { + // Enhance the widget with information coming from it's type definition + [tableWidget.id]: + this.widgetTypesService.mergeWithWidgetType(tableWidget), + }; + + // Setting the widget dimensions and position (this is for gridster) + const positions: Record = { + [tableWidget.id]: { + cols: 12, + rows: 6, + y: 0, + x: 0, + }, + }; + + // Finally, assigning the variables we created above to the dashboard + this.dashboard = { + positions, + widgets: widgetIndex, + }; + } +} + +const TABLE_COLUMNS: ITableWidgetColumnConfig[] = [ + { + id: "column1", + label: $localize\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\`Beer Name\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\`, + isActive: true, + width: 185, + formatter: { + componentType: "RawFormatterComponent", + properties: { + dataFieldIds: { + value: "name", + }, + }, + }, + }, + { + id: "column2", + label: $localize\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\`Tagline\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\`, + isActive: true, + width: 250, + formatter: { + componentType: "RawFormatterComponent", + properties: { + dataFieldIds: { + value: "tagline", + }, + }, + }, + }, + { + id: "column3", + label: $localize\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\`First Brewed\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\`, + isActive: true, + width: 100, + formatter: { + componentType: "RawFormatterComponent", + properties: { + dataFieldIds: { + value: "first_brewed", + }, + }, + }, + }, + { + id: "column4", + label: $localize\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\`Description\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\`, + isActive: true, + formatter: { + componentType: "RawFormatterComponent", + properties: { + dataFieldIds: { + value: "description", + }, + }, + }, + }, +]; + +export const widgetConfig: IWidget = { + id: "tableWidgetId", + type: "table", + pizzagna: { + configuration: { + header: { + properties: { + title: "Stupendous Suds", + subtitle: "Try These Brilliant Brews", + }, + }, + table: { + providers: { + [WellKnownProviders.DataSource]: { + providerId: BeerDataSource.providerId, + }, + }, + properties: { + configuration: { + columns: TABLE_COLUMNS, + sortable: true, + sorterConfiguration: { + descendantSorting: false, + sortBy: "", + }, + hasVirtualScroll: true, + }, + }, + }, + }, + }, +}; +\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\`, + "widget-types/table/table-widget-interactive/table-widget-interactive-example.component.ts": \\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\`// © 2022 SolarWinds Worldwide, LLC. All rights reserved. +// +// Permission is hereby granted, free of charge, to any person obtaining a copy +// of this software and associated documentation files (the "Software"), to +// deal in the Software without restriction, including without limitation the +// rights to use, copy, modify, merge, publish, distribute, sublicense, and/or +// sell copies of the Software, and to permit persons to whom the Software is +// furnished to do so, subject to the following conditions: +// +// The above copyright notice and this permission notice shall be included in +// all copies or substantial portions of the Software. +// +// THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR +// IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, +// FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE +// AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER +// LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, +// OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN +// THE SOFTWARE. + +import { ChangeDetectorRef, Component, OnInit } from "@angular/core"; +import { GridsterConfig, GridsterItem } from "angular-gridster2"; +import orderBy from "lodash/orderBy"; +import { BehaviorSubject, firstValueFrom, from } from "rxjs"; +import { map, tap } from "rxjs/operators"; + +import { + DataSourceService, + IDataField, + INovaFilteringOutputs, + INovaFilters, + nameof, +} from "@nova-ui/bits"; +import { + DATA_SOURCE, + DEFAULT_PIZZAGNA_ROOT, + IDashboard, + ITableWidgetColumnConfig, + IWidget, + IWidgets, + NOVA_URL_INTERACTION_HANDLER, + ProviderRegistryService, + WellKnownPathKey, + WellKnownProviders, + WidgetTypesService, +} from "@nova-ui/dashboards"; + +export const BREW_API_URL = "https://api.punkapi.com/v2/beers"; + +export interface IBrewInfo { + id: string; + name: string; + tagline: string; + first_brewed: string; + description: string; + brewers_tips: string; +} + +export interface IBrewDatasourceResponse { + brewInfo: IBrewInfo[]; + total: number; +} + +export class MockBeerDataSource extends DataSourceService { + public static providerId = "MockBeerDataSource"; + + private cache: IBrewInfo[] = []; + + public busy = new BehaviorSubject(false); + + public dataFields: Array = [ + { + id: nameof("id"), + label: "No", + dataType: "number", + sortable: true, + }, + // To indicate that a column should not be sortable, set the optional IDataField 'sortable' property to false + { + id: nameof("name"), + label: "Name", + dataType: "string", + sortable: true, + }, + { + id: nameof("tagline"), + label: "Tagline", + dataType: "string", + sortable: true, + }, + { + id: nameof("first_brewed"), + label: "First Brewed", + dataType: "string", + sortable: true, + }, + { + id: nameof("description"), + label: "Description", + dataType: "string", + sortable: false, + }, + { + id: nameof("brewers_tips"), + label: "Brewer's Tips", + dataType: "string", + sortable: false, + }, + ]; + + public async getFilteredData( + filters: INovaFilters + ): Promise { + const start = filters.virtualScroll?.value?.start ?? 0; + const end = filters.virtualScroll?.value?.end ?? 0; + + // Resetting cache on first page request + if (start === 0) { + this.cache = []; + } + + // extract sorter settings to send to the backend + // filters.sorterValue.sortBy; filters.sorterValue.direction + return firstValueFrom( + from(this.fetch(start, end)).pipe( + tap((response) => { + if (!response) { + return; + } + this.cache = this.sortData( + this.cache.concat(response.brewInfo), + filters + ); + this.dataSubject.next(this.cache); + }), + map(() => ({ + repeat: { itemsSource: this.cache }, + dataFields: this.dataFields, + })) + ) + ); + } + + public async fetch( + start: number, + end: number + ): Promise { + const delta: number = end - start; + const currentPage: number = end / delta || 0; + const response: object | Array = await ( + await fetch( + \\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\`\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\${BREW_API_URL}/?page=\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\${currentPage}&per_page=\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\${delta}\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\` + ) + ).json(); + console.log( + "📘 table-widget-interactive-example.component: 85# -> response:", + response + ); + + // Note: In case request fails we should not proceed with mapping + if (!Array.isArray(response)) { + return undefined; + } + + return { + brewInfo: response.map((result: IBrewInfo) => ({ + id: result.id, + name: result.name, + tagline: result.tagline, + first_brewed: result.first_brewed, + description: result.description, + brewers_tips: result.brewers_tips, + })), + total: response.length, + }; + } + + private sortData(data: IBrewInfo[], filters: INovaFilters): IBrewInfo[] { + return orderBy( + data, + filters.sorter?.value?.sortBy, + filters.sorter?.value?.direction as "desc" | "asc" + ); + } +} + +/** + * A component that instantiates the dashboard + */ +@Component({ + selector: "table-widget-interactive-example", + templateUrl: "./table-widget-interactive-example.component.html", + styleUrls: ["./table-widget-interactive-example.component.less"], + standalone: false, +}) +export class TableWidgetInteractiveExampleComponent implements OnInit { + // This variable will hold all the data needed to define the layout and behavior of the widgets. + // Pass this to the dashboard component's dashboard input in the template. + public dashboard: IDashboard | undefined; + + // Angular gridster requires a configuration object even if it's empty. + // Pass this to the dashboard component's gridsterConfig input in the template. + public gridsterConfig: GridsterConfig = {}; + + // Boolean passed as an input to the dashboard. When true, widgets can be moved, resized, removed, or edited + public editMode: boolean = false; + + constructor( + // WidgetTypesService provides the widget's necessary structure information + private widgetTypesService: WidgetTypesService, + // In general, the ProviderRegistryService is used for making entities available for injection into dynamically loaded components. + private providerRegistry: ProviderRegistryService, + private changeDetectorRef: ChangeDetectorRef + ) {} + + public ngOnInit(): void { + // Grabbing the widget's default template which will be needed as a parameter for setNode + const widgetTemplate = this.widgetTypesService.getWidgetType( + "table", + 1 + ); + + // Registering our data sources as dropdown options in the widget editor/configurator + // Note: This could also be done in the parent module's constructor so that + // multiple dashboards could have access to the same widget template modification. + this.widgetTypesService.setNode( + // This is the template we grabbed above with getWidgetType + widgetTemplate, + // We are setting the editor/configurator part of the widget template + "configurator", + // This indicates which node you are changing and we want to change + // the data source providers available for selection in the editor. + WellKnownPathKey.DataSourceProviders, + // We are setting the data sources available for selection in the editor + [MockBeerDataSource.providerId] + ); + + // Registering the data source for injection into the widget. + this.providerRegistry.setProviders({ + [MockBeerDataSource.providerId]: { + provide: DATA_SOURCE, + useClass: MockBeerDataSource, + // Any dependencies that need to be injected into the provider must be listed here + deps: [], + }, + }); + + this.initializeDashboard(); + } + + /** Used for restoring widgets state */ + public reInitializeDashboard(): void { + // destroys the components and their providers so the dashboard can re init data + this.dashboard = undefined; + this.changeDetectorRef.detectChanges(); + + this.initializeDashboard(); + } + + public initializeDashboard(): void { + // We're using a static configuration object for this example, but this is where + // the widget's configuration could potentially be populated from a database + const tableWidget = widgetConfig; + const widgetIndex: IWidgets = { + // Enhance the widget with information coming from it's type definition + [tableWidget.id]: + this.widgetTypesService.mergeWithWidgetType(tableWidget), + }; + + // Setting the widget dimensions and position (this is for gridster) + const positions: Record = { + [tableWidget.id]: { + cols: 12, + rows: 6, + y: 0, + x: 0, + }, + }; + + // Finally, assigning the variables we created above to the dashboard + this.dashboard = { + positions, + widgets: widgetIndex, + }; + } +} + +const TABLE_COLUMNS: ITableWidgetColumnConfig[] = [ + { + id: "column1", + label: $localize\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\`Beer Name\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\`, + isActive: true, + width: 185, + formatter: { + componentType: "RawFormatterComponent", + properties: { + dataFieldIds: { + value: "name", + }, + }, + }, + }, + { + id: "column2", + label: $localize\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\`Tagline\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\`, + isActive: true, + width: 250, + formatter: { + componentType: "RawFormatterComponent", + properties: { + dataFieldIds: { + value: "tagline", + }, + }, + }, + }, + { + id: "column3", + label: $localize\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\`First Brewed\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\`, + isActive: true, + width: 100, + formatter: { + componentType: "RawFormatterComponent", + properties: { + dataFieldIds: { + value: "first_brewed", + }, + }, + }, + }, + { + id: "column4", + label: $localize\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\`Description\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\`, + isActive: true, + formatter: { + componentType: "RawFormatterComponent", + properties: { + dataFieldIds: { + value: "description", + }, + }, + }, + }, +]; + +export const widgetConfig: IWidget = { + id: "tableWidgetId", + type: "table", + pizzagna: { + configuration: { + [DEFAULT_PIZZAGNA_ROOT]: { + providers: { + [WellKnownProviders.InteractionHandler]: { + // Configuring the UrlInteractionHandler to handle interactions + providerId: NOVA_URL_INTERACTION_HANDLER, + properties: { + // the 'url' property tells the handler what link to use when interaction occurs on the series + url: "\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\${'https://untappd.com/search?q='+data.name}", + // by default the link is opened in the current window, set 'newWindow' to true to open in a new tab instead + newWindow: true, + }, + }, + }, + }, + header: { + properties: { + title: "Stupendous Suds", + subtitle: "Try These Brilliant Brews", + }, + }, + table: { + providers: { + [WellKnownProviders.DataSource]: { + providerId: MockBeerDataSource.providerId, + }, + }, + properties: { + configuration: { + // set interactions to true on the table + interactive: true, + columns: TABLE_COLUMNS, + sortable: true, + sorterConfiguration: { + descendantSorting: false, + sortBy: "", + }, + hasVirtualScroll: true, + }, + }, + }, + }, + }, +}; +\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\`, + "widget-types/table/table-widget-paginator/table-widget-paginator-example.component.ts": \\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\`// © 2022 SolarWinds Worldwide, LLC. All rights reserved. +// +// Permission is hereby granted, free of charge, to any person obtaining a copy +// of this software and associated documentation files (the "Software"), to +// deal in the Software without restriction, including without limitation the +// rights to use, copy, modify, merge, publish, distribute, sublicense, and/or +// sell copies of the Software, and to permit persons to whom the Software is +// furnished to do so, subject to the following conditions: +// +// The above copyright notice and this permission notice shall be included in +// all copies or substantial portions of the Software. +// +// THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR +// IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, +// FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE +// AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER +// LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, +// OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN +// THE SOFTWARE. + +import { HttpClient } from "@angular/common/http"; +import { ChangeDetectorRef, Component, OnInit } from "@angular/core"; +import { GridsterConfig, GridsterItem } from "angular-gridster2"; + +import { LoggerService } from "@nova-ui/bits"; +import { + DATA_SOURCE, + DEFAULT_PIZZAGNA_ROOT, + IDashboard, + IProviderConfiguration, + ITableWidgetConfig, + IWidget, + IWidgets, + NOVA_URL_INTERACTION_HANDLER, + PizzagnaLayer, + ProviderRegistryService, + RawFormatterComponent, + ScrollType, + WellKnownPathKey, + WellKnownProviders, + WidgetTypesService, +} from "@nova-ui/dashboards"; + +import { AcmeTableMockDataSource } from "../../../../prototypes/data/table/acme-table-mock-data-source.service"; + +/** + * A component that instantiates the dashboard + */ +@Component({ + selector: "table-widget-paginator-example", + templateUrl: "./table-widget-paginator-example.component.html", + styleUrls: ["./table-widget-paginator-example.component.less"], + standalone: false, +}) +export class TableWidgetPaginatorExampleComponent implements OnInit { + public dashboard: IDashboard | undefined; + public gridsterConfig: GridsterConfig = {}; + public editMode: boolean = false; + + constructor( + private widgetTypesService: WidgetTypesService, + private providerRegistry: ProviderRegistryService, + private changeDetectorRef: ChangeDetectorRef + ) {} + + public ngOnInit(): void { + const widgetTemplate = this.widgetTypesService.getWidgetType( + "table", + 1 + ); + this.widgetTypesService.setNode( + widgetTemplate, + "configurator", + WellKnownPathKey.DataSourceProviders, + [AcmeTableMockDataSource.providerId] + ); + + this.providerRegistry.setProviders({ + [AcmeTableMockDataSource.providerId]: { + provide: DATA_SOURCE, + useClass: AcmeTableMockDataSource, + deps: [LoggerService, HttpClient], + }, + }); + + this.initializeDashboard(); + } + + /** Used for restoring widgets state */ + public reInitializeDashboard(): void { + // destroys the components and their providers so the dashboard can re init data + this.dashboard = undefined; + this.changeDetectorRef.detectChanges(); + + this.initializeDashboard(); + } + + public initializeDashboard(): void { + const tableWithPaginator = tableWidgetWithPaginator; + const tableWithVirtualScroll = tableWidgetWithVirtualScroll; + + const widgetIndex: IWidgets = { + [tableWithPaginator.id]: + this.widgetTypesService.mergeWithWidgetType(tableWithPaginator), + [tableWithVirtualScroll.id]: + this.widgetTypesService.mergeWithWidgetType( + tableWithVirtualScroll + ), + }; + + const positions: Record = { + [tableWithPaginator.id]: { + cols: 6, + rows: 6, + y: 0, + x: 0, + }, + [tableWithVirtualScroll.id]: { + cols: 6, + rows: 6, + y: 0, + x: 0, + }, + }; + + this.dashboard = { + positions, + widgets: widgetIndex, + }; + } +} + +export const tableWidgetWithPaginator: IWidget = { + id: "widget1", + type: "table", + pizzagna: { + [PizzagnaLayer.Configuration]: { + [DEFAULT_PIZZAGNA_ROOT]: { + providers: { + [WellKnownProviders.InteractionHandler]: { + providerId: NOVA_URL_INTERACTION_HANDLER, + }, + }, + }, + header: { + properties: { + title: "Table Widget with paginator!", + subtitle: "Basic table widget", + collapsible: true, + }, + }, + table: { + providers: { + [WellKnownProviders.DataSource]: { + providerId: AcmeTableMockDataSource.providerId, + } as IProviderConfiguration, + }, + properties: { + configuration: { + interactive: true, + columns: [ + { + id: "column1", + label: "No.", + isActive: true, + formatter: { + componentType: + RawFormatterComponent.lateLoadKey, + properties: { + dataFieldIds: { + value: "position", + }, + }, + }, + }, + { + id: "column2", + label: "Name", + isActive: true, + formatter: { + componentType: + RawFormatterComponent.lateLoadKey, + properties: { + dataFieldIds: { + value: "name", + }, + }, + }, + }, + { + id: "column3", + label: "Status", + isActive: true, + formatter: { + componentType: + RawFormatterComponent.lateLoadKey, + properties: { + dataFieldIds: { + value: "status", + }, + }, + }, + }, + ], + sorterConfiguration: { + descendantSorting: false, + sortBy: "column1", + }, + scrollType: ScrollType.paginator, + paginatorConfiguration: { + pageSize: 5, + pageSizeSet: [5, 10, 20, 30], + }, + hasVirtualScroll: false, + searchConfiguration: { + enabled: true, + }, + } as ITableWidgetConfig, + }, + }, + }, + }, +}; + +export const tableWidgetWithVirtualScroll: IWidget = { + id: "widget2", + type: "table", + pizzagna: { + [PizzagnaLayer.Configuration]: { + [DEFAULT_PIZZAGNA_ROOT]: { + providers: { + [WellKnownProviders.InteractionHandler]: { + providerId: NOVA_URL_INTERACTION_HANDLER, + }, + }, + }, + header: { + properties: { + title: "Table Widget with virtual scroll!", + subtitle: "Basic table widget", + collapsible: true, + }, + }, + table: { + providers: { + [WellKnownProviders.DataSource]: { + providerId: AcmeTableMockDataSource.providerId, + } as IProviderConfiguration, + }, + properties: { + configuration: { + interactive: true, + columns: [ + { + id: "column1", + label: "No.", + isActive: true, + formatter: { + componentType: + RawFormatterComponent.lateLoadKey, + properties: { + dataFieldIds: { + value: "position", + }, + }, + }, + }, + { + id: "column2", + label: "Name", + isActive: true, + formatter: { + componentType: + RawFormatterComponent.lateLoadKey, + properties: { + dataFieldIds: { + value: "name", + }, + }, + }, + }, + { + id: "column3", + label: "Status", + isActive: true, + formatter: { + componentType: + RawFormatterComponent.lateLoadKey, + properties: { + dataFieldIds: { + value: "status", + }, + }, + }, + }, + ], + sorterConfiguration: { + descendantSorting: false, + sortBy: "column1", + }, + hasVirtualScroll: true, + searchConfiguration: { + enabled: true, + }, + } as ITableWidgetConfig, + }, + }, + }, + }, +}; +\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\`, + "widget-types/table/table-widget-search/table-widget-search-example.component.ts": \\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\`// © 2022 SolarWinds Worldwide, LLC. All rights reserved. +// +// Permission is hereby granted, free of charge, to any person obtaining a copy +// of this software and associated documentation files (the "Software"), to +// deal in the Software without restriction, including without limitation the +// rights to use, copy, modify, merge, publish, distribute, sublicense, and/or +// sell copies of the Software, and to permit persons to whom the Software is +// furnished to do so, subject to the following conditions: +// +// The above copyright notice and this permission notice shall be included in +// all copies or substantial portions of the Software. +// +// THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR +// IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, +// FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE +// AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER +// LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, +// OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN +// THE SOFTWARE. + +import { HttpClient } from "@angular/common/http"; +import { + ChangeDetectorRef, + Component, + Injectable, + OnInit, +} from "@angular/core"; +import { GridsterConfig, GridsterItem } from "angular-gridster2"; +import isEqual from "lodash/isEqual"; +import isNil from "lodash/isNil"; +import { BehaviorSubject, firstValueFrom, Observable, of, Subject } from "rxjs"; +import { + catchError, + delay, + finalize, + map, + // eslint-disable-next-line import/no-deprecated + switchMap, + tap, +} from "rxjs/operators"; + +import { + DataSourceFeatures, + DataSourceService, + IDataField, + IDataSource, + IDataSourceFeatures, + IDataSourceFeaturesConfiguration, + IDataSourceOutput, + IFilter, + IFilters, + INovaFilteringOutputs, + INovaFilters, + LoggerService, +} from "@nova-ui/bits"; +import { + DATA_SOURCE, + IDashboard, + ITableWidgetConfig, + IWidget, + IWidgets, + ProviderRegistryService, + WellKnownPathKey, + WellKnownProviders, + WidgetTypesService, +} from "@nova-ui/dashboards"; + +import { GBOOKS_API_URL } from "../../../../prototypes/data/table/constants"; + +interface IGBooksApiResponse { + kind: string; + totalItems: number; + items: IGBooksItemModel[]; + [key: string]: any; +} + +interface IGBooksItemModel { + id: string; + volumeInfo: { + title: string; + subtitle: string; + authors: string[]; + [key: string]: any; + }; + accessInfo: { [key: string]: any }; + saleInfo: { [key: string]: any }; +} + +interface IGBooksData { + books: IGBooksVolume[]; + totalItems: number; +} + +interface IGBooksVolume { + title: string; + authors: string; +} + +type searchableColumnType = "title" | "authors"; + +@Injectable() +export class AcmeTableGBooksDataSource + extends DataSourceService + implements IDataSource +{ + public static providerId = "AcmeTableGBooksDataSource"; + public static mockError = false; + + public searchableColumn: searchableColumnType = "title"; + + public page: number = 1; + public busy = new BehaviorSubject(false); + public features: IDataSourceFeaturesConfiguration; + + private cache = Array.from({ length: 0 }); + private previousFilters: INovaFilters; + // DataSource Features declared + private supportedFeatures: IDataSourceFeatures = { + search: { enabled: true }, + pagination: { enabled: true }, + }; + private columnToQueryParamMap: { [k in searchableColumnType]: string } = { + title: "intitle", + authors: "inauthor", + }; + + private applyFilters$ = new Subject(); + + public dataFields: Array = [ + { + id: "title", + label: $localize\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\`Title\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\`, + dataType: "string", + sortable: false, + }, + { + id: "authors", + label: $localize\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\`Authors\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\`, + dataType: "string", + sortable: false, + }, + ]; + + constructor(private logger: LoggerService, private http: HttpClient) { + super(); + // Using Nova DataSourceFeatures implementation for the features + this.features = new DataSourceFeatures(this.supportedFeatures); + + this.applyFilters$ + // eslint-disable-next-line import/no-deprecated + .pipe(switchMap((filters) => this.getData(filters))) + .subscribe(async (res) => { + this.outputsSubject.next(await this.getFilteredData(res)); + }); + } + + public async getFilteredData( + booksData: IGBooksData + ): Promise> { + return firstValueFrom( + of(booksData).pipe( + tap((response) => { + this.cache = this.cache.concat(response.books); + }), + map((response) => ({ + result: { + repeat: { itemsSource: this.cache }, + paginator: { total: response.totalItems }, + dataFields: this.dataFields, + }, + })) + ) + ); + } + + private getData(filters: INovaFilters): Observable { + if ( + this.isNewSearchTerm(filters.search) && + filters.virtualScroll?.value.start === 0 + ) { + this.cache = []; + } + + return this.http + .get(this.getComposedUrl(filters)) + .pipe( + tap(() => this.busy.next(true)), + delay(300), // mock + map((response) => ({ + books: + response.items?.map((volume) => ({ + title: volume.volumeInfo.title, + authors: + volume.volumeInfo.authors?.join(", ") || "", + })) || [], + totalItems: response.totalItems, + })), + catchError((e) => { + this.logger.error(e); + return of({ + books: [], + totalItems: 0, + }); + }), + finalize(() => { + this.busy.next(false); + this.previousFilters = filters; + }) + ); + } + + private getComposedUrl(filters: INovaFilters) { + const initialUrl = \\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\`\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\${GBOOKS_API_URL}?q=\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\`; + const maxResults = \\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\`maxResults=\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\${ + (filters.virtualScroll?.value.end || 0) - + (filters.virtualScroll?.value.start || 0) + }\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\`; + + const virtualScrollPart = filters.virtualScroll + ? \\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\`startIndex=\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\${filters.virtualScroll.value.start}\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\` + : ""; + + const searchQueryParam = + this.columnToQueryParamMap[this.searchableColumn]; + const searchPart = filters.search + ? \\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\`\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\${searchQueryParam}:\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\${filters.search.value}\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\` + : "_"; // google books api requires some criteria to do the search + + return \\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\`\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\${initialUrl}\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\${searchPart}&\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\${maxResults}&\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\${virtualScrollPart}&filter=full\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\`; + } + + private isNewSearchTerm(search: IFilter | undefined) { + return ( + !isNil(search?.value) && + !isEqual(search?.value, this.previousFilters?.search?.value) + ); + } + + // redefine parent method + public async applyFilters(): Promise { + this.applyFilters$.next(this.getFilters()); + } +} + +/** + * A component that instantiates the dashboard + */ +@Component({ + selector: "table-widget-search-example", + templateUrl: "./table-widget-search-example.component.html", + styleUrls: ["./table-widget-search-example.component.less"], + standalone: false, +}) +export class TableWidgetSearchExampleComponent implements OnInit { + public dashboard: IDashboard | undefined; + public gridsterConfig: GridsterConfig = {}; + public editMode: boolean = false; + + constructor( + private widgetTypesService: WidgetTypesService, + private providerRegistry: ProviderRegistryService, + private changeDetectorRef: ChangeDetectorRef + ) {} + + public ngOnInit(): void { + const widgetTemplate = this.widgetTypesService.getWidgetType( + "table", + 1 + ); + this.widgetTypesService.setNode( + widgetTemplate, + "configurator", + WellKnownPathKey.DataSourceProviders, + [AcmeTableGBooksDataSource.providerId] + ); + + this.providerRegistry.setProviders({ + [AcmeTableGBooksDataSource.providerId]: { + provide: DATA_SOURCE, + useClass: AcmeTableGBooksDataSource, + deps: [LoggerService, HttpClient], + }, + }); + + this.initializeDashboard(); + } + + /** Used for restoring widgets state */ + public reInitializeDashboard(): void { + // destroys the components and their providers so the dashboard can re init data + this.dashboard = undefined; + this.changeDetectorRef.detectChanges(); + + this.initializeDashboard(); + } + + public initializeDashboard(): void { + const tableWidget = widgetConfig; + const widgetIndex: IWidgets = { + [tableWidget.id]: + this.widgetTypesService.mergeWithWidgetType(tableWidget), + }; + + const positions: Record = { + [tableWidget.id]: { + cols: 12, + rows: 6, + y: 0, + x: 0, + }, + }; + + this.dashboard = { + positions, + widgets: widgetIndex, + }; + } +} + +export const widgetConfig: IWidget = { + id: "tableWidgetId", + type: "table", + pizzagna: { + configuration: { + header: { + properties: { + title: "Google Books", + }, + }, + table: { + providers: { + [WellKnownProviders.DataSource]: { + providerId: AcmeTableGBooksDataSource.providerId, + }, + }, + properties: { + configuration: { + columns: [ + { + id: "column1", + label: $localize\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\`Title\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\`, + isActive: true, + formatter: { + componentType: "RawFormatterComponent", + properties: { + dataFieldIds: { + value: "title", + }, + }, + }, + }, + { + id: "column2", + label: $localize\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\`Author\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\`, + isActive: true, + formatter: { + componentType: "RawFormatterComponent", + properties: { + dataFieldIds: { + value: "authors", + }, + }, + }, + }, + ], + sortable: false, + // define search configuration here + searchConfiguration: { + enabled: true, + // following properties below can be configured as well + // searchTerm: "search criteria here", + // searchDebounce: 300, + }, + hasVirtualScroll: true, + } as ITableWidgetConfig, + }, + }, + }, + }, +}; +\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\`, + "widget-types/table/table-widget-search-docs.component.ts": \\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\`// © 2022 SolarWinds Worldwide, LLC. All rights reserved. +// +// Permission is hereby granted, free of charge, to any person obtaining a copy +// of this software and associated documentation files (the "Software"), to +// deal in the Software without restriction, including without limitation the +// rights to use, copy, modify, merge, publish, distribute, sublicense, and/or +// sell copies of the Software, and to permit persons to whom the Software is +// furnished to do so, subject to the following conditions: +// +// The above copyright notice and this permission notice shall be included in +// all copies or substantial portions of the Software. +// +// THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR +// IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, +// FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE +// AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER +// LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, +// OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN +// THE SOFTWARE. + +import { Component } from "@angular/core"; + +@Component({ + selector: "nui-table-search-docs", + templateUrl: "./table-widget-search-docs.component.html", + standalone: false, +}) +export class TableSearchDocsComponent { + public featuredDeclaredText = \\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\` + private supportedFeatures: IDataSourceFeatures = { + search: { enabled: true }, + pagination: { enabled: true }, + };\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\`; + public featuresUsedText = \\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\` + this.features = new DataSourceFeatures(this.supportedFeatures); + \\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\`; + public tableConfigurationText = \\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\` + "table": { + ... + properties: { + configuration: { + // define search configuration here + searchConfiguration: { + enabled: true, + // following optional properties below can be configured as well + // searchTerm: "search criteria here", + // searchDebounce: 300, + }, + } as ITableWidgetConfig, + }, + }, + \\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\`; +} +\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\`, + "widget-types/table/table-widget-selectable/table-widget-selectable-multi/table-widget-selectable-multi.example.component.ts": \\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\`// © 2022 SolarWinds Worldwide, LLC. All rights reserved. +// +// Permission is hereby granted, free of charge, to any person obtaining a copy +// of this software and associated documentation files (the "Software"), to +// deal in the Software without restriction, including without limitation the +// rights to use, copy, modify, merge, publish, distribute, sublicense, and/or +// sell copies of the Software, and to permit persons to whom the Software is +// furnished to do so, subject to the following conditions: +// +// The above copyright notice and this permission notice shall be included in +// all copies or substantial portions of the Software. +// +// THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR +// IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, +// FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE +// AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER +// LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, +// OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN +// THE SOFTWARE. + +import { Component } from "@angular/core"; + +import { TableSelectionMode } from "@nova-ui/bits"; +import { TableWidgetSelectionConfig } from "@nova-ui/dashboards"; + +/** + * A component that instantiates the dashboard + */ +@Component({ + selector: "table-widget-selectable-multi-example", + templateUrl: "./table-widget-selectable-multi.example.component.html", + styleUrls: ["./table-widget-selectable-multi.example.component.less"], + standalone: false, +}) +export class TableWidgetSelectableMultiExampleComponent { + public selectionConfiguration: TableWidgetSelectionConfig = { + enabled: true, + selectionMode: TableSelectionMode.Multi, + trackByProperty: "id", + clickableRow: true, + }; +} +\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\`, + "widget-types/table/table-widget-selectable/table-widget-selectable-radio/table-widget-selectable-radio.example.component.ts": \\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\`// © 2022 SolarWinds Worldwide, LLC. All rights reserved. +// +// Permission is hereby granted, free of charge, to any person obtaining a copy +// of this software and associated documentation files (the "Software"), to +// deal in the Software without restriction, including without limitation the +// rights to use, copy, modify, merge, publish, distribute, sublicense, and/or +// sell copies of the Software, and to permit persons to whom the Software is +// furnished to do so, subject to the following conditions: +// +// The above copyright notice and this permission notice shall be included in +// all copies or substantial portions of the Software. +// +// THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR +// IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, +// FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE +// AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER +// LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, +// OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN +// THE SOFTWARE. + +import { Component } from "@angular/core"; + +import { TableSelectionMode } from "@nova-ui/bits"; +import { TableWidgetSelectionConfig } from "@nova-ui/dashboards"; + +/** + * A component that instantiates the dashboard + */ +@Component({ + selector: "table-widget-selectable-radio-example", + templateUrl: "./table-widget-selectable-radio.example.component.html", + styleUrls: ["./table-widget-selectable-radio.example.component.less"], + standalone: false, +}) +export class TableWidgetSelectableRadioExampleComponent { + public selectionConfiguration: TableWidgetSelectionConfig = { + enabled: true, + selectionMode: TableSelectionMode.Radio, + trackByProperty: "id", + clickableRow: true, + }; +} +\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\`, + "widget-types/table/table-widget-selectable/table-widget-selectable-single/table-widget-selectable-single.example.component.ts": \\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\`// © 2022 SolarWinds Worldwide, LLC. All rights reserved. +// +// Permission is hereby granted, free of charge, to any person obtaining a copy +// of this software and associated documentation files (the "Software"), to +// deal in the Software without restriction, including without limitation the +// rights to use, copy, modify, merge, publish, distribute, sublicense, and/or +// sell copies of the Software, and to permit persons to whom the Software is +// furnished to do so, subject to the following conditions: +// +// The above copyright notice and this permission notice shall be included in +// all copies or substantial portions of the Software. +// +// THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR +// IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, +// FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE +// AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER +// LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, +// OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN +// THE SOFTWARE. + +import { Component } from "@angular/core"; + +import { TableSelectionMode } from "@nova-ui/bits"; +import { TableWidgetSelectionConfig } from "@nova-ui/dashboards"; + +/** + * A component that instantiates the dashboard + */ +@Component({ + selector: "table-widget-selectable-single-example", + templateUrl: "./table-widget-selectable-single.example.component.html", + styleUrls: ["./table-widget-selectable-single.example.component.less"], + standalone: false, +}) +export class TableWidgetSelectableSingleExampleComponent { + public selectionConfiguration: TableWidgetSelectionConfig = { + enabled: true, + selectionMode: TableSelectionMode.Single, + trackByProperty: "id", + }; +} +\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\`, + "widget-types/table/table-widget-selectable/table-widget-selectable.example.component.ts": \\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\`// © 2022 SolarWinds Worldwide, LLC. All rights reserved. +// +// Permission is hereby granted, free of charge, to any person obtaining a copy +// of this software and associated documentation files (the "Software"), to +// deal in the Software without restriction, including without limitation the +// rights to use, copy, modify, merge, publish, distribute, sublicense, and/or +// sell copies of the Software, and to permit persons to whom the Software is +// furnished to do so, subject to the following conditions: +// +// The above copyright notice and this permission notice shall be included in +// all copies or substantial portions of the Software. +// +// THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR +// IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, +// FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE +// AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER +// LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, +// OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN +// THE SOFTWARE. + +import { HttpClient } from "@angular/common/http"; +import { ChangeDetectorRef, Component, Input, OnInit } from "@angular/core"; +import { GridsterConfig, GridsterItem } from "angular-gridster2"; + +import { LoggerService, TableSelectionMode } from "@nova-ui/bits"; +import { + DATA_SOURCE, + DEFAULT_PIZZAGNA_ROOT, + IDashboard, + IProviderConfiguration, + ITableWidgetConfig, + IWidget, + IWidgets, + NOVA_URL_INTERACTION_HANDLER, + PizzagnaLayer, + ProviderRegistryService, + RawFormatterComponent, + TableWidgetSelectionConfig, + WellKnownPathKey, + WellKnownProviders, + WidgetTypesService, +} from "@nova-ui/dashboards"; + +import { AcmeTableMockDataSource } from "../../../../prototypes/data/table/acme-table-mock-data-source.service"; + +/** + * A component that instantiates the dashboard + */ +@Component({ + selector: "table-widget-selectable-example", + templateUrl: "./table-widget-selectable.example.component.html", + styleUrls: ["./table-widget-selectable.example.component.less"], + standalone: false, +}) +export class TableWidgetSelectableExampleComponent implements OnInit { + public dashboard: IDashboard | undefined; + public gridsterConfig: GridsterConfig = {}; + public editMode: boolean = false; + + @Input() public selectionConfiguration: TableWidgetSelectionConfig = { + enabled: false, + selectionMode: TableSelectionMode.None, + }; + + constructor( + private widgetTypesService: WidgetTypesService, + private providerRegistry: ProviderRegistryService, + private changeDetectorRef: ChangeDetectorRef + ) {} + + public ngOnInit(): void { + const widgetTemplate = this.widgetTypesService.getWidgetType( + "table", + 1 + ); + this.widgetTypesService.setNode( + widgetTemplate, + "configurator", + WellKnownPathKey.DataSourceProviders, + [AcmeTableMockDataSource.providerId] + ); + + this.providerRegistry.setProviders({ + [AcmeTableMockDataSource.providerId]: { + provide: DATA_SOURCE, + useClass: AcmeTableMockDataSource, + deps: [LoggerService, HttpClient], + }, + }); + + this.initializeDashboard(); + } + + /** Used for restoring widgets state */ + public reInitializeDashboard(): void { + // destroys the components and their providers so the dashboard can re init data + this.dashboard = undefined; + this.changeDetectorRef.detectChanges(); + + this.initializeDashboard(); + } + + public initializeDashboard(): void { + const tableWidget = this.widgetConfig; + const widgetIndex: IWidgets = { + [tableWidget.id]: + this.widgetTypesService.mergeWithWidgetType(tableWidget), + }; + + const positions: Record = { + [tableWidget.id]: { + cols: 12, + rows: 6, + y: 0, + x: 0, + }, + }; + + this.dashboard = { + positions, + widgets: widgetIndex, + }; + } + + private get widgetConfig(): IWidget { + return { + id: "widget1", + type: "table", + pizzagna: { + [PizzagnaLayer.Configuration]: { + [DEFAULT_PIZZAGNA_ROOT]: { + providers: { + [WellKnownProviders.InteractionHandler]: { + providerId: NOVA_URL_INTERACTION_HANDLER, + }, + }, + }, + header: { + properties: { + title: "Table Widget with Selection!", + subtitle: "Basic table widget", + collapsible: true, + }, + }, + table: { + providers: { + [WellKnownProviders.DataSource]: { + providerId: AcmeTableMockDataSource.providerId, + } as IProviderConfiguration, + }, + properties: { + configuration: { + // enabling selection here + selectionConfiguration: + this.selectionConfiguration, + columns: [ + { + id: "column1", + label: "No.", + isActive: true, + formatter: { + componentType: + RawFormatterComponent.lateLoadKey, + properties: { + dataFieldIds: { + value: "position", + }, + }, + }, + }, + { + id: "column2", + label: "Name", + isActive: true, + formatter: { + componentType: + RawFormatterComponent.lateLoadKey, + properties: { + dataFieldIds: { + value: "name", + }, + }, + }, + }, + { + id: "column3", + label: "Status", + isActive: true, + formatter: { + componentType: + RawFormatterComponent.lateLoadKey, + properties: { + dataFieldIds: { + value: "status", + }, + }, + }, + }, + ], + } as ITableWidgetConfig, + }, + }, + }, + }, + }; + } +} +\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\`, + "widget-types/timeseries/timeseries-docs.component.ts": \\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\`// © 2022 SolarWinds Worldwide, LLC. All rights reserved. +// +// Permission is hereby granted, free of charge, to any person obtaining a copy +// of this software and associated documentation files (the "Software"), to +// deal in the Software without restriction, including without limitation the +// rights to use, copy, modify, merge, publish, distribute, sublicense, and/or +// sell copies of the Software, and to permit persons to whom the Software is +// furnished to do so, subject to the following conditions: +// +// The above copyright notice and this permission notice shall be included in +// all copies or substantial portions of the Software. +// +// THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR +// IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, +// FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE +// AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER +// LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, +// OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN +// THE SOFTWARE. + +import { Component, OnInit } from "@angular/core"; + +import { mapContentFile } from "../../../../demo-files-factory"; + +@Component({ + selector: "nui-timeseries-docs", + templateUrl: "./timeseries-docs.component.html", + standalone: false, +}) +export class TimeseriesDocsComponent implements OnInit { + public timeseriesWidgetFileText = ""; + public timeseriesConfiguratorFileText = ""; + + async ngOnInit(): Promise { + this.timeseriesWidgetFileText = await import( + "./../../../../../../src/lib/widget-types/timeseries/timeseries-widget" + ).then(mapContentFile); + this.timeseriesConfiguratorFileText = await import( + "./../../../../../../src/lib/widget-types/timeseries/timeseries-configurator" + ).then(mapContentFile); + } +} +\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\`, + "widget-types/timeseries/timeseries-docs.module.ts": \\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\`// © 2022 SolarWinds Worldwide, LLC. All rights reserved. +// +// Permission is hereby granted, free of charge, to any person obtaining a copy +// of this software and associated documentation files (the "Software"), to +// deal in the Software without restriction, including without limitation the +// rights to use, copy, modify, merge, publish, distribute, sublicense, and/or +// sell copies of the Software, and to permit persons to whom the Software is +// furnished to do so, subject to the following conditions: +// +// The above copyright notice and this permission notice shall be included in +// all copies or substantial portions of the Software. +// +// THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR +// IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, +// FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE +// AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER +// LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, +// OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN +// THE SOFTWARE. + +import { NgModule } from "@angular/core"; +import { RouterModule, Routes } from "@angular/router"; + +import { DEMO_PATH_TOKEN } from "@nova-ui/bits"; +import { + NuiButtonModule, + NuiDocsModule, + NuiMessageModule, + NuiSwitchModule, +} from "@nova-ui/bits"; +import { NuiDashboardsModule } from "@nova-ui/dashboards"; + +import { TimeseriesDocsComponent } from "./timeseries-docs.component"; +import { TimeseriesWidgetExampleComponent } from "./timeseries-widget-example/timeseries-widget-example.component"; +import { TimeseriesWidgetInteractiveExampleComponent } from "./timeseries-widget-interactive-example/timeseries-widget-interactive-example.component"; +import { TimeseriesWidgetStatusBarExampleComponent } from "./timeseries-widget-status-bar-example/timeseries-widget-status-bar-example.component"; +import { getDemoFiles } from "../../../../demo-files-factory"; + +const routes: Routes = [ + { + path: "", + component: TimeseriesDocsComponent, + data: { + srlc: { + hideIndicator: true, + }, + showThemeSwitcher: true, + }, + }, + { + path: "example", + component: TimeseriesWidgetExampleComponent, + data: { + srlc: { + hideIndicator: true, + }, + }, + }, +]; + +@NgModule({ + imports: [ + RouterModule.forChild(routes), + NuiButtonModule, + NuiDocsModule, + NuiMessageModule, + NuiSwitchModule, + NuiDashboardsModule, + ], + declarations: [ + TimeseriesDocsComponent, + TimeseriesWidgetExampleComponent, + TimeseriesWidgetInteractiveExampleComponent, + TimeseriesWidgetStatusBarExampleComponent, + ], + providers: [ + { + provide: DEMO_PATH_TOKEN, + useValue: getDemoFiles("timeseries"), + }, + ], +}) +export default class TimeseriesDocsModule {} +\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\`, + "widget-types/timeseries/timeseries-widget-example/timeseries-widget-example.component.ts": \\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\`// © 2022 SolarWinds Worldwide, LLC. All rights reserved. +// +// Permission is hereby granted, free of charge, to any person obtaining a copy +// of this software and associated documentation files (the "Software"), to +// deal in the Software without restriction, including without limitation the +// rights to use, copy, modify, merge, publish, distribute, sublicense, and/or +// sell copies of the Software, and to permit persons to whom the Software is +// furnished to do so, subject to the following conditions: +// +// The above copyright notice and this permission notice shall be included in +// all copies or substantial portions of the Software. +// +// THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR +// IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, +// FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE +// AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER +// LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, +// OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN +// THE SOFTWARE. + +import { + ChangeDetectorRef, + Component, + Injectable, + OnInit, +} from "@angular/core"; +import { GridsterConfig, GridsterItem } from "angular-gridster2"; +import cloneDeep from "lodash/cloneDeep"; +import keyBy from "lodash/keyBy"; +import moment, { Moment } from "moment/moment"; +import { BehaviorSubject } from "rxjs"; + +import { + DataSourceService, + IDataSource, + INovaFilters, + ITimeframe, +} from "@nova-ui/bits"; +import { + DATA_SOURCE, + DEFAULT_PIZZAGNA_ROOT, + IDashboard, + IDataSourceOutput, + IProviderConfiguration, + ISerializableTimeframe, + ITimeseriesItemConfiguration, + ITimeseriesOutput, + ITimeseriesScaleConfig, + ITimeseriesWidgetConfig, + ITimeseriesWidgetData, + ITimeseriesWidgetSeriesData, + IWidget, + LegendPlacement, + PizzagnaLayer, + ProviderRegistryService, + TimeseriesChartPreset, + TimeseriesScaleType, + WellKnownPathKey, + WellKnownProviders, + WidgetTypesService, +} from "@nova-ui/dashboards"; + +/** + * A simple Timeseries data source implementation + */ +@Injectable() +export class BeerVsReadingMockDataSource + extends DataSourceService + implements IDataSource +{ + public static providerId = "BeerVsReadingMockDataSource"; + + public busy = new BehaviorSubject(false); + + public async getFilteredData( + filters: INovaFilters + ): Promise> { + // In this example we're using some static mock data located at the bottom of this file. In a real-world + // scenario, the data for the chart would likely be retrieved via an asynchronous backend call. + let filteredData = getData(); + + this.busy.next(true); + + // Filtering using the filter registered by the TimeFrameBar + const timeframeFilter = filters.timeframe?.value as ITimeframe; + if (timeframeFilter) { + filteredData = filteredData.map((item: ITimeseriesWidgetData) => ({ + id: item.id, + name: item.name, + description: item.description, + data: item.data.filter( + (seriesData: ITimeseriesWidgetSeriesData) => + filterDates( + seriesData.x, + timeframeFilter.startDatetime, + timeframeFilter.endDatetime + ) + ), + })); + } + + this.busy.next(false); + + return { result: { series: filteredData } }; + } +} + +function filterDates(dateToCheck: Date, startDate: Moment, endDate: Moment) { + const mom = moment(dateToCheck); + return ( + mom.isBetween(startDate, endDate) || + mom.isSame(startDate) || + mom.isSame(endDate) + ); +} + +/** + * A component that instantiates the dashboard + */ +@Component({ + selector: "timeseries-widget-example", + templateUrl: "./timeseries-widget-example.component.html", + styleUrls: ["./timeseries-widget-example.component.less"], + standalone: false, +}) +export class TimeseriesWidgetExampleComponent implements OnInit { + // This variable will hold all the data needed to define the layout and behavior of the widgets. + // Pass this to the dashboard component's dashboard input in the template. + public dashboard: IDashboard | undefined; + + // Angular gridster requires a configuration object even if it's empty. + // Pass this to the dashboard component's gridsterConfig input in the template. + public gridsterConfig: GridsterConfig = {}; + + // Boolean passed as an input to the dashboard. When true, widgets can be moved, resized, removed, or edited + public editMode: boolean = false; + + constructor( + // WidgetTypesService provides the widget's necessary structure information + private widgetTypesService: WidgetTypesService, + + // In general, the ProviderRegistryService is used for making entities available for injection into dynamically loaded components. + private providerRegistry: ProviderRegistryService, + + // Angular's ChangeDetectorRef + private changeDetectorRef: ChangeDetectorRef + ) {} + + public ngOnInit(): void { + // Grabbing the widget's default template which will be needed as a parameter for setNode + const widgetTemplate = this.widgetTypesService.getWidgetType( + "timeseries", + 1 + ); + // Registering our data sources as dropdown options in the widget editor/configurator + // Note: This could also be done in the parent module's constructor so that + // multiple dashboards could have access to the same widget template modification. + this.widgetTypesService.setNode( + // This is the template we grabbed above with getWidgetType + widgetTemplate, + // We are setting the editor/configurator part of the widget template + "configurator", + // This indicates which node you are changing and we want to change + // the data source providers available for selection in the editor. + WellKnownPathKey.DataSourceProviders, + // We are setting the data sources available for selection in the editor + [BeerVsReadingMockDataSource.providerId] + ); + + // Registering the data source for injection into the widget. + this.providerRegistry.setProviders({ + [BeerVsReadingMockDataSource.providerId]: { + provide: DATA_SOURCE, + useClass: BeerVsReadingMockDataSource, + deps: [], + }, + }); + + this.initializeDashboard(); + } + + public initializeDashboard(): void { + // We're using a static configuration object for this example, but this is where + // the widget's configuration could potentially be populated from a database + const widgetsWithStructure = widgetConfigs.map((w) => + this.widgetTypesService.mergeWithWidgetType(w) + ); + const widgetsIndex = keyBy(widgetsWithStructure, (w: IWidget) => w.id); + + // Finally, assigning the variables we created above to the dashboard + this.dashboard = { + positions: cloneDeep(positions), + widgets: widgetsIndex, + }; + } + + /** Used for restoring widgets state */ + public reInitializeDashboard(): void { + // destroys the components and their providers so the dashboard can re init data + this.dashboard = undefined; + this.changeDetectorRef.detectChanges(); + + this.initializeDashboard(); + } +} + +const widgetConfigs: IWidget[] = [ + { + id: "lineWidgetId", + type: "timeseries", + pizzagna: { + [PizzagnaLayer.Configuration]: { + [DEFAULT_PIZZAGNA_ROOT]: { + providers: { + [WellKnownProviders.DataSource]: { + // Setting the initially selected data source providerId + providerId: BeerVsReadingMockDataSource.providerId, + } as IProviderConfiguration, + }, + }, + header: { + properties: { + title: "Line Chart", + subtitle: "Survey of 1000 Solarians", + }, + }, + chart: { + providers: { + [WellKnownProviders.Adapter]: { + properties: { + // Setting the series and corresponding labels to initially display on the chart + series: [ + { + id: "series-1", + label: "Beer Tasting", + selectedSeriesId: "series-1", + }, + { + id: "series-2", + label: "Reading", + selectedSeriesId: "series-2", + }, + ] as ITimeseriesItemConfiguration[], + }, + } as Partial, + }, + properties: { + // Setting the general chart configuration + configuration: { + legendPlacement: LegendPlacement.Right, + enableZoom: true, + leftAxisLabel: "Solarians (%)", + // You can optionally define custom colors for the chart by setting the 'chartColors' configuration property + // "chartColors": [ + // "var(--nui-color-chart-eight)", + // "var(--nui-color-chart-nine)", + // "var(--nui-color-chart-ten)", + // ], + } as ITimeseriesWidgetConfig, + }, + }, + timeframeSelection: { + properties: { + // Setting the initial timeframe selected in the timeframe bar + timeframe: { + selectedPresetId: "last7Days", + } as ISerializableTimeframe, + minDate: moment().subtract(60, "days").format(), + maxDate: moment().format(), + }, + }, + }, + }, + }, + { + id: "stackedAreaWidgetId", + type: "timeseries", + pizzagna: { + [PizzagnaLayer.Configuration]: { + [DEFAULT_PIZZAGNA_ROOT]: { + providers: { + [WellKnownProviders.DataSource]: { + // Setting the initially selected data source providerId + providerId: BeerVsReadingMockDataSource.providerId, + } as IProviderConfiguration, + }, + }, + header: { + properties: { + title: "Stacked Area Chart", + subtitle: "Survey of 1000 Solarians", + }, + }, + chart: { + providers: { + [WellKnownProviders.Adapter]: { + properties: { + // Setting the series and corresponding labels to initially display on the chart + series: [ + { + id: "series-1", + label: "Beer Tasting", + selectedSeriesId: "series-1", + }, + { + id: "series-2", + label: "Reading", + selectedSeriesId: "series-2", + }, + ] as ITimeseriesItemConfiguration[], + }, + } as Partial, + }, + properties: { + // Setting the general chart configuration + configuration: { + legendPlacement: LegendPlacement.Right, + enableZoom: true, + // Setting the preset to stacked area + preset: TimeseriesChartPreset.StackedArea, + leftAxisLabel: "Solarians (%)", + // You can optionally define custom colors for the chart by setting the 'chartColors' configuration property + // "chartColors": [ + // "var(--nui-color-chart-eight)", + // "var(--nui-color-chart-nine)", + // "var(--nui-color-chart-ten)", + // ], + } as ITimeseriesWidgetConfig, + }, + }, + timeframeSelection: { + properties: { + // Setting the initial timeframe selected in the timeframe bar + timeframe: { + selectedPresetId: "last7Days", + } as ISerializableTimeframe, + minDate: moment().subtract(60, "days").format(), + maxDate: moment().format(), + }, + }, + }, + }, + }, + { + id: "stackedPercentageAreaWidgetId", + type: "timeseries", + pizzagna: { + [PizzagnaLayer.Configuration]: { + [DEFAULT_PIZZAGNA_ROOT]: { + providers: { + [WellKnownProviders.DataSource]: { + // Setting the initially selected data source providerId + providerId: BeerVsReadingMockDataSource.providerId, + } as IProviderConfiguration, + }, + }, + header: { + properties: { + title: "Stacked Percentage Area Chart", + subtitle: "Survey of 1000 Solarians", + }, + }, + chart: { + providers: { + [WellKnownProviders.Adapter]: { + properties: { + // Setting the series and corresponding labels to initially display on the chart + series: [ + { + id: "series-1", + label: "Beer Tasting", + selectedSeriesId: "series-1", + }, + { + id: "series-2", + label: "Reading", + selectedSeriesId: "series-2", + }, + ] as ITimeseriesItemConfiguration[], + }, + } as Partial, + }, + properties: { + // Setting the general chart configuration + configuration: { + legendPlacement: LegendPlacement.Right, + enableZoom: true, + // Setting the preset to stacked percentage area + preset: TimeseriesChartPreset.StackedPercentageArea, + leftAxisLabel: "Solarians (%)", + // You can optionally define custom colors for the chart by setting the 'chartColors' configuration property + // "chartColors": [ + // "var(--nui-color-chart-eight)", + // "var(--nui-color-chart-nine)", + // "var(--nui-color-chart-ten)", + // ], + } as ITimeseriesWidgetConfig, + }, + }, + timeframeSelection: { + properties: { + // Setting the initial timeframe selected in the timeframe bar + timeframe: { + selectedPresetId: "last7Days", + } as ISerializableTimeframe, + minDate: moment().subtract(60, "days").format(), + maxDate: moment().format(), + }, + }, + }, + }, + }, + { + id: "stackedBarWidgetId", + type: "timeseries", + pizzagna: { + [PizzagnaLayer.Configuration]: { + [DEFAULT_PIZZAGNA_ROOT]: { + providers: { + [WellKnownProviders.DataSource]: { + // Setting the initially selected data source providerId + providerId: BeerVsReadingMockDataSource.providerId, + } as IProviderConfiguration, + }, + }, + header: { + properties: { + title: "Stacked Bar Chart", + subtitle: "Survey of 1000 Solarians", + }, + }, + chart: { + providers: { + [WellKnownProviders.Adapter]: { + properties: { + // Setting the series and corresponding labels to initially display on the chart + series: [ + { + id: "series-1", + label: "Beer Tasting", + selectedSeriesId: "series-1", + }, + { + id: "series-2", + label: "Reading", + selectedSeriesId: "series-2", + }, + ] as ITimeseriesItemConfiguration[], + }, + } as Partial, + }, + properties: { + configuration: { + legendPlacement: LegendPlacement.Right, + enableZoom: true, + leftAxisLabel: "Solarians (%)", + // Setting the preset to stacked bar + preset: TimeseriesChartPreset.StackedBar, + scales: { + x: { + type: TimeseriesScaleType.TimeInterval, + properties: { + interval: 24 * 60 * 60, + }, + } as ITimeseriesScaleConfig, + }, + } as ITimeseriesWidgetConfig, + }, + }, + timeframeSelection: { + properties: { + // Setting the initial timeframe selected in the timeframe bar + timeframe: { + selectedPresetId: "last7Days", + } as ISerializableTimeframe, + minDate: moment().subtract(60, "days").format(), + maxDate: moment().format(), + }, + }, + }, + }, + }, +]; + +// using startOf("day") so that each band for the bar chart corresponds to a calendar day +const now = moment().startOf("day"); + +export const getData = (): ITimeseriesWidgetData[] => [ + { + id: "series-1", + name: "Beer Tasting", + description: "Havin' some suds", + data: [ + { x: now.clone().subtract(20, "day").toDate(), y: 30 }, + { x: now.clone().subtract(19, "day").toDate(), y: 35 }, + { x: now.clone().subtract(18, "day").toDate(), y: 33 }, + { x: now.clone().subtract(17, "day").toDate(), y: 40 }, + { x: now.clone().subtract(16, "day").toDate(), y: 35 }, + { x: now.clone().subtract(15, "day").toDate(), y: 30 }, + { x: now.clone().subtract(14, "day").toDate(), y: 35 }, + { x: now.clone().subtract(13, "day").toDate(), y: 15 }, + { x: now.clone().subtract(12, "day").toDate(), y: 30 }, + { x: now.clone().subtract(11, "day").toDate(), y: 45 }, + { x: now.clone().subtract(10, "day").toDate(), y: 60 }, + { x: now.clone().subtract(9, "day").toDate(), y: 54 }, + { x: now.clone().subtract(8, "day").toDate(), y: 42 }, + { x: now.clone().subtract(7, "day").toDate(), y: 44 }, + { x: now.clone().subtract(6, "day").toDate(), y: 54 }, + { x: now.clone().subtract(5, "day").toDate(), y: 43 }, + { x: now.clone().subtract(4, "day").toDate(), y: 76 }, + { x: now.clone().subtract(3, "day").toDate(), y: 54 }, + { x: now.clone().subtract(2, "day").toDate(), y: 42 }, + { x: now.clone().subtract(1, "day").toDate(), y: 34 }, + ], + }, + { + id: "series-2", + name: "Reading", + description: "Hittin' the books", + data: [ + { x: now.clone().subtract(20, "day").toDate(), y: 60 }, + { x: now.clone().subtract(19, "day").toDate(), y: 64 }, + { x: now.clone().subtract(18, "day").toDate(), y: 70 }, + { x: now.clone().subtract(17, "day").toDate(), y: 55 }, + { x: now.clone().subtract(16, "day").toDate(), y: 55 }, + { x: now.clone().subtract(15, "day").toDate(), y: 45 }, + { x: now.clone().subtract(14, "day").toDate(), y: 60 }, + { x: now.clone().subtract(13, "day").toDate(), y: 65 }, + { x: now.clone().subtract(12, "day").toDate(), y: 63 }, + { x: now.clone().subtract(11, "day").toDate(), y: 60 }, + { x: now.clone().subtract(10, "day").toDate(), y: 61 }, + { x: now.clone().subtract(9, "day").toDate(), y: 65 }, + { x: now.clone().subtract(8, "day").toDate(), y: 63 }, + { x: now.clone().subtract(7, "day").toDate(), y: 58 }, + { x: now.clone().subtract(6, "day").toDate(), y: 64 }, + { x: now.clone().subtract(5, "day").toDate(), y: 63 }, + { x: now.clone().subtract(4, "day").toDate(), y: 60 }, + { x: now.clone().subtract(3, "day").toDate(), y: 62 }, + { x: now.clone().subtract(2, "day").toDate(), y: 61 }, + { x: now.clone().subtract(1, "day").toDate(), y: 62 }, + ], + }, +]; + +// Setting the widget dimensions and position (this is for gridster) +const positions: Record = { + [widgetConfigs[0].id]: { + cols: 6, + rows: 6, + y: 0, + x: 0, + }, + [widgetConfigs[1].id]: { + cols: 6, + rows: 6, + y: 0, + x: 6, + }, + [widgetConfigs[3].id]: { + cols: 6, + rows: 6, + y: 6, + x: 0, + }, + [widgetConfigs[2].id]: { + cols: 6, + rows: 6, + y: 6, + x: 6, + }, +}; +\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\`, + "widget-types/timeseries/timeseries-widget-interactive-example/timeseries-widget-interactive-example.component.ts": \\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\`// © 2022 SolarWinds Worldwide, LLC. All rights reserved. +// +// Permission is hereby granted, free of charge, to any person obtaining a copy +// of this software and associated documentation files (the "Software"), to +// deal in the Software without restriction, including without limitation the +// rights to use, copy, modify, merge, publish, distribute, sublicense, and/or +// sell copies of the Software, and to permit persons to whom the Software is +// furnished to do so, subject to the following conditions: +// +// The above copyright notice and this permission notice shall be included in +// all copies or substantial portions of the Software. +// +// THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR +// IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, +// FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE +// AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER +// LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, +// OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN +// THE SOFTWARE. + +import { + ChangeDetectorRef, + Component, + Injectable, + OnInit, +} from "@angular/core"; +import { GridsterConfig, GridsterItem } from "angular-gridster2"; +import cloneDeep from "lodash/cloneDeep"; +import keyBy from "lodash/keyBy"; +import moment, { Moment } from "moment/moment"; +import { BehaviorSubject } from "rxjs"; + +import { + DataSourceService, + IDataSource, + INovaFilters, + ITimeframe, +} from "@nova-ui/bits"; +import { + DATA_SOURCE, + DEFAULT_PIZZAGNA_ROOT, + IDashboard, + IDataSourceOutput, + IProviderConfiguration, + ISerializableTimeframe, + ITimeseriesItemConfiguration, + ITimeseriesOutput, + ITimeseriesScaleConfig, + ITimeseriesWidgetConfig, + ITimeseriesWidgetData, + ITimeseriesWidgetSeriesData, + IWidget, + NOVA_URL_INTERACTION_HANDLER, + LegendPlacement, + PizzagnaLayer, + ProviderRegistryService, + TimeseriesChartPreset, + TimeseriesScaleType, + WellKnownPathKey, + WellKnownProviders, + WidgetTypesService, +} from "@nova-ui/dashboards"; + +/** + * A simple Timeseries data source implementation + */ +@Injectable() +export class TimeseriesMockDataSource + extends DataSourceService + implements IDataSource +{ + public static providerId = "TimeseriesMockDataSource"; + + public busy = new BehaviorSubject(false); + + public async getFilteredData( + filters: INovaFilters + ): Promise> { + // In this example we're using some static mock data located at the bottom of this file. In a real-world + // scenario, the data for the chart would likely be retrieved via an asynchronous backend call. + let filteredData = getData(); + + this.busy.next(true); + + // Filtering using the filter registered by the TimeFrameBar + const timeframeFilter = filters.timeframe?.value as ITimeframe; + if (timeframeFilter) { + filteredData = filteredData.map((item: ITimeseriesWidgetData) => ({ + id: item.id, + name: item.name, + description: item.description, + // the filtered data should return the provided links if they are set. + link: item?.link, + secondaryLink: item?.secondaryLink, + data: item.data.filter( + (seriesData: ITimeseriesWidgetSeriesData) => + filterDates( + seriesData.x, + timeframeFilter.startDatetime, + timeframeFilter.endDatetime + ) + ), + })); + } + + this.busy.next(false); + + return { result: { series: filteredData } }; + } +} + +function filterDates(dateToCheck: Date, startDate: Moment, endDate: Moment) { + const mom = moment(dateToCheck); + return ( + mom.isBetween(startDate, endDate) || + mom.isSame(startDate) || + mom.isSame(endDate) + ); +} + +/** + * A component that instantiates the dashboard + */ +@Component({ + selector: "timeseries-widget-interactive-example", + templateUrl: "./timeseries-widget-interactive-example.component.html", + styleUrls: ["./timeseries-widget-interactive-example.component.less"], + standalone: false, +}) +export class TimeseriesWidgetInteractiveExampleComponent implements OnInit { + // This variable will hold all the data needed to define the layout and behavior of the widgets. + // Pass this to the dashboard component's dashboard input in the template. + public dashboard: IDashboard | undefined; + + // Angular gridster requires a configuration object even if it's empty. + // Pass this to the dashboard component's gridsterConfig input in the template. + public gridsterConfig: GridsterConfig = {}; + + // Boolean passed as an input to the dashboard. When true, widgets can be moved, resized, removed, or edited + public editMode: boolean = false; + + constructor( + // WidgetTypesService provides the widget's necessary structure information + private widgetTypesService: WidgetTypesService, + + // In general, the ProviderRegistryService is used for making entities available for injection into dynamically loaded components. + private providerRegistry: ProviderRegistryService, + + // Angular's ChangeDetectorRef + private changeDetectorRef: ChangeDetectorRef + ) {} + + public ngOnInit(): void { + // Grabbing the widget's default template which will be needed as a parameter for setNode + const widgetTemplate = this.widgetTypesService.getWidgetType( + "timeseries", + 1 + ); + // Registering our data sources as dropdown options in the widget editor/configurator + // Note: This could also be done in the parent module's constructor so that + // multiple dashboards could have access to the same widget template modification. + this.widgetTypesService.setNode( + // This is the template we grabbed above with getWidgetType + widgetTemplate, + // We are setting the editor/configurator part of the widget template + "configurator", + // This indicates which node you are changing and we want to change + // the data source providers available for selection in the editor. + WellKnownPathKey.DataSourceProviders, + // We are setting the data sources available for selection in the editor + [TimeseriesMockDataSource.providerId] + ); + + // Registering the data source for injection into the widget. + this.providerRegistry.setProviders({ + [TimeseriesMockDataSource.providerId]: { + provide: DATA_SOURCE, + useClass: TimeseriesMockDataSource, + deps: [], + }, + }); + + this.initializeDashboard(); + } + + public initializeDashboard(): void { + // We're using a static configuration object for this example, but this is where + // the widget's configuration could potentially be populated from a database + const widgetsWithStructure = widgetConfigs.map((w) => + this.widgetTypesService.mergeWithWidgetType(w) + ); + const widgetsIndex = keyBy(widgetsWithStructure, (w: IWidget) => w.id); + + // Finally, assigning the variables we created above to the dashboard + this.dashboard = { + positions: cloneDeep(positions), + widgets: widgetsIndex, + }; + } + + /** Used for restoring widgets state */ + public reInitializeDashboard(): void { + // destroys the components and their providers so the dashboard can re init data + this.dashboard = undefined; + this.changeDetectorRef.detectChanges(); + + this.initializeDashboard(); + } +} + +const widgetConfigs: IWidget[] = [ + { + id: "lineWidgetId", + type: "timeseries", + pizzagna: { + [PizzagnaLayer.Configuration]: { + [DEFAULT_PIZZAGNA_ROOT]: { + providers: { + [WellKnownProviders.DataSource]: { + // Setting the initially selected data source providerId + providerId: TimeseriesMockDataSource.providerId, + } as IProviderConfiguration, + [WellKnownProviders.InteractionHandler]: { + // Setting the UrlInteractionHandler as an interactionHandler + providerId: NOVA_URL_INTERACTION_HANDLER, + properties: { + // the 'url' property tells the handler what link to use when interaction occurs on the series + url: "\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\${data.link || 'https://en.wikipedia.org/wiki/'+data.legendDescriptionPrimary}", + // by default the link is opened in the current window, set 'newWindow' to true to open in a new tab instead + // newWindow: true, + }, + }, + }, + }, + header: { + properties: { + title: "Line Chart", + subtitle: "Basic Timeseries with Interaction", + }, + }, + chart: { + providers: { + [WellKnownProviders.Adapter]: { + properties: { + // Setting the series and corresponding labels to initially display on the chart + series: [ + { + id: "series-1", + label: "Nur-Sultan", + selectedSeriesId: "series-1", + }, + { + id: "series-2", + label: "Brno", + selectedSeriesId: "series-2", + }, + { + id: "series-3", + label: "Lisbon", + selectedSeriesId: "series-3", + }, + { + id: "series-4", + label: "Austin", + selectedSeriesId: "series-4", + }, + ] as ITimeseriesItemConfiguration[], + }, + } as Partial, + }, + properties: { + // Setting the general chart configuration + configuration: { + // setting interaction to 'series' will make all series in the chart interactable + interaction: "series", + legendPlacement: LegendPlacement.Right, + enableZoom: true, + } as ITimeseriesWidgetConfig, + }, + }, + timeframeSelection: { + properties: { + timeframe: { + selectedPresetId: "last7Days", + } as ISerializableTimeframe, + minDate: moment().subtract(60, "days").format(), + maxDate: moment().format(), + }, + }, + }, + }, + }, + { + id: "stackedBarWidgetId", + type: "timeseries", + pizzagna: { + [PizzagnaLayer.Configuration]: { + [DEFAULT_PIZZAGNA_ROOT]: { + providers: { + [WellKnownProviders.DataSource]: { + // Setting the initially selected data source providerId + providerId: TimeseriesMockDataSource.providerId, + } as IProviderConfiguration, + }, + }, + header: { + properties: { + title: "Stacked Bar Chart", + subtitle: + "Basic Timeseries without Interaction Handler", + }, + }, + chart: { + providers: { + [WellKnownProviders.Adapter]: { + properties: { + // Setting the series and corresponding labels to initially display on the chart + series: [ + { + id: "series-1", + label: "Nur-Sultan", + selectedSeriesId: "series-1", + }, + { + id: "series-2", + label: "Brno", + selectedSeriesId: "series-2", + }, + { + id: "series-3", + label: "Lisbon", + selectedSeriesId: "series-3", + }, + { + id: "series-4", + label: "Austin", + selectedSeriesId: "series-4", + }, + ] as ITimeseriesItemConfiguration[], + }, + } as Partial, + }, + properties: { + // Setting the general chart configuration + configuration: { + legendPlacement: LegendPlacement.Right, + enableZoom: true, + // Setting the preset to stacked bar + preset: TimeseriesChartPreset.StackedBar, + scales: { + x: { + type: TimeseriesScaleType.TimeInterval, + properties: { + interval: 24 * 60 * 60, + }, + } as ITimeseriesScaleConfig, + }, + } as ITimeseriesWidgetConfig, + }, + }, + timeframeSelection: { + properties: { + // Setting the initial timeframe selected in the timeframe bar + timeframe: { + selectedPresetId: "last7Days", + } as ISerializableTimeframe, + minDate: moment().subtract(60, "days").format(), + maxDate: moment().format(), + }, + }, + }, + }, + }, +]; + +// using startOf("day") so that each band for the bar chart corresponds to a calendar day +const startOfToday = moment().startOf("day").toDate(); + +export const getData = (): ITimeseriesWidgetData[] => [ + { + id: "series-1", + name: "Nur-Sultan", + description: "'link' only", + link: "https://en.wikipedia.org/wiki/Nur-Sultan", + data: [ + { x: moment(startOfToday).subtract(59, "day").toDate(), y: 35 }, + { x: moment(startOfToday).subtract(58, "day").toDate(), y: 33 }, + { x: moment(startOfToday).subtract(57, "day").toDate(), y: 40 }, + { x: moment(startOfToday).subtract(56, "day").toDate(), y: 35 }, + { x: moment(startOfToday).subtract(55, "day").toDate(), y: 30 }, + { x: moment(startOfToday).subtract(54, "day").toDate(), y: 35 }, + { x: moment(startOfToday).subtract(53, "day").toDate(), y: 15 }, + { x: moment(startOfToday).subtract(52, "day").toDate(), y: 30 }, + { x: moment(startOfToday).subtract(51, "day").toDate(), y: 35 }, + { x: moment(startOfToday).subtract(50, "day").toDate(), y: 34 }, + { x: moment(startOfToday).subtract(49, "day").toDate(), y: 35 }, + { x: moment(startOfToday).subtract(48, "day").toDate(), y: 33 }, + { x: moment(startOfToday).subtract(47, "day").toDate(), y: 40 }, + { x: moment(startOfToday).subtract(46, "day").toDate(), y: 35 }, + { x: moment(startOfToday).subtract(45, "day").toDate(), y: 30 }, + { x: moment(startOfToday).subtract(44, "day").toDate(), y: 35 }, + { x: moment(startOfToday).subtract(43, "day").toDate(), y: 15 }, + { x: moment(startOfToday).subtract(42, "day").toDate(), y: 30 }, + { x: moment(startOfToday).subtract(41, "day").toDate(), y: 35 }, + { x: moment(startOfToday).subtract(40, "day").toDate(), y: 34 }, + { x: moment(startOfToday).subtract(39, "day").toDate(), y: 35 }, + { x: moment(startOfToday).subtract(38, "day").toDate(), y: 33 }, + { x: moment(startOfToday).subtract(37, "day").toDate(), y: 40 }, + { x: moment(startOfToday).subtract(36, "day").toDate(), y: 35 }, + { x: moment(startOfToday).subtract(35, "day").toDate(), y: 30 }, + { x: moment(startOfToday).subtract(34, "day").toDate(), y: 35 }, + { x: moment(startOfToday).subtract(33, "day").toDate(), y: 15 }, + { x: moment(startOfToday).subtract(32, "day").toDate(), y: 30 }, + { x: moment(startOfToday).subtract(31, "day").toDate(), y: 35 }, + { x: moment(startOfToday).subtract(30, "day").toDate(), y: 34 }, + { x: moment(startOfToday).subtract(29, "day").toDate(), y: 35 }, + { x: moment(startOfToday).subtract(28, "day").toDate(), y: 33 }, + { x: moment(startOfToday).subtract(27, "day").toDate(), y: 40 }, + { x: moment(startOfToday).subtract(26, "day").toDate(), y: 35 }, + { x: moment(startOfToday).subtract(25, "day").toDate(), y: 30 }, + { x: moment(startOfToday).subtract(24, "day").toDate(), y: 35 }, + { x: moment(startOfToday).subtract(23, "day").toDate(), y: 15 }, + { x: moment(startOfToday).subtract(22, "day").toDate(), y: 30 }, + { x: moment(startOfToday).subtract(21, "day").toDate(), y: 35 }, + { x: moment(startOfToday).subtract(20, "day").toDate(), y: 34 }, + { x: moment(startOfToday).subtract(19, "day").toDate(), y: 35 }, + { x: moment(startOfToday).subtract(18, "day").toDate(), y: 33 }, + { x: moment(startOfToday).subtract(17, "day").toDate(), y: 40 }, + { x: moment(startOfToday).subtract(16, "day").toDate(), y: 35 }, + { x: moment(startOfToday).subtract(15, "day").toDate(), y: 30 }, + { x: moment(startOfToday).subtract(14, "day").toDate(), y: 35 }, + { x: moment(startOfToday).subtract(13, "day").toDate(), y: 15 }, + { x: moment(startOfToday).subtract(12, "day").toDate(), y: 30 }, + { x: moment(startOfToday).subtract(11, "day").toDate(), y: 35 }, + { x: moment(startOfToday).subtract(10, "day").toDate(), y: 34 }, + { x: moment(startOfToday).subtract(9, "day").toDate(), y: 33 }, + { x: moment(startOfToday).subtract(8, "day").toDate(), y: 35 }, + { x: moment(startOfToday).subtract(7, "day").toDate(), y: 36 }, + { x: moment(startOfToday).subtract(6, "day").toDate(), y: 34 }, + { x: moment(startOfToday).subtract(5, "day").toDate(), y: 33 }, + { x: moment(startOfToday).subtract(4, "day").toDate(), y: 30 }, + { x: moment(startOfToday).subtract(3, "day").toDate(), y: 32 }, + { x: moment(startOfToday).subtract(2, "day").toDate(), y: 31 }, + { x: moment(startOfToday).subtract(1, "day").toDate(), y: 34 }, + { x: moment(startOfToday).toDate(), y: 25 }, + ], + }, + { + id: "series-2", + name: "Brno", + description: "'link' and 'secondaryLink'", + link: "https://en.wikipedia.org/wiki/Brno", + secondaryLink: "https://en.wikipedia.org/wiki/Europe", + data: [ + { x: moment(startOfToday).subtract(59, "day").toDate(), y: 35 }, + { x: moment(startOfToday).subtract(58, "day").toDate(), y: 33 }, + { x: moment(startOfToday).subtract(57, "day").toDate(), y: 40 }, + { x: moment(startOfToday).subtract(56, "day").toDate(), y: 35 }, + { x: moment(startOfToday).subtract(55, "day").toDate(), y: 30 }, + { x: moment(startOfToday).subtract(54, "day").toDate(), y: 35 }, + { x: moment(startOfToday).subtract(53, "day").toDate(), y: 15 }, + { x: moment(startOfToday).subtract(52, "day").toDate(), y: 30 }, + { x: moment(startOfToday).subtract(51, "day").toDate(), y: 35 }, + { x: moment(startOfToday).subtract(50, "day").toDate(), y: 34 }, + { x: moment(startOfToday).subtract(49, "day").toDate(), y: 35 }, + { x: moment(startOfToday).subtract(48, "day").toDate(), y: 33 }, + { x: moment(startOfToday).subtract(47, "day").toDate(), y: 40 }, + { x: moment(startOfToday).subtract(46, "day").toDate(), y: 35 }, + { x: moment(startOfToday).subtract(45, "day").toDate(), y: 30 }, + { x: moment(startOfToday).subtract(44, "day").toDate(), y: 35 }, + { x: moment(startOfToday).subtract(43, "day").toDate(), y: 15 }, + { x: moment(startOfToday).subtract(42, "day").toDate(), y: 30 }, + { x: moment(startOfToday).subtract(41, "day").toDate(), y: 35 }, + { x: moment(startOfToday).subtract(40, "day").toDate(), y: 34 }, + { x: moment(startOfToday).subtract(39, "day").toDate(), y: 35 }, + { x: moment(startOfToday).subtract(38, "day").toDate(), y: 33 }, + { x: moment(startOfToday).subtract(37, "day").toDate(), y: 40 }, + { x: moment(startOfToday).subtract(36, "day").toDate(), y: 35 }, + { x: moment(startOfToday).subtract(35, "day").toDate(), y: 30 }, + { x: moment(startOfToday).subtract(34, "day").toDate(), y: 35 }, + { x: moment(startOfToday).subtract(33, "day").toDate(), y: 15 }, + { x: moment(startOfToday).subtract(32, "day").toDate(), y: 30 }, + { x: moment(startOfToday).subtract(31, "day").toDate(), y: 35 }, + { x: moment(startOfToday).subtract(30, "day").toDate(), y: 34 }, + { x: moment(startOfToday).subtract(29, "day").toDate(), y: 35 }, + { x: moment(startOfToday).subtract(28, "day").toDate(), y: 33 }, + { x: moment(startOfToday).subtract(27, "day").toDate(), y: 40 }, + { x: moment(startOfToday).subtract(26, "day").toDate(), y: 35 }, + { x: moment(startOfToday).subtract(25, "day").toDate(), y: 30 }, + { x: moment(startOfToday).subtract(24, "day").toDate(), y: 35 }, + { x: moment(startOfToday).subtract(23, "day").toDate(), y: 15 }, + { x: moment(startOfToday).subtract(22, "day").toDate(), y: 30 }, + { x: moment(startOfToday).subtract(21, "day").toDate(), y: 35 }, + { x: moment(startOfToday).subtract(20, "day").toDate(), y: 34 }, + { x: moment(startOfToday).subtract(19, "day").toDate(), y: 64 }, + { x: moment(startOfToday).subtract(18, "day").toDate(), y: 70 }, + { x: moment(startOfToday).subtract(17, "day").toDate(), y: 55 }, + { x: moment(startOfToday).subtract(16, "day").toDate(), y: 55 }, + { x: moment(startOfToday).subtract(15, "day").toDate(), y: 45 }, + { x: moment(startOfToday).subtract(14, "day").toDate(), y: 10 }, + { x: moment(startOfToday).subtract(13, "day").toDate(), y: 65 }, + { x: moment(startOfToday).subtract(12, "day").toDate(), y: 35 }, + { x: moment(startOfToday).subtract(11, "day").toDate(), y: 60 }, + { x: moment(startOfToday).subtract(10, "day").toDate(), y: 61 }, + { x: moment(startOfToday).subtract(9, "day").toDate(), y: 65 }, + { x: moment(startOfToday).subtract(8, "day").toDate(), y: 63 }, + { x: moment(startOfToday).subtract(7, "day").toDate(), y: 58 }, + { x: moment(startOfToday).subtract(6, "day").toDate(), y: 64 }, + { x: moment(startOfToday).subtract(5, "day").toDate(), y: 63 }, + { x: moment(startOfToday).subtract(4, "day").toDate(), y: 60 }, + { x: moment(startOfToday).subtract(3, "day").toDate(), y: 62 }, + { x: moment(startOfToday).subtract(2, "day").toDate(), y: 61 }, + { x: moment(startOfToday).subtract(1, "day").toDate(), y: 62 }, + { x: moment(startOfToday).toDate(), y: 55 }, + ], + }, + { + id: "series-3", + name: "Lisbon", + description: "'secondaryLink' only", + secondaryLink: "https://en.wikipedia.org/wiki/Lisbon", + data: [ + { x: moment(startOfToday).subtract(59, "day").toDate(), y: 35 }, + { x: moment(startOfToday).subtract(58, "day").toDate(), y: 33 }, + { x: moment(startOfToday).subtract(57, "day").toDate(), y: 40 }, + { x: moment(startOfToday).subtract(56, "day").toDate(), y: 35 }, + { x: moment(startOfToday).subtract(55, "day").toDate(), y: 30 }, + { x: moment(startOfToday).subtract(54, "day").toDate(), y: 35 }, + { x: moment(startOfToday).subtract(53, "day").toDate(), y: 15 }, + { x: moment(startOfToday).subtract(52, "day").toDate(), y: 30 }, + { x: moment(startOfToday).subtract(51, "day").toDate(), y: 35 }, + { x: moment(startOfToday).subtract(50, "day").toDate(), y: 34 }, + { x: moment(startOfToday).subtract(49, "day").toDate(), y: 35 }, + { x: moment(startOfToday).subtract(48, "day").toDate(), y: 33 }, + { x: moment(startOfToday).subtract(47, "day").toDate(), y: 40 }, + { x: moment(startOfToday).subtract(46, "day").toDate(), y: 35 }, + { x: moment(startOfToday).subtract(45, "day").toDate(), y: 30 }, + { x: moment(startOfToday).subtract(44, "day").toDate(), y: 35 }, + { x: moment(startOfToday).subtract(43, "day").toDate(), y: 15 }, + { x: moment(startOfToday).subtract(42, "day").toDate(), y: 30 }, + { x: moment(startOfToday).subtract(41, "day").toDate(), y: 35 }, + { x: moment(startOfToday).subtract(40, "day").toDate(), y: 34 }, + { x: moment(startOfToday).subtract(39, "day").toDate(), y: 35 }, + { x: moment(startOfToday).subtract(38, "day").toDate(), y: 33 }, + { x: moment(startOfToday).subtract(37, "day").toDate(), y: 40 }, + { x: moment(startOfToday).subtract(36, "day").toDate(), y: 35 }, + { x: moment(startOfToday).subtract(35, "day").toDate(), y: 30 }, + { x: moment(startOfToday).subtract(34, "day").toDate(), y: 35 }, + { x: moment(startOfToday).subtract(33, "day").toDate(), y: 15 }, + { x: moment(startOfToday).subtract(32, "day").toDate(), y: 30 }, + { x: moment(startOfToday).subtract(31, "day").toDate(), y: 35 }, + { x: moment(startOfToday).subtract(30, "day").toDate(), y: 34 }, + { x: moment(startOfToday).subtract(29, "day").toDate(), y: 35 }, + { x: moment(startOfToday).subtract(28, "day").toDate(), y: 33 }, + { x: moment(startOfToday).subtract(27, "day").toDate(), y: 40 }, + { x: moment(startOfToday).subtract(26, "day").toDate(), y: 35 }, + { x: moment(startOfToday).subtract(25, "day").toDate(), y: 30 }, + { x: moment(startOfToday).subtract(24, "day").toDate(), y: 35 }, + { x: moment(startOfToday).subtract(23, "day").toDate(), y: 15 }, + { x: moment(startOfToday).subtract(22, "day").toDate(), y: 30 }, + { x: moment(startOfToday).subtract(21, "day").toDate(), y: 35 }, + { x: moment(startOfToday).subtract(20, "day").toDate(), y: 34 }, + { x: moment(startOfToday).subtract(19, "day").toDate(), y: 80 }, + { x: moment(startOfToday).subtract(18, "day").toDate(), y: 70 }, + { x: moment(startOfToday).subtract(17, "day").toDate(), y: 95 }, + { x: moment(startOfToday).subtract(16, "day").toDate(), y: 90 }, + { x: moment(startOfToday).subtract(15, "day").toDate(), y: 85 }, + { x: moment(startOfToday).subtract(14, "day").toDate(), y: 70 }, + { x: moment(startOfToday).subtract(13, "day").toDate(), y: 75 }, + { x: moment(startOfToday).subtract(12, "day").toDate(), y: 69 }, + { x: moment(startOfToday).subtract(11, "day").toDate(), y: 75 }, + { x: moment(startOfToday).subtract(10, "day").toDate(), y: 81 }, + { x: moment(startOfToday).subtract(9, "day").toDate(), y: 93 }, + { x: moment(startOfToday).subtract(8, "day").toDate(), y: 83 }, + { x: moment(startOfToday).subtract(7, "day").toDate(), y: 70 }, + { x: moment(startOfToday).subtract(6, "day").toDate(), y: 74 }, + { x: moment(startOfToday).subtract(5, "day").toDate(), y: 73 }, + { x: moment(startOfToday).subtract(4, "day").toDate(), y: 68 }, + { x: moment(startOfToday).subtract(3, "day").toDate(), y: 72 }, + { x: moment(startOfToday).subtract(2, "day").toDate(), y: 61 }, + { x: moment(startOfToday).subtract(1, "day").toDate(), y: 69 }, + { x: moment(startOfToday).toDate(), y: 60 }, + ], + }, + { + id: "series-4", + name: "Austin", + description: "No links", + data: [ + { x: moment(startOfToday).subtract(59, "day").toDate(), y: 25 }, + { x: moment(startOfToday).subtract(58, "day").toDate(), y: 43 }, + { x: moment(startOfToday).subtract(57, "day").toDate(), y: 40 }, + { x: moment(startOfToday).subtract(56, "day").toDate(), y: 65 }, + { x: moment(startOfToday).subtract(55, "day").toDate(), y: 30 }, + { x: moment(startOfToday).subtract(54, "day").toDate(), y: 25 }, + { x: moment(startOfToday).subtract(53, "day").toDate(), y: 45 }, + { x: moment(startOfToday).subtract(52, "day").toDate(), y: 30 }, + { x: moment(startOfToday).subtract(51, "day").toDate(), y: 85 }, + { x: moment(startOfToday).subtract(50, "day").toDate(), y: 74 }, + { x: moment(startOfToday).subtract(49, "day").toDate(), y: 55 }, + { x: moment(startOfToday).subtract(48, "day").toDate(), y: 23 }, + { x: moment(startOfToday).subtract(47, "day").toDate(), y: 40 }, + { x: moment(startOfToday).subtract(46, "day").toDate(), y: 15 }, + { x: moment(startOfToday).subtract(45, "day").toDate(), y: 20 }, + { x: moment(startOfToday).subtract(44, "day").toDate(), y: 65 }, + { x: moment(startOfToday).subtract(43, "day").toDate(), y: 25 }, + { x: moment(startOfToday).subtract(42, "day").toDate(), y: 40 }, + { x: moment(startOfToday).subtract(41, "day").toDate(), y: 25 }, + { x: moment(startOfToday).subtract(40, "day").toDate(), y: 54 }, + { x: moment(startOfToday).subtract(39, "day").toDate(), y: 65 }, + { x: moment(startOfToday).subtract(38, "day").toDate(), y: 33 }, + { x: moment(startOfToday).subtract(37, "day").toDate(), y: 50 }, + { x: moment(startOfToday).subtract(36, "day").toDate(), y: 45 }, + { x: moment(startOfToday).subtract(35, "day").toDate(), y: 20 }, + { x: moment(startOfToday).subtract(34, "day").toDate(), y: 25 }, + { x: moment(startOfToday).subtract(33, "day").toDate(), y: 35 }, + { x: moment(startOfToday).subtract(32, "day").toDate(), y: 20 }, + { x: moment(startOfToday).subtract(31, "day").toDate(), y: 35 }, + { x: moment(startOfToday).subtract(30, "day").toDate(), y: 14 }, + { x: moment(startOfToday).subtract(29, "day").toDate(), y: 55 }, + { x: moment(startOfToday).subtract(28, "day").toDate(), y: 23 }, + { x: moment(startOfToday).subtract(27, "day").toDate(), y: 10 }, + { x: moment(startOfToday).subtract(26, "day").toDate(), y: 5 }, + { x: moment(startOfToday).subtract(25, "day").toDate(), y: 20 }, + { x: moment(startOfToday).subtract(24, "day").toDate(), y: 35 }, + { x: moment(startOfToday).subtract(23, "day").toDate(), y: 15 }, + { x: moment(startOfToday).subtract(22, "day").toDate(), y: 30 }, + { x: moment(startOfToday).subtract(21, "day").toDate(), y: 35 }, + { x: moment(startOfToday).subtract(20, "day").toDate(), y: 34 }, + { x: moment(startOfToday).subtract(19, "day").toDate(), y: 50 }, + { x: moment(startOfToday).subtract(18, "day").toDate(), y: 60 }, + { x: moment(startOfToday).subtract(17, "day").toDate(), y: 95 }, + { x: moment(startOfToday).subtract(16, "day").toDate(), y: 80 }, + { x: moment(startOfToday).subtract(15, "day").toDate(), y: 65 }, + { x: moment(startOfToday).subtract(14, "day").toDate(), y: 80 }, + { x: moment(startOfToday).subtract(13, "day").toDate(), y: 85 }, + { x: moment(startOfToday).subtract(12, "day").toDate(), y: 69 }, + { x: moment(startOfToday).subtract(11, "day").toDate(), y: 65 }, + { x: moment(startOfToday).subtract(10, "day").toDate(), y: 71 }, + { x: moment(startOfToday).subtract(9, "day").toDate(), y: 73 }, + { x: moment(startOfToday).subtract(8, "day").toDate(), y: 43 }, + { x: moment(startOfToday).subtract(7, "day").toDate(), y: 70 }, + { x: moment(startOfToday).subtract(6, "day").toDate(), y: 84 }, + { x: moment(startOfToday).subtract(5, "day").toDate(), y: 73 }, + { x: moment(startOfToday).subtract(4, "day").toDate(), y: 38 }, + { x: moment(startOfToday).subtract(3, "day").toDate(), y: 72 }, + { x: moment(startOfToday).subtract(2, "day").toDate(), y: 81 }, + { x: moment(startOfToday).subtract(1, "day").toDate(), y: 59 }, + { x: moment(startOfToday).toDate(), y: 60 }, + ], + }, +]; +// Setting the widget dimensions and position (this is for gridster) +const positions: Record = { + [widgetConfigs[0].id]: { + cols: 6, + rows: 6, + y: 0, + x: 0, + }, + [widgetConfigs[1].id]: { + cols: 6, + rows: 6, + y: 0, + x: 6, + }, +}; +\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\`, + "widget-types/timeseries/timeseries-widget-status-bar-example/timeseries-widget-status-bar-example.component.ts": \\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\`// © 2022 SolarWinds Worldwide, LLC. All rights reserved. +// +// Permission is hereby granted, free of charge, to any person obtaining a copy +// of this software and associated documentation files (the "Software"), to +// deal in the Software without restriction, including without limitation the +// rights to use, copy, modify, merge, publish, distribute, sublicense, and/or +// sell copies of the Software, and to permit persons to whom the Software is +// furnished to do so, subject to the following conditions: +// +// The above copyright notice and this permission notice shall be included in +// all copies or substantial portions of the Software. +// +// THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR +// IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, +// FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE +// AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER +// LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, +// OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN +// THE SOFTWARE. + +import { + ChangeDetectorRef, + Component, + Injectable, + OnInit, +} from "@angular/core"; +import { GridsterConfig, GridsterItem } from "angular-gridster2"; +import keyBy from "lodash/keyBy"; +import moment, { Moment } from "moment/moment"; +import { BehaviorSubject } from "rxjs"; + +import { + DataSourceService, + IDataSource, + IDataSourceOutput, + INovaFilters, + ITimeframe, +} from "@nova-ui/bits"; +import { CHART_PALETTE_CS_S_EXTENDED } from "@nova-ui/charts"; +import { + applyStatusEndpoints, + DATA_SOURCE, + DEFAULT_PIZZAGNA_ROOT, + IDashboard, + IProviderConfiguration, + ISerializableTimeframe, + ITimeseriesItemConfiguration, + ITimeseriesOutput, + ITimeseriesScaleConfig, + ITimeseriesWidgetConfig, + ITimeseriesWidgetData, + ITimeseriesWidgetSeriesData, + ITimeseriesWidgetStatusData, + IWidget, + LegendPlacement, + PizzagnaLayer, + ProviderRegistryService, + TimeseriesChartPreset, + TimeseriesScaleType, + WellKnownPathKey, + WellKnownProviders, + WidgetTypesService, +} from "@nova-ui/dashboards"; + +/** + * A simple Timeseries data source implementation with continuous (non-interval-based) output + */ +@Injectable() +export class TimeseriesStatusContinuousDataSource + extends DataSourceService + implements IDataSource> +{ + public static providerId = "TimeseriesStatusContinuousDataSource"; + + public busy = new BehaviorSubject(false); + + public async getFilteredData( + filters: INovaFilters + ): Promise< + IDataSourceOutput> + > { + // In this example we're using some static mock data located at the bottom of this file. In a real-world + // scenario, the data for the chart would likely be retrieved via an asynchronous backend call. + const data = getContinuousData(); + let filteredData = data; + + this.busy.next(true); + + // Filtering using the filter registered by the TimeFrameBar + const timeframeFilter = filters.timeframe?.value as ITimeframe; + if (timeframeFilter) { + filteredData = filteredData.map((item: ITimeseriesWidgetData) => ({ + id: item.id, + name: item.name, + description: item.description, + data: item.data.filter( + (seriesData: ITimeseriesWidgetSeriesData) => + filterDates( + seriesData.x, + timeframeFilter.startDatetime, + timeframeFilter.endDatetime + ) + ), + })); + + // apply endpoints on the filtered status data so that when the status chart is zoomed (filtered), + // each status visualizations is ensured to have valid start and end values + filteredData = applyStatusEndpoints( + timeframeFilter, + filteredData, + data + ); + } + + this.busy.next(false); + return { result: { series: filteredData } }; + } +} + +/** + * A simple Timeseries data source implementation with interval-based output + */ +@Injectable() +export class TimeseriesStatusIntervalDataSource + extends DataSourceService + implements IDataSource> +{ + public static providerId = "TimeseriesStatusIntervalDataSource"; + + public busy = new BehaviorSubject(false); + + public async getFilteredData( + filters: INovaFilters + ): Promise< + IDataSourceOutput> + > { + // In this example we're using some static mock data located at the bottom of this file. In a real-world + // scenario, the data for the chart would likely be retrieved via an asynchronous backend call. + const data = getIntervalData(); + let filteredData = data; + + this.busy.next(true); + + // Filtering using the filter registered by the TimeFrameBar + const timeframeFilter = filters.timeframe?.value as ITimeframe; + if (timeframeFilter) { + filteredData = filteredData.map((item: ITimeseriesWidgetData) => ({ + id: item.id, + name: item.name, + description: item.description, + data: item.data.filter( + (seriesData: ITimeseriesWidgetSeriesData) => + filterDates( + seriesData.x, + timeframeFilter.startDatetime, + timeframeFilter.endDatetime + ) + ), + })); + + // Note: There's no need to apply filter endpoints to the status data in this case since we know it's visualized in regular intervals + } + + this.busy.next(false); + return { result: { series: filteredData } }; + } +} + +function filterDates(dateToCheck: Date, startDate: Moment, endDate: Moment) { + const mom = moment(dateToCheck); + return ( + mom.isBetween(startDate, endDate) || + mom.isSame(startDate) || + mom.isSame(endDate) + ); +} + +/** + * A component that instantiates the dashboard + */ +@Component({ + selector: "timeseries-widget-status-bar-example", + templateUrl: "./timeseries-widget-status-bar-example.component.html", + styleUrls: ["./timeseries-widget-status-bar-example.component.less"], + standalone: false, +}) +export class TimeseriesWidgetStatusBarExampleComponent implements OnInit { + // This variable will hold all the data needed to define the layout and behavior of the widgets. + // Pass this to the dashboard component's dashboard input in the template. + public dashboard: IDashboard | undefined; + + // Angular gridster requires a configuration object even if it's empty. + // Pass this to the dashboard component's gridsterConfig input in the template. + public gridsterConfig: GridsterConfig = {}; + + // Boolean passed as an input to the dashboard. When true, widgets can be moved, resized, removed, or edited + public editMode: boolean = false; + + constructor( + // WidgetTypesService provides the widget's necessary structure information + private widgetTypesService: WidgetTypesService, + + // In general, the ProviderRegistryService is used for making entities available for injection into dynamically loaded components. + private providerRegistry: ProviderRegistryService, + private changeDetectorRef: ChangeDetectorRef + ) {} + + public ngOnInit(): void { + // Grabbing the widget's default template which will be needed as a parameter for setNode + const widgetTemplate = this.widgetTypesService.getWidgetType( + "timeseries", + 1 + ); + // Registering our data sources as dropdown options in the widget editor/configurator + // Note: This could also be done in the parent module's constructor so that + // multiple dashboards could have access to the same widget template modification. + this.widgetTypesService.setNode( + // This is the template we grabbed above with getWidgetType + widgetTemplate, + // We are setting the editor/configurator part of the widget template + "configurator", + // This indicates which node you are changing and we want to change + // the data source providers available for selection in the editor. + WellKnownPathKey.DataSourceProviders, + // We are setting the data sources available for selection in the editor + [ + TimeseriesStatusContinuousDataSource.providerId, + TimeseriesStatusIntervalDataSource.providerId, + ] + ); + + // Registering the data source for injection into the widget. + this.providerRegistry.setProviders({ + [TimeseriesStatusContinuousDataSource.providerId]: { + provide: DATA_SOURCE, + useClass: TimeseriesStatusContinuousDataSource, + deps: [], + }, + [TimeseriesStatusIntervalDataSource.providerId]: { + provide: DATA_SOURCE, + useClass: TimeseriesStatusIntervalDataSource, + deps: [], + }, + }); + + this.initializeDashboard(); + } + + /** Used for restoring widgets state */ + public reInitializeDashboard(): void { + // destroys the components and their providers so the dashboard can re init data + this.dashboard = undefined; + this.changeDetectorRef.detectChanges(); + + this.initializeDashboard(); + } + + public initializeDashboard(): void { + // We're using a static configuration object for this example, but this is where + // the widget's configuration could potentially be populated from a database + const widgetsWithStructure = widgetConfigs.map((w) => + this.widgetTypesService.mergeWithWidgetType(w) + ); + const widgetsIndex = keyBy(widgetsWithStructure, (w: IWidget) => w.id); + + // Finally, assigning the variables we created above to the dashboard + this.dashboard = { + positions, + widgets: widgetsIndex, + }; + } +} + +const widgetConfigs: IWidget[] = [ + { + id: "statusChartWidgetId", + type: "timeseries", + pizzagna: { + [PizzagnaLayer.Configuration]: { + [DEFAULT_PIZZAGNA_ROOT]: { + providers: { + [WellKnownProviders.DataSource]: { + // Setting the initially selected data source providerId + providerId: + TimeseriesStatusContinuousDataSource.providerId, + } as IProviderConfiguration, + }, + }, + header: { + properties: { + title: "Status Bar Chart with Continuous (Non-Interval) Scale", + subtitle: "Basic Timeseries Widget", + }, + }, + chart: { + providers: { + [WellKnownProviders.Adapter]: { + properties: { + // Setting the series and corresponding labels to initially display on the chart + series: [ + { + id: "series-1", + label: "Node Status", + selectedSeriesId: "series-1", + }, + { + id: "series-2", + label: "Node Status", + selectedSeriesId: "series-2", + }, + ] as ITimeseriesItemConfiguration[], + }, + } as Partial, + }, + properties: { + configuration: { + legendPlacement: LegendPlacement.Right, + enableZoom: true, + // Setting the preset to status bar + preset: TimeseriesChartPreset.StatusBar, + } as ITimeseriesWidgetConfig, + }, + }, + timeframeSelection: { + properties: { + // Setting the initial timeframe selected in the timeframe bar + timeframe: { + selectedPresetId: "last7Days", + } as ISerializableTimeframe, + maxDate: moment().format(), + }, + }, + }, + }, + }, + { + id: "statusIntervalChartWidgetId", + type: "timeseries", + pizzagna: { + [PizzagnaLayer.Configuration]: { + [DEFAULT_PIZZAGNA_ROOT]: { + providers: { + [WellKnownProviders.DataSource]: { + // Setting the initially selected data source providerId + providerId: + TimeseriesStatusIntervalDataSource.providerId, + } as IProviderConfiguration, + }, + }, + header: { + properties: { + title: "Status Bar Chart with Interval Scale", + subtitle: "Basic Timeseries Widget", + }, + }, + chart: { + providers: { + [WellKnownProviders.Adapter]: { + properties: { + // Setting the series and corresponding labels to initially display on the chart + series: [ + { + id: "series-1", + label: "Node Status", + selectedSeriesId: "series-1", + }, + { + id: "series-2", + label: "Node Status", + selectedSeriesId: "series-2", + }, + ] as ITimeseriesItemConfiguration[], + }, + } as Partial, + }, + properties: { + configuration: { + legendPlacement: LegendPlacement.Right, + enableZoom: true, + // Setting the preset to status bar + preset: TimeseriesChartPreset.StatusBar, + scales: { + x: { + type: TimeseriesScaleType.TimeInterval, + properties: { + // one-day interval in seconds + interval: 24 * 60 * 60, + }, + } as ITimeseriesScaleConfig, + }, + } as ITimeseriesWidgetConfig, + }, + }, + timeframeSelection: { + properties: { + // Setting the initial timeframe selected in the timeframe bar + timeframe: { + selectedPresetId: "last7Days", + } as ISerializableTimeframe, + maxDate: moment().format(), + }, + }, + }, + }, + }, +]; + +export const startOfToday = (): Moment => moment().startOf("day"); + +export const getContinuousData = + (): ITimeseriesWidgetData[] => { + const series: ITimeseriesWidgetData[] = [ + { + id: "series-1", + name: "Node Status", + description: "lastchance.demo.lab", + data: [ + // the 'x' value is set to the time and 'y' to the status at that given time + { + x: startOfToday().subtract(20, "day").toDate(), + y: Status.Warning, + }, + { + x: startOfToday().subtract(19, "day").toDate(), + y: Status.Down, + }, + { + x: startOfToday().subtract(17, "day").toDate(), + y: Status.Critical, + }, + { + x: startOfToday().subtract(16, "day").toDate(), + y: Status.Warning, + }, + { + x: startOfToday().subtract(15, "day").toDate(), + y: Status.Down, + }, + { + x: startOfToday().subtract(14, "day").toDate(), + y: Status.Critical, + }, + { + x: startOfToday().subtract(12, "day").toDate(), + y: Status.Unknown, + }, + { + x: startOfToday().subtract(10, "day").toDate(), + y: Status.Up, + }, + { + x: startOfToday().subtract(9, "day").toDate(), + y: Status.Critical, + }, + { + x: startOfToday().subtract(6, "day").toDate(), + y: Status.Up, + }, + { + x: startOfToday().subtract(3, "day").toDate(), + y: Status.Warning, + }, + { + x: startOfToday().subtract(2, "day").toDate(), + y: Status.Critical, + }, + { + x: startOfToday().subtract(1, "day").toDate(), + y: Status.Up, + }, + // This data point will be ignored and is only here to provide an endpoint for the previous status. + { x: moment().toDate(), y: Status.Up }, + ], + }, + { + id: "series-2", + name: "Node Status", + description: "newhope.demo.lab", + data: [ + { + x: startOfToday().subtract(19, "day").toDate(), + y: Status.Critical, + }, + { + x: startOfToday().subtract(18, "day").toDate(), + y: Status.Unknown, + }, + { + x: startOfToday().subtract(17, "day").toDate(), + y: Status.Warning, + }, + { + x: startOfToday().subtract(15, "day").toDate(), + y: Status.Down, + }, + { + x: startOfToday().subtract(8, "day").toDate(), + y: Status.Critical, + }, + { + x: startOfToday().subtract(7, "day").toDate(), + y: Status.Down, + }, + { + x: startOfToday().subtract(6, "day").toDate(), + y: Status.Up, + }, + { + x: startOfToday().subtract(5, "day").toDate(), + y: Status.Critical, + }, + { + x: startOfToday().subtract(4, "day").toDate(), + y: Status.Up, + }, + { + x: startOfToday().subtract(3, "day").toDate(), + y: Status.Warning, + }, + { + x: startOfToday().subtract(2, "day").toDate(), + y: Status.Up, + }, + { + x: startOfToday().subtract(1, "day").toDate(), + y: Status.Down, + }, + // This data point will be ignored and is only here to provide an endpoint for the previous status. + { x: moment().toDate(), y: Status.Down }, + ], + }, + ]; + + for (const s of series) { + // here are we setting the color and icon associated to the status for each data point + s.data = s.data.map((d: any, i: number) => ({ + ...d, + color: statusColors[d.y as Status], + // The thickness of the line is dependant on the status. If the status equals 'Up' then 'thick' is set to false. + thick: d.y !== Status.Up, + icon: "status_" + d.y, + })); + } + + return series; + }; + +// Note that the output of this function is spaced evenly at one-day intervals +export const getIntervalData = + (): ITimeseriesWidgetData[] => { + const series: ITimeseriesWidgetData[] = [ + { + id: "series-1", + name: "Node Status", + description: "lastchance.demo.lab", + data: [ + // the 'x' value is set to the time and 'y' to the status at that given time + { + x: startOfToday().subtract(20, "day").toDate(), + y: Status.Up, + }, + { + x: startOfToday().subtract(19, "day").toDate(), + y: Status.Critical, + }, + { + x: startOfToday().subtract(18, "day").toDate(), + y: Status.Warning, + }, + { + x: startOfToday().subtract(17, "day").toDate(), + y: Status.Down, + }, + { + x: startOfToday().subtract(16, "day").toDate(), + y: Status.Critical, + }, + { + x: startOfToday().subtract(15, "day").toDate(), + y: Status.Down, + }, + { + x: startOfToday().subtract(14, "day").toDate(), + y: Status.Up, + }, + { + x: startOfToday().subtract(13, "day").toDate(), + y: Status.Warning, + }, + { + x: startOfToday().subtract(12, "day").toDate(), + y: Status.Up, + }, + { + x: startOfToday().subtract(11, "day").toDate(), + y: Status.Critical, + }, + { + x: startOfToday().subtract(10, "day").toDate(), + y: Status.Up, + }, + { + x: startOfToday().subtract(9, "day").toDate(), + y: Status.Down, + }, + { + x: startOfToday().subtract(8, "day").toDate(), + y: Status.Critical, + }, + { + x: startOfToday().subtract(7, "day").toDate(), + y: Status.Warning, + }, + { + x: startOfToday().subtract(6, "day").toDate(), + y: Status.Down, + }, + { + x: startOfToday().subtract(5, "day").toDate(), + y: Status.Critical, + }, + { + x: startOfToday().subtract(4, "day").toDate(), + y: Status.Down, + }, + { + x: startOfToday().subtract(3, "day").toDate(), + y: Status.Up, + }, + { + x: startOfToday().subtract(2, "day").toDate(), + y: Status.Warning, + }, + { + x: startOfToday().subtract(1, "day").toDate(), + y: Status.Critical, + }, + { x: startOfToday().toDate(), y: Status.Up }, + ], + }, + { + id: "series-2", + name: "Node Status", + description: "newhope.demo.lab", + data: [ + { + x: startOfToday().subtract(20, "day").toDate(), + y: Status.Up, + }, + { + x: startOfToday().subtract(19, "day").toDate(), + y: Status.Up, + }, + { + x: startOfToday().subtract(18, "day").toDate(), + y: Status.Down, + }, + { + x: startOfToday().subtract(17, "day").toDate(), + y: Status.Critical, + }, + { + x: startOfToday().subtract(16, "day").toDate(), + y: Status.Down, + }, + { + x: startOfToday().subtract(15, "day").toDate(), + y: Status.Up, + }, + { + x: startOfToday().subtract(14, "day").toDate(), + y: Status.Critical, + }, + { + x: startOfToday().subtract(13, "day").toDate(), + y: Status.Up, + }, + { + x: startOfToday().subtract(12, "day").toDate(), + y: Status.Critical, + }, + { + x: startOfToday().subtract(11, "day").toDate(), + y: Status.Warning, + }, + { + x: startOfToday().subtract(10, "day").toDate(), + y: Status.Up, + }, + { + x: startOfToday().subtract(9, "day").toDate(), + y: Status.Down, + }, + { + x: startOfToday().subtract(8, "day").toDate(), + y: Status.Up, + }, + { + x: startOfToday().subtract(7, "day").toDate(), + y: Status.Down, + }, + { + x: startOfToday().subtract(6, "day").toDate(), + y: Status.Critical, + }, + { + x: startOfToday().subtract(5, "day").toDate(), + y: Status.Down, + }, + { + x: startOfToday().subtract(4, "day").toDate(), + y: Status.Up, + }, + { + x: startOfToday().subtract(3, "day").toDate(), + y: Status.Critical, + }, + { + x: startOfToday().subtract(2, "day").toDate(), + y: Status.Up, + }, + { + x: startOfToday().subtract(1, "day").toDate(), + y: Status.Warning, + }, + { x: startOfToday().toDate(), y: Status.Critical }, + ], + }, + ]; + + for (const s of series) { + // here are we setting the color and icon associated to the status for each data point + s.data = s.data.map((d: any, i: number) => ({ + ...d, + color: statusColors[d.y as Status], + // The thickness of the line is dependant on the status. If the status equals 'Up' then 'thick' is set to false. + thick: d.y !== Status.Up, + icon: "status_" + d.y, + })); + } + + return series; + }; + +// An enumeration of statuses +enum Status { + Unknown = "unknown", + Up = "up", + Warning = "warning", + Down = "down", + Critical = "critical", +} + +// This is the map used for setting the color of each status bar +const statusColors: Record = { + [Status.Unknown]: CHART_PALETTE_CS_S_EXTENDED[6], + [Status.Up]: CHART_PALETTE_CS_S_EXTENDED[8], + [Status.Warning]: CHART_PALETTE_CS_S_EXTENDED[4], + [Status.Down]: CHART_PALETTE_CS_S_EXTENDED[0], + [Status.Critical]: CHART_PALETTE_CS_S_EXTENDED[2], +}; + +// Setting the widget dimensions and position (this is for gridster) +const positions: Record = { + [widgetConfigs[0].id]: { + cols: 12, + rows: 4, + y: 0, + x: 0, + }, + [widgetConfigs[1].id]: { + cols: 12, + rows: 4, + y: 4, + x: 0, + }, +}; +\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\`, + "widget-types/view-components/kpi-tile-view-basic/kpi-tile-view-basic-example.component.ts": \\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\`// © 2022 SolarWinds Worldwide, LLC. All rights reserved. +// +// Permission is hereby granted, free of charge, to any person obtaining a copy +// of this software and associated documentation files (the "Software"), to +// deal in the Software without restriction, including without limitation the +// rights to use, copy, modify, merge, publish, distribute, sublicense, and/or +// sell copies of the Software, and to permit persons to whom the Software is +// furnished to do so, subject to the following conditions: +// +// The above copyright notice and this permission notice shall be included in +// all copies or substantial portions of the Software. +// +// THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR +// IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, +// FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE +// AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER +// LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, +// OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN +// THE SOFTWARE. + +import { Component } from "@angular/core"; + +type KpiTileState = "normal" | "loading" | "empty"; + +/** + * KPI Tile View - Playground example. + * Switch between all visual states (normal / loading / empty) and toggle + * interactivity to explore every variant the standalone tile supports. + */ +@Component({ + selector: "kpi-tile-view-basic-example", + templateUrl: "./kpi-tile-view-basic-example.component.html", + standalone: false, +}) +export class KpiTileViewBasicExampleComponent { + public state: KpiTileState = "normal"; + public interactive = false; + public lastClicked = ""; + + public readonly stateOptions: KpiTileState[] = ["normal", "loading", "empty"]; + + public onTileClick(label: string): void { + this.lastClicked = label; + } +} +\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\`, + "widget-types/view-components/kpi-tile-view-interactive/kpi-tile-view-interactive-example.component.ts": \\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\`// © 2022 SolarWinds Worldwide, LLC. All rights reserved. +// +// Permission is hereby granted, free of charge, to any person obtaining a copy +// of this software and associated documentation files (the "Software"), to +// deal in the Software without restriction, including without limitation the +// rights to use, copy, modify, merge, publish, distribute, sublicense, and/or +// sell copies of the Software, and to permit persons to whom the Software is +// furnished to do so, subject to the following conditions: +// +// The above copyright notice and this permission notice shall be included in +// all copies or substantial portions of the Software. +// +// THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR +// IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, +// FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE +// AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER +// LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, +// OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN +// THE SOFTWARE. + +import { Component, TemplateRef, ViewChild } from "@angular/core"; + +/** + * Interactive KPI Tile View example with custom value formatting + * and click event handling. + */ +@Component({ + selector: "kpi-tile-view-interactive-example", + templateUrl: "./kpi-tile-view-interactive-example.component.html", + standalone: false, +}) +export class KpiTileViewInteractiveExampleComponent { + public currentValue = 1_247; + public lastClickedTile = ""; + + @ViewChild("customValueTpl", { static: true }) + public customValueTpl: TemplateRef; + + public onTileClick(): void { + this.lastClickedTile = "Active Sessions"; + } + + public onUptimeClick(): void { + this.lastClickedTile = "Uptime"; + } +} +\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\`, + "widget-types/view-components/proportional-chart-view-bar/proportional-chart-view-bar-example.component.ts": \\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\`// © 2022 SolarWinds Worldwide, LLC. All rights reserved. +// +// Permission is hereby granted, free of charge, to any person obtaining a copy +// of this software and associated documentation files (the "Software"), to +// deal in the Software without restriction, including without limitation the +// rights to use, copy, modify, merge, publish, distribute, sublicense, and/or +// sell copies of the Software, and to permit persons to whom the Software is +// furnished to do so, subject to the following conditions: +// +// The above copyright notice and this permission notice shall be included in +// all copies or substantial portions of the Software. +// +// THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR +// IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, +// FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE +// AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER +// LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, +// OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN +// THE SOFTWARE. + +import { Component } from "@angular/core"; + +import { IProportionalDataItem } from "@nova-ui/dashboards"; + +/** + * Proportional Chart View - Bar chart with interaction. + * Demonstrates horizontal bar chart variant with click handling. + */ +@Component({ + selector: "proportional-chart-view-bar-example", + templateUrl: "./proportional-chart-view-bar-example.component.html", + standalone: false, +}) +export class ProportionalChartViewBarExampleComponent { + public lastClicked: IProportionalDataItem | null = null; + + public colors: Array = [ + "#0058e9", + "#2cc079", + "#f3a002", + "#dc3545", + "#8a2be2", + ]; + + public chartData: Array = [ + { id: "chrome", name: "Chrome", value: 64 }, + { id: "firefox", name: "Firefox", value: 18 }, + { id: "safari", name: "Safari", value: 10 }, + { id: "edge", name: "Edge", value: 5 }, + { id: "other", name: "Other", value: 3 }, + ]; + + public onItemClick(item: IProportionalDataItem): void { + this.lastClicked = item; + } +} +\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\`, + "widget-types/view-components/proportional-chart-view-donut/proportional-chart-view-donut-example.component.ts": \\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\`// © 2022 SolarWinds Worldwide, LLC. All rights reserved. +// +// Permission is hereby granted, free of charge, to any person obtaining a copy +// of this software and associated documentation files (the "Software"), to +// deal in the Software without restriction, including without limitation the +// rights to use, copy, modify, merge, publish, distribute, sublicense, and/or +// sell copies of the Software, and to permit persons to whom the Software is +// furnished to do so, subject to the following conditions: +// +// The above copyright notice and this permission notice shall be included in +// all copies or substantial portions of the Software. +// +// THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR +// IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, +// FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE +// AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER +// LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, +// OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN +// THE SOFTWARE. + +import { Component } from "@angular/core"; + +import { IProportionalDataItem } from "@nova-ui/dashboards"; + +type ProportionalChartType = "donut" | "pie" | "verticalBar" | "horizontalBar"; +type LegendPlacement = "right" | "bottom" | "none"; + +/** + * Proportional Chart View - Playground example. + * Lets you switch between all supported chart types and legend placements + * to see every visual variant the standalone view component provides. + */ +@Component({ + selector: "proportional-chart-view-donut-example", + templateUrl: "./proportional-chart-view-donut-example.component.html", + standalone: false, +}) +export class ProportionalChartViewDonutExampleComponent { + public chartType: ProportionalChartType = "donut"; + public legendPlacement: LegendPlacement = "right"; + + public readonly chartTypeOptions: ProportionalChartType[] = [ + "donut", + "pie", + "verticalBar", + "horizontalBar", + ]; + public readonly legendPlacementOptions: LegendPlacement[] = [ + "right", + "bottom", + "none", + ]; + + public colors: Record = { + down: "#dc3545", + up: "#2cc079", + warning: "#f3a002", + unknown: "#707070", + }; + + public chartData: Array = [ + { id: "up", name: "Up", value: 78 }, + { id: "down", name: "Down", value: 8 }, + { id: "warning", name: "Warning", value: 12 }, + { id: "unknown", name: "Unknown", value: 2 }, + ]; +} +\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\`, + "widget-types/view-components/view-components-docs.component.ts": \\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\`// © 2022 SolarWinds Worldwide, LLC. All rights reserved. +// +// Permission is hereby granted, free of charge, to any person obtaining a copy +// of this software and associated documentation files (the "Software"), to +// deal in the Software without restriction, including without limitation the +// rights to use, copy, modify, merge, publish, distribute, sublicense, and/or +// sell copies of the Software, and to permit persons to whom the Software is +// furnished to do so, subject to the following conditions: +// +// The above copyright notice and this permission notice shall be included in +// all copies or substantial portions of the Software. +// +// THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR +// IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, +// FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE +// AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER +// LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, +// OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN +// THE SOFTWARE. + +import { Component } from "@angular/core"; + +@Component({ + selector: "nui-view-components-docs", + templateUrl: "./view-components-docs.component.html", + standalone: false, +}) +export class ViewComponentsDocsComponent {} +\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\`, + "widget-types/view-components/view-components-docs.module.ts": \\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\`// © 2022 SolarWinds Worldwide, LLC. All rights reserved. +// +// Permission is hereby granted, free of charge, to any person obtaining a copy +// of this software and associated documentation files (the "Software"), to +// deal in the Software without restriction, including without limitation the +// rights to use, copy, modify, merge, publish, distribute, sublicense, and/or +// sell copies of the Software, and to permit persons to whom the Software is +// furnished to do so, subject to the following conditions: +// +// The above copyright notice and this permission notice shall be included in +// all copies or substantial portions of the Software. +// +// THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR +// IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, +// FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE +// AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER +// LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, +// OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN +// THE SOFTWARE. + +import { CommonModule } from "@angular/common"; +import { NgModule } from "@angular/core"; +import { RouterModule, Routes } from "@angular/router"; + +import { + NuiDocsModule, + NuiIconModule, + NuiMessageModule, + DEMO_PATH_TOKEN, +} from "@nova-ui/bits"; +import { NuiDashboardViewsModule } from "@nova-ui/dashboards"; + +import { getDemoFiles } from "../../../../demo-files-factory"; +import { KpiTileViewBasicExampleComponent } from "./kpi-tile-view-basic/kpi-tile-view-basic-example.component"; +import { KpiTileViewInteractiveExampleComponent } from "./kpi-tile-view-interactive/kpi-tile-view-interactive-example.component"; +import { ProportionalChartViewBarExampleComponent } from "./proportional-chart-view-bar/proportional-chart-view-bar-example.component"; +import { ProportionalChartViewDonutExampleComponent } from "./proportional-chart-view-donut/proportional-chart-view-donut-example.component"; +import { ViewComponentsDocsComponent } from "./view-components-docs.component"; + +const routes: Routes = [ + { + path: "", + component: ViewComponentsDocsComponent, + data: { + srlc: { + hideIndicator: true, + }, + showThemeSwitcher: true, + }, + }, + { + path: "kpi-tile-view-basic", + component: KpiTileViewBasicExampleComponent, + data: { + srlc: { + hideIndicator: true, + }, + }, + }, + { + path: "proportional-chart-view-donut", + component: ProportionalChartViewDonutExampleComponent, + data: { + srlc: { + hideIndicator: true, + }, + }, + }, + { + path: "proportional-chart-view-bar", + component: ProportionalChartViewBarExampleComponent, + data: { + srlc: { + hideIndicator: true, + }, + }, + }, +]; + +@NgModule({ + imports: [ + CommonModule, + RouterModule.forChild(routes), + NuiDocsModule, + NuiMessageModule, + NuiIconModule, + NuiDashboardViewsModule, + ], + declarations: [ + ViewComponentsDocsComponent, + KpiTileViewBasicExampleComponent, + KpiTileViewInteractiveExampleComponent, + ProportionalChartViewDonutExampleComponent, + ProportionalChartViewBarExampleComponent, + ], + providers: [ + { + provide: DEMO_PATH_TOKEN, + useValue: getDemoFiles("view-components"), + }, + ], +}) +export default class ViewComponentsDocsModule {} +\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\`, + "widget-types/widget-types.module.ts": \\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\`// © 2022 SolarWinds Worldwide, LLC. All rights reserved. +// +// Permission is hereby granted, free of charge, to any person obtaining a copy +// of this software and associated documentation files (the "Software"), to +// deal in the Software without restriction, including without limitation the +// rights to use, copy, modify, merge, publish, distribute, sublicense, and/or +// sell copies of the Software, and to permit persons to whom the Software is +// furnished to do so, subject to the following conditions: +// +// The above copyright notice and this permission notice shall be included in +// all copies or substantial portions of the Software. +// +// THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR +// IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, +// FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE +// AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER +// LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, +// OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN +// THE SOFTWARE. + +import { NgModule, Type } from "@angular/core"; +import { RouterModule, Routes } from "@angular/router"; + +import { NuiDocsModule } from "@nova-ui/bits"; +import { + ConfiguratorHeadingService, + NuiDashboardsModule, +} from "@nova-ui/dashboards"; + +export enum WidgetTypesRoute { + kpi = "kpi", + riskScore = "risk-score", + timeseries = "timeseries", + table = "table", + proportional = "proportional", + embedded = "embedded", + drilldown = "drilldown", + viewComponents = "view-components", +} + +const routes: Routes = [ + { + path: WidgetTypesRoute.kpi, + loadChildren: async () => + import("./kpi/kpi-docs.module") as object as Promise>, + data: { + srlc: { + hideIndicator: true, + }, + }, + }, + { + path: WidgetTypesRoute.riskScore, + loadChildren: async () => + import("./risk-score/risk-score-docs.module") as object as Promise< + Type + >, + data: { + srlc: { + hideIndicator: true, + }, + }, + }, + { + path: WidgetTypesRoute.timeseries, + loadChildren: async () => + import("./timeseries/timeseries-docs.module") as object as Promise< + Type + >, + data: { + srlc: { + hideIndicator: true, + }, + }, + }, + { + path: WidgetTypesRoute.table, + loadChildren: async () => + import("./table/table-docs.module") as object as Promise>, + data: { + srlc: { + hideIndicator: true, + }, + }, + }, + { + path: WidgetTypesRoute.proportional, + loadChildren: async () => + import( + "./proportional/proportional-docs.module" + ) as object as Promise>, + data: { + srlc: { + hideIndicator: true, + }, + }, + }, + { + path: WidgetTypesRoute.embedded, + loadChildren: async () => + import( + "./embedded-content/embedded-content-docs.module" + ) as object as Promise>, + data: { + srlc: { + hideIndicator: true, + }, + }, + }, + { + path: WidgetTypesRoute.drilldown, + loadChildren: async () => + import( + "./drilldown/drilldown-widget-docs.module" + ) as object as Promise>, + data: { + srlc: { + hideIndicator: true, + }, + }, + }, + { + path: WidgetTypesRoute.viewComponents, + loadChildren: async () => + import( + "./view-components/view-components-docs.module" + ) as object as Promise>, + data: { + srlc: { + hideIndicator: true, + }, + }, + }, +]; + +@NgModule({ + imports: [ + RouterModule.forChild(routes), + NuiDocsModule, + NuiDashboardsModule, + ], + providers: [ConfiguratorHeadingService], +}) +export default class WidgetTypesModule {} +\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\`, +}; +\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\`, + "overview/hero/dashboard/hero-dashboard.component.ts": \\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\`// © 2022 SolarWinds Worldwide, LLC. All rights reserved. +// +// Permission is hereby granted, free of charge, to any person obtaining a copy +// of this software and associated documentation files (the "Software"), to +// deal in the Software without restriction, including without limitation the +// rights to use, copy, modify, merge, publish, distribute, sublicense, and/or +// sell copies of the Software, and to permit persons to whom the Software is +// furnished to do so, subject to the following conditions: +// +// The above copyright notice and this permission notice shall be included in +// all copies or substantial portions of the Software. +// +// THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR +// IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, +// FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE +// AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER +// LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, +// OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN +// THE SOFTWARE. + +import { HttpClient } from "@angular/common/http"; +import { + ChangeDetectionStrategy, + Component, + OnInit, + ViewEncapsulation, +} from "@angular/core"; +import keyBy from "lodash/keyBy"; + +import { LoggerService, ThemeSwitchService } from "@nova-ui/bits"; +import { + DATA_SOURCE, + IDashboard, + IWidget, + ProviderRegistryService, + WidgetTypesService, +} from "@nova-ui/dashboards"; + +import { positions, widgets } from "./widget-configs"; +import { + HarryPotterAverageRatingDataSource, + HarryPotterRatingsCountDataSource, +} from "../data/kpi-datasources"; +import { + BeerReviewCountsByCityMockDataSource, + BeerReviewCountsByCityMockDataSource2, +} from "../data/proportional-datasources"; +import { BeerDataSource } from "../data/table/beer-data-source"; +import { RandomUserDataSource } from "../data/table/random-user-data-source"; +import { + BeerVsReadingMockDataSource, + LoungingVsFrisbeeGolfMockDataSource, +} from "../data/timeseries-data-sources"; + +/** + * A component that instantiates the dashboard + */ +@Component({ + selector: "hero-dashboard", + templateUrl: "./hero-dashboard.component.html", + styleUrls: ["./hero-dashboard.component.less"], + encapsulation: ViewEncapsulation.Emulated, + changeDetection: ChangeDetectionStrategy.Default, + standalone: false, +}) +export class HeroDashboardComponent implements OnInit { + public dashboard: IDashboard = { + positions: {}, + widgets: {}, + }; + + public gridsterConfig = {}; + public editMode = false; + + constructor( + private providerRegistry: ProviderRegistryService, + public themeSwitcherService: ThemeSwitchService, + private widgetTypesService: WidgetTypesService + ) { + this.providerRegistry.setProviders({ + [HarryPotterAverageRatingDataSource.providerId]: { + provide: DATA_SOURCE, + useClass: HarryPotterAverageRatingDataSource, + deps: [HttpClient], + }, + [HarryPotterRatingsCountDataSource.providerId]: { + provide: DATA_SOURCE, + useClass: HarryPotterRatingsCountDataSource, + deps: [HttpClient], + }, + [BeerReviewCountsByCityMockDataSource.providerId]: { + provide: DATA_SOURCE, + useClass: BeerReviewCountsByCityMockDataSource, + deps: [], + }, + [BeerReviewCountsByCityMockDataSource2.providerId]: { + provide: DATA_SOURCE, + useClass: BeerReviewCountsByCityMockDataSource2, + deps: [], + }, + [RandomUserDataSource.providerId]: { + provide: DATA_SOURCE, + useClass: RandomUserDataSource, + deps: [LoggerService], + }, + [BeerDataSource.providerId]: { + provide: DATA_SOURCE, + useClass: BeerDataSource, + deps: [LoggerService], + }, + [BeerVsReadingMockDataSource.providerId]: { + provide: DATA_SOURCE, + useClass: BeerVsReadingMockDataSource, + deps: [], + }, + [LoungingVsFrisbeeGolfMockDataSource.providerId]: { + provide: DATA_SOURCE, + useClass: LoungingVsFrisbeeGolfMockDataSource, + deps: [], + }, + }); + } + + public ngOnInit(): void { + const widgetsWithStructure = widgets.map((w) => ({ + ...w, + pizzagna: { + ...this.widgetTypesService.getWidgetType(w.type, w.version) + .widget, + ...w.pizzagna, + }, + })); + const widgetsIndex = keyBy(widgetsWithStructure, (w: IWidget) => w.id); + + this.dashboard = { + positions: positions, + widgets: widgetsIndex, + }; + } +} +\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\`, + "overview/hero/dashboard/widget-configs.ts": \\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\`// © 2022 SolarWinds Worldwide, LLC. All rights reserved. +// +// Permission is hereby granted, free of charge, to any person obtaining a copy +// of this software and associated documentation files (the "Software"), to +// deal in the Software without restriction, including without limitation the +// rights to use, copy, modify, merge, publish, distribute, sublicense, and/or +// sell copies of the Software, and to permit persons to whom the Software is +// furnished to do so, subject to the following conditions: +// +// The above copyright notice and this permission notice shall be included in +// all copies or substantial portions of the Software. +// +// THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR +// IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, +// FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE +// AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER +// LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, +// OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN +// THE SOFTWARE. + +import { GridsterItem } from "angular-gridster2"; + +import { IWidget } from "@nova-ui/dashboards"; + +import { kpiConfig } from "../widget-configs/kpi"; +import { proportionalConfig } from "../widget-configs/proportional"; +import { tableConfig } from "../widget-configs/table"; +import { timeseriesConfig } from "../widget-configs/timeseries"; + +export const positions: Record = { + [tableConfig.id]: { + cols: 7, + rows: 7, + y: 0, + x: 0, + }, + [proportionalConfig.id]: { + cols: 5, + rows: 7, + y: 0, + x: 7, + }, + [kpiConfig.id]: { + cols: 6, + rows: 7, + y: 7, + x: 0, + }, + [timeseriesConfig.id]: { + cols: 6, + rows: 7, + y: 7, + x: 6, + }, +}; + +export const widgets: IWidget[] = [ + { + ...tableConfig, + }, + { + ...proportionalConfig, + }, + { + ...kpiConfig, + }, + { + ...timeseriesConfig, + }, +]; +\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\`, + "overview/hero/data/kpi-datasources.ts": \\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\`// © 2022 SolarWinds Worldwide, LLC. All rights reserved. +// +// Permission is hereby granted, free of charge, to any person obtaining a copy +// of this software and associated documentation files (the "Software"), to +// deal in the Software without restriction, including without limitation the +// rights to use, copy, modify, merge, publish, distribute, sublicense, and/or +// sell copies of the Software, and to permit persons to whom the Software is +// furnished to do so, subject to the following conditions: +// +// The above copyright notice and this permission notice shall be included in +// all copies or substantial portions of the Software. +// +// THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR +// IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, +// FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE +// AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER +// LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, +// OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN +// THE SOFTWARE. + +import { HttpClient, HttpErrorResponse } from "@angular/common/http"; +import { Injectable, OnDestroy } from "@angular/core"; +import { BehaviorSubject } from "rxjs"; +import { finalize } from "rxjs/operators"; + +import { DataSourceService, IFilteringOutputs } from "@nova-ui/bits"; +import { IKpiData } from "@nova-ui/dashboards"; + +import { GOOGLE_BOOKS_URL } from "./table/constants"; + +@Injectable() +export class HarryPotterAverageRatingDataSource + extends DataSourceService + implements OnDestroy +{ + public static providerId = "HarryPotterAverageRatingDataSource"; + + public busy = new BehaviorSubject(false); + + constructor(private http: HttpClient) { + super(); + } + + public async getFilteredData(): Promise { + this.busy.next(true); + return new Promise((resolve) => { + // *** Make a resource request to an external API (if needed) + this.http + .get(\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\`\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\${GOOGLE_BOOKS_URL}/5MQFrgEACAAJ\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\`) + .pipe(finalize(() => this.busy.next(false))) + .subscribe({ + next: (data: any) => { + resolve({ + result: { + value: data.volumeInfo.averageRating, + }, + }); + }, + error: (error: HttpErrorResponse) => { + resolve({ + result: null, + error: { + type: error.status, + }, + }); + }, + }); + }); + } + + public ngOnDestroy(): void { + this.outputsSubject.complete(); + } +} + +@Injectable() +export class HarryPotterRatingsCountDataSource + extends DataSourceService + implements OnDestroy +{ + public static providerId = "HarryPotterRatingsCountDataSource"; + + public busy = new BehaviorSubject(false); + + constructor(private http: HttpClient) { + super(); + } + + public async getFilteredData(): Promise { + this.busy.next(true); + return new Promise((resolve) => { + this.http + .get(\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\`\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\${GOOGLE_BOOKS_URL}/5MQFrgEACAAJ\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\`) + .pipe(finalize(() => this.busy.next(false))) + .subscribe({ + next: (data: any) => { + resolve({ + result: { + value: data.volumeInfo.ratingsCount, + }, + }); + }, + error: (error: HttpErrorResponse) => { + resolve({ + result: null, + error: { + type: error.status, + }, + }); + }, + }); + }); + } + + public ngOnDestroy(): void { + this.outputsSubject.complete(); + } +} +\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\`, + "overview/hero/data/proportional-datasources.ts": \\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\`// © 2022 SolarWinds Worldwide, LLC. All rights reserved. +// +// Permission is hereby granted, free of charge, to any person obtaining a copy +// of this software and associated documentation files (the "Software"), to +// deal in the Software without restriction, including without limitation the +// rights to use, copy, modify, merge, publish, distribute, sublicense, and/or +// sell copies of the Software, and to permit persons to whom the Software is +// furnished to do so, subject to the following conditions: +// +// The above copyright notice and this permission notice shall be included in +// all copies or substantial portions of the Software. +// +// THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR +// IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, +// FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE +// AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER +// LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, +// OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN +// THE SOFTWARE. + +import { Injectable, OnDestroy } from "@angular/core"; +import { BehaviorSubject } from "rxjs"; + +import { + DataSourceService, + IDataSource, + IFilteringOutputs, +} from "@nova-ui/bits"; + +import { + getMockBeerReviewCountsByCity, + getMockBeerReviewCountsByCity2, + IProportionalWidgetData, +} from "./widget-data"; + +@Injectable() +export class BeerReviewCountsByCityMockDataSource + extends DataSourceService + implements IDataSource, OnDestroy +{ + // This is the ID we'll use to identify the provider + public static providerId = "BeerReviewCountsByCityMockDataSource"; + public busy = new BehaviorSubject(false); + + public async getFilteredData(): Promise { + this.busy.next(true); + return new Promise((resolve) => { + setTimeout(() => { + this.outputsSubject.next({ + result: getMockBeerReviewCountsByCity(), + }); + this.busy.next(false); + }, 300); + }); + } + + public ngOnDestroy(): void { + this.outputsSubject.complete(); + } +} + +@Injectable() +export class BeerReviewCountsByCityMockDataSource2 + extends DataSourceService + implements IDataSource, OnDestroy +{ + // This is the ID we'll use to identify the provider + public static providerId = "BeerReviewCountsByCityMockDataSource2"; + public busy = new BehaviorSubject(false); + + public async getFilteredData(): Promise { + this.busy.next(true); + return new Promise((resolve) => { + setTimeout(() => { + this.outputsSubject.next({ + result: getMockBeerReviewCountsByCity2(), + }); + this.busy.next(false); + }, 1500); + }); + } + + public ngOnDestroy(): void { + this.outputsSubject.complete(); + } +} +\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\`, + "overview/hero/data/table/beer-data-source.ts": \\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\`// © 2022 SolarWinds Worldwide, LLC. All rights reserved. +// +// Permission is hereby granted, free of charge, to any person obtaining a copy +// of this software and associated documentation files (the "Software"), to +// deal in the Software without restriction, including without limitation the +// rights to use, copy, modify, merge, publish, distribute, sublicense, and/or +// sell copies of the Software, and to permit persons to whom the Software is +// furnished to do so, subject to the following conditions: +// +// The above copyright notice and this permission notice shall be included in +// all copies or substantial portions of the Software. +// +// THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR +// IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, +// FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE +// AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER +// LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, +// OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN +// THE SOFTWARE. + +import { ListRange } from "@angular/cdk/collections"; +import { Injectable } from "@angular/core"; +import isEqual from "lodash/isEqual"; +import orderBy from "lodash/orderBy"; +import { BehaviorSubject } from "rxjs"; + +import { + DataSourceService, + IDataField, + INovaFilteringOutputs, + INovaFilters, + ISorterFilter, + LoggerService, +} from "@nova-ui/bits"; + +import { IBrewDatasourceResponse, IBrewInfo } from "../types"; +import { BREW_API_URL } from "./constants"; + +@Injectable() +export class BeerDataSource extends DataSourceService { + public static providerId = "BeerDataSource"; + + private cache = Array.from({ length: 0 }); + private lastSortValue?: ISorterFilter; + private lastVirtualScroll?: ListRange; + private totalItems: number = 325; + + public page: number = 1; + public busy = new BehaviorSubject(false); + + public dataFields: Array = [ + { id: "id", label: "No", dataType: "number" }, + { id: "name", label: "Name", dataType: "string" }, + { id: "tagline", label: "Tagline", dataType: "string" }, + { id: "first_brewed", label: "First Brewed", dataType: "string" }, + { id: "description", label: "Description", dataType: "string" }, + { id: "brewers_tips", label: "Brewer's Tips", dataType: "string" }, + ]; + + constructor(private logger: LoggerService) { + super(); + } + + public async getFilteredData( + filters: INovaFilters + ): Promise { + const start = filters.virtualScroll?.value?.start ?? 0; + const end = filters.virtualScroll?.value?.end ?? 0; + const delta = end - start; + + // This condition handles sorting. We want to sort columns without fetching another chunk of data. + // Since the data is being fetched when scrolled, we compare virtual scroll indexes here in the condition as well. + if (filters.sorter?.value) { + if ( + !isEqual(this.lastSortValue, filters.sorter.value) && + isEqual(this.lastVirtualScroll, filters.virtualScroll?.value) + ) { + const totalPages = Math.ceil( + delta ? this.totalItems / delta : 1 + ); + const itemsPerPage: number = Math.max( + delta < 80 ? delta : 80, + 1 + ); + let response: Array | null = null; + let map: IBrewDatasourceResponse; + + if (filters.sorter?.value?.direction === "desc") { + this.cache = []; + for (let i = 0; i < this.page; ++i) { + response = await ( + await fetch( + \\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\`\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\${BREW_API_URL}/?page=\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\${ + totalPages - i || 1 + }&per_page=\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\${itemsPerPage}\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\` + ) + ).json(); + + // since the last page contains only 5 items we need to fetch another page to give virtual scroll enough space to work + if (response && response.length < itemsPerPage) { + this.page++; + } + map = { + brewInfo: response?.map((result: IBrewInfo) => ({ + id: result.id, + name: result.name, + tagline: result.tagline, + first_brewed: result.first_brewed, + description: result.description, + brewers_tips: result.brewers_tips, + })), + total: response?.length, + } as IBrewDatasourceResponse; + this.cache = + totalPages - i !== 0 + ? this.cache.concat(map.brewInfo) + : this.cache; + } + } + + if (filters.sorter?.value?.direction === "asc") { + this.cache = []; + for (let i = 0; i < this.page; i++) { + response = await ( + await fetch( + \\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\`\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\${BREW_API_URL}/?page=\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\${ + i + 1 + }&per_page=\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\${itemsPerPage}\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\` + ) + ).json(); + map = { + brewInfo: response?.map((result: IBrewInfo) => ({ + id: result.id, + name: result.name, + tagline: result.tagline, + first_brewed: result.first_brewed, + description: result.description, + brewers_tips: result.brewers_tips, + })), + total: response?.length, + } as IBrewDatasourceResponse; + this.cache = this.cache.concat(map.brewInfo); + } + } + + this.lastSortValue = filters.sorter?.value; + this.lastVirtualScroll = filters.virtualScroll?.value; + + return { + repeat: { itemsSource: this.sortData(this.cache, filters) }, + paginator: { total: this.totalItems }, + dataFields: this.dataFields, + }; + } + } + + this.busy.next(true); + return new Promise((resolve) => { + setTimeout(() => { + this.getData(start, end, filters).then( + (response: INovaFilteringOutputs) => { + if (!response) { + return; + } + + this.cache = this.cache.concat(response.brewInfo); + + this.dataSubject.next(this.cache); + resolve({ + repeat: { + itemsSource: this.sortData(this.cache, filters), + }, + paginator: { total: this.totalItems }, + dataFields: this.dataFields, + }); + + this.lastSortValue = filters.sorter?.value; + this.lastVirtualScroll = filters.virtualScroll?.value; + this.busy.next(false); + } + ); + }, 500); + }); + } + + public async getData( + start: number = 0, + end: number = 20, + filters: INovaFilters + ): Promise { + const delta = end - start; + const totalPages = Math.ceil(delta ? this.totalItems / delta : 1); + let response: Array | null = null; + // The api.punk.com is able to return only 80 items per page + const itemsPerPage: number = Math.max(delta < 80 ? delta : 80, 1); + + if (filters.sorter?.value?.direction === "asc") { + response = await ( + await fetch( + \\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\`\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\${BREW_API_URL}/?page=\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\${this.page}&per_page=\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\${itemsPerPage}\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\` + ) + ).json(); + } + + if (filters.sorter?.value?.direction === "desc") { + response = await ( + await fetch( + \\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\`\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\${BREW_API_URL}/?page=\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\${ + totalPages - this.page + }&per_page=\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\${itemsPerPage}\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\` + ) + ).json(); + } + + if (!filters.sorter) { + response = await ( + await fetch( + \\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\`\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\${BREW_API_URL}/?page=\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\${this.page}&per_page=\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\${itemsPerPage}\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\` + ) + ).json(); + } + return { + brewInfo: response?.map((result: IBrewInfo, i: number) => ({ + id: result.id, + name: result.name, + tagline: result.tagline, + first_brewed: result.first_brewed, + description: result.description, + brewers_tips: result.brewers_tips, + })), + total: response?.length, + } as IBrewDatasourceResponse; + } + + private sortData(data: IBrewInfo[], filters: INovaFilters) { + return orderBy( + data, + filters.sorter?.value?.sortBy, + filters.sorter?.value?.direction as "desc" | "asc" + ); + } +} +\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\`, + "overview/hero/data/table/constants.ts": \\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\`// © 2022 SolarWinds Worldwide, LLC. All rights reserved. +// +// Permission is hereby granted, free of charge, to any person obtaining a copy +// of this software and associated documentation files (the "Software"), to +// deal in the Software without restriction, including without limitation the +// rights to use, copy, modify, merge, publish, distribute, sublicense, and/or +// sell copies of the Software, and to permit persons to whom the Software is +// furnished to do so, subject to the following conditions: +// +// The above copyright notice and this permission notice shall be included in +// all copies or substantial portions of the Software. +// +// THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR +// IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, +// FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE +// AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER +// LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, +// OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN +// THE SOFTWARE. + +export const corsProxy = "https://cors-anywhere.herokuapp.com"; +export const RANDOMUSER_API_URL = "https://randomuser.me"; +export const BREW_API_URL = "https://api.punkapi.com/v2/beers"; +export const GOOGLE_BOOKS_URL = "https://www.googleapis.com/books/v1/volumes"; +export const apiRoute = "api/1.3"; +export const responseError = \\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\`Error responding from server. Please visit \\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\${RANDOMUSER_API_URL} and \\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\${corsProxy} to see if they're available\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\`; +\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\`, + "overview/hero/data/table/random-user-data-source.ts": \\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\`// © 2022 SolarWinds Worldwide, LLC. All rights reserved. +// +// Permission is hereby granted, free of charge, to any person obtaining a copy +// of this software and associated documentation files (the "Software"), to +// deal in the Software without restriction, including without limitation the +// rights to use, copy, modify, merge, publish, distribute, sublicense, and/or +// sell copies of the Software, and to permit persons to whom the Software is +// furnished to do so, subject to the following conditions: +// +// The above copyright notice and this permission notice shall be included in +// all copies or substantial portions of the Software. +// +// THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR +// IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, +// FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE +// AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER +// LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, +// OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN +// THE SOFTWARE. + +import { ListRange } from "@angular/cdk/collections"; +import { Injectable } from "@angular/core"; +import isEqual from "lodash/isEqual"; +import orderBy from "lodash/orderBy"; +import { BehaviorSubject } from "rxjs"; + +import { + DataSourceService, + IDataField, + INovaFilteringOutputs, + INovaFilters, + ISorterFilter, + LoggerService, +} from "@nova-ui/bits"; + +import { + IRandomUserResponse, + IRandomUserResults, + IRandomUserTableModel, + UsersQueryResponse, +} from "../types"; +import { + apiRoute, + corsProxy, + RANDOMUSER_API_URL, + responseError, +} from "./constants"; + +@Injectable() +export class RandomUserDataSource extends DataSourceService { + public static providerId = "RandomUserDataSource"; + + private readonly seed = "sw"; + + private cache = Array.from({ length: 0 }); + private lastSortValue?: ISorterFilter; + private lastVirtualScroll?: ListRange; + + public page: number = 1; + public busy = new BehaviorSubject(false); + + public dataFields: Array = [ + { id: "no", label: $localize\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\`No\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\`, dataType: "number" }, + { id: "nameTitle", label: $localize\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\`Title\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\`, dataType: "string" }, + { id: "nameFirst", label: $localize\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\`First\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\`, dataType: "string" }, + { id: "nameLast", label: $localize\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\`Last\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\`, dataType: "string" }, + { id: "gender", label: $localize\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\`Gender\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\`, dataType: "string" }, + { id: "country", label: $localize\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\`Country\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\`, dataType: "string" }, + { id: "city", label: $localize\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\`City\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\`, dataType: "string" }, + { id: "postcode", label: $localize\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\`Postcode\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\`, dataType: "number" }, + { id: "email", label: $localize\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\`E-Mail\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\`, dataType: "string" }, + { id: "cell", label: $localize\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\`Cell\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\`, dataType: "string" }, + ]; + + constructor(private logger: LoggerService) { + super(); + } + + public async getFilteredData( + filters: INovaFilters + ): Promise { + // This condition handles sorting. We want to sort columns without fetching another chunk of data. + // Since the data is being fetched when scrolled, we compare virtual scroll indexes here in the condition as well. + if (filters.sorter?.value) { + if ( + !isEqual(this.lastSortValue, filters.sorter.value) && + isEqual(this.lastVirtualScroll, filters.virtualScroll?.value) + ) { + this.lastSortValue = filters.sorter?.value; + this.lastVirtualScroll = filters.virtualScroll?.value; + + return { + repeat: { itemsSource: this.sortData(this.cache, filters) }, + paginator: { total: 200 }, + dataFields: this.dataFields, + }; + } + } + this.busy.next(true); + + const virtualScrollFilter = + filters.virtualScroll && filters.virtualScroll.value; + const start = virtualScrollFilter + ? filters.virtualScroll?.value.start + : 0; + const end = virtualScrollFilter ? filters.virtualScroll?.value.end : 0; + + // We're returning Promise with setTimeout here to make the response from the server longer, as the API being used sends responses + // almost immediately. We need it longer to be able the show the spinner component on data load + return new Promise((resolve) => { + setTimeout(() => { + this.getData(start, end).then( + (response: INovaFilteringOutputs | undefined) => { + if (!response) { + return; + } + + this.cache = this.cache.concat(response.users); + + this.dataSubject.next(this.cache); + resolve({ + repeat: { + itemsSource: this.sortData(this.cache, filters), + }, + // This API can return thousands of results, however doesn't return the max number of results, + // so we set the max number of result manually here. + paginator: { total: 200 }, + dataFields: this.dataFields, + }); + + this.lastSortValue = filters.sorter?.value; + this.lastVirtualScroll = filters.virtualScroll?.value; + this.busy.next(false); + } + ); + }, 300); + }); + } + + public async getData( + start: number = 0, + end: number = 20 + ): Promise { + let response: IRandomUserResponse | null = null; + try { + response = await ( + await fetch( + \\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\`\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\${corsProxy}/\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\${RANDOMUSER_API_URL}/\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\${apiRoute}/?page=\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\${ + this.page + }&results=\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\${end - start}&seed=\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\${this.seed}\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\` + ) + ).json(); + return { + users: response?.results.map( + (result: IRandomUserResults, i: number) => ({ + no: this.cache.length + i + 1, + nameTitle: result.name.title, + nameFirst: result.name.first, + nameLast: result.name.last, + gender: result.gender, + country: result.location.country, + city: result.location.city, + postcode: result.location.postcode, + email: result.email, + cell: result.cell, + }) + ), + total: response?.results.length, + start: start, + } as UsersQueryResponse; + } catch (e) { + this.logger.error(responseError); + } + } + + private sortData(data: IRandomUserTableModel[], filters: INovaFilters) { + return orderBy( + data, + filters.sorter?.value?.sortBy, + filters.sorter?.value?.direction as "desc" | "asc" + ); + } +} +\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\`, + "overview/hero/data/table/types.ts": \\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\`// © 2022 SolarWinds Worldwide, LLC. All rights reserved. +// +// Permission is hereby granted, free of charge, to any person obtaining a copy +// of this software and associated documentation files (the "Software"), to +// deal in the Software without restriction, including without limitation the +// rights to use, copy, modify, merge, publish, distribute, sublicense, and/or +// sell copies of the Software, and to permit persons to whom the Software is +// furnished to do so, subject to the following conditions: +// +// The above copyright notice and this permission notice shall be included in +// all copies or substantial portions of the Software. +// +// THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR +// IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, +// FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE +// AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER +// LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, +// OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN +// THE SOFTWARE. + +import { IDataField, INovaFilteringOutputs } from "@nova-ui/bits"; +export interface BasicTableModel { + position: number; + name: string; + features: any; + status: string; + checks: any; + "cpu-load": number; + firstUrl: string; + firstUrlLabel: string; + secondUrl: string; + secondUrlLabel: string; +} + +export interface ITableDataSourceOutput extends INovaFilteringOutputs { + dataFields: IDataField[]; +} +\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\`, + "overview/hero/data/timeseries-data-sources.ts": \\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\`// © 2022 SolarWinds Worldwide, LLC. All rights reserved. +// +// Permission is hereby granted, free of charge, to any person obtaining a copy +// of this software and associated documentation files (the "Software"), to +// deal in the Software without restriction, including without limitation the +// rights to use, copy, modify, merge, publish, distribute, sublicense, and/or +// sell copies of the Software, and to permit persons to whom the Software is +// furnished to do so, subject to the following conditions: +// +// The above copyright notice and this permission notice shall be included in +// all copies or substantial portions of the Software. +// +// THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR +// IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, +// FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE +// AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER +// LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, +// OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN +// THE SOFTWARE. + +import { Injectable } from "@angular/core"; +import { Moment } from "moment/moment"; +import { BehaviorSubject } from "rxjs"; + +import { DataSourceService, IDataSource, INovaFilters } from "@nova-ui/bits"; +import { + ITimeseriesOutput, + ITimeseriesWidgetData, + ITimeseriesWidgetSeriesData, +} from "@nova-ui/dashboards"; + +import { + BEER_VS_READING_DATA, + LOUNGING_VS_ULTIMATE_FRISBEE_DATA, +} from "./widget-data"; + +@Injectable() +export class BeerVsReadingMockDataSource + extends DataSourceService + implements IDataSource +{ + public static providerId = "BeerVsReadingMockDataSource"; + + public busy = new BehaviorSubject(false); + + constructor() { + super(); + } + + public async getFilteredData( + filters: INovaFilters + ): Promise { + this.busy.next(true); + const result = await delay( + { series: getData(filters, BEER_VS_READING_DATA) }, + 1000 + ); + this.busy.next(false); + return result; + } +} + +@Injectable() +export class LoungingVsFrisbeeGolfMockDataSource + extends DataSourceService + implements IDataSource +{ + public static providerId = "LoungingVsFrisbeeGolfMockDataSource"; + + public busy = new BehaviorSubject(false); + + constructor() { + super(); + } + + public async getFilteredData( + filters: INovaFilters + ): Promise { + this.busy.next(true); + const result = await delay( + { series: getData(filters, LOUNGING_VS_ULTIMATE_FRISBEE_DATA) }, + 1000 + ); + this.busy.next(false); + return result; + } +} + +function getData( + filters: INovaFilters, + data: ITimeseriesWidgetData[] +): ITimeseriesWidgetData[] { + const timeframeFilter = filters.timeframe; + let filteredData = data; + // TIME FRAME PICKER FILTERING + if (timeframeFilter) { + filteredData = filteredData.map((item: ITimeseriesWidgetData) => ({ + id: item.id, + name: item.name, + description: item.description, + data: item.data.filter((seriesData: ITimeseriesWidgetSeriesData) => + filterDates( + seriesData.x, + timeframeFilter.value.startDatetime, + timeframeFilter.value.endDatetime + ) + ), + })); + } + + return filteredData; +} + +function filterDates(dateToCheck: Moment, startDate: Moment, endDate: Moment) { + return ( + dateToCheck.isBetween(startDate, endDate) || + dateToCheck.isSame(startDate) || + dateToCheck.isSame(endDate) + ); +} + +async function delay( + value: ITimeseriesOutput, + ms: number +): Promise { + return new Promise((resolve) => setTimeout(() => resolve(value), ms)); +} +\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\`, + "overview/hero/data/types.ts": \\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\`// © 2022 SolarWinds Worldwide, LLC. All rights reserved. +// +// Permission is hereby granted, free of charge, to any person obtaining a copy +// of this software and associated documentation files (the "Software"), to +// deal in the Software without restriction, including without limitation the +// rights to use, copy, modify, merge, publish, distribute, sublicense, and/or +// sell copies of the Software, and to permit persons to whom the Software is +// furnished to do so, subject to the following conditions: +// +// The above copyright notice and this permission notice shall be included in +// all copies or substantial portions of the Software. +// +// THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR +// IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, +// FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE +// AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER +// LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, +// OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN +// THE SOFTWARE. + +export interface UsersQueryResponse { + users: IRandomUserTableModel[]; + total: number; + start: number; +} + +export interface IRandomUserResponse { + info: Array; + results: Array; +} + +export interface IRandomUserInfo { + page: number; + results: number; + seed: string; + version: string; +} + +export interface IRandomUserResults { + cell: string; + dob: { + age: number; + date: string; + }; + email: string; + gender: string; + id: any; + location: IRandomUserLocation; + login: { + md5: string; + password: string; + salt: string; + sha1: string; + sha256: string; + username: string; + uuid: string; + }; + name: { + title: string; + first: string; + last: string; + }; + nat: string; + phone: string; + picture: { + large: string; + medium: string; + thumbnail: string; + }; + registered: { + date: string; + age: number; + }; +} + +export interface IRandomUserTableModel { + no: number; + nameTitle: string; + nameFirst: string; + nameLast: string; + gender: string; + country: string; + city: string; + postcode: number; + email: string; + cell: string; +} + +export interface IRandomUserLocation { + city: string; + coordinates: { latitude: string; longitude: string }; + country: string; + postcode: number; + state: string; + street: { number: number; name: string }; + timezone: any; +} + +export interface IBrewInfo { + id: number; + name: string; + tagline: string; + first_brewed: string; + description: string; + brewers_tips: string; +} + +export interface IBrewDatasourceResponse { + brewInfo: IBrewInfo[]; + total: number; +} +\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\`, + "overview/hero/data/widget-data.ts": \\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\`// © 2022 SolarWinds Worldwide, LLC. All rights reserved. +// +// Permission is hereby granted, free of charge, to any person obtaining a copy +// of this software and associated documentation files (the "Software"), to +// deal in the Software without restriction, including without limitation the +// rights to use, copy, modify, merge, publish, distribute, sublicense, and/or +// sell copies of the Software, and to permit persons to whom the Software is +// furnished to do so, subject to the following conditions: +// +// The above copyright notice and this permission notice shall be included in +// all copies or substantial portions of the Software. +// +// THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR +// IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, +// FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE +// AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER +// LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, +// OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN +// THE SOFTWARE. + +import moment from "moment/moment"; + +import { ITimeseriesWidgetData } from "@nova-ui/dashboards"; + +import { BasicTableModel } from "./table/types"; + +export interface IProportionalWidgetData { + id: string; + name: string; + data: number[]; + link: string; + value: string; +} + +export function getMockBeerReviewCountsByCity(): IProportionalWidgetData[] { + return [ + { + id: "Brno", + name: "Brno", + data: [Math.round(Math.random() * 100000)], + link: "https://en.wikipedia.org/wiki/Brno", + value: "Brno", + }, + { + id: "kyiv", + name: "Kyiv", + data: [Math.round(Math.random() * 100000)], + link: "https://en.wikipedia.org/wiki/Kyiv", + value: "Kyiv", + }, + { + id: "austin", + name: "Austin", + data: [Math.round(Math.random() * 100000)], + link: "https://en.wikipedia.org/wiki/Austin", + value: "Austin", + }, + { + id: "lisbon", + name: "Lisbon", + data: [Math.round(Math.random() * 100000)], + link: "https://en.wikipedia.org/wiki/Lisbon", + value: "Lisbon", + }, + { + id: "sydney", + name: "Sydney", + data: [Math.round(Math.random() * 100000)], + link: "https://en.wikipedia.org/wiki/Sydney", + value: "Sydney", + }, + { + id: "nur-sultan", + name: "Nur-Sultan", + data: [Math.round(Math.random() * 100000)], + link: "https://en.wikipedia.org/wiki/Nur-Sultan", + value: "Nur-Sultan", + }, + ].sort((a, b) => a.data[0] - b.data[0]); +} + +export function getMockBeerReviewCountsByCity2(): IProportionalWidgetData[] { + return [ + { + id: "london", + name: "London", + data: [Math.round(Math.random() * 100000)], + link: "https://en.wikipedia.org/wiki/London", + value: "London", + }, + { + id: "paris", + name: "Paris", + data: [Math.round(Math.random() * 100000)], + link: "https://en.wikipedia.org/wiki/Paris", + value: "Paris", + }, + { + id: "rio", + name: "Rio", + data: [Math.round(Math.random() * 100000)], + link: "https://en.wikipedia.org/wiki/Rio_de_Janeiro", + value: "Rio", + }, + ].sort((a, b) => a.data[0] - b.data[0]); +} + +export const BEER_VS_READING_DATA: ITimeseriesWidgetData[] = [ + { + id: "series-1", + name: "Beer Tasting", + description: "Havin' some suds", + data: [ + { x: moment().subtract(10, "day"), y: 30 }, + { x: moment().subtract(9, "day"), y: 35 }, + { x: moment().subtract(8, "day"), y: 33 }, + { x: moment().subtract(7, "day"), y: 40 }, + { x: moment().subtract(6, "day"), y: 35 }, + { x: moment().subtract(5, "day"), y: 30 }, + { x: moment().subtract(4, "day"), y: 35 }, + { x: moment().subtract(3, "day"), y: 15 }, + { x: moment().subtract(2, "day"), y: 30 }, + { x: moment().subtract(1, "day"), y: 35 }, + { x: moment().subtract(24, "hour"), y: 34 }, + { x: moment().subtract(15, "hour"), y: 33 }, + { x: moment().subtract(10, "hour"), y: 35 }, + { x: moment().subtract(5, "hour"), y: 36 }, + { x: moment().subtract(1, "hour"), y: 34 }, + { x: moment().subtract(50, "minute"), y: 33 }, + { x: moment().subtract(40, "minute"), y: 30 }, + { x: moment().subtract(30, "minute"), y: 32 }, + { x: moment().subtract(20, "minute"), y: 31 }, + { x: moment().subtract(10, "minute"), y: 34 }, + ], + }, + { + id: "series-2", + name: "Reading", + description: "Hittin' the books", + data: [ + { x: moment().subtract(10, "day"), y: 60 }, + { x: moment().subtract(9, "day"), y: 64 }, + { x: moment().subtract(8, "day"), y: 70 }, + { x: moment().subtract(7, "day"), y: 55 }, + { x: moment().subtract(6, "day"), y: 55 }, + { x: moment().subtract(5, "day"), y: 45 }, + { x: moment().subtract(4, "day"), y: 10 }, + { x: moment().subtract(3, "day"), y: 65 }, + { x: moment().subtract(2, "day"), y: 35 }, + { x: moment().subtract(1, "day"), y: 60 }, + { x: moment().subtract(24, "hour"), y: 61 }, + { x: moment().subtract(15, "hour"), y: 65 }, + { x: moment().subtract(10, "hour"), y: 63 }, + { x: moment().subtract(5, "hour"), y: 58 }, + { x: moment().subtract(1, "hour"), y: 64 }, + { x: moment().subtract(50, "minute"), y: 63 }, + { x: moment().subtract(40, "minute"), y: 60 }, + { x: moment().subtract(30, "minute"), y: 62 }, + { x: moment().subtract(20, "minute"), y: 61 }, + { x: moment().subtract(10, "minute"), y: 62 }, + ], + }, +]; + +export const LOUNGING_VS_ULTIMATE_FRISBEE_DATA: ITimeseriesWidgetData[] = [ + { + id: "series-a", + name: "Lounging", + description: "Shootin' the Breeze", + data: [ + { x: moment().subtract(10, "day"), y: 10 }, + { x: moment().subtract(9, "day"), y: 15 }, + { x: moment().subtract(8, "day"), y: 13 }, + { x: moment().subtract(7, "day"), y: 20 }, + { x: moment().subtract(6, "day"), y: 15 }, + { x: moment().subtract(5, "day"), y: 10 }, + { x: moment().subtract(4, "day"), y: 15 }, + { x: moment().subtract(3, "day"), y: 5 }, + { x: moment().subtract(2, "day"), y: 10 }, + { x: moment().subtract(1, "day"), y: 15 }, + { x: moment().subtract(24, "hour"), y: 14 }, + { x: moment().subtract(15, "hour"), y: 13 }, + { x: moment().subtract(10, "hour"), y: 15 }, + { x: moment().subtract(5, "hour"), y: 16 }, + { x: moment().subtract(1, "hour"), y: 14 }, + { x: moment().subtract(50, "minute"), y: 13 }, + { x: moment().subtract(40, "minute"), y: 10 }, + { x: moment().subtract(30, "minute"), y: 12 }, + { x: moment().subtract(20, "minute"), y: 11 }, + { x: moment().subtract(10, "minute"), y: 14 }, + ], + }, + { + id: "series-b", + name: "Frisbee Golfing", + description: "Golfin' with a disc", + data: [ + { x: moment().subtract(10, "day"), y: 80 }, + { x: moment().subtract(9, "day"), y: 84 }, + { x: moment().subtract(8, "day"), y: 80 }, + { x: moment().subtract(7, "day"), y: 75 }, + { x: moment().subtract(6, "day"), y: 95 }, + { x: moment().subtract(5, "day"), y: 85 }, + { x: moment().subtract(4, "day"), y: 80 }, + { x: moment().subtract(3, "day"), y: 85 }, + { x: moment().subtract(2, "day"), y: 85 }, + { x: moment().subtract(1, "day"), y: 80 }, + { x: moment().subtract(24, "hour"), y: 81 }, + { x: moment().subtract(15, "hour"), y: 85 }, + { x: moment().subtract(10, "hour"), y: 83 }, + { x: moment().subtract(5, "hour"), y: 88 }, + { x: moment().subtract(1, "hour"), y: 84 }, + { x: moment().subtract(50, "minute"), y: 83 }, + { x: moment().subtract(40, "minute"), y: 80 }, + { x: moment().subtract(30, "minute"), y: 82 }, + { x: moment().subtract(20, "minute"), y: 81 }, + { x: moment().subtract(10, "minute"), y: 82 }, + ], + }, +]; + +export const TABLE_DATA: BasicTableModel[] = [ + { + position: 1, + name: "FOCUS-SVR-02258", + features: ["remote-access-vpn-tunnel", "patch-manager01"], + status: "Active", + checks: { + icon: "status_up", + num: 25, + }, + "cpu-load": 86, + firstUrl: "https://en.wikipedia.org/wiki/Brno", + firstUrlLabel: "Brno", + secondUrl: "https://en.wikipedia.org/wiki/VMware_Workstation", + secondUrlLabel: "Workstation", + }, + { + position: 2, + name: "FOCUS-SVR-03312", + features: ["tools", "database", "orion-ape-backup"], + status: "Active", + checks: { + icon: "status_critical", + num: 25, + }, + "cpu-load": 47, + firstUrl: "https://en.wikipedia.org/wiki/Brno", + firstUrlLabel: "Brno", + secondUrl: "https://en.wikipedia.org/wiki/VMware_Workstation", + secondUrlLabel: "Workstation", + }, + { + position: 3, + name: "FOCUS-SVR-02258", + features: [ + "remote-access-vpn-tunnel", + "database", + "orion-ape-backup", + "patch-manager01", + ], + status: "Active", + checks: { + icon: "status_down", + num: 25, + }, + "cpu-load": 53, + firstUrl: "https://en.wikipedia.org/wiki/Kyiv", + firstUrlLabel: "Kyiv", + secondUrl: "https://en.wikipedia.org/wiki/VMware_Workstation", + secondUrlLabel: "Workstation", + }, + { + position: 4, + name: "Man-LT-JYJ4AD5", + features: [ + "remote-access-vpn-tunnel", + "tools", + "database", + "orion-ape-backup", + ], + status: "Active", + checks: { + icon: "status_up", + num: 25, + }, + "cpu-load": 32, + firstUrl: "https://en.wikipedia.org/wiki/Kyiv", + firstUrlLabel: "Kyiv", + secondUrl: "https://en.wikipedia.org/wiki/VirtualBox", + secondUrlLabel: "VirtualBox", + }, + { + position: 5, + name: "Man-LT-JYJ425", + features: [ + "remote-access-vpn-tunnel", + "tools", + "database", + "orion-ape-backup", + ], + status: "Active", + checks: { + icon: "status_up", + num: 25, + }, + "cpu-load": 22, + firstUrl: "https://en.wikipedia.org/wiki/Kyiv", + firstUrlLabel: "Kyiv", + secondUrl: "https://en.wikipedia.org/wiki/VirtualBox", + secondUrlLabel: "VirtualBox", + }, + { + position: 6, + name: "Man-LT-JYJ4333", + features: [ + "remote-access-vpn-tunnel", + "tools", + "database", + "orion-ape-backup", + ], + status: "Active", + checks: { + icon: "status_up", + num: 25, + }, + "cpu-load": 12, + firstUrl: "https://en.wikipedia.org/wiki/Kyiv", + firstUrlLabel: "Kyiv", + secondUrl: "https://en.wikipedia.org/wiki/VirtualBox", + secondUrlLabel: "VirtualBox", + }, + { + position: 7, + name: "FOCUS-SVR-02258", + features: ["remote-access-vpn-tunnel", "patch-manager01"], + status: "Active", + checks: { + icon: "status_up", + num: 25, + }, + "cpu-load": 86, + firstUrl: "https://en.wikipedia.org/wiki/Austin", + firstUrlLabel: "Austin", + secondUrl: "https://en.wikipedia.org/wiki/VMware_Workstation", + secondUrlLabel: "Workstation", + }, + { + position: 8, + name: "Man-LT-JYJ4AD5", + features: [ + "remote-access-vpn-tunnel", + "tools", + "database", + "orion-ape-backup", + "patch-manager01", + ], + status: "Active", + checks: { + icon: "status_inactive", + num: 25, + }, + "cpu-load": 35, + firstUrl: "https://en.wikipedia.org/wiki/Austin", + firstUrlLabel: "Austin", + secondUrl: "https://en.wikipedia.org/wiki/VMware_Workstation", + secondUrlLabel: "Workstation", + }, + { + position: 9, + name: "Man-LT-JYJ4AD5", + features: [ + "remote-access-vpn-tunnel", + "tools", + "database", + "patch-manager01", + ], + status: "Active", + checks: { + icon: "status_up", + num: 25, + }, + "cpu-load": 32, + firstUrl: "https://en.wikipedia.org/wiki/Brno", + firstUrlLabel: "Brno", + secondUrl: "https://en.wikipedia.org/wiki/VMware_Workstation", + secondUrlLabel: "Workstation", + }, + { + position: 10, + name: "Man-LT-JYJ4AD5", + features: [ + "remote-access-vpn-tunnel", + "database", + "orion-ape-backup", + "patch-manager01", + ], + status: "Active", + checks: { + icon: "status_up", + num: 25, + }, + "cpu-load": 64, + firstUrl: "https://en.wikipedia.org/wiki/Kyiv", + firstUrlLabel: "Kyiv", + secondUrl: "https://en.wikipedia.org/wiki/VMware_Workstation", + secondUrlLabel: "Workstation", + }, + { + position: 11, + name: "Man-LT-111", + features: [], + status: "Active", + checks: { + icon: "status_external", + num: 25, + }, + "cpu-load": 55, + firstUrl: "https://en.wikipedia.org/wiki/Brno", + firstUrlLabel: "Brno", + secondUrl: "https://en.wikipedia.org/wiki/VMware_Workstation", + secondUrlLabel: "Workstation", + }, + { + position: 12, + name: "Man-LT-2222", + features: [ + "remote-access-vpn-tunnel", + "tools", + "database", + "orion-ape-backup", + "patch-manager01", + ], + status: "Active", + checks: { + icon: "status_inactive", + num: 25, + }, + "cpu-load": 34, + firstUrl: "https://en.wikipedia.org/wiki/Brno", + firstUrlLabel: "Brno", + secondUrl: "https://en.wikipedia.org/wiki/VMware_Workstation", + secondUrlLabel: "Workstation", + }, + { + position: 13, + name: "Man-LT-333333", + features: [ + "remote-access-vpn-tunnel", + "tools", + "database", + "patch-manager01", + ], + status: "Active", + checks: { + icon: "status_up", + num: 25, + }, + "cpu-load": 56, + firstUrl: "https://en.wikipedia.org/wiki/Kyiv", + firstUrlLabel: "Kyiv", + secondUrl: "https://en.wikipedia.org/wiki/VMware_Workstation", + secondUrlLabel: "Workstation", + }, + { + position: 14, + name: "Man-LT-444444", + features: [ + "remote-access-vpn-tunnel", + "database", + "orion-ape-backup", + "patch-manager01", + ], + status: "Active", + checks: { + icon: "status_up", + num: 25, + }, + "cpu-load": 26, + firstUrl: "https://en.wikipedia.org/wiki/Kyiv", + firstUrlLabel: "Kyiv", + secondUrl: "https://en.wikipedia.org/wiki/VMware_Workstation", + secondUrlLabel: "Workstation", + }, + { + position: 15, + name: "Man-LT-555555", + features: [ + "remote-access-vpn-tunnel", + "database", + "orion-ape-backup", + "patch-manager01", + ], + status: "Active", + checks: { + icon: "status_up", + num: 25, + }, + "cpu-load": 76, + firstUrl: "https://en.wikipedia.org/wiki/Austin", + firstUrlLabel: "Austin", + secondUrl: "https://en.wikipedia.org/wiki/VMware_Workstation", + secondUrlLabel: "Workstation", + }, + { + position: 16, + name: "FOCUS-SVR-02258", + features: ["remote-access-vpn-tunnel", "patch-manager01"], + status: "Active", + checks: { + icon: "status_up", + num: 25, + }, + "cpu-load": 86, + firstUrl: "https://en.wikipedia.org/wiki/Brno", + firstUrlLabel: "Brno", + secondUrl: "https://en.wikipedia.org/wiki/VMware_Workstation", + secondUrlLabel: "Workstation", + }, + { + position: 17, + name: "FOCUS-SVR-03312", + features: ["tools", "database", "orion-ape-backup"], + status: "Active", + checks: { + icon: "status_critical", + num: 25, + }, + "cpu-load": 47, + firstUrl: "https://en.wikipedia.org/wiki/Brno", + firstUrlLabel: "Brno", + secondUrl: "https://en.wikipedia.org/wiki/VMware_Workstation", + secondUrlLabel: "Workstation", + }, + { + position: 18, + name: "FOCUS-SVR-02258", + features: [ + "remote-access-vpn-tunnel", + "database", + "orion-ape-backup", + "patch-manager01", + ], + status: "Active", + checks: { + icon: "status_down", + num: 25, + }, + "cpu-load": 53, + firstUrl: "https://en.wikipedia.org/wiki/Kyiv", + firstUrlLabel: "Kyiv", + secondUrl: "https://en.wikipedia.org/wiki/VMware_Workstation", + secondUrlLabel: "Workstation", + }, + { + position: 19, + name: "Man-LT-JYJ4AD5", + features: [ + "remote-access-vpn-tunnel", + "tools", + "database", + "orion-ape-backup", + ], + status: "Active", + checks: { + icon: "status_up", + num: 25, + }, + "cpu-load": 32, + firstUrl: "https://en.wikipedia.org/wiki/Kyiv", + firstUrlLabel: "Kyiv", + secondUrl: "https://en.wikipedia.org/wiki/VirtualBox", + secondUrlLabel: "VirtualBox", + }, + { + position: 20, + name: "Man-LT-JYJ425", + features: [ + "remote-access-vpn-tunnel", + "tools", + "database", + "orion-ape-backup", + ], + status: "Active", + checks: { + icon: "status_up", + num: 25, + }, + "cpu-load": 22, + firstUrl: "https://en.wikipedia.org/wiki/Kyiv", + firstUrlLabel: "Kyiv", + secondUrl: "https://en.wikipedia.org/wiki/VirtualBox", + secondUrlLabel: "VirtualBox", + }, + { + position: 21, + name: "Man-LT-JYJ4333", + features: [ + "remote-access-vpn-tunnel", + "tools", + "database", + "orion-ape-backup", + ], + status: "Active", + checks: { + icon: "status_up", + num: 25, + }, + "cpu-load": 12, + firstUrl: "https://en.wikipedia.org/wiki/Kyiv", + firstUrlLabel: "Kyiv", + secondUrl: "https://en.wikipedia.org/wiki/VirtualBox", + secondUrlLabel: "VirtualBox", + }, + { + position: 22, + name: "FOCUS-SVR-02258", + features: ["remote-access-vpn-tunnel", "patch-manager01"], + status: "Active", + checks: { + icon: "status_up", + num: 25, + }, + "cpu-load": 86, + firstUrl: "https://en.wikipedia.org/wiki/Austin", + firstUrlLabel: "Austin", + secondUrl: "https://en.wikipedia.org/wiki/VMware_Workstation", + secondUrlLabel: "Workstation", + }, + { + position: 23, + name: "Man-LT-JYJ4AD5", + features: [ + "remote-access-vpn-tunnel", + "tools", + "database", + "orion-ape-backup", + "patch-manager01", + ], + status: "Active", + checks: { + icon: "status_inactive", + num: 25, + }, + "cpu-load": 35, + firstUrl: "https://en.wikipedia.org/wiki/Austin", + firstUrlLabel: "Austin", + secondUrl: "https://en.wikipedia.org/wiki/VMware_Workstation", + secondUrlLabel: "Workstation", + }, + { + position: 24, + name: "Man-LT-JYJ4AD5", + features: [ + "remote-access-vpn-tunnel", + "tools", + "database", + "patch-manager01", + ], + status: "Active", + checks: { + icon: "status_up", + num: 25, + }, + "cpu-load": 32, + firstUrl: "https://en.wikipedia.org/wiki/Brno", + firstUrlLabel: "Brno", + secondUrl: "https://en.wikipedia.org/wiki/VMware_Workstation", + secondUrlLabel: "Workstation", + }, + { + position: 25, + name: "Man-LT-JYJ4AD5", + features: [ + "remote-access-vpn-tunnel", + "database", + "orion-ape-backup", + "patch-manager01", + ], + status: "Active", + checks: { + icon: "status_up", + num: 25, + }, + "cpu-load": 64, + firstUrl: "https://en.wikipedia.org/wiki/Kyiv", + firstUrlLabel: "Kyiv", + secondUrl: "https://en.wikipedia.org/wiki/VMware_Workstation", + secondUrlLabel: "Workstation", + }, + { + position: 26, + name: "Man-LT-111", + features: [], + status: "Active", + checks: { + icon: "status_external", + num: 25, + }, + "cpu-load": 55, + firstUrl: "https://en.wikipedia.org/wiki/Brno", + firstUrlLabel: "Brno", + secondUrl: "https://en.wikipedia.org/wiki/VMware_Workstation", + secondUrlLabel: "Workstation", + }, + { + position: 27, + name: "Man-LT-2222", + features: [ + "remote-access-vpn-tunnel", + "tools", + "database", + "orion-ape-backup", + "patch-manager01", + ], + status: "Active", + checks: { + icon: "status_inactive", + num: 25, + }, + "cpu-load": 34, + firstUrl: "https://en.wikipedia.org/wiki/Brno", + firstUrlLabel: "Brno", + secondUrl: "https://en.wikipedia.org/wiki/VMware_Workstation", + secondUrlLabel: "Workstation", + }, + { + position: 28, + name: "Man-LT-333333", + features: [ + "remote-access-vpn-tunnel", + "tools", + "database", + "patch-manager01", + ], + status: "Active", + checks: { + icon: "status_up", + num: 25, + }, + "cpu-load": 56, + firstUrl: "https://en.wikipedia.org/wiki/Kyiv", + firstUrlLabel: "Kyiv", + secondUrl: "https://en.wikipedia.org/wiki/VMware_Workstation", + secondUrlLabel: "Workstation", + }, + { + position: 29, + name: "Man-LT-444444", + features: [ + "remote-access-vpn-tunnel", + "database", + "orion-ape-backup", + "patch-manager01", + ], + status: "Active", + checks: { + icon: "status_up", + num: 25, + }, + "cpu-load": 26, + firstUrl: "https://en.wikipedia.org/wiki/Kyiv", + firstUrlLabel: "Kyiv", + secondUrl: "https://en.wikipedia.org/wiki/VMware_Workstation", + secondUrlLabel: "Workstation", + }, + { + position: 30, + name: "Man-LT-555555", + features: [ + "remote-access-vpn-tunnel", + "database", + "orion-ape-backup", + "patch-manager01", + ], + status: "Active", + checks: { + icon: "status_up", + num: 25, + }, + "cpu-load": 76, + firstUrl: "https://en.wikipedia.org/wiki/Austin", + firstUrlLabel: "Austin", + secondUrl: "https://en.wikipedia.org/wiki/VMware_Workstation", + secondUrlLabel: "Workstation", + }, + { + position: 31, + name: "FOCUS-SVR-02258", + features: ["remote-access-vpn-tunnel", "patch-manager01"], + status: "Active", + checks: { + icon: "status_up", + num: 25, + }, + "cpu-load": 86, + firstUrl: "https://en.wikipedia.org/wiki/Brno", + firstUrlLabel: "Brno", + secondUrl: "https://en.wikipedia.org/wiki/VMware_Workstation", + secondUrlLabel: "Workstation", + }, + { + position: 32, + name: "FOCUS-SVR-03312", + features: ["tools", "database", "orion-ape-backup"], + status: "Active", + checks: { + icon: "status_critical", + num: 25, + }, + "cpu-load": 47, + firstUrl: "https://en.wikipedia.org/wiki/Brno", + firstUrlLabel: "Brno", + secondUrl: "https://en.wikipedia.org/wiki/VMware_Workstation", + secondUrlLabel: "Workstation", + }, + { + position: 33, + name: "FOCUS-SVR-02258", + features: [ + "remote-access-vpn-tunnel", + "database", + "orion-ape-backup", + "patch-manager01", + ], + status: "Active", + checks: { + icon: "status_down", + num: 25, + }, + "cpu-load": 53, + firstUrl: "https://en.wikipedia.org/wiki/Kyiv", + firstUrlLabel: "Kyiv", + secondUrl: "https://en.wikipedia.org/wiki/VMware_Workstation", + secondUrlLabel: "Workstation", + }, + { + position: 34, + name: "Man-LT-JYJ4AD5", + features: [ + "remote-access-vpn-tunnel", + "tools", + "database", + "orion-ape-backup", + ], + status: "Active", + checks: { + icon: "status_up", + num: 25, + }, + "cpu-load": 32, + firstUrl: "https://en.wikipedia.org/wiki/Kyiv", + firstUrlLabel: "Kyiv", + secondUrl: "https://en.wikipedia.org/wiki/VirtualBox", + secondUrlLabel: "VirtualBox", + }, + { + position: 35, + name: "Man-LT-JYJ425", + features: [ + "remote-access-vpn-tunnel", + "tools", + "database", + "orion-ape-backup", + ], + status: "Active", + checks: { + icon: "status_up", + num: 25, + }, + "cpu-load": 22, + firstUrl: "https://en.wikipedia.org/wiki/Kyiv", + firstUrlLabel: "Kyiv", + secondUrl: "https://en.wikipedia.org/wiki/VirtualBox", + secondUrlLabel: "VirtualBox", + }, + { + position: 36, + name: "Man-LT-JYJ4333", + features: [ + "remote-access-vpn-tunnel", + "tools", + "database", + "orion-ape-backup", + ], + status: "Active", + checks: { + icon: "status_up", + num: 25, + }, + "cpu-load": 12, + firstUrl: "https://en.wikipedia.org/wiki/Kyiv", + firstUrlLabel: "Kyiv", + secondUrl: "https://en.wikipedia.org/wiki/VirtualBox", + secondUrlLabel: "VirtualBox", + }, + { + position: 37, + name: "FOCUS-SVR-02258", + features: ["remote-access-vpn-tunnel", "patch-manager01"], + status: "Active", + checks: { + icon: "status_up", + num: 25, + }, + "cpu-load": 86, + firstUrl: "https://en.wikipedia.org/wiki/Austin", + firstUrlLabel: "Austin", + secondUrl: "https://en.wikipedia.org/wiki/VMware_Workstation", + secondUrlLabel: "Workstation", + }, + { + position: 38, + name: "Man-LT-JYJ4AD5", + features: [ + "remote-access-vpn-tunnel", + "tools", + "database", + "orion-ape-backup", + "patch-manager01", + ], + status: "Active", + checks: { + icon: "status_inactive", + num: 25, + }, + "cpu-load": 35, + firstUrl: "https://en.wikipedia.org/wiki/Austin", + firstUrlLabel: "Austin", + secondUrl: "https://en.wikipedia.org/wiki/VMware_Workstation", + secondUrlLabel: "Workstation", + }, + { + position: 39, + name: "Man-LT-JYJ4AD5", + features: [ + "remote-access-vpn-tunnel", + "tools", + "database", + "patch-manager01", + ], + status: "Active", + checks: { + icon: "status_up", + num: 25, + }, + "cpu-load": 32, + firstUrl: "https://en.wikipedia.org/wiki/Brno", + firstUrlLabel: "Brno", + secondUrl: "https://en.wikipedia.org/wiki/VMware_Workstation", + secondUrlLabel: "Workstation", + }, + { + position: 40, + name: "Man-LT-JYJ4AD5", + features: [ + "remote-access-vpn-tunnel", + "database", + "orion-ape-backup", + "patch-manager01", + ], + status: "Active", + checks: { + icon: "status_up", + num: 25, + }, + "cpu-load": 64, + firstUrl: "https://en.wikipedia.org/wiki/Kyiv", + firstUrlLabel: "Kyiv", + secondUrl: "https://en.wikipedia.org/wiki/VMware_Workstation", + secondUrlLabel: "Workstation", + }, + { + position: 41, + name: "Man-LT-111", + features: [], + status: "Active", + checks: { + icon: "status_external", + num: 25, + }, + "cpu-load": 55, + firstUrl: "https://en.wikipedia.org/wiki/Brno", + firstUrlLabel: "Brno", + secondUrl: "https://en.wikipedia.org/wiki/VMware_Workstation", + secondUrlLabel: "Workstation", + }, + { + position: 42, + name: "Man-LT-2222", + features: [ + "remote-access-vpn-tunnel", + "tools", + "database", + "orion-ape-backup", + "patch-manager01", + ], + status: "Active", + checks: { + icon: "status_inactive", + num: 25, + }, + "cpu-load": 34, + firstUrl: "https://en.wikipedia.org/wiki/Brno", + firstUrlLabel: "Brno", + secondUrl: "https://en.wikipedia.org/wiki/VMware_Workstation", + secondUrlLabel: "Workstation", + }, + { + position: 43, + name: "Man-LT-333333", + features: [ + "remote-access-vpn-tunnel", + "tools", + "database", + "patch-manager01", + ], + status: "Active", + checks: { + icon: "status_up", + num: 25, + }, + "cpu-load": 56, + firstUrl: "https://en.wikipedia.org/wiki/Kyiv", + firstUrlLabel: "Kyiv", + secondUrl: "https://en.wikipedia.org/wiki/VMware_Workstation", + secondUrlLabel: "Workstation", + }, + { + position: 44, + name: "Man-LT-444444", + features: [ + "remote-access-vpn-tunnel", + "database", + "orion-ape-backup", + "patch-manager01", + ], + status: "Active", + checks: { + icon: "status_up", + num: 25, + }, + "cpu-load": 26, + firstUrl: "https://en.wikipedia.org/wiki/Kyiv", + firstUrlLabel: "Kyiv", + secondUrl: "https://en.wikipedia.org/wiki/VMware_Workstation", + secondUrlLabel: "Workstation", + }, + { + position: 45, + name: "Man-LT-555555", + features: [ + "remote-access-vpn-tunnel", + "database", + "orion-ape-backup", + "patch-manager01", + ], + status: "Active", + checks: { + icon: "status_up", + num: 25, + }, + "cpu-load": 76, + firstUrl: "https://en.wikipedia.org/wiki/Austin", + firstUrlLabel: "Austin", + secondUrl: "https://en.wikipedia.org/wiki/VMware_Workstation", + secondUrlLabel: "Workstation", + }, +]; +\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\`, + "overview/hero/widget-configs/kpi.ts": \\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\`// © 2022 SolarWinds Worldwide, LLC. All rights reserved. +// +// Permission is hereby granted, free of charge, to any person obtaining a copy +// of this software and associated documentation files (the "Software"), to +// deal in the Software without restriction, including without limitation the +// rights to use, copy, modify, merge, publish, distribute, sublicense, and/or +// sell copies of the Software, and to permit persons to whom the Software is +// furnished to do so, subject to the following conditions: +// +// The above copyright notice and this permission notice shall be included in +// all copies or substantial portions of the Software. +// +// THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR +// IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, +// FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE +// AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER +// LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, +// OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN +// THE SOFTWARE. + +import { + DEFAULT_PIZZAGNA_ROOT, + IProviderConfiguration, + IRefresherProperties, + IWidget, + KpiComponent, + NOVA_KPI_DATASOURCE_ADAPTER, + PizzagnaLayer, + WellKnownProviders, +} from "@nova-ui/dashboards"; + +import { + HarryPotterAverageRatingDataSource, + HarryPotterRatingsCountDataSource, +} from "../data/kpi-datasources"; + +export const kpiConfig: IWidget = { + id: "kpiWidgetId", + type: "kpi", + pizzagna: { + [PizzagnaLayer.Configuration]: { + [DEFAULT_PIZZAGNA_ROOT]: { + providers: { + [WellKnownProviders.Refresher]: { + properties: { + // Configuring the refresher interval so that our data source is invoked every ten minutes + interval: 60 * 10, + enabled: true, + } as IRefresherProperties, + } as Partial, + }, + }, + header: { + properties: { + title: "Harry Potter and the Sorcerer's Stone", + subtitle: "By J. K. Rowling", + }, + }, + tiles: { + properties: { + nodes: ["kpi1", "kpi2"], + }, + }, + kpi1: { + id: "kpi1", + componentType: KpiComponent.lateLoadKey, + properties: { + widgetData: { + label: "Average Rating", + backgroundColor: "var(--nui-color-chart-three)", + units: "out of 5 Stars", + }, + }, + providers: { + [WellKnownProviders.DataSource]: { + // Setting the data source providerId for the tile with id "kpi1" + providerId: + HarryPotterAverageRatingDataSource.providerId, + } as IProviderConfiguration, + [WellKnownProviders.Adapter]: { + providerId: NOVA_KPI_DATASOURCE_ADAPTER, + properties: { + componentId: "kpi1", + propertyPath: "widgetData", + }, + } as IProviderConfiguration, + }, + }, + kpi2: { + id: "kpi2", + componentType: KpiComponent.lateLoadKey, + properties: { + widgetData: { + label: "Reader Feedback", + units: "Ratings", + }, + }, + providers: { + [WellKnownProviders.DataSource]: { + // Setting the data source providerId for the tile with id "kpi" + providerId: + HarryPotterRatingsCountDataSource.providerId, + } as IProviderConfiguration, + [WellKnownProviders.Adapter]: { + providerId: NOVA_KPI_DATASOURCE_ADAPTER, + properties: { + componentId: "kpi2", + propertyPath: "widgetData", + }, + } as IProviderConfiguration, + }, + }, + }, + }, +}; +\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\`, + "overview/hero/widget-configs/proportional.ts": \\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\`// © 2022 SolarWinds Worldwide, LLC. All rights reserved. +// +// Permission is hereby granted, free of charge, to any person obtaining a copy +// of this software and associated documentation files (the "Software"), to +// deal in the Software without restriction, including without limitation the +// rights to use, copy, modify, merge, publish, distribute, sublicense, and/or +// sell copies of the Software, and to permit persons to whom the Software is +// furnished to do so, subject to the following conditions: +// +// The above copyright notice and this permission notice shall be included in +// all copies or substantial portions of the Software. +// +// THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR +// IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, +// FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE +// AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER +// LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, +// OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN +// THE SOFTWARE. + +import { + DEFAULT_PIZZAGNA_ROOT, + IProportionalWidgetChartOptions, + IProviderConfiguration, + IWidget, + LegendPlacement, + PizzagnaLayer, + ProportionalWidgetChartTypes, + WellKnownProviders, +} from "@nova-ui/dashboards"; + +import { BeerReviewCountsByCityMockDataSource } from "../data/proportional-datasources"; + +export const proportionalConfig: IWidget = { + id: "proportionalWidgetId", + type: "proportional", + pizzagna: { + [PizzagnaLayer.Configuration]: { + [DEFAULT_PIZZAGNA_ROOT]: { + providers: { + [WellKnownProviders.Refresher]: { + properties: { + interval: 0, + }, + }, + }, + }, + header: { + properties: { + title: "Beer Review Tally by City", + subtitle: "These People Love Beer", + }, + }, + chart: { + providers: { + [WellKnownProviders.DataSource]: { + providerId: + BeerReviewCountsByCityMockDataSource.providerId, + } as IProviderConfiguration, + }, + properties: { + configuration: { + chartOptions: { + type: ProportionalWidgetChartTypes.DonutChart, + legendPlacement: LegendPlacement.Right, + } as IProportionalWidgetChartOptions, + }, + }, + }, + }, + }, +}; +\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\`, + "overview/hero/widget-configs/risk-score.ts": \\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\`// © 2023 SolarWinds Worldwide, LLC. All rights reserved. +// +// Permission is hereby granted, free of charge, to any person obtaining a copy +// of this software and associated documentation files (the "Software"), to +// deal in the Software without restriction, including without limitation the +// rights to use, copy, modify, merge, publish, distribute, sublicense, and/or +// sell copies of the Software, and to permit persons to whom the Software is +// furnished to do so, subject to the following conditions: +// +// The above copyright notice and this permission notice shall be included in +// all copies or substantial portions of the Software. +// +// THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR +// IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, +// FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE +// AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER +// LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, +// OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN +// THE SOFTWARE. + +import { + DEFAULT_PIZZAGNA_ROOT, + IProviderConfiguration, + IRefresherProperties, + IWidget, + RiskScoreTileComponent, + NOVA_KPI_DATASOURCE_ADAPTER, + PizzagnaLayer, + WellKnownProviders, +} from "@nova-ui/dashboards"; + +import { HarryPotterAverageRatingDataSource } from "../data/kpi-datasources"; + +export const riskScoreConfig: IWidget = { + id: "riskScoreWidgetId", + type: "risk-score", + pizzagna: { + [PizzagnaLayer.Configuration]: { + [DEFAULT_PIZZAGNA_ROOT]: { + providers: { + [WellKnownProviders.Refresher]: { + properties: { + // Configuring the refresher interval so that our data source is invoked every ten minutes + interval: 60 * 10, + enabled: true, + } as IRefresherProperties, + } as Partial, + }, + }, + header: { + properties: { + title: "Harry Potter and the Sorcerer's Stone", + subtitle: "By J. K. Rowling", + }, + }, + tiles: { + properties: { + nodes: ["riskScore1"], + }, + }, + riskScore1: { + id: "riskScore1", + componentType: RiskScoreTileComponent.lateLoadKey, + properties: { + widgetData: { + minValue: 0, + maxValue: 5, + useStaticLabel: false, + staticLabel: undefined, + label: \\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\`Average Rating\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\`, + description: \\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\`Harry Potter and the Sorcerer's Stone By J. K. Rowling Average Rating Risk Score\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\`, + }, + }, + providers: { + [WellKnownProviders.DataSource]: { + // Setting the data source providerId for the tile with id "riskScore1" + providerId: + HarryPotterAverageRatingDataSource.providerId, + } as IProviderConfiguration, + [WellKnownProviders.Adapter]: { + providerId: NOVA_KPI_DATASOURCE_ADAPTER, + properties: { + componentId: "riskScore1", + propertyPath: "widgetData", + }, + } as IProviderConfiguration, + }, + }, + }, + }, +}; +\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\`, + "overview/hero/widget-configs/table.ts": \\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\`// © 2022 SolarWinds Worldwide, LLC. All rights reserved. +// +// Permission is hereby granted, free of charge, to any person obtaining a copy +// of this software and associated documentation files (the "Software"), to +// deal in the Software without restriction, including without limitation the +// rights to use, copy, modify, merge, publish, distribute, sublicense, and/or +// sell copies of the Software, and to permit persons to whom the Software is +// furnished to do so, subject to the following conditions: +// +// The above copyright notice and this permission notice shall be included in +// all copies or substantial portions of the Software. +// +// THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR +// IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, +// FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE +// AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER +// LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, +// OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN +// THE SOFTWARE. + +import { + ITableWidgetColumnConfig, + ITableWidgetSorterConfig, + IWidget, + PizzagnaLayer, + RawFormatterComponent, + WellKnownProviders, +} from "@nova-ui/dashboards"; + +import { BeerDataSource } from "../data/table/beer-data-source"; + +export const tableConfig: IWidget = { + id: "tableWidgetId", + type: "table", + pizzagna: { + [PizzagnaLayer.Configuration]: { + header: { + properties: { + title: "Stupendous Suds", + subtitle: "Try These Brilliant Brews", + }, + }, + table: { + providers: { + [WellKnownProviders.DataSource]: { + providerId: BeerDataSource.providerId, + }, + }, + properties: { + configuration: { + columns: [ + { + id: "column1", + label: "Beer Name", + isActive: true, + width: 185, + formatter: { + componentType: + RawFormatterComponent.lateLoadKey, + properties: { + dataFieldIds: { + value: "name", + }, + }, + }, + }, + { + id: "column2", + label: "Tagline", + isActive: true, + width: 250, + formatter: { + componentType: + RawFormatterComponent.lateLoadKey, + properties: { + dataFieldIds: { + value: "tagline", + }, + }, + }, + }, + { + id: "column3", + label: "First Brewed", + isActive: true, + formatter: { + componentType: + RawFormatterComponent.lateLoadKey, + properties: { + dataFieldIds: { + value: "first_brewed", + }, + }, + }, + }, + { + id: "column4", + label: "Description", + isActive: true, + width: 275, + formatter: { + componentType: + RawFormatterComponent.lateLoadKey, + properties: { + dataFieldIds: { + value: "description", + }, + }, + }, + }, + ] as ITableWidgetColumnConfig[], + sorterConfiguration: { + descendantSorting: false, + sortBy: "", + } as ITableWidgetSorterConfig, + hasVirtualScroll: true, + }, + }, + }, + }, + }, +}; +\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\`, + "overview/hero/widget-configs/timeseries.ts": \\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\`// © 2022 SolarWinds Worldwide, LLC. All rights reserved. +// +// Permission is hereby granted, free of charge, to any person obtaining a copy +// of this software and associated documentation files (the "Software"), to +// deal in the Software without restriction, including without limitation the +// rights to use, copy, modify, merge, publish, distribute, sublicense, and/or +// sell copies of the Software, and to permit persons to whom the Software is +// furnished to do so, subject to the following conditions: +// +// The above copyright notice and this permission notice shall be included in +// all copies or substantial portions of the Software. +// +// THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR +// IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, +// FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE +// AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER +// LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, +// OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN +// THE SOFTWARE. + +import moment from "moment/moment"; + +import { + DEFAULT_PIZZAGNA_ROOT, + IProviderConfiguration, + ISerializableTimeframe, + ITimeseriesItemConfiguration, + IWidget, + LegendPlacement, + WellKnownProviders, +} from "@nova-ui/dashboards"; + +import { BeerVsReadingMockDataSource } from "../data/timeseries-data-sources"; + +export const timeseriesConfig: IWidget = { + id: "timeseriesWidgetId", + type: "timeseries", + pizzagna: { + configuration: { + [DEFAULT_PIZZAGNA_ROOT]: { + providers: { + [WellKnownProviders.DataSource]: { + providerId: BeerVsReadingMockDataSource.providerId, + } as IProviderConfiguration, + }, + }, + header: { + properties: { + title: "Primary Leisure Activity Over Time", + subtitle: "Survey of 1000 Solarians", + }, + }, + chart: { + providers: { + [WellKnownProviders.Adapter]: { + properties: { + series: [ + { + id: "series-1", + label: "Beer Tasting", + selectedSeriesId: "series-1", + }, + { + id: "series-2", + label: "Reading", + selectedSeriesId: "series-2", + }, + ] as ITimeseriesItemConfiguration[], + }, + } as Partial, + }, + properties: { + configuration: { + legendPlacement: LegendPlacement.Right, + enableZoom: true, + leftAxisLabel: "Solarians (%)", + }, + }, + }, + timeframeSelection: { + properties: { + timeframe: { + selectedPresetId: "last7Days", + } as ISerializableTimeframe, + minDate: moment().subtract(10, "days").format(), + maxDate: moment().format(), + }, + }, + }, + }, +}; +\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\`, + "overview/overview-docs.component.ts": \\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\`// © 2022 SolarWinds Worldwide, LLC. All rights reserved. +// +// Permission is hereby granted, free of charge, to any person obtaining a copy +// of this software and associated documentation files (the "Software"), to +// deal in the Software without restriction, including without limitation the +// rights to use, copy, modify, merge, publish, distribute, sublicense, and/or +// sell copies of the Software, and to permit persons to whom the Software is +// furnished to do so, subject to the following conditions: +// +// The above copyright notice and this permission notice shall be included in +// all copies or substantial portions of the Software. +// +// THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR +// IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, +// FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE +// AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER +// LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, +// OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN +// THE SOFTWARE. + +import { Component } from "@angular/core"; + +@Component({ + selector: "nui-dashboard-overview-docs", + templateUrl: "./overview-docs.component.html", + standalone: false, +}) +export class OverviewDocsComponent {} +\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\`, + "overview/overview.module.ts": \\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\`// © 2022 SolarWinds Worldwide, LLC. All rights reserved. +// +// Permission is hereby granted, free of charge, to any person obtaining a copy +// of this software and associated documentation files (the "Software"), to +// deal in the Software without restriction, including without limitation the +// rights to use, copy, modify, merge, publish, distribute, sublicense, and/or +// sell copies of the Software, and to permit persons to whom the Software is +// furnished to do so, subject to the following conditions: +// +// The above copyright notice and this permission notice shall be included in +// all copies or substantial portions of the Software. +// +// THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR +// IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, +// FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE +// AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER +// LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, +// OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN +// THE SOFTWARE. + +import { CommonModule } from "@angular/common"; +import { NgModule } from "@angular/core"; +import { RouterModule } from "@angular/router"; + +import { + NuiBusyModule, + NuiButtonModule, + NuiDocsModule, + NuiIconModule, + NuiMessageModule, + NuiSwitchModule, +} from "@nova-ui/bits"; +import { + ConfiguratorHeadingService, + IFormatterDefinition, + LinkFormatterComponent, + NuiDashboardsModule, + WellKnownPathKey, + WidgetTypesService, +} from "@nova-ui/dashboards"; + +import { HeroDashboardComponent } from "./hero/dashboard/hero-dashboard.component"; +import { + HarryPotterAverageRatingDataSource, + HarryPotterRatingsCountDataSource, +} from "./hero/data/kpi-datasources"; +import { + BeerReviewCountsByCityMockDataSource, + BeerReviewCountsByCityMockDataSource2, +} from "./hero/data/proportional-datasources"; +import { BeerDataSource } from "./hero/data/table/beer-data-source"; +import { RandomUserDataSource } from "./hero/data/table/random-user-data-source"; +import { + BeerVsReadingMockDataSource, + LoungingVsFrisbeeGolfMockDataSource, +} from "./hero/data/timeseries-data-sources"; +import { OverviewDocsComponent } from "./overview-docs.component"; + +const routes = [ + { + path: "", + component: OverviewDocsComponent, + data: { + srlc: { + hideIndicator: true, + }, + showThemeSwitcher: true, + }, + }, + { + path: "hero", + component: HeroDashboardComponent, + data: { + srlc: { + hideIndicator: true, + }, + }, + }, +]; + +@NgModule({ + imports: [ + CommonModule, + NuiDashboardsModule, + NuiBusyModule, + NuiButtonModule, + NuiDocsModule, + NuiMessageModule, + NuiSwitchModule, + NuiIconModule, + RouterModule.forChild(routes), + ], + declarations: [OverviewDocsComponent, HeroDashboardComponent], + providers: [ConfiguratorHeadingService], +}) +export default class OverviewModule { + constructor(private widgetTypesService: WidgetTypesService) { + this.setupDataSourceProviders(); + this.setupProportionalLegendFormatters(); + } + + private setupDataSourceProviders() { + this.setDataSourceProviders("table", [ + RandomUserDataSource.providerId, + BeerDataSource.providerId, + ]); + this.setDataSourceProviders("kpi", [ + HarryPotterAverageRatingDataSource.providerId, + HarryPotterRatingsCountDataSource.providerId, + ]); + this.setDataSourceProviders("risk-score", [ + HarryPotterAverageRatingDataSource.providerId, + HarryPotterRatingsCountDataSource.providerId, + ]); + this.setDataSourceProviders("proportional", [ + BeerReviewCountsByCityMockDataSource.providerId, + BeerReviewCountsByCityMockDataSource2.providerId, + ]); + this.setDataSourceProviders("timeseries", [ + BeerVsReadingMockDataSource.providerId, + LoungingVsFrisbeeGolfMockDataSource.providerId, + ]); + } + + private setDataSourceProviders(type: string, providers: string[]) { + const widgetTemplate = this.widgetTypesService.getWidgetType(type, 1); + this.widgetTypesService.setNode( + widgetTemplate, + "configurator", + WellKnownPathKey.DataSourceProviders, + providers + ); + } + + private setupProportionalLegendFormatters() { + const formatters: IFormatterDefinition[] = [ + { + componentType: LinkFormatterComponent.lateLoadKey, + label: $localize\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\`Link\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\`, + dataTypes: { + value: "label", + link: "link", + }, + }, + ]; + + const widgetTemplate = this.widgetTypesService.getWidgetType( + "proportional", + 1 + ); + this.widgetTypesService.setNode( + widgetTemplate, + "configurator", + WellKnownPathKey.Formatters, + formatters + ); + } +} +\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\`, + "tutorials/customization/configurator-section/custom-configurator-section/custom-configurator-section.example.component.ts": \\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\`// © 2022 SolarWinds Worldwide, LLC. All rights reserved. +// +// Permission is hereby granted, free of charge, to any person obtaining a copy +// of this software and associated documentation files (the "Software"), to +// deal in the Software without restriction, including without limitation the +// rights to use, copy, modify, merge, publish, distribute, sublicense, and/or +// sell copies of the Software, and to permit persons to whom the Software is +// furnished to do so, subject to the following conditions: +// +// The above copyright notice and this permission notice shall be included in +// all copies or substantial portions of the Software. +// +// THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR +// IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, +// FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE +// AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER +// LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, +// OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN +// THE SOFTWARE. + +import { HttpClient, HttpErrorResponse } from "@angular/common/http"; +import { + ChangeDetectionStrategy, + ChangeDetectorRef, + Component, + EventEmitter, + Injectable, + Input, + OnChanges, + OnDestroy, + OnInit, + Output, + SimpleChanges, +} from "@angular/core"; +import { FormBuilder, FormGroup, Validators } from "@angular/forms"; +import { GridsterConfig, GridsterItem } from "angular-gridster2"; +// eslint-disable-next-line import/no-deprecated +import { BehaviorSubject, combineLatest, Observable } from "rxjs"; +// eslint-disable-next-line import/no-deprecated +import { finalize, map, startWith } from "rxjs/operators"; + +import { DataSourceService, IFilteringOutputs } from "@nova-ui/bits"; +import { + ComponentRegistryService, + DATA_SOURCE, + DEFAULT_PIZZAGNA_ROOT, + IDashboard, + IHasChangeDetector, + IHasForm, + IKpiData, + IProviderConfiguration, + IRefresherProperties, + IWidget, + IWidgets, + KpiComponent, + NOVA_KPI_DATASOURCE_ADAPTER, + PizzagnaLayer, + ProviderRegistryService, + WellKnownPathKey, + WellKnownProviders, + WidgetTypesService, +} from "@nova-ui/dashboards"; + +/** + * A custom version of the KpiDescriptionConfigurationComponent provided by the dashboards framework. + * --- + * For this example, the existing background color selection functionality has been replaced by custom + * template content. + */ +@Component({ + selector: "custom-kpi-description-configuration", + template: \\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\` + + +
+
+ + + +
+ + +
+
+ Custom Content +
+
+ The default version of this configurator section + displays a background color selector here. +
+
+ + +
+ + + +
+
+
+ \\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\`, + styleUrls: ["./custom-configurator-section.example.component.less"], + changeDetection: ChangeDetectionStrategy.OnPush, + standalone: false, +}) +// Remember to declare this class in the parent module +export class CustomKpiDescriptionConfigurationComponent + implements OnInit, OnChanges, IHasChangeDetector, IHasForm +{ + // Ensure that the lateLoadKey value matches class name + public static lateLoadKey = "CustomKpiDescriptionConfigurationComponent"; + + @Input() componentId: string; + @Input() configurableUnits: boolean; + + @Input() label: string = ""; + @Input() units: string = ""; + + @Output() formReady = new EventEmitter(); + + public form: FormGroup; + public subtitle$: Observable; + + constructor( + public changeDetector: ChangeDetectorRef, + private formBuilder: FormBuilder + ) {} + + public ngOnInit(): void { + this.form = this.formBuilder.group({ + label: [this.label, [Validators.required]], + }); + + if (this.configurableUnits) { + this.form.addControl("units", this.formBuilder.control(this.units)); + } + + const label = this.form.get("label"); + // eslint-disable-next-line import/no-deprecated + const labelValue = label?.valueChanges.pipe(startWith(label?.value)); + + // eslint-disable-next-line import/no-deprecated + this.subtitle$ = combineLatest([ + labelValue?.pipe(map((t) => t || $localize\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\`no label\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\`)), + ]).pipe(map((labels) => labels.join(", "))); + + this.formReady.emit(this.form); + } + + public ngOnChanges(changes: SimpleChanges): void { + if (changes.label) { + this.form.patchValue({ label: changes.label.currentValue }); + } + if (changes.units) { + this.form.patchValue({ units: changes.units.currentValue }); + } + } +} + +/** + * A simple KPI data source to retrieve the average rating of Harry Potter and the Sorcerer's Stone (book) via googleapis + */ +@Injectable() +export class AverageRatingKpiDataSource + extends DataSourceService + implements OnDestroy +{ + // This is the ID we'll use to identify the provider + public static providerId = "AverageRatingKpiDataSource"; + + // Use this subject to communicate the data source's busy state + public busy = new BehaviorSubject(false); + + constructor(private http: HttpClient) { + super(); + } + + // In this example, getFilteredData is invoked every 10 minutes (Take a look at the refresher + // provider definition in the widget configuration below to see how the interval is set) + public async getFilteredData(): Promise { + this.busy.next(true); + return new Promise((resolve) => { + // *** Make a resource request to an external API (if needed) + this.http + .get("https://www.googleapis.com/books/v1/volumes/5MQFrgEACAAJ") + .pipe(finalize(() => this.busy.next(false))) + .subscribe({ + next: (data: any) => { + resolve({ + result: { + value: data.volumeInfo.averageRating, + }, + }); + }, + error: (error: HttpErrorResponse) => { + resolve({ + result: null, + error: { + type: error.status, + }, + }); + }, + }); + }); + } + + public ngOnDestroy(): void { + this.outputsSubject.complete(); + } +} + +/** + * A simple KPI data source to retrieve the ratings count of Harry Potter and the Sorcerer's Stone (book) via googleapis + */ +@Injectable() +export class RatingsCountKpiDataSource + extends DataSourceService + implements OnDestroy +{ + public static providerId = "RatingsCountKpiDataSource"; + + // Use this subject to communicate the data source's busy state + public busy = new BehaviorSubject(false); + + constructor(private http: HttpClient) { + super(); + } + + public async getFilteredData(): Promise { + this.busy.next(true); + return new Promise((resolve) => { + this.http + .get("https://www.googleapis.com/books/v1/volumes/5MQFrgEACAAJ") + .pipe(finalize(() => this.busy.next(false))) + .subscribe({ + next: (data: any) => { + resolve({ + result: { + value: data.volumeInfo.ratingsCount, + }, + }); + }, + error: (error: HttpErrorResponse) => { + resolve({ + result: null, + error: { + type: error.status, + }, + }); + }, + }); + }); + } + + public ngOnDestroy(): void { + this.outputsSubject.complete(); + } +} + +/** + * A component that instantiates the dashboard + */ +@Component({ + selector: "custom-configurator-section-example", + templateUrl: "./custom-configurator-section.example.component.html", + styleUrls: ["./custom-configurator-section.example.component.less"], + standalone: false, +}) +export class CustomConfiguratorSectionExampleComponent implements OnInit { + // This variable will hold all the data needed to define the layout and behavior of the widgets. + // Pass this to the dashboard component's dashboard input in the template. + public dashboard: IDashboard | undefined; + + // Angular gridster requires a configuration object even if it's empty. + // Pass this to the dashboard component's gridsterConfig input in the template. + public gridsterConfig: GridsterConfig = {}; + + // Boolean which dashboard takes in as an input if its true it allows you to move widgets around. + public editMode: boolean = false; + + constructor( + // WidgetTypesService provides the widget's necessary structure information + private widgetTypesService: WidgetTypesService, + + // In general, the ProviderRegistryService is used for making entities available for injection into dynamically loaded components. + private providerRegistry: ProviderRegistryService, + + // Inject the ComponentRegistryService to make our custom component available for late loading by the dashboards framework + private componentRegistry: ComponentRegistryService, + + private changeDetectorRef: ChangeDetectorRef + ) {} + + public ngOnInit(): void { + // Grab the widget's default template which will be needed as a parameter for setNode. + const widgetTemplate = this.widgetTypesService.getWidgetType("kpi", 1); + + // Replace the default KPI description configuration component with our custom one. + // Note: This could also be done in the parent module's constructor to give + // multiple dashboards access to the same custom configurator section. + this.widgetTypesService.setNode( + widgetTemplate, + "configurator", + WellKnownPathKey.TileDescriptionConfigComponentType, + CustomKpiDescriptionConfigurationComponent.lateLoadKey + ); + + // Register the custom configurator section with the component registry to make it available + // for late loading by the dashboards framework. + this.componentRegistry.registerByLateLoadKey( + CustomKpiDescriptionConfigurationComponent + ); + + // Register our data sources as dropdown options in the widget editor/configurator + // Note: This could also be done in the parent module's constructor so that + // multiple dashboards could have access to the same widget template modification. + this.widgetTypesService.setNode( + // This is the template we grabbed above with getWidgetType + widgetTemplate, + // We are setting the editor/configurator part of the widget template + "configurator", + // This indicates which node you are changing and we want to change + // the data source providers available for selection in the editor. + WellKnownPathKey.DataSourceProviders, + // We are setting the data sources available for selection in the editor + [ + AverageRatingKpiDataSource.providerId, + RatingsCountKpiDataSource.providerId, + ] + ); + + // Register the data sources available for injection into the KPI tiles. + // Note: Each tile of a KPI widget is assigned its own instance of a data source + this.providerRegistry.setProviders({ + [AverageRatingKpiDataSource.providerId]: { + provide: DATA_SOURCE, + useClass: AverageRatingKpiDataSource, + // Any dependencies that need to be injected into the provider must be listed here + deps: [HttpClient], + }, + [RatingsCountKpiDataSource.providerId]: { + provide: DATA_SOURCE, + useClass: RatingsCountKpiDataSource, + deps: [HttpClient], + }, + }); + + this.initializeDashboard(); + } + + public initializeDashboard(): void { + // We're using a static configuration object for this example (see widgetConfig at the bottom of the file), + // but this is where the widget's configuration could potentially be populated from a database + const kpiWidget = widgetConfig; + const widgetIndex: IWidgets = { + // Complete the KPI widget with information coming from its type definition + [kpiWidget.id]: + this.widgetTypesService.mergeWithWidgetType(kpiWidget), + }; + + // Setting the widget dimensions and position (this is for gridster) + const positions: Record = { + [kpiWidget.id]: { + cols: 4, + rows: 6, + y: 0, + x: 0, + }, + }; + + // Finally, assigning the variables we created above to the dashboard + this.dashboard = { + positions, + widgets: widgetIndex, + }; + } + + /** Used for restoring widgets state */ + public reInitializeDashboard(): void { + // destroys the components and their providers so the dashboard can re init data + this.dashboard = undefined; + this.changeDetectorRef.detectChanges(); + + this.initializeDashboard(); + } +} + +const widgetConfig: IWidget = { + id: "widget1", + type: "kpi", + pizzagna: { + [PizzagnaLayer.Configuration]: { + [DEFAULT_PIZZAGNA_ROOT]: { + providers: { + [WellKnownProviders.Refresher]: { + properties: { + // Configuring the refresher interval so that our data source is invoked every ten minutes + interval: 60 * 10, + enabled: true, + } as IRefresherProperties, + } as Partial, + }, + }, + header: { + properties: { + title: "Harry Potter and the Sorcerer's Stone", + subtitle: "By J. K. Rowling", + }, + }, + tiles: { + properties: { + nodes: ["kpi1"], + }, + }, + kpi1: { + id: "kpi1", + componentType: KpiComponent.lateLoadKey, + properties: { + widgetData: { + units: "out of 5 Stars", + label: "Average Rating", + }, + }, + providers: { + [WellKnownProviders.DataSource]: { + // Setting the data source providerId for the tile with id "kpi1" + providerId: AverageRatingKpiDataSource.providerId, + } as IProviderConfiguration, + [WellKnownProviders.Adapter]: { + providerId: NOVA_KPI_DATASOURCE_ADAPTER, + properties: { + componentId: "kpi1", + propertyPath: "widgetData", + }, + } as IProviderConfiguration, + }, + }, + }, + }, +}; +\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\`, + "tutorials/customization/configurator-section/custom-configurator-section-docs.component.ts": \\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\`// © 2022 SolarWinds Worldwide, LLC. All rights reserved. +// +// Permission is hereby granted, free of charge, to any person obtaining a copy +// of this software and associated documentation files (the "Software"), to +// deal in the Software without restriction, including without limitation the +// rights to use, copy, modify, merge, publish, distribute, sublicense, and/or +// sell copies of the Software, and to permit persons to whom the Software is +// furnished to do so, subject to the following conditions: +// +// The above copyright notice and this permission notice shall be included in +// all copies or substantial portions of the Software. +// +// THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR +// IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, +// FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE +// AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER +// LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, +// OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN +// THE SOFTWARE. + +import { Component } from "@angular/core"; + +@Component({ + selector: "custom-configurator-section-docs", + templateUrl: "./custom-configurator-section-docs.component.html", + standalone: false, +}) +export class CustomConfiguratorSectionDocsComponent {} +\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\`, + "tutorials/customization/configurator-section/custom-configurator-section.module.ts": \\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\`// © 2022 SolarWinds Worldwide, LLC. All rights reserved. +// +// Permission is hereby granted, free of charge, to any person obtaining a copy +// of this software and associated documentation files (the "Software"), to +// deal in the Software without restriction, including without limitation the +// rights to use, copy, modify, merge, publish, distribute, sublicense, and/or +// sell copies of the Software, and to permit persons to whom the Software is +// furnished to do so, subject to the following conditions: +// +// The above copyright notice and this permission notice shall be included in +// all copies or substantial portions of the Software. +// +// THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR +// IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, +// FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE +// AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER +// LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, +// OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN +// THE SOFTWARE. + +import { CommonModule } from "@angular/common"; +import { HttpClientModule } from "@angular/common/http"; +import { NgModule } from "@angular/core"; +import { ReactiveFormsModule } from "@angular/forms"; +import { RouterModule } from "@angular/router"; + +import { + NuiButtonModule, + NuiDocsModule, + NuiFormFieldModule, + NuiIconModule, + NuiMessageModule, + NuiSwitchModule, + NuiTextboxModule, + DEMO_PATH_TOKEN, +} from "@nova-ui/bits"; +import { + NuiDashboardConfiguratorModule, + NuiDashboardsModule, +} from "@nova-ui/dashboards"; + +import { + CustomConfiguratorSectionExampleComponent, + CustomKpiDescriptionConfigurationComponent, +} from "./custom-configurator-section/custom-configurator-section.example.component"; +import { CustomConfiguratorSectionDocsComponent } from "./custom-configurator-section-docs.component"; +import { getDemoFiles } from "../../../../../demo-files-factory"; + +const routes = [ + { + path: "", + component: CustomConfiguratorSectionDocsComponent, + data: { + srlc: { + hideIndicator: true, + }, + showThemeSwitcher: true, + }, + }, + { + path: "example", + component: CustomConfiguratorSectionExampleComponent, + data: { + srlc: { + hideIndicator: true, + }, + }, + }, +]; + +@NgModule({ + imports: [ + CommonModule, + ReactiveFormsModule, + HttpClientModule, + NuiDashboardsModule, + NuiDashboardConfiguratorModule, + NuiDocsModule, + NuiFormFieldModule, + NuiIconModule, + NuiMessageModule, + NuiSwitchModule, + NuiTextboxModule, + NuiButtonModule, + RouterModule.forChild(routes), + ], + declarations: [ + CustomConfiguratorSectionDocsComponent, + CustomKpiDescriptionConfigurationComponent, + CustomConfiguratorSectionExampleComponent, + ], + providers: [ + { + provide: DEMO_PATH_TOKEN, + useValue: getDemoFiles("configurator-section"), + }, + ], +}) +export default class CustomConfiguratorSectionModule {} +\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\`, + "tutorials/customization/customization.module.ts": \\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\`// © 2022 SolarWinds Worldwide, LLC. All rights reserved. +// +// Permission is hereby granted, free of charge, to any person obtaining a copy +// of this software and associated documentation files (the "Software"), to +// deal in the Software without restriction, including without limitation the +// rights to use, copy, modify, merge, publish, distribute, sublicense, and/or +// sell copies of the Software, and to permit persons to whom the Software is +// furnished to do so, subject to the following conditions: +// +// The above copyright notice and this permission notice shall be included in +// all copies or substantial portions of the Software. +// +// THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR +// IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, +// FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE +// AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER +// LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, +// OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN +// THE SOFTWARE. + +import { NgModule, Type } from "@angular/core"; +import { RouterModule, Routes } from "@angular/router"; + +import { ConfiguratorHeadingService } from "@nova-ui/dashboards"; + +enum CustomizationModuleRoute { + ConfiguratorSection = "configurator-section", + Widget = "widget", + Formatter = "formatter", + DataSourceConfigurator = "data-source-configurator", +} + +const routes: Routes = [ + { + path: CustomizationModuleRoute.ConfiguratorSection, + loadChildren: async () => + import( + "./configurator-section/custom-configurator-section.module" + ) as object as Promise>, + }, + { + path: CustomizationModuleRoute.Widget, + loadChildren: async () => + import("./widget/custom-widget.module") as object as Promise< + Type + >, + }, + { + path: CustomizationModuleRoute.Formatter, + loadChildren: async () => + import("./formatter/custom-formatter.module") as object as Promise< + Type + >, + }, + { + path: CustomizationModuleRoute.DataSourceConfigurator, + loadChildren: async () => + import( + "./data-source-configurator/custom-data-source-configurator.module" + ) as object as Promise>, + }, +]; + +@NgModule({ + imports: [RouterModule.forChild(routes)], + providers: [ConfiguratorHeadingService], +}) +export default class CustomizationModule {} +\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\`, + "tutorials/customization/data-source-configurator/custom-data-source-configurator-docs.component.ts": \\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\`// © 2022 SolarWinds Worldwide, LLC. All rights reserved. +// +// Permission is hereby granted, free of charge, to any person obtaining a copy +// of this software and associated documentation files (the "Software"), to +// deal in the Software without restriction, including without limitation the +// rights to use, copy, modify, merge, publish, distribute, sublicense, and/or +// sell copies of the Software, and to permit persons to whom the Software is +// furnished to do so, subject to the following conditions: +// +// The above copyright notice and this permission notice shall be included in +// all copies or substantial portions of the Software. +// +// THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR +// IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, +// FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE +// AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER +// LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, +// OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN +// THE SOFTWARE. + +import { Component } from "@angular/core"; + +@Component({ + selector: "nui-custom-data-source-configurator-docs", + templateUrl: "./custom-data-source-configurator-docs.component.html", + standalone: false, +}) +export class CustomDataSourceConfiguratorDocComponent {} +\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\`, + "tutorials/customization/data-source-configurator/custom-data-source-configurator.module.ts": \\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\`// © 2022 SolarWinds Worldwide, LLC. All rights reserved. +// +// Permission is hereby granted, free of charge, to any person obtaining a copy +// of this software and associated documentation files (the "Software"), to +// deal in the Software without restriction, including without limitation the +// rights to use, copy, modify, merge, publish, distribute, sublicense, and/or +// sell copies of the Software, and to permit persons to whom the Software is +// furnished to do so, subject to the following conditions: +// +// The above copyright notice and this permission notice shall be included in +// all copies or substantial portions of the Software. +// +// THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR +// IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, +// FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE +// AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER +// LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, +// OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN +// THE SOFTWARE. + +import { NgModule } from "@angular/core"; +import { ReactiveFormsModule } from "@angular/forms"; +import { RouterModule, Routes } from "@angular/router"; + +// eslint-disable-next-line max-len +import { + NuiButtonModule, + NuiDocsModule, + NuiFormFieldModule, + NuiIconModule, + NuiMessageModule, + NuiSelectV2Module, + NuiSwitchModule, + NuiTextboxModule, + NuiValidationMessageModule, + DEMO_PATH_TOKEN, +} from "@nova-ui/bits"; +import { + NuiDashboardConfiguratorModule, + NuiDashboardsModule, +} from "@nova-ui/dashboards"; + +import { CustomDataSourceConfiguratorDocComponent } from "./custom-data-source-configurator-docs.component"; +import { + CustomDataSourceConfiguratorExampleComponent, + HarryPotterDataSourceConfiguratorComponent, +} from "./example/custom-data-source-configurator-example.component"; +import { getDemoFiles } from "../../../../../demo-files-factory"; + +const routes: Routes = [ + { + path: "", + component: CustomDataSourceConfiguratorDocComponent, + data: { + srlc: { + hideIndicator: true, + }, + showThemeSwitcher: true, + }, + }, +]; + +@NgModule({ + imports: [ + RouterModule.forChild(routes), + NuiDocsModule, + NuiButtonModule, + NuiMessageModule, + NuiDashboardConfiguratorModule, + NuiDashboardsModule, + NuiFormFieldModule, + NuiTextboxModule, + NuiSwitchModule, + NuiSelectV2Module, + NuiValidationMessageModule, + NuiIconModule, + ReactiveFormsModule, + ], + declarations: [ + CustomDataSourceConfiguratorDocComponent, + CustomDataSourceConfiguratorExampleComponent, + HarryPotterDataSourceConfiguratorComponent, + ], + providers: [ + { + provide: DEMO_PATH_TOKEN, + useValue: getDemoFiles("data-source-configurator"), + }, + ], +}) +export default class CustomDataSourceConfiguratorModuleRoute {} +\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\`, + "tutorials/customization/data-source-configurator/example/custom-data-source-configurator-example.component.ts": \\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\`// © 2022 SolarWinds Worldwide, LLC. All rights reserved. +// +// Permission is hereby granted, free of charge, to any person obtaining a copy +// of this software and associated documentation files (the "Software"), to +// deal in the Software without restriction, including without limitation the +// rights to use, copy, modify, merge, publish, distribute, sublicense, and/or +// sell copies of the Software, and to permit persons to whom the Software is +// furnished to do so, subject to the following conditions: +// +// The above copyright notice and this permission notice shall be included in +// all copies or substantial portions of the Software. +// +// THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR +// IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, +// FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE +// AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER +// LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, +// OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN +// THE SOFTWARE. + +import { HttpClient, HttpErrorResponse } from "@angular/common/http"; +import { + ChangeDetectorRef, + Component, + Inject, + Injectable, + Injector, + OnDestroy, + OnInit, +} from "@angular/core"; +import { FormBuilder, Validators } from "@angular/forms"; +import { GridsterConfig, GridsterItem } from "angular-gridster2"; +import { BehaviorSubject } from "rxjs"; +import { finalize } from "rxjs/operators"; + +import { + DataSourceService, + EventBus, + IEvent, + IFilteringOutputs, + LoggerService, +} from "@nova-ui/bits"; +import { + ComponentRegistryService, + ConfiguratorHeadingService, + DataSourceConfigurationV2Component, + DATA_SOURCE, + DEFAULT_PIZZAGNA_ROOT, + IConfigurable, + IDashboard, + IKpiData, + IProperties, + IProviderConfiguration, + IRefresherProperties, + IWidget, + IWidgets, + KpiComponent, + NOVA_KPI_DATASOURCE_ADAPTER, + PizzagnaLayer, + PIZZAGNA_EVENT_BUS, + ProviderRegistryService, + WellKnownPathKey, + WellKnownProviders, + WidgetTypesService, +} from "@nova-ui/dashboards"; + +/** + * This component will serve as the data source accordion in the configurator. + */ +@Component({ + selector: "harry-potter-data-source-configurator", + styleUrls: ["./custom-data-source-configurator-example.component.less"], + template: \\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\` + +
+ +
+ Data Source +
+ Harry Potter Books +
+
+
+
+ + + + {{ book.title }} + + + +
+
+ + + + {{ metric.label }} + + + +
+
+ \\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\`, + standalone: false, +}) +@Injectable() +export class HarryPotterDataSourceConfiguratorComponent + extends DataSourceConfigurationV2Component + implements OnInit +{ + // This lateLoadKey allows the component to be able to be registered by the componentRegistry + public static lateLoadKey = "HarryPotterDataSourceConfiguratorComponent"; + + // Array of books that will populate the book select + public books = [ + { + id: "5MQFrgEACAAJ", + title: $localize\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\`Harry Potter and the Sorcerer's Stone\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\`, + }, + { + id: "5iTebBW-w7QC", + title: $localize\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\`Harry Potter and the Chamber of Secrets\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\`, + }, + ]; + + // Array of metrics that will populate the metric select + public metrics = [ + { + id: "averageRating", + label: $localize\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\`Average Rating\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\`, + }, + { + id: "ratingsCount", + label: $localize\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\`Ratings Count\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\`, + }, + ]; + + // These need to be injected because DataSourceConfigurationV2Component uses them + constructor( + changeDetector: ChangeDetectorRef, + configuratorHeading: ConfiguratorHeadingService, + formBuilder: FormBuilder, + providerRegistryService: ProviderRegistryService, + @Inject(PIZZAGNA_EVENT_BUS) eventBus: EventBus, + injector: Injector, + logger: LoggerService + ) { + super( + changeDetector, + configuratorHeading, + formBuilder, + providerRegistryService, + eventBus, + injector, + logger + ); + } + + // Overriding 'ngOnInit' to add custom controls to the 'properties' form group + public ngOnInit(): void { + super.ngOnInit(); + + // Overriding the 'properties' control on the form to create a form group that accommodates our custom properties + this.form.setControl( + "properties", + this.formBuilder.group({ + bookId: [this.properties?.bookId ?? "", Validators.required], + metric: [this.properties?.metric ?? "", Validators.required], + }) + ); + // The default data source control has a required validator we're removing that validator here since we aren't using it. + this.form.setControl("dataSource", this.formBuilder.control(null)); + // Here we set the providerId to our only data source so when a new tile gets created it will default to it. + this.form.get("providerId")?.setValue(AcmeKpiDataSource.providerId); + // Here we subscribe to the form and if there are any changes we invoke the data source + this.form.valueChanges.subscribe((value) => { + if (!value.providerId) { + return; + } + this.invokeDataSource(value); + }); + } +} + +/** + * A simple KPI data source to retrieve the average rating of Harry Potter and the Sorcerer's Stone (book) via googleapis + */ +@Injectable() +export class AcmeKpiDataSource + extends DataSourceService + implements OnDestroy, IConfigurable +{ + // This is the ID we'll use to identify the provider + public static providerId = "AcmeKpiDataSource"; + + // Use this subject to communicate the data source's busy state + public busy = new BehaviorSubject(false); + + public properties: IProperties; + + constructor(private http: HttpClient) { + super(); + } + + // This function MUST be implemented in order to receive property updates from our configurator + public updateConfiguration(properties: IProperties): void { + // Saving the properties because we will need it for this data source. + this.properties = properties; + } + + // In this example, getFilteredData is invoked every 10 minutes (Take a look at the refresher + // provider definition in the widget configuration below to see how the interval is set) + public async getFilteredData(): Promise { + // For loading indicator to show + this.busy.next(true); + return new Promise((resolve) => { + // *** Make a resource request to an external API (if needed) + this.http + .get( + \\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\`https://www.googleapis.com/books/v1/volumes/\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\${this.properties?.bookId}\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\` + ) + // For loading indicator to be hidden + .pipe(finalize(() => this.busy.next(false))) + .subscribe({ + next: (data: any) => { + resolve({ + result: { + value: data.volumeInfo[this.properties?.metric], + }, + }); + }, + error: (error: HttpErrorResponse) => { + resolve({ + result: null, + error: { + type: error.status, + }, + }); + }, + }); + }); + } + + public ngOnDestroy(): void { + this.outputsSubject.complete(); + } +} + +/** + * A component that instantiates the dashboard + */ +@Component({ + selector: "custom-data-source-configurator-example", + templateUrl: "./custom-data-source-configurator-example.component.html", + styleUrls: ["./custom-data-source-configurator-example.component.less"], + standalone: false, +}) +export class CustomDataSourceConfiguratorExampleComponent implements OnInit { + // This variable will hold all the data needed to define the layout and behavior of the widgets. + // Pass this to the dashboard component's dashboard input in the template. + public dashboard: IDashboard; + + // Angular gridster requires a configuration object even if it's empty. + // Pass this to the dashboard component's gridsterConfig input in the template. + public gridsterConfig: GridsterConfig = {}; + + // Boolean which dashboard takes in as an input if its true it allows you to move widgets around. + public editMode: boolean = false; + + constructor( + // WidgetTypesService provides the widget's necessary structure information + private widgetTypesService: WidgetTypesService, + + // In general, the ProviderRegistryService is used for making entities available for injection into dynamically loaded components. + private providerRegistry: ProviderRegistryService, + + // Inject the ComponentRegistryService to make our custom component available for late loading by the dashboards framework + private componentRegistry: ComponentRegistryService + ) {} + + public ngOnInit(): void { + // Registering the new data source configurator so it can be used. + this.componentRegistry.registerByLateLoadKey( + HarryPotterDataSourceConfiguratorComponent + ); + // Registering the data source for injection into the KPI tile. + // Note: Each tile of a KPI widget is assigned its own instance of the data source + this.providerRegistry.setProviders({ + [AcmeKpiDataSource.providerId]: { + provide: DATA_SOURCE, + useClass: AcmeKpiDataSource, + // Any dependencies that need to be injected into the provider must be listed here + deps: [HttpClient], + }, + }); + + const kpiWidgetTemplate = this.widgetTypesService.getWidgetType( + "kpi", + 1 + ); + + this.widgetTypesService.setNode( + // This is the template we grabbed above with getWidgetType + kpiWidgetTemplate, + // We are setting the editor/configurator part of the widget template + "configurator", + // This is the path to go to the data source config component type. + WellKnownPathKey.DataSourceConfigComponentType, + // We are changing it to use the component we just created above instead of the default. + HarryPotterDataSourceConfiguratorComponent.lateLoadKey + ); + + // We're using a static configuration object for this example, but this is where + // the widget's configuration could potentially be populated from a database + const kpiWidget = widgetConfig; + const widgetIndex: IWidgets = { + // Complete the KPI widget with information coming from its type definition + [kpiWidget.id]: + this.widgetTypesService.mergeWithWidgetType(kpiWidget), + }; + + // Setting the widget dimensions and position (this is for gridster) + const positions: Record = { + [kpiWidget.id]: { + cols: 4, + rows: 6, + y: 0, + x: 0, + }, + }; + + // Finally, assigning the variables we created above to the dashboard + this.dashboard = { + positions, + widgets: widgetIndex, + }; + } +} + +const widgetConfig: IWidget = { + id: "widget1", + type: "kpi", + pizzagna: { + [PizzagnaLayer.Configuration]: { + [DEFAULT_PIZZAGNA_ROOT]: { + providers: { + [WellKnownProviders.Refresher]: { + properties: { + // Configuring the refresher interval so that our data source is invoked every ten minutes + interval: 60 * 10, + enabled: true, + } as IRefresherProperties, + } as Partial, + }, + }, + header: { + properties: { + title: "Harry Potter and the Sorcerer's Stone", + subtitle: "By J. K. Rowling", + }, + }, + tiles: { + properties: { + nodes: ["kpi1"], + }, + }, + kpi1: { + id: "kpi1", + componentType: KpiComponent.lateLoadKey, + properties: { + widgetData: { + units: "out of 5 Stars", + label: "Average Rating", + }, + }, + providers: { + [WellKnownProviders.DataSource]: { + // Setting the data source providerId for the tile with id "kpi1" + providerId: AcmeKpiDataSource.providerId, + properties: { + bookId: "5MQFrgEACAAJ", + metric: "averageRating", + }, + } as IProviderConfiguration, + [WellKnownProviders.Adapter]: { + providerId: NOVA_KPI_DATASOURCE_ADAPTER, + properties: { + componentId: "kpi1", + propertyPath: "widgetData", + }, + } as IProviderConfiguration, + }, + }, + }, + }, +}; +\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\`, + "tutorials/customization/formatter/custom-formatter.module.ts": \\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\`// © 2022 SolarWinds Worldwide, LLC. All rights reserved. +// +// Permission is hereby granted, free of charge, to any person obtaining a copy +// of this software and associated documentation files (the "Software"), to +// deal in the Software without restriction, including without limitation the +// rights to use, copy, modify, merge, publish, distribute, sublicense, and/or +// sell copies of the Software, and to permit persons to whom the Software is +// furnished to do so, subject to the following conditions: +// +// The above copyright notice and this permission notice shall be included in +// all copies or substantial portions of the Software. +// +// THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR +// IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, +// FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE +// AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER +// LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, +// OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN +// THE SOFTWARE. + +import { NgModule } from "@angular/core"; +import { ReactiveFormsModule } from "@angular/forms"; +import { RouterModule, Routes } from "@angular/router"; + +// eslint-disable-next-line max-len +import { + NuiButtonModule, + NuiDocsModule, + NuiFormFieldModule, + NuiIconModule, + NuiMessageModule, + NuiSelectV2Module, + NuiSwitchModule, + NuiTextboxModule, + NuiValidationMessageModule, + DEMO_PATH_TOKEN, +} from "@nova-ui/bits"; +import { NuiDashboardsModule } from "@nova-ui/dashboards"; + +import { CustomDonutContentFormatterDocComponent } from "./donut-content-formatter-example/custom-donut-content-formatter-docs.component"; +import { + CustomDonutContentFormatterComponent, + CustomDonutContentFormatterConfiguratorComponent, + CustomDonutContentFormatterExampleComponent, +} from "./donut-content-formatter-example/custom-donut-content-formatter-example.component"; +import { CustomFormatterDocComponent } from "./formatter-example/custom-formatter-docs.component"; +import { + CustomFormatterComponent, + CustomFormatterConfiguratorComponent, + CustomFormatterExampleComponent, +} from "./formatter-example/custom-formatter-example.component"; +import { getDemoFiles } from "../../../../../demo-files-factory"; + +const routes: Routes = [ + { + path: "table-formatter", + component: CustomFormatterDocComponent, + data: { + srlc: { + hideIndicator: true, + }, + showThemeSwitcher: true, + }, + }, + { + path: "donut-content-formatter", + component: CustomDonutContentFormatterDocComponent, + data: { + srlc: { + hideIndicator: true, + }, + showThemeSwitcher: true, + }, + }, +]; + +@NgModule({ + imports: [ + RouterModule.forChild(routes), + NuiDocsModule, + NuiButtonModule, + NuiMessageModule, + NuiDashboardsModule, + NuiFormFieldModule, + NuiTextboxModule, + NuiSwitchModule, + NuiSelectV2Module, + NuiValidationMessageModule, + NuiIconModule, + ReactiveFormsModule, + ], + declarations: [ + CustomDonutContentFormatterComponent, + CustomDonutContentFormatterExampleComponent, + CustomDonutContentFormatterConfiguratorComponent, + CustomDonutContentFormatterDocComponent, + CustomFormatterDocComponent, + CustomFormatterExampleComponent, + CustomFormatterConfiguratorComponent, + CustomFormatterComponent, + ], + providers: [ + { + provide: DEMO_PATH_TOKEN, + useValue: getDemoFiles("formatter"), + }, + ], +}) +export default class CustomFormatterModuleRoute {} +\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\`, + "tutorials/customization/formatter/donut-content-formatter-example/custom-donut-content-formatter-docs.component.ts": \\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\`// © 2022 SolarWinds Worldwide, LLC. All rights reserved. +// +// Permission is hereby granted, free of charge, to any person obtaining a copy +// of this software and associated documentation files (the "Software"), to +// deal in the Software without restriction, including without limitation the +// rights to use, copy, modify, merge, publish, distribute, sublicense, and/or +// sell copies of the Software, and to permit persons to whom the Software is +// furnished to do so, subject to the following conditions: +// +// The above copyright notice and this permission notice shall be included in +// all copies or substantial portions of the Software. +// +// THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR +// IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, +// FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE +// AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER +// LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, +// OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN +// THE SOFTWARE. + +import { Component } from "@angular/core"; + +@Component({ + selector: "nui-custom-donut-content-formatter-docs", + templateUrl: "./custom-donut-content-formatter-docs.component.html", + standalone: false, +}) +export class CustomDonutContentFormatterDocComponent {} +\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\`, + "tutorials/customization/formatter/donut-content-formatter-example/custom-donut-content-formatter-example.component.ts": \\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\`// © 2022 SolarWinds Worldwide, LLC. All rights reserved. +// +// Permission is hereby granted, free of charge, to any person obtaining a copy +// of this software and associated documentation files (the "Software"), to +// deal in the Software without restriction, including without limitation the +// rights to use, copy, modify, merge, publish, distribute, sublicense, and/or +// sell copies of the Software, and to permit persons to whom the Software is +// furnished to do so, subject to the following conditions: +// +// The above copyright notice and this permission notice shall be included in +// all copies or substantial portions of the Software. +// +// THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR +// IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, +// FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE +// AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER +// LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, +// OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN +// THE SOFTWARE. + +import { HttpClient } from "@angular/common/http"; +import { + ChangeDetectorRef, + Component, + Injectable, + Input, + OnChanges, + OnDestroy, + OnInit, + SimpleChanges, +} from "@angular/core"; +import { FormBuilder, FormGroup, Validators } from "@angular/forms"; +import { GridsterConfig, GridsterItem } from "angular-gridster2"; +import { Subject } from "rxjs"; +import { takeUntil, tap } from "rxjs/operators"; + +import { + DataSourceService, + IconService, + IDataSource, + IFilteringOutputs, + LoggerService, +} from "@nova-ui/bits"; +import { + ChartAssist, + IAccessors, + IChartAssistEvent, + IChartAssistSeries, +} from "@nova-ui/charts"; +import { + ComponentRegistryService, + ConfiguratorHeadingService, + DATA_SOURCE, + DonutChartFormatterConfiguratorComponent, + DonutContentPercentageConfigurationComponent, + DonutContentPercentageFormatterComponent, + DonutContentSumFormatterComponent, + IDashboard, + IFormatterDefinition, + IHasChangeDetector, + IProperties, + IProportionalWidgetChartOptions, + IProportionalWidgetConfig, + IProviderConfiguration, + IWidget, + IWidgets, + LegendPlacement, + PizzagnaLayer, + ProportionalWidgetChartTypes, + ProviderRegistryService, + WellKnownPathKey, + WellKnownProviders, + WidgetTypesService, +} from "@nova-ui/dashboards"; + +export enum Units { + Days = "Day(s)", + Weeks = "Week(s)", + Hours = "Hour(s)", +} + +@Component({ + selector: "custom-donut-content-formatter", + host: { class: "d-flex flex-column align-items-center" }, + template: \\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\` +
+ + {{ chartMetric || properties?.currentMetric || data[0].id }} +
+
+ {{ chartContent }} +
+
+ {{ units }} +
+
\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\`, + styleUrls: ["./custom-donut-content-formatter-example.component.less"], + standalone: false, +}) +export class CustomDonutContentFormatterComponent + implements IHasChangeDetector, OnInit, OnChanges +{ + public static lateLoadKey = "CustomDonutContentFormatterComponent"; + + // Used to emphasize the chart series when user interacts either with the chart legend, or chart segments. + public emphasizedSeriesData: IChartAssistSeries | undefined; + + // Current raw value of the metric to display + public currentMetricData: number; + + // Metric value rendered inside the template, when user selects a metric, and gets automatically recalculated depending on selected units + public chartContent: number; + + // Metric value rendered inside the template, when user interacts with either chart legend, or chart segments + public chartMetric: number; + + // Units which user can select from the configuration + public units: Units = Units.Days; + + private readonly destroy$ = new Subject(); + + constructor(public changeDetector: ChangeDetectorRef) {} + + // The data we receive from the chart, including metrics names and their values + @Input() data: IChartAssistSeries[]; + + // We use this chart assist instance to subscribe to the events triggered when an interaction with the chart occurs + @Input() chartAssist: ChartAssist; + + // These are the current properties from pizzagna. Used to use data set at the configuration layer + @Input() properties: IProperties; + + public ngOnChanges(changes: SimpleChanges): void { + if (changes.properties || !this.properties) { + // If current metric is not in the list of metrics any more we fall back to the very first one from the list we get from the datasource + this.currentMetricData = + this.data.find( + (item) => item.id === this.properties?.currentMetric + )?.data[0] || this.data[0].data[0]; + + // We either take the selected value, or fall back to the preselected default one + this.units = this.properties?.units || this.units; + } + + this.setContentValue(); + } + + public ngOnInit(): void { + // Here 'chartAssistSubject' is the entity that emits events every time user interacts with either chart legend, or chart segments. + // Subscribing to properly react on these kind of events + this.chartAssist.chartAssistSubject + .pipe( + tap( + (data: IChartAssistEvent) => + (this.emphasizedSeriesData = this.data.find( + (item) => item.id === data.payload.seriesId + )) + ), + tap(() => this.setContentValue()), + tap(() => this.setMetricValue()), + takeUntil(this.destroy$) + ) + .subscribe(); + } + + public getConvertedData(emphData: number): number { + // Recalculating data depending on the units user selected from the configuration view + switch (this.units) { + case Units.Weeks: + return this.emphasizedSeriesData + ? this.convertToWeeks(emphData) + : this.convertToWeeks(this.currentMetricData); + + case Units.Hours: + return this.emphasizedSeriesData + ? this.convertToHours(emphData) + : this.convertToHours(this.currentMetricData); + + default: + return this.emphasizedSeriesData + ? emphData + : this.currentMetricData; + } + } + + public setContentValue(): void { + this.chartContent = this.getConvertedData( + this.emphasizedSeriesData?.data[0] + ); + } + + public setMetricValue(): void { + this.chartMetric = this.emphasizedSeriesData + ? this.data.find( + (item) => + this.getConvertedData(item.data[0]) === + this.getConvertedData(this.emphasizedSeriesData?.data[0]) + )?.id + : // if metric was not initially selected we fall back to the very first one + this.properties?.currentMetric || this.data[0].id; + } + + private convertToWeeks(days: number | undefined): number { + return days ? Number((days / 7).toFixed(2)) : 0; + } + + private convertToHours(days: number | undefined): number { + return days ? Number((days * 24).toFixed(2)) : 0; + } +} + +@Component({ + selector: "custom-donut-content-formatter-configurator", + styleUrls: ["./custom-donut-content-formatter-example.component.less"], + template: \\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\` +
+
+ + + + {{ itemValue?.name }} + + + + This field is required + + +
+
+ + + + {{ itemValue }} + + + + This field is required + + +
+
+ \\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\`, + standalone: false, +}) +export class CustomDonutContentFormatterConfiguratorComponent + extends DonutChartFormatterConfiguratorComponent + implements OnChanges, OnInit, IHasChangeDetector +{ + public static lateLoadKey = "CustomFormatterConfiguratorComponent"; + + constructor( + changeDetector: ChangeDetectorRef, + formBuilder: FormBuilder, + logger: LoggerService, + public iconService: IconService, + public configuratorHeading: ConfiguratorHeadingService + ) { + super(changeDetector, formBuilder, logger); + } + + public availableUnits: Units[] = [Units.Days, Units.Hours, Units.Weeks]; + + protected addCustomFormControls(form: FormGroup): void { + form.addControl( + "units", + this.formBuilder.control(Units.Days, Validators.required) + ); + } +} + +/** + * A component that instantiates the dashboard + */ +@Component({ + selector: "custom-donut-content-formatter-example", + templateUrl: "./custom-donut-content-formatter-example.component.html", + styleUrls: ["./custom-donut-content-formatter-example.component.less"], + standalone: false, +}) +export class CustomDonutContentFormatterExampleComponent implements OnInit { + public editMode: boolean = false; + // This variable will hold all the data needed to define the layout and behavior of the widgets. + // Pass this to the dashboard component's dashboard input in the template. + public dashboard: IDashboard | undefined; + + // Angular gridster requires a configuration object even if it's empty. + // Pass this to the dashboard component's gridsterConfig input in the template. + public gridsterConfig: GridsterConfig = {}; + + constructor( + // WidgetTypesService provides the widget's necessary structure information + private widgetTypesService: WidgetTypesService, + // In general, the ProviderRegistryService is used for making entities available for injection into dynamically loaded components. + private providerRegistry: ProviderRegistryService, + // Inject the ComponentRegistryService to make our custom component available for late loading by the dashboards framework + private componentRegistry: ComponentRegistryService, + private changeDetectorRef: ChangeDetectorRef + ) { + // Register the custom configurator component with the component registry to make it available + // for late loading by the dashboard framework. + this.componentRegistry.registerByLateLoadKey( + CustomDonutContentFormatterConfiguratorComponent + ); + // Register the custom formatter component with the component registry to make it available + // for late loading by the dashboard framework. + this.componentRegistry.registerByLateLoadKey( + CustomDonutContentFormatterComponent + ); + + // Grab the widget's default template which will be needed as a parameter for setNode below. + const proportional = this.widgetTypesService.getWidgetType( + "proportional", + 1 + ); + + const donutFormatters: IFormatterDefinition[] = [ + { + componentType: DonutContentSumFormatterComponent.lateLoadKey, + label: $localize\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\`Sum\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\`, + } as IFormatterDefinition, + { + componentType: + DonutContentPercentageFormatterComponent.lateLoadKey, + label: $localize\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\`Percentage\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\`, + configurationComponent: + DonutContentPercentageConfigurationComponent.lateLoadKey, + } as IFormatterDefinition, + { + componentType: CustomDonutContentFormatterComponent.lateLoadKey, + label: $localize\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\`Custom\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\`, + // This is a custom configurator that will pop up below the formatter once it gets selected + configurationComponent: + CustomDonutContentFormatterConfiguratorComponent.lateLoadKey, + } as IFormatterDefinition, + ]; + + this.widgetTypesService.setNode( + // This is the template we grabbed above with getWidgetType + proportional, + // We are setting the editor/configurator part of the widget template + "configurator", + // This indicates which node you are changing and we want to change the formatters available for selection in the editor. + WellKnownPathKey.Formatters, + // We are setting the available formatters with the array we created above. + donutFormatters + ); + + // This sets the donut chart's datasource to have the StatusesExampleDatasource so the drop down is filled similar to the line above. + this.widgetTypesService.setNode( + proportional, + "configurator", + WellKnownPathKey.DataSourceProviders, + [StatusesExampleDatasource.providerId] + ); + + // Registering the data source for injection into the widget. + this.providerRegistry.setProviders({ + [StatusesExampleDatasource.providerId]: { + provide: DATA_SOURCE, + useClass: StatusesExampleDatasource, + // Any dependencies that need to be injected into the provider must be listed here + deps: [], + }, + }); + } + + public ngOnInit(): void { + this.initializeDashboard(); + } + + /** Used for restoring widgets state */ + public reInitializeDashboard(): void { + // destroys the components and their providers so the dashboard can re init data + this.dashboard = undefined; + this.changeDetectorRef.detectChanges(); + + this.initializeDashboard(); + } + + public initializeDashboard(): void { + // We're using a static configuration object for this example, but this is where + // the widget's configuration could potentially be populated from a database + const proportionalWidget = widgetConfig; + const widgetIndex: IWidgets = { + // Enhance the widget with information coming from it's type definition + [proportionalWidget.id]: + this.widgetTypesService.mergeWithWidgetType(proportionalWidget), + }; + + // Setting the widget dimensions and position (this is for gridster) + const positions: Record = { + [proportionalWidget.id]: { + cols: 12, + rows: 6, + y: 0, + x: 0, + }, + }; + + // Finally, assigning the variables we created above to the dashboard + this.dashboard = { + positions, + widgets: widgetIndex, + }; + } +} + +export interface IStatusesWidgetData { + id: string; + name: string; + data: number[]; +} + +export const randomStatusesWidgetData: IStatusesWidgetData[] = [ + { + id: "Down", + name: "Down", + data: [Math.round(Math.random() * 100)], + }, + { + id: "Critical", + name: "Critical", + data: [Math.round(Math.random() * 100)], + }, + { + id: "Warning", + name: "Warning", + data: [Math.round(Math.random() * 100)], + }, + { + id: "Unknown", + name: "Unknown", + data: [Math.round(Math.random() * 100)], + }, + { + id: "Up", + name: "Up", + data: [Math.round(Math.random() * 100)], + }, + { + id: "Unmanaged", + name: "Unmanaged", + data: [Math.round(Math.random() * 100)], + }, +]; + +@Injectable() +export class StatusesExampleDatasource + extends DataSourceService + implements IDataSource, OnDestroy +{ + public static providerId = "StatusesExampleDatasource"; + + public busy = new Subject(); + + constructor(private http: HttpClient) { + super(); + } + + public async getFilteredData(): Promise { + this.busy.next(true); + + return new Promise((resolve) => { + setTimeout(() => { + resolve({ + result: randomStatusesWidgetData, + }); + this.busy.next(false); + }, 1000); + }); + } + + public ngOnDestroy(): void { + this.outputsSubject.complete(); + } +} + +export const widgetConfig: IWidget = { + id: "proportionalWidgetId", + type: "proportional", + pizzagna: { + [PizzagnaLayer.Configuration]: { + header: { + properties: { + title: "Proportional Widget!", + subtitle: "Proportional widget with legend formatters", + }, + }, + chart: { + providers: { + [WellKnownProviders.DataSource]: { + providerId: StatusesExampleDatasource.providerId, + } as IProviderConfiguration, + }, + properties: { + configuration: { + interactive: true, + chartOptions: { + type: ProportionalWidgetChartTypes.DonutChart, + legendPlacement: LegendPlacement.Right, + contentFormatter: { + componentType: + CustomDonutContentFormatterComponent.lateLoadKey, + properties: { + // here you can set the default value for the metric you receive. If not set the first one from the list will be taken + currentMetric: "Down", + // here you set the default value for your custom controls. If not set the first one from the list will be taken + units: Units.Weeks, + }, + }, + } as IProportionalWidgetChartOptions, + chartColors: [ + "var(--nui-color-chart-eight)", + "var(--nui-color-chart-nine)", + "var(--nui-color-chart-ten)", + ], + } as IProportionalWidgetConfig, + }, + }, + }, + }, +}; +\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\`, + "tutorials/customization/formatter/formatter-example/custom-formatter-docs.component.ts": \\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\`// © 2022 SolarWinds Worldwide, LLC. All rights reserved. +// +// Permission is hereby granted, free of charge, to any person obtaining a copy +// of this software and associated documentation files (the "Software"), to +// deal in the Software without restriction, including without limitation the +// rights to use, copy, modify, merge, publish, distribute, sublicense, and/or +// sell copies of the Software, and to permit persons to whom the Software is +// furnished to do so, subject to the following conditions: +// +// The above copyright notice and this permission notice shall be included in +// all copies or substantial portions of the Software. +// +// THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR +// IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, +// FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE +// AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER +// LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, +// OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN +// THE SOFTWARE. + +import { Component } from "@angular/core"; + +@Component({ + selector: "nui-custom-formatter-docs", + templateUrl: "./custom-formatter-docs.component.html", + standalone: false, +}) +export class CustomFormatterDocComponent {} +\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\`, + "tutorials/customization/formatter/formatter-example/custom-formatter-example.component.ts": \\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\`// © 2022 SolarWinds Worldwide, LLC. All rights reserved. +// +// Permission is hereby granted, free of charge, to any person obtaining a copy +// of this software and associated documentation files (the "Software"), to +// deal in the Software without restriction, including without limitation the +// rights to use, copy, modify, merge, publish, distribute, sublicense, and/or +// sell copies of the Software, and to permit persons to whom the Software is +// furnished to do so, subject to the following conditions: +// +// The above copyright notice and this permission notice shall be included in +// all copies or substantial portions of the Software. +// +// THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR +// IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, +// FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE +// AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER +// LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, +// OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN +// THE SOFTWARE. + +import { ListRange } from "@angular/cdk/collections"; +import { ChangeDetectorRef, Component, Input, OnInit } from "@angular/core"; +import { FormBuilder, FormGroup, Validators } from "@angular/forms"; +import { GridsterConfig, GridsterItem } from "angular-gridster2"; +import isEqual from "lodash/isEqual"; +import orderBy from "lodash/orderBy"; +import { BehaviorSubject } from "rxjs"; + +import { + DataSourceService, + IconService, + IDataField, + INovaFilteringOutputs, + INovaFilters, + ISorterFilter, + LoggerService, +} from "@nova-ui/bits"; +import { + ComponentRegistryService, + ConfiguratorHeadingService, + DATA_SOURCE, + FormatterConfiguratorComponent, + IDashboard, + IDataSourceOutput, + IFormatterDefinition, + IHasChangeDetector, + ITableWidgetColumnConfig, + ITableWidgetSorterConfig, + IWidget, + IWidgets, + PizzagnaLayer, + ProviderRegistryService, + RawFormatterComponent, + TableFormatterRegistryService, + WellKnownPathKey, + WellKnownProviders, + WidgetTypesService, +} from "@nova-ui/dashboards"; + +export const BREW_API_URL = "https://api.punkapi.com/v2/beers"; + +@Component({ + selector: "custom-formatter", + host: { class: "d-flex" }, + template: \\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\` +
+
+ +
+
+ {{ data.value }} +
+
+ \\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\`, + styleUrls: ["./custom-formatter-example.component.less"], + standalone: false, +}) +export class CustomFormatterComponent implements IHasChangeDetector { + public static lateLoadKey = "CustomFormatterComponent"; + + constructor(public changeDetector: ChangeDetectorRef) {} + + @Input() public data: any; + @Input() public icon: string; + @Input() public threshold: string; + + public isAboveThreshold(): boolean { + return parseFloat(this.threshold) <= this.data.value; + } +} + +@Component({ + selector: "custom-formatter-configurator", + styleUrls: ["./custom-formatter-example.component.less"], + template: \\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\` +
+
+ + + + {{ item.label }} + + + + This field is required + + +
+
+ + + + + + + + This field is required + + +
+
+ + + + + This field is required + + +
+
+ +
+
+ +
+ + +
+ + + Select Item + +
+ \\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\`, + standalone: false, +}) +export class CustomFormatterConfiguratorComponent + extends FormatterConfiguratorComponent + implements OnInit, IHasChangeDetector +{ + public static lateLoadKey = "CustomFormatterConfiguratorComponent"; + + constructor( + changeDetector: ChangeDetectorRef, + configuratorHeading: ConfiguratorHeadingService, + formBuilder: FormBuilder, + logger: LoggerService, + public iconService: IconService + ) { + super(changeDetector, configuratorHeading, formBuilder, logger); + } + + public formatterFormGroup: FormGroup; + // This array is where the icon names will be stored + public options: string[] = []; + + public ngOnInit(): void { + for (const icon of this.iconService.icons) { + if (icon.category === "severity") { + this.options.push(icon.name); + } + } + } + + protected addCustomFormControls(form: FormGroup): void { + form.addControl( + "icon", + this.formBuilder.control("", Validators.required) + ); + form.addControl( + "threshold", + this.formBuilder.control(null, Validators.required) + ); + } +} + +/** + * A component that instantiates the dashboard + */ +@Component({ + selector: "custom-formatter-example", + templateUrl: "./custom-formatter-example.component.html", + styleUrls: ["./custom-formatter-example.component.less"], + standalone: false, +}) +export class CustomFormatterExampleComponent implements OnInit { + public editMode: boolean = false; + // This variable will hold all the data needed to define the layout and behavior of the widgets. + // Pass this to the dashboard component's dashboard input in the template. + public dashboard: IDashboard | undefined; + + // Angular gridster requires a configuration object even if it's empty. + // Pass this to the dashboard component's gridsterConfig input in the template. + public gridsterConfig: GridsterConfig = {}; + + constructor( + // WidgetTypesService provides the widget's necessary structure information + private widgetTypesService: WidgetTypesService, + // In general, the ProviderRegistryService is used for making entities available for injection into dynamically loaded components. + private providerRegistry: ProviderRegistryService, + // Inject the ComponentRegistryService to make our custom component available for late loading by the dashboards framework + private componentRegistry: ComponentRegistryService, + private tableFormatterRegistryService: TableFormatterRegistryService, + private changeDetectorRef: ChangeDetectorRef + ) { + // Register the custom configurator component with the component registry to make it available + // for late loading by the dashboard framework. + this.componentRegistry.registerByLateLoadKey( + CustomFormatterConfiguratorComponent + ); + // Register the custom formatter component with the component registry to make it available + // for late loading by the dashboard framework. + this.componentRegistry.registerByLateLoadKey(CustomFormatterComponent); + + // Grab the widget's default template which will be needed as a parameter for setNode below. + const table = this.widgetTypesService.getWidgetType("table", 1); + + const tableFormatters: IFormatterDefinition[] = [ + { + // This will be the component that will format the data + componentType: RawFormatterComponent.lateLoadKey, + // This is the label for what the formatter is selected in the drop down + label: $localize\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\`:table formatter|:No formatter\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\`, + // This says what datatype the formatter supports. If the value node is null, it accepts any data type. + dataTypes: { + // @ts-ignore: Ignoring compiler error to keep the same flow + value: null, + }, + }, + { + componentType: CustomFormatterComponent.lateLoadKey, + label: $localize\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\`:table formatter|:Custom formatter\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\`, + // This is a custom configurator that will pop up below the formatter once it gets selected + configurationComponent: + CustomFormatterConfiguratorComponent.lateLoadKey, + // This says what data types the formatter supports. + // In this case, it supports abv values only. + // If you look below in the table data source you'll see where we define our column's data types. + dataTypes: { + value: ["abv"], + }, + }, + ]; + + // Registering the formatters + this.tableFormatterRegistryService.addItems(tableFormatters); + + // This sets the table's datasource to have the BeerDataSource so the drop down is filled similar to the line above. + this.widgetTypesService.setNode( + table, + "configurator", + WellKnownPathKey.DataSourceProviders, + [BeerDataSource.providerId] + ); + + // Registering the data source for injection into the widget. + this.providerRegistry.setProviders({ + [BeerDataSource.providerId]: { + provide: DATA_SOURCE, + useClass: BeerDataSource, + // Any dependencies that need to be injected into the provider must be listed here + deps: [], + }, + }); + } + + public ngOnInit(): void { + this.initializeDashboard(); + } + + /** Used for restoring widgets state */ + public reInitializeDashboard(): void { + // destroys the components and their providers so the dashboard can re init data + this.dashboard = undefined; + this.changeDetectorRef.detectChanges(); + + this.initializeDashboard(); + } + + public initializeDashboard(): void { + // We're using a static configuration object for this example, but this is where + // the widget's configuration could potentially be populated from a database + const tableWidget = widgetConfig; + const widgetIndex: IWidgets = { + // Enhance the widget with information coming from it's type definition + [tableWidget.id]: + this.widgetTypesService.mergeWithWidgetType(tableWidget), + }; + + // Setting the widget dimensions and position (this is for gridster) + const positions: Record = { + [tableWidget.id]: { + cols: 12, + rows: 6, + y: 0, + x: 0, + }, + }; + + // Finally, assigning the variables we created above to the dashboard + this.dashboard = { + positions, + widgets: widgetIndex, + }; + } +} + +export interface IBrewInfo { + id: number; + name: string; + tagline: string; + first_brewed: string; + description: string; + brewers_tips: string; + abv: number; +} + +export interface IBrewDatasourceResponse { + brewInfo: IBrewInfo[]; + total: number; +} + +export class BeerDataSource extends DataSourceService { + public static providerId = "BeerDataSource"; + + private cache = Array.from({ length: 0 }); + private lastSortValue?: ISorterFilter; + private lastVirtualScroll?: ListRange; + // For simplicity, the totalItems value is hard-coded here, but in a real-world scenario the value would likely be retrieved via an async backend call + private totalItems: number = 325; + + public page: number = 1; + public busy = new BehaviorSubject(false); + + public dataFields: Array = [ + { id: "id", label: "No", dataType: "number" }, + { id: "name", label: "Name", dataType: "string" }, + { id: "tagline", label: "Tagline", dataType: "string" }, + { id: "first_brewed", label: "First Brewed", dataType: "string" }, + { id: "description", label: "Description", dataType: "string" }, + { id: "brewers_tips", label: "Brewer's Tips", dataType: "string" }, + // We are giving this field a custom data type of 'abv' so the dropdown in the custom formatter configurator can use it to filter out other data types + { id: "abv", label: "Alcohol By Volume", dataType: "abv" }, + ]; + + constructor(private logger: LoggerService) { + super(); + } + + public async getFilteredData( + filters: INovaFilters + ): Promise> { + const start = filters.virtualScroll?.value?.start ?? 0; + const end = filters.virtualScroll?.value?.end ?? 0; + const delta = end - start; + + // Note: We should start with a clean cache every time first page is requested + if (start === 0) { + this.cache = []; + } + + // This condition handles sorting. We want to sort columns without fetching another chunk of data. + // Since the data is being fetched when scrolled, we compare virtual scroll indexes here in the condition as well. + if (filters.sorter?.value) { + if ( + !isEqual(this.lastSortValue, filters.sorter.value) && + filters.virtualScroll?.value.start === 0 && + !!this.lastVirtualScroll + ) { + const totalPages = Math.ceil( + delta ? this.totalItems / delta : 1 + ); + const itemsPerPage: number = Math.max( + delta < 80 ? delta : 80, + 1 + ); + let response: Array | null = null; + let map: IBrewDatasourceResponse; + + if (filters.sorter?.value?.direction === "desc") { + this.cache = []; + for (let i = 0; i < this.page; ++i) { + response = await ( + await fetch( + \\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\`\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\${BREW_API_URL}/?page=\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\${ + totalPages - i || 1 + }&per_page=\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\${itemsPerPage}\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\` + ) + ).json(); + + // since the last page contains only 5 items we need to fetch another page to give virtual scroll enough space to work + if (response && response.length < itemsPerPage) { + this.page++; + } + map = { + brewInfo: response?.map((result: IBrewInfo) => ({ + id: result.id, + name: result.name, + tagline: result.tagline, + first_brewed: result.first_brewed, + description: result.description, + brewers_tips: result.brewers_tips, + })), + total: response?.length, + } as IBrewDatasourceResponse; + this.cache = + totalPages - i !== 0 + ? this.cache.concat(map.brewInfo) + : this.cache; + } + } + + if (filters.sorter?.value?.direction === "asc") { + this.cache = []; + for (let i = 0; i < this.page; i++) { + response = await ( + await fetch( + \\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\`\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\${BREW_API_URL}/?page=\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\${ + i + 1 + }&per_page=\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\${itemsPerPage}\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\` + ) + ).json(); + map = { + brewInfo: response?.map((result: IBrewInfo) => ({ + id: result.id, + name: result.name, + tagline: result.tagline, + first_brewed: result.first_brewed, + description: result.description, + brewers_tips: result.brewers_tips, + })), + total: response?.length, + } as IBrewDatasourceResponse; + this.cache = this.cache.concat(map.brewInfo); + } + } + + this.lastSortValue = filters.sorter?.value; + this.lastVirtualScroll = filters.virtualScroll?.value; + + return { + result: { + repeat: { + itemsSource: this.sortData(this.cache, filters), + }, + paginator: { total: this.totalItems }, + dataFields: this.dataFields, + }, + }; + } + } + + this.busy.next(true); + return new Promise((resolve) => { + setTimeout(() => { + this.getData(start, end, filters).then( + (response: INovaFilteringOutputs) => { + if (!response) { + return; + } + + this.cache = this.cache.concat(response.brewInfo); + + this.dataSubject.next(this.cache); + resolve({ + result: { + repeat: { + itemsSource: this.sortData( + this.cache, + filters + ), + }, + paginator: { total: this.totalItems }, + dataFields: this.dataFields, + }, + }); + + this.lastSortValue = filters.sorter?.value; + this.lastVirtualScroll = filters.virtualScroll?.value; + this.busy.next(false); + } + ); + }, 500); + }); + } + + public async getData( + start: number = 0, + end: number = 20, + filters: INovaFilters + ): Promise { + const delta = end - start; + const totalPages = Math.ceil(delta ? this.totalItems / delta : 1); + let response: Array | null = null; + // The api.punk.com is able to return only 80 items per page + const itemsPerPage: number = Math.max(delta < 80 ? delta : 80, 1); + + if (filters.sorter?.value?.direction === "asc") { + response = await ( + await fetch( + \\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\`\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\${BREW_API_URL}/?page=\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\${this.page}&per_page=\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\${itemsPerPage}\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\` + ) + ).json(); + } + + if (filters.sorter?.value?.direction === "desc") { + response = await ( + await fetch( + \\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\`\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\${BREW_API_URL}/?page=\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\${ + totalPages - this.page + }&per_page=\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\${itemsPerPage}\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\` + ) + ).json(); + } + + if (!filters.sorter) { + response = await ( + await fetch( + \\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\`\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\${BREW_API_URL}/?page=\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\${this.page}&per_page=\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\${itemsPerPage}\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\` + ) + ).json(); + } + return { + brewInfo: response?.map((result: IBrewInfo, i: number) => ({ + id: result.id, + abv: result.abv, + name: result.name, + tagline: result.tagline, + first_brewed: result.first_brewed, + description: result.description, + brewers_tips: result.brewers_tips, + })), + total: response?.length, + } as IBrewDatasourceResponse; + } + + private sortData(data: IBrewInfo[], filters: INovaFilters) { + return orderBy( + data, + filters.sorter?.value?.sortBy, + filters.sorter?.value?.direction as "desc" | "asc" + ); + } +} + +export const widgetConfig: IWidget = { + id: "tableWidgetId", + type: "table", + pizzagna: { + [PizzagnaLayer.Configuration]: { + header: { + properties: { + title: "Stupendous Suds", + subtitle: "Try These Brilliant Brews", + }, + }, + table: { + providers: { + [WellKnownProviders.DataSource]: { + providerId: BeerDataSource.providerId, + }, + }, + properties: { + configuration: { + columns: [ + { + id: "column1", + label: "Beer Name", + isActive: true, + width: 185, + formatter: { + componentType: + RawFormatterComponent.lateLoadKey, + properties: { + dataFieldIds: { + value: "name", + }, + }, + }, + }, + { + id: "column2", + label: "Tagline", + isActive: true, + width: 250, + formatter: { + componentType: + RawFormatterComponent.lateLoadKey, + properties: { + dataFieldIds: { + value: "tagline", + }, + }, + }, + }, + { + id: "column3", + label: "Alcohol By Volume", + isActive: true, + width: 150, + formatter: { + componentType: + CustomFormatterComponent.lateLoadKey, + properties: { + dataFieldIds: { + value: "abv", + }, + icon: "severity_error", + threshold: "5", + }, + }, + }, + { + id: "column4", + label: "Description", + isActive: true, + formatter: { + componentType: + RawFormatterComponent.lateLoadKey, + properties: { + dataFieldIds: { + value: "description", + }, + }, + }, + }, + ] as ITableWidgetColumnConfig[], + sorterConfiguration: { + descendantSorting: false, + sortBy: "", + } as ITableWidgetSorterConfig, + hasVirtualScroll: true, + }, + }, + }, + }, + }, +}; +\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\`, + "tutorials/customization/widget/custom-widget-docs.component.ts": \\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\`// © 2022 SolarWinds Worldwide, LLC. All rights reserved. +// +// Permission is hereby granted, free of charge, to any person obtaining a copy +// of this software and associated documentation files (the "Software"), to +// deal in the Software without restriction, including without limitation the +// rights to use, copy, modify, merge, publish, distribute, sublicense, and/or +// sell copies of the Software, and to permit persons to whom the Software is +// furnished to do so, subject to the following conditions: +// +// The above copyright notice and this permission notice shall be included in +// all copies or substantial portions of the Software. +// +// THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR +// IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, +// FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE +// AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER +// LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, +// OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN +// THE SOFTWARE. + +import { Component } from "@angular/core"; + +@Component({ + selector: "custom-widget-docs", + templateUrl: "./custom-widget-docs.component.html", + standalone: false, +}) +export class CustomWidgetDocsComponent {} +\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\`, + "tutorials/customization/widget/custom-widget.component.ts": \\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\`// © 2022 SolarWinds Worldwide, LLC. All rights reserved. +// +// Permission is hereby granted, free of charge, to any person obtaining a copy +// of this software and associated documentation files (the "Software"), to +// deal in the Software without restriction, including without limitation the +// rights to use, copy, modify, merge, publish, distribute, sublicense, and/or +// sell copies of the Software, and to permit persons to whom the Software is +// furnished to do so, subject to the following conditions: +// +// The above copyright notice and this permission notice shall be included in +// all copies or substantial portions of the Software. +// +// THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR +// IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, +// FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE +// AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER +// LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, +// OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN +// THE SOFTWARE. + +import { + ChangeDetectionStrategy, + ChangeDetectorRef, + Component, + EventEmitter, + HostBinding, + Input, + OnChanges, + OnInit, + Output, + SimpleChanges, +} from "@angular/core"; +import { FormBuilder, FormGroup, Validators } from "@angular/forms"; +import { GridsterConfig, GridsterItem } from "angular-gridster2"; + +import { IMenuItem } from "@nova-ui/bits"; +import { + ComponentRegistryService, + ConfiguratorHeadingService, + DEFAULT_PIZZAGNA_ROOT, + EVENT_PROXY, + FormStackComponent, + IConverterFormPartsProperties, + IDashboard, + IHasChangeDetector, + IHasForm, + IProviderConfiguration, + IWidget, + IWidgets, + IWidgetTypeDefinition, + NOVA_GENERIC_CONVERTER, + NOVA_TITLE_AND_DESCRIPTION_CONVERTER, + PizzagnaLayer, + refresher, + StackComponent, + TitleAndDescriptionConfigurationComponent, + WellKnownPathKey, + WellKnownProviders, + widgetBodyContentNodes, + WidgetConfiguratorSectionComponent, + WidgetTypesService, + WIDGET_BODY, + WIDGET_HEADER, + WIDGET_LOADING, +} from "@nova-ui/dashboards"; + +// The custom widget type name we'll use +const CUSTOM_WIDGET_TYPENAME = "example-custom-widget"; +// The path key we'll use for image selection in the configurator definition +const IMAGE_SELECTION_CONFIGURATOR_PATH_KEY = "imageSelection"; + +@Component({ + selector: "custom-widget-body", + // A simple template for our custom widget + template: \\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\`\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\`, + styleUrls: ["./custom-widget.component.less"], + changeDetection: ChangeDetectionStrategy.OnPush, + standalone: false, +}) +// Remember to declare this class in the parent module +export class CustomWidgetBodyContentComponent implements IHasChangeDetector { + // Ensure that the lateLoadKey value matches class name + public static lateLoadKey = "CustomWidgetBodyContentComponent"; + + // Optionally, providing an input for styling of the host element + @Input() @HostBinding("class") public elementClass = ""; + + // We'll map this input with the configurator form using the NOVA_GENERIC_CONVERTER. + // See the customWidget definition at the bottom of the file. + @Input() public imageSource: string; + + // Injecting the ChangeDetectorRef to implement IHasChangeDetector. + // This allows the dashboard framework to reliably propagate component property changes to the DOM. + constructor(public changeDetector: ChangeDetectorRef) {} +} + +/** + * A custom configurator section component for selecting the image source for the custom widget + */ +@Component({ + selector: "custom-configurator-section", + template: \\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\` + + + + +
+ + +
+ Image Selection +
+ {{ imageDisplayValue }} +
+
+
+
+ + + + + {{ item.title }} + + + +
+
+ \\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\`, + styleUrls: ["./custom-widget.component.less"], + changeDetection: ChangeDetectionStrategy.OnPush, + standalone: false, +}) +// Remember to declare this class in the parent module +export class CustomConfiguratorSectionComponent + implements OnInit, OnChanges, IHasChangeDetector, IHasForm +{ + // Ensure that the lateLoadKey value matches the class name + public static lateLoadKey = "CustomConfiguratorSectionComponent"; + + /** + * This input serves as the itemsSource a user can select an image from. + */ + @Input() imageItems: IMenuItem[] = []; + /** + * This property holds the currently selected image source string. + */ + @Input() imageSource: string; + + /** + * An output for emitting formReady to allow the immediate parent formGroup component to register us as a form control + * in the larger form. In this case, the immediate parent would be the WidgetConfiguratorSectionComponent as specified + * in the customWidget configurator definition at the bottom of this file. + */ + @Output() formReady = new EventEmitter(); + + public form: FormGroup; + public imageDisplayValue: string; + + constructor( + public changeDetector: ChangeDetectorRef, + private formBuilder: FormBuilder, + public configuratorHeading: ConfiguratorHeadingService + ) {} + + public ngOnInit(): void { + // Initializing the form + this.form = this.formBuilder.group({ + // Note: When using the NOVA_GENERIC_CONVERTER, the form control name, in this case 'imageSource', must match the input name on + // this component as well as that of the corresponding property on the custom widget body component. + imageSource: [{}, [Validators.required]], + }); + + // Emitting the formReady as described above. + this.formReady.emit(this.form); + } + + public ngOnChanges(changes: SimpleChanges): void { + if (changes.imageSource && !changes.imageSource.isFirstChange()) { + const previousValue: string = changes.imageSource.previousValue; + if (previousValue !== this.imageSource) { + // Setting the display value according to the current imageSource value + this.imageDisplayValue = this.imageItems.find( + (item: IMenuItem) => item.url === this.imageSource + )?.title; + + // Updating the form when the imageSource input gets updated + this.form.get("imageSource")?.setValue(this.imageSource); + } + } + } + + public onChanged(newValue: string): void { + // Keeping the display value updated as the user changes the dropdown selection + this.imageDisplayValue = this.imageItems.find( + (item: IMenuItem) => item.url === newValue + )?.title; + } +} + +/** + * The component that instantiates the dashboard + */ +@Component({ + selector: "custom-widget", + templateUrl: "./custom-widget.component.html", + styleUrls: ["./custom-widget.component.less"], + standalone: false, +}) +export class CustomWidgetComponent implements OnInit { + // This variable will hold all the data needed to define the layout and behavior of the widgets. + // Pass this to the dashboard component's dashboard input in the template. + public dashboard: IDashboard | undefined; + + // Angular gridster requires a configuration object even if it's empty. + // Pass this to the dashboard component's gridsterConfig input in the template. + public gridsterConfig: GridsterConfig = {}; + + // Boolean which dashboard takes in as an input if its true it allows you to move widgets around. + public editMode: boolean = false; + + constructor( + // WidgetTypesService provides the widget's necessary structure information + private widgetTypesService: WidgetTypesService, + + // Inject the ComponentRegistryService to make our custom component available for late loading by the dashboards framework + private componentRegistry: ComponentRegistryService, + private changeDetectorRef: ChangeDetectorRef + ) {} + + public ngOnInit(): void { + // Register the custom widget type and custom components + // Note: This could also be done in the parent module's constructor so that + // multiple dashboards could have access to the same registrations. + this.prepareNovaDashboards(); + + // Register some image items as dropdown options in the widget editor/configurator + // Note: This could also be done in the parent module's constructor so that + // multiple dashboards could have access to the same dropdown options. + this.registerImageOptions(); + + // Initialize our current instance of a dashboard with an instance of our custom widget + this.initializeDashboard(); + } + + /** Used for restoring widgets state */ + public reInitializeDashboard(): void { + // destroys the components and their providers so the dashboard can re init data + this.dashboard = undefined; + this.changeDetectorRef.detectChanges(); + + this.initializeDashboard(); + } + + public initializeDashboard(): void { + // We're using a static configuration object for this example (see widgetConfig at the bottom of the file), + // but this is where the widget's configuration could potentially be populated from a database + const widget = widgetConfig; + + // Create an index of widgets complete with structure and configuration to assign to the dashboard + const widgets: IWidgets = { + // Complete the custom widget with structure information coming from its type definition + [widget.id]: this.widgetTypesService.mergeWithWidgetType(widget), + }; + + // Setting the widget dimensions and position (this is for gridster) + const positions: Record = { + [widget.id]: { + cols: 4, + rows: 11, + y: 0, + x: 0, + }, + }; + + // Finally, assigning the variables we created above to the dashboard + this.dashboard = { positions, widgets }; + } + + private prepareNovaDashboards() { + // Register the custom widget type + this.widgetTypesService.registerWidgetType( + CUSTOM_WIDGET_TYPENAME, + 1, + customWidget + ); + + // Register the custom widget body component with the component registry to make it available + // for late loading by the dashboard framework. + this.componentRegistry.registerByLateLoadKey( + CustomWidgetBodyContentComponent + ); + + // Register the custom configurator section with the component registry to make it available + // for late loading by the dashboard framework. + this.componentRegistry.registerByLateLoadKey( + CustomConfiguratorSectionComponent + ); + } + + private registerImageOptions() { + // Grab the widget's default template which will be needed as a parameter for setNode below. + const widgetTemplate = this.widgetTypesService.getWidgetType( + CUSTOM_WIDGET_TYPENAME, + 1 + ); + + // Register some image items as dropdown options in the widget editor/configurator + this.widgetTypesService.setNode( + // This is the template we grabbed above with getWidgetType + widgetTemplate, + // We are setting the editor/configurator part of the widget template + "configurator", + // This indicates which node you are changing and we want to change the image items available for selection in the editor. + // For reference, see the 'paths' property of the custom widget's IWidgetTypeDefinition at the bottom of this file. + IMAGE_SELECTION_CONFIGURATOR_PATH_KEY, + // We are setting the image items available for selection in the editor. 'imageItems' is defined + // at the bottom of this file. + imageItems + ); + } +} + +/*************************************************************************************************** + * This is the type definition of our custom widget + ***************************************************************************************************/ +const customWidget: IWidgetTypeDefinition = { + /*************************************************************************************************** + * Paths to important settings in this type definition + ***************************************************************************************************/ + paths: { + widget: { + [WellKnownPathKey.Root]: DEFAULT_PIZZAGNA_ROOT, + }, + configurator: { + [WellKnownPathKey.Root]: DEFAULT_PIZZAGNA_ROOT, + // for the custom configuration component, this is the path for the list of image items available for selection + [IMAGE_SELECTION_CONFIGURATOR_PATH_KEY]: + "imageSelection.properties.imageItems", + }, + }, + /*************************************************************************************************** + * Widget section describes the structural part of the custom widget + ***************************************************************************************************/ + widget: { + [PizzagnaLayer.Structure]: { + [DEFAULT_PIZZAGNA_ROOT]: { + id: DEFAULT_PIZZAGNA_ROOT, + // base layout of the widget - all components referenced herein will be stacked in a column + componentType: StackComponent.lateLoadKey, + providers: { + // When enabled, this provider emits the REFRESH event on the pizzagna event bus every X seconds + [WellKnownProviders.Refresher]: refresher(), + // event proxy manages the transmission of events between widget and dashboard such as the WIDGET_EDIT and WIDGET_REMOVE events + [WellKnownProviders.EventProxy]: EVENT_PROXY, + }, + properties: { + // these values reference child components in the widget structure defined below + nodes: ["header", "loading", "body"], + }, + }, + // standard widget header + header: WIDGET_HEADER, + // this is the loading bar below the header + loading: WIDGET_LOADING, + // the body node + body: WIDGET_BODY, + + // retrieving the definitions for the body content nodes. the argument corresponds to the main content node key + ...widgetBodyContentNodes("mainContent"), + + // the component that supplies the content of our custom widget + mainContent: { + id: "mainContent", + componentType: CustomWidgetBodyContentComponent.lateLoadKey, + properties: { + elementClass: "d-flex w-100 justify-content-center", + }, + }, + }, + [PizzagnaLayer.Configuration]: { + [DEFAULT_PIZZAGNA_ROOT]: { + id: DEFAULT_PIZZAGNA_ROOT, + providers: { + // default refresher configuration + [WellKnownProviders.Refresher]: refresher(false, 60), + }, + }, + // default header configuration + header: { + properties: { + title: $localize\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\`Empty Custom Widget\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\`, + }, + }, + }, + }, + /*************************************************************************************************** + * Configurator section describes the form that's used to configure the widget + ***************************************************************************************************/ + configurator: { + [PizzagnaLayer.Structure]: { + [DEFAULT_PIZZAGNA_ROOT]: { + id: DEFAULT_PIZZAGNA_ROOT, + // base layout of the configurator - all form components referenced herein will be stacked in a column + componentType: FormStackComponent.lateLoadKey, + properties: { + elementClass: + "flex-grow-1 overflow-auto nui-scroll-shadows", + // these values reference child components laid out in this form (defined below) + nodes: ["presentation", "customConfig"], + }, + }, + // /presentation + presentation: { + id: "presentation", + componentType: WidgetConfiguratorSectionComponent.lateLoadKey, + properties: { + headerText: $localize\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\`Presentation\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\`, + nodes: ["titleAndDescription"], + }, + }, + // /presentation/titleAndDescription + titleAndDescription: { + id: "titleAndDescription", + componentType: + TitleAndDescriptionConfigurationComponent.lateLoadKey, + providers: { + converter: { + providerId: NOVA_TITLE_AND_DESCRIPTION_CONVERTER, + } as IProviderConfiguration, + }, + }, + // /customConfig + customConfig: { + id: "customConfig", + componentType: WidgetConfiguratorSectionComponent.lateLoadKey, + properties: { + headerText: $localize\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\`Custom Widget Configuration\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\`, + nodes: ["imageSelection"], + }, + }, + // /customConfig/imageSelection + imageSelection: { + id: "imageSelection", + // Here's where we set the configurator to use our custom configurator section + componentType: CustomConfiguratorSectionComponent.lateLoadKey, + properties: { + // This corresponds to the 'imageItems' input on the custom configurator section component + // which defines the list of image items to pick from. The empty value shown here is overridden + // in the 'registerImageOptions' method above. + imageItems: [] as IMenuItem[], + }, + providers: { + // Using the generic converter to map the selected image source between the widget and the form + [WellKnownProviders.Converter]: { + providerId: NOVA_GENERIC_CONVERTER, + properties: { + formParts: [ + { + // Setting up the generic converter to update the 'imageSource' property of the custom widget 'mainContent' component + previewPath: "mainContent.properties", + // Note: To use the NOVA_GENERIC_CONVERTER, the linked properties must have the same name between the configurator + // section component and the widget 'mainContent' component. Additionally, the property name must match the formControl + // name used in the configurator section component. In this case, the common name among all three is 'imageSource'. + keys: ["imageSource"], + }, + ] as IConverterFormPartsProperties[], + }, + } as IProviderConfiguration, + }, + }, + }, + }, +}; + +// For this example, we're using static items for the image selection dropdown. In a more realistic scenario, +// the items available for selection might come from a backend database. +const imageItems = [ + { + title: "Harry Potter Book Cover", + url: "https://imgc.allpostersimages.com/img/print/u-g-F8PQ9I0.jpg?w=550&h=550&p=0", + }, + { + title: "Harry Potter Movie Poster", + url: "https://images-na.ssl-images-amazon.com/images/I/81gpmMdKOHL._AC_SY741_.jpg", + }, +] as IMenuItem[]; + +// We're using a static configuration object for this example. In a more realistic scenario, +// a widget's configuration would likely be stored in a database. +const widgetConfig: IWidget = { + id: "widget1", + // This custom type is registered in the 'prepareNovaDashboards' method above. + type: CUSTOM_WIDGET_TYPENAME, + pizzagna: { + [PizzagnaLayer.Configuration]: { + header: { + // Setting the initial property values for the WidgetHeaderComponent + properties: { + title: "Harry Potter and the Sorcerer's Stone", + subtitle: "By J. K. Rowling", + }, + }, + mainContent: { + properties: { + // Setting the initial value for the 'imageSource' property on our custom widget body + imageSource: imageItems[0].url, + }, + }, + }, + }, +}; +\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\`, + "tutorials/customization/widget/custom-widget.module.ts": \\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\`// © 2022 SolarWinds Worldwide, LLC. All rights reserved. +// +// Permission is hereby granted, free of charge, to any person obtaining a copy +// of this software and associated documentation files (the "Software"), to +// deal in the Software without restriction, including without limitation the +// rights to use, copy, modify, merge, publish, distribute, sublicense, and/or +// sell copies of the Software, and to permit persons to whom the Software is +// furnished to do so, subject to the following conditions: +// +// The above copyright notice and this permission notice shall be included in +// all copies or substantial portions of the Software. +// +// THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR +// IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, +// FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE +// AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER +// LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, +// OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN +// THE SOFTWARE. + +import { CommonModule } from "@angular/common"; +import { HttpClientModule } from "@angular/common/http"; +import { NgModule } from "@angular/core"; +import { ReactiveFormsModule } from "@angular/forms"; +import { RouterModule } from "@angular/router"; + +import { + NuiButtonModule, + NuiDocsModule, + NuiFormFieldModule, + NuiIconModule, + NuiImageModule, + NuiMessageModule, + NuiSelectV2Module, + NuiSwitchModule, + DEMO_PATH_TOKEN, +} from "@nova-ui/bits"; +import { + NuiDashboardConfiguratorModule, + NuiDashboardsModule, +} from "@nova-ui/dashboards"; + +import { CustomWidgetDocsComponent } from "./custom-widget-docs.component"; +import { + CustomConfiguratorSectionComponent, + CustomWidgetBodyContentComponent, + CustomWidgetComponent, +} from "./custom-widget.component"; +import { getDemoFiles } from "../../../../../demo-files-factory"; + +const routes = [ + { + path: "", + component: CustomWidgetDocsComponent, + data: { + srlc: { + hideIndicator: true, + }, + showThemeSwitcher: true, + }, + }, + { + path: "example", + component: CustomWidgetComponent, + data: { + srlc: { + hideIndicator: true, + }, + }, + }, +]; + +@NgModule({ + imports: [ + CommonModule, + ReactiveFormsModule, + HttpClientModule, + NuiDashboardsModule, + NuiDashboardConfiguratorModule, + NuiDocsModule, + NuiFormFieldModule, + NuiIconModule, + NuiImageModule, + NuiMessageModule, + NuiSelectV2Module, + NuiSwitchModule, + NuiButtonModule, + RouterModule.forChild(routes), + ], + declarations: [ + CustomWidgetDocsComponent, + CustomConfiguratorSectionComponent, + CustomWidgetBodyContentComponent, + CustomWidgetComponent, + ], + providers: [ + { + provide: DEMO_PATH_TOKEN, + useValue: getDemoFiles("widget"), + }, + ], +}) +export default class CustomWidgetModule {} +\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\`, + "tutorials/data-source-setup/data-source-setup-docs.component.ts": \\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\`// © 2022 SolarWinds Worldwide, LLC. All rights reserved. +// +// Permission is hereby granted, free of charge, to any person obtaining a copy +// of this software and associated documentation files (the "Software"), to +// deal in the Software without restriction, including without limitation the +// rights to use, copy, modify, merge, publish, distribute, sublicense, and/or +// sell copies of the Software, and to permit persons to whom the Software is +// furnished to do so, subject to the following conditions: +// +// The above copyright notice and this permission notice shall be included in +// all copies or substantial portions of the Software. +// +// THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR +// IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, +// FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE +// AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER +// LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, +// OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN +// THE SOFTWARE. + +import { Component } from "@angular/core"; + +@Component({ + selector: "nui-dashboard-data-source-docs", + templateUrl: "./data-source-setup-docs.component.html", + standalone: false, +}) +export class DataSourceDocsComponent {} +\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\`, + "tutorials/data-source-setup/data-source-setup.component.ts": \\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\`// © 2022 SolarWinds Worldwide, LLC. All rights reserved. +// +// Permission is hereby granted, free of charge, to any person obtaining a copy +// of this software and associated documentation files (the "Software"), to +// deal in the Software without restriction, including without limitation the +// rights to use, copy, modify, merge, publish, distribute, sublicense, and/or +// sell copies of the Software, and to permit persons to whom the Software is +// furnished to do so, subject to the following conditions: +// +// The above copyright notice and this permission notice shall be included in +// all copies or substantial portions of the Software. +// +// THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR +// IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, +// FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE +// AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER +// LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, +// OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN +// THE SOFTWARE. + +import { HttpClient, HttpErrorResponse } from "@angular/common/http"; +import { Component, Injectable, OnDestroy, OnInit } from "@angular/core"; +import { GridsterConfig, GridsterItem } from "angular-gridster2"; +import { BehaviorSubject } from "rxjs"; +import { finalize } from "rxjs/operators"; + +import { DataSourceService, IFilteringOutputs } from "@nova-ui/bits"; +import { + DATA_SOURCE, + DEFAULT_PIZZAGNA_ROOT, + IDashboard, + IKpiData, + IProviderConfiguration, + IRefresherProperties, + IWidget, + IWidgets, + KpiComponent, + NOVA_KPI_DATASOURCE_ADAPTER, + PizzagnaLayer, + ProviderRegistryService, + WellKnownProviders, + WidgetTypesService, +} from "@nova-ui/dashboards"; + +/** + * A simple KPI data source to retrieve the average rating of Harry Potter and the Sorcerer's Stone (book) via googleapis + */ +@Injectable() +export class AverageRatingKpiDataSource + extends DataSourceService + implements OnDestroy +{ + // This is the ID we'll use to identify the provider + public static providerId = "AverageRatingKpiDataSource"; + + // Use this subject to communicate the data source's busy state + public busy = new BehaviorSubject(false); + + constructor(private http: HttpClient) { + super(); + } + + // In this example, getFilteredData is invoked every 10 minutes (Take a look at the refresher + // provider definition in the widget configuration below to see how the interval is set) + public async getFilteredData(): Promise { + this.busy.next(true); + return new Promise((resolve) => { + // *** Make a resource request to an external API (if needed) + this.http + .get("https://www.googleapis.com/books/v1/volumes/5MQFrgEACAAJ") + .pipe(finalize(() => this.busy.next(false))) + .subscribe({ + next: (data: any) => { + resolve({ + result: { + value: data.volumeInfo.averageRating, + }, + }); + }, + error: (error: HttpErrorResponse) => { + resolve({ + result: null, + error: { + type: error.status, + }, + }); + }, + }); + }); + } + + public ngOnDestroy(): void { + this.outputsSubject.complete(); + } +} + +/** + * A component that instantiates the dashboard + */ +@Component({ + selector: "data-source-setup", + templateUrl: "./data-source-setup.component.html", + styleUrls: ["./data-source-setup.component.less"], + standalone: false, +}) +export class DataSourceSetupComponent implements OnInit { + // This variable will hold all the data needed to define the layout and behavior of the widgets. + // Pass this to the dashboard component's dashboard input in the template. + public dashboard: IDashboard; + + // Angular gridster requires a configuration object even if it's empty. + // Pass this to the dashboard component's gridsterConfig input in the template. + public gridsterConfig: GridsterConfig = {}; + + constructor( + // WidgetTypesService provides the widget's necessary structure information + private widgetTypesService: WidgetTypesService, + + // In general, the ProviderRegistryService is used for making entities available for injection into dynamically loaded components. + private providerRegistry: ProviderRegistryService + ) {} + + public ngOnInit(): void { + // Registering the data source for injection into the KPI tile. + // Note: Each tile of a KPI widget is assigned its own instance of the data source + this.providerRegistry.setProviders({ + [AverageRatingKpiDataSource.providerId]: { + provide: DATA_SOURCE, + useClass: AverageRatingKpiDataSource, + // Any dependencies that need to be injected into the provider must be listed here + deps: [HttpClient], + }, + }); + // We're using a static configuration object for this example, but this is where + // the widget's configuration could potentially be populated from a database + const kpiWidget = widgetConfig; + const widgetIndex: IWidgets = { + // Complete the KPI widget with information coming from its type definition + [kpiWidget.id]: + this.widgetTypesService.mergeWithWidgetType(kpiWidget), + }; + + // Setting the widget dimensions and position (this is for gridster) + const positions: Record = { + [kpiWidget.id]: { + cols: 4, + rows: 6, + y: 0, + x: 0, + }, + }; + + // Finally, assigning the variables we created above to the dashboard + this.dashboard = { + positions, + widgets: widgetIndex, + }; + } +} + +const widgetConfig: IWidget = { + id: "widget1", + type: "kpi", + pizzagna: { + [PizzagnaLayer.Configuration]: { + [DEFAULT_PIZZAGNA_ROOT]: { + providers: { + [WellKnownProviders.Refresher]: { + properties: { + // Configuring the refresher interval so that our data source is invoked every ten minutes + interval: 60 * 10, + enabled: true, + } as IRefresherProperties, + } as Partial, + }, + }, + header: { + properties: { + title: "Harry Potter and the Sorcerer's Stone", + subtitle: "By J. K. Rowling", + }, + }, + tiles: { + properties: { + nodes: ["kpi1"], + }, + }, + kpi1: { + id: "kpi1", + componentType: KpiComponent.lateLoadKey, + properties: { + widgetData: { + units: "out of 5 Stars", + label: "Average Rating", + }, + }, + providers: { + [WellKnownProviders.DataSource]: { + // Setting the data source providerId for the tile with id "kpi1" + providerId: AverageRatingKpiDataSource.providerId, + } as IProviderConfiguration, + [WellKnownProviders.Adapter]: { + providerId: NOVA_KPI_DATASOURCE_ADAPTER, + properties: { + componentId: "kpi1", + propertyPath: "widgetData", + }, + } as IProviderConfiguration, + }, + }, + }, + }, +}; +\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\`, + "tutorials/data-source-setup/data-source-setup.module.ts": \\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\`// © 2022 SolarWinds Worldwide, LLC. All rights reserved. +// +// Permission is hereby granted, free of charge, to any person obtaining a copy +// of this software and associated documentation files (the "Software"), to +// deal in the Software without restriction, including without limitation the +// rights to use, copy, modify, merge, publish, distribute, sublicense, and/or +// sell copies of the Software, and to permit persons to whom the Software is +// furnished to do so, subject to the following conditions: +// +// The above copyright notice and this permission notice shall be included in +// all copies or substantial portions of the Software. +// +// THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR +// IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, +// FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE +// AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER +// LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, +// OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN +// THE SOFTWARE. + +import { CommonModule } from "@angular/common"; +import { HttpClientModule } from "@angular/common/http"; +import { NgModule } from "@angular/core"; +import { RouterModule } from "@angular/router"; + +import { + NuiDocsModule, + NuiMessageModule, + DEMO_PATH_TOKEN, +} from "@nova-ui/bits"; +import { NuiDashboardsModule } from "@nova-ui/dashboards"; + +import { DataSourceDocsComponent } from "./data-source-setup-docs.component"; +import { DataSourceSetupComponent } from "./data-source-setup.component"; +import { getDemoFiles } from "../../../../demo-files-factory"; + +const routes = [ + { + path: "", + component: DataSourceDocsComponent, + data: { + srlc: { + hideIndicator: true, + }, + showThemeSwitcher: true, + }, + }, + { + path: "example", + component: DataSourceSetupComponent, + data: { + srlc: { + hideIndicator: true, + }, + }, + }, +]; + +@NgModule({ + imports: [ + CommonModule, + HttpClientModule, + NuiDashboardsModule, + NuiDocsModule, + NuiMessageModule, + RouterModule.forChild(routes), + ], + declarations: [DataSourceDocsComponent, DataSourceSetupComponent], + providers: [ + { + provide: DEMO_PATH_TOKEN, + useValue: getDemoFiles("data-source-setup"), + }, + ], +}) +export default class DataSourceSetupModule {} +\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\`, + "tutorials/dynamic-header-links/dynamic-header-links-docs.component.ts": \\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\`// © 2022 SolarWinds Worldwide, LLC. All rights reserved. +// +// Permission is hereby granted, free of charge, to any person obtaining a copy +// of this software and associated documentation files (the "Software"), to +// deal in the Software without restriction, including without limitation the +// rights to use, copy, modify, merge, publish, distribute, sublicense, and/or +// sell copies of the Software, and to permit persons to whom the Software is +// furnished to do so, subject to the following conditions: +// +// The above copyright notice and this permission notice shall be included in +// all copies or substantial portions of the Software. +// +// THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR +// IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, +// FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE +// AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER +// LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, +// OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN +// THE SOFTWARE. + +import { Component } from "@angular/core"; + +@Component({ + selector: "nui-dynamic-header-links-docs", + templateUrl: "./dynamic-header-links-docs.component.html", + standalone: false, +}) +export class DynamicHeaderLinksDocsComponent {} +\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\`, + "tutorials/dynamic-header-links/dynamic-header-links-docs.module.ts": \\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\`// © 2022 SolarWinds Worldwide, LLC. All rights reserved. +// +// Permission is hereby granted, free of charge, to any person obtaining a copy +// of this software and associated documentation files (the "Software"), to +// deal in the Software without restriction, including without limitation the +// rights to use, copy, modify, merge, publish, distribute, sublicense, and/or +// sell copies of the Software, and to permit persons to whom the Software is +// furnished to do so, subject to the following conditions: +// +// The above copyright notice and this permission notice shall be included in +// all copies or substantial portions of the Software. +// +// THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR +// IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, +// FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE +// AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER +// LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, +// OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN +// THE SOFTWARE. + +import { NgModule } from "@angular/core"; +import { RouterModule } from "@angular/router"; + +import { DynamicHeaderLinksDocsComponent } from "./dynamic-header-links-docs.component"; + +const routes = [ + { + path: "", + component: DynamicHeaderLinksDocsComponent, + data: { + srlc: { + hideIndicator: true, + }, + showThemeSwitcher: true, + }, + }, +]; + +@NgModule({ + imports: [RouterModule.forChild(routes)], + declarations: [DynamicHeaderLinksDocsComponent], +}) +export default class DynamicHeaderLinksDocsModule {} +\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\`, + "tutorials/hello-dashboards/hello-dashboards-docs.component.ts": \\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\`// © 2022 SolarWinds Worldwide, LLC. All rights reserved. +// +// Permission is hereby granted, free of charge, to any person obtaining a copy +// of this software and associated documentation files (the "Software"), to +// deal in the Software without restriction, including without limitation the +// rights to use, copy, modify, merge, publish, distribute, sublicense, and/or +// sell copies of the Software, and to permit persons to whom the Software is +// furnished to do so, subject to the following conditions: +// +// The above copyright notice and this permission notice shall be included in +// all copies or substantial portions of the Software. +// +// THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR +// IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, +// FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE +// AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER +// LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, +// OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN +// THE SOFTWARE. + +import { Component } from "@angular/core"; + +@Component({ + selector: "nui-dashboard-hello-dashboards-docs", + templateUrl: "./hello-dashboards-docs.component.html", + standalone: false, +}) +export class HelloDashboardsDocsComponent {} +\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\`, + "tutorials/hello-dashboards/hello-dashboards-example/hello-dashboards-example.component.ts": \\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\`// © 2022 SolarWinds Worldwide, LLC. All rights reserved. +// +// Permission is hereby granted, free of charge, to any person obtaining a copy +// of this software and associated documentation files (the "Software"), to +// deal in the Software without restriction, including without limitation the +// rights to use, copy, modify, merge, publish, distribute, sublicense, and/or +// sell copies of the Software, and to permit persons to whom the Software is +// furnished to do so, subject to the following conditions: +// +// The above copyright notice and this permission notice shall be included in +// all copies or substantial portions of the Software. +// +// THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR +// IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, +// FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE +// AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER +// LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, +// OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN +// THE SOFTWARE. + +import { Component, OnInit } from "@angular/core"; +import { GridsterConfig, GridsterItem } from "angular-gridster2"; + +import { + IDashboard, + IWidget, + IWidgets, + KpiComponent, + PizzagnaLayer, + WidgetTypesService, +} from "@nova-ui/dashboards"; + +/** + * A component that instantiates the dashboard + */ +@Component({ + selector: "hello-dashboards-example", + templateUrl: "./hello-dashboards-example.component.html", + styleUrls: ["./hello-dashboards-example.component.less"], + standalone: false, +}) +export class HelloDashboardsExampleComponent implements OnInit { + // This variable will have all the data needed to render the widgets widgets. + // Pass this to the dashboard component's dashboard input. + public dashboard: IDashboard; + // Angular gridster requires a configuration object even if its empty. + // Pass this to the dashboard component's gridsterConfig input. + public gridsterConfig: GridsterConfig = {}; + + // WidgetTypesService provides the widget's necessary structure information + constructor(private widgetTypesService: WidgetTypesService) {} + + public ngOnInit(): void { + // Here we are hard-coding the widget config for this example, but this is where you + // could potentially populate the widget's configuration from a database + const kpiWidget = widgetConfig; + const widgetIndex: IWidgets = { + // Complete the KPI widget with information coming from its type definition + [kpiWidget.id]: + this.widgetTypesService.mergeWithWidgetType(kpiWidget), + }; + // Setting widget position and dimensions (this is for gridster) + const positions: Record = { + [kpiWidget.id]: { + cols: 4, + rows: 6, + y: 0, + x: 0, + }, + }; + // Finally, assigning the variables we created above to the dashboard + this.dashboard = { + positions, + widgets: widgetIndex, + }; + } +} + +// In a real-world scenario, this configuration would typically be fetched from a database or at least live in another file +const widgetConfig: IWidget = { + id: "widget1", + type: "kpi", + pizzagna: { + [PizzagnaLayer.Configuration]: { + header: { + properties: { + title: "Hello, KPI Widget!", + subtitle: "A Venue for Meaningful Values", + }, + }, + tiles: { + properties: { + nodes: ["kpi1"], + }, + }, + kpi1: { + id: "kpi1", + componentType: KpiComponent.lateLoadKey, + properties: { + widgetData: { + id: "totalStorage", + value: 1, + label: "Total storage", + units: "TB", + }, + }, + }, + }, + }, +}; +\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\`, + "tutorials/hello-dashboards/hello-dashboards.module.ts": \\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\`// © 2022 SolarWinds Worldwide, LLC. All rights reserved. +// +// Permission is hereby granted, free of charge, to any person obtaining a copy +// of this software and associated documentation files (the "Software"), to +// deal in the Software without restriction, including without limitation the +// rights to use, copy, modify, merge, publish, distribute, sublicense, and/or +// sell copies of the Software, and to permit persons to whom the Software is +// furnished to do so, subject to the following conditions: +// +// The above copyright notice and this permission notice shall be included in +// all copies or substantial portions of the Software. +// +// THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR +// IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, +// FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE +// AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER +// LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, +// OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN +// THE SOFTWARE. + +import { CommonModule } from "@angular/common"; +import { NgModule } from "@angular/core"; +import { RouterModule } from "@angular/router"; + +import { + NuiDocsModule, + NuiMessageModule, + DEMO_PATH_TOKEN, +} from "@nova-ui/bits"; +import { NuiDashboardsModule } from "@nova-ui/dashboards"; + +import { HelloDashboardsDocsComponent } from "./hello-dashboards-docs.component"; +import { HelloDashboardsExampleComponent } from "./hello-dashboards-example/hello-dashboards-example.component"; +import { getDemoFiles } from "../../../../demo-files-factory"; + +const routes = [ + { + path: "", + component: HelloDashboardsDocsComponent, + data: { + srlc: { + hideIndicator: true, + }, + showThemeSwitcher: true, + }, + }, + { + path: "example", + component: HelloDashboardsExampleComponent, + data: { + srlc: { + hideIndicator: true, + }, + }, + }, +]; + +@NgModule({ + imports: [ + CommonModule, + NuiDashboardsModule, + NuiDocsModule, + NuiMessageModule, + RouterModule.forChild(routes), + ], + declarations: [ + HelloDashboardsDocsComponent, + HelloDashboardsExampleComponent, + ], + providers: [ + { + provide: DEMO_PATH_TOKEN, + useValue: getDemoFiles("hello-dashboards"), + }, + ], +}) +export default class HelloDashboardsModule {} +\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\`, + "tutorials/persistence-handler-setup/persistence-handler-setup-docs.component.ts": \\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\`// © 2022 SolarWinds Worldwide, LLC. All rights reserved. +// +// Permission is hereby granted, free of charge, to any person obtaining a copy +// of this software and associated documentation files (the "Software"), to +// deal in the Software without restriction, including without limitation the +// rights to use, copy, modify, merge, publish, distribute, sublicense, and/or +// sell copies of the Software, and to permit persons to whom the Software is +// furnished to do so, subject to the following conditions: +// +// The above copyright notice and this permission notice shall be included in +// all copies or substantial portions of the Software. +// +// THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR +// IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, +// FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE +// AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER +// LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, +// OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN +// THE SOFTWARE. + +import { Component } from "@angular/core"; + +@Component({ + selector: "nui-dashboard-persistence-handler-setup-docs", + templateUrl: "./persistence-handler-setup-docs.component.html", + standalone: false, +}) +export class PersistenceHandlerSetupDocsComponent {} +\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\`, + "tutorials/persistence-handler-setup/persistence-handler-setup.component.ts": \\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\`// © 2022 SolarWinds Worldwide, LLC. All rights reserved. +// +// Permission is hereby granted, free of charge, to any person obtaining a copy +// of this software and associated documentation files (the "Software"), to +// deal in the Software without restriction, including without limitation the +// rights to use, copy, modify, merge, publish, distribute, sublicense, and/or +// sell copies of the Software, and to permit persons to whom the Software is +// furnished to do so, subject to the following conditions: +// +// The above copyright notice and this permission notice shall be included in +// all copies or substantial portions of the Software. +// +// THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR +// IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, +// FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE +// AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER +// LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, +// OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN +// THE SOFTWARE. + +import { HttpClient, HttpErrorResponse } from "@angular/common/http"; +import { + ChangeDetectorRef, + Component, + Injectable, + OnDestroy, + OnInit, +} from "@angular/core"; +import { GridsterConfig, GridsterItem } from "angular-gridster2"; +import { BehaviorSubject, Observable, Subject } from "rxjs"; +import { finalize } from "rxjs/operators"; + +import { + DataSourceService, + IFilteringOutputs, + ToastService, + uuid, +} from "@nova-ui/bits"; +import { + DATA_SOURCE, + DEFAULT_PIZZAGNA_ROOT, + IDashboard, + IDashboardPersistenceHandler, + IKpiData, + IProviderConfiguration, + IRefresherProperties, + IWidget, + IWidgets, + KpiComponent, + NOVA_KPI_DATASOURCE_ADAPTER, + PizzagnaLayer, + ProviderRegistryService, + WellKnownPathKey, + WellKnownProviders, + WidgetTypesService, +} from "@nova-ui/dashboards"; + +/** + * A simple persistence handler that is tied to the widget editor directive + */ +@Injectable() +// The realizer of IDashboardPersistenceHandler may implement a trySubmit and/or a tryRemove method. +export class PersistenceHandler implements IDashboardPersistenceHandler { + // This variable is just to show how to handle error handling. + private persistenceSucceeded: boolean = true; + + // The example uses the toast service to demonstrate the + // invocation of each of the persistence handler callbacks + constructor(private toastService: ToastService) { + // toastService options to let it sit on the page for 2 seconds. + this.toastService.setConfig({ + timeOut: 2000, + }); + } + + // This method will be invoked anytime the widget editor form gets submitted. + public trySubmit = (widget: IWidget): Observable => { + // Since we are working asynchronously, we'll return a subject. So, after the submit attempt + // succeeds or fails, we can let the subscriber know the result. + const subject = new Subject(); + + if (!widget.id) { + // Creates an id if the widget has no id. + // (This step will make more sense in the context of the widget cloning tutorial + // in which we handle the persistence of a newly created widget.) + widget.id = uuid(); + } + + // For this example, we're using a setTimeout to mock an asynchronous persistence request to a backend + setTimeout(() => { + if (this.persistenceSucceeded) { + // Passes along the new widget after one second. + subject.next(widget); + // Toast on the page on success. + this.toastService.success({ + title: $localize\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\`Submit succeeded.\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\`, + }); + } else { + const errorText = $localize\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\`Submit failed.\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\`; + // Toast on the page on failure. + this.toastService.error({ title: errorText }); + // Makes the subject say there is an error. + subject.error(errorText); + } + // Completes the subject so whoever subscribes to it knows its finished. + subject.complete(); + }, 1000); + + // Returns the subject as an observable. + return subject.asObservable(); + }; + + // This method will be invoked anytime there's a widget removal attempt. + public tryRemove = (widgetId: string): Observable => { + const subject = new Subject(); + + setTimeout(() => { + if (this.persistenceSucceeded) { + // Pass through the id of the widget that was removed. + subject.next(widgetId); + this.toastService.success({ + title: $localize\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\`Removal succeeded.\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\`, + }); + } else { + const errorText = $localize\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\`Removal failed.\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\`; + this.toastService.error({ title: errorText }); + subject.error(errorText); + } + subject.complete(); + }, 1000); + + return subject.asObservable(); + }; +} + +/** + * A component that instantiates the dashboard + */ +@Component({ + selector: "persistence-handler-setup", + templateUrl: "./persistence-handler-setup.component.html", + styleUrls: ["./persistence-handler-setup.component.less"], + // Here we provide our persistence handler at the component level; this can also be done in the module. + providers: [PersistenceHandler], + standalone: false, +}) +export class PersistenceHandlerSetupComponent implements OnInit { + // This variable will hold all the data needed to define the layout and behavior of the widgets. + // Pass this to the dashboard component's dashboard input in the template. + public dashboard: IDashboard | undefined; + + // Angular gridster requires a configuration object even if it's empty. + // Pass this to the dashboard component's gridsterConfig input in the template. + public gridsterConfig: GridsterConfig = {}; + + // Boolean which dashboard takes in as an input if its true it allows you to move widgets around. + public editMode: boolean = false; + + constructor( + // WidgetTypesService provides the widget's necessary structure information + private widgetTypesService: WidgetTypesService, + + // In general, the ProviderRegistryService is used for making entities available for injection into dynamically loaded components. + private providerRegistry: ProviderRegistryService, + + // We are injecting the PersistenceHandler we created and assigning it to a property we use in the template. + public persistenceHandler: PersistenceHandler, + private changeDetectorRef: ChangeDetectorRef + ) {} + + public ngOnInit(): void { + // Grabbing the widget's default template which will be needed as a parameter for setNode + const widgetTemplate = this.widgetTypesService.getWidgetType("kpi", 1); + // Registering our data sources as dropdown options in the widget editor/configurator + // Note: This could also be done in the parent module's constructor so that + // multiple dashboards could have access to the same widget template modification. + this.widgetTypesService.setNode( + // This is the template we grabbed above with getWidgetType + widgetTemplate, + // We are setting the editor/configurator part of the widget template + "configurator", + // This indicates which node you are changing and we want to change + // the data source providers available for selection in the editor. + WellKnownPathKey.DataSourceProviders, + // We are setting the data sources available for selection in the editor + [ + AverageRatingKpiDataSource.providerId, + RatingsCountKpiDataSource.providerId, + ] + ); + + // Registering the data sources available for injection into the KPI tiles. + // Note: Each tile of a KPI widget is assigned its own instance of a data source + this.providerRegistry.setProviders({ + [AverageRatingKpiDataSource.providerId]: { + provide: DATA_SOURCE, + useClass: AverageRatingKpiDataSource, + // Any dependencies that need to be injected into the provider must be listed here + deps: [HttpClient], + }, + [RatingsCountKpiDataSource.providerId]: { + provide: DATA_SOURCE, + useClass: RatingsCountKpiDataSource, + deps: [HttpClient], + }, + }); + + this.initializeDashboard(); + } + + /** Used for restoring widgets state */ + public reInitializeDashboard(): void { + // destroys the components and their providers so the dashboard can re init data + this.dashboard = undefined; + this.changeDetectorRef.detectChanges(); + + this.initializeDashboard(); + } + + public initializeDashboard(): void { + // We're using a static configuration object for this example (see widgetConfig at the bottom of the file), + // but this is where the widget's configuration could potentially be populated from a database + const kpiWidget = widgetConfig; + const widgetIndex: IWidgets = { + // Complete the KPI widget with information coming from its type definition + [kpiWidget.id]: + this.widgetTypesService.mergeWithWidgetType(kpiWidget), + }; + + // Setting the widget dimensions and position (this is for gridster) + const positions: Record = { + [kpiWidget.id]: { + cols: 4, + rows: 6, + y: 0, + x: 0, + }, + }; + + // Finally, assigning the variables we created above to the dashboard + this.dashboard = { + positions, + widgets: widgetIndex, + }; + } +} + +/** + * A simple KPI data source to retrieve the average rating of Harry Potter and the Sorcerer's Stone (book) via googleapis + */ +@Injectable() +export class AverageRatingKpiDataSource + extends DataSourceService + implements OnDestroy +{ + // This is the ID we'll use to identify the provider + public static providerId = "AverageRatingKpiDataSource"; + + // Use this subject to communicate the data source's busy state + public busy = new BehaviorSubject(false); + + constructor(private http: HttpClient) { + super(); + } + + // In this example, getFilteredData is invoked every 10 minutes (Take a look at the refresher + // provider definition in the widget configuration below to see how the interval is set) + public async getFilteredData(): Promise { + this.busy.next(true); + return new Promise((resolve) => { + // *** Make a resource request to an external API (if needed) + this.http + .get("https://www.googleapis.com/books/v1/volumes/5MQFrgEACAAJ") + .pipe(finalize(() => this.busy.next(false))) + .subscribe({ + next: (data: any) => { + resolve({ + result: { + value: data.volumeInfo.averageRating, + }, + }); + }, + error: (error: HttpErrorResponse) => { + resolve({ + result: null, + error: { + type: error.status, + }, + }); + }, + }); + }); + } + + public ngOnDestroy(): void { + this.outputsSubject.complete(); + } +} + +/** + * A simple KPI data source to retrieve the ratings count of Harry Potter and the Sorcerer's Stone (book) via googleapis + */ +@Injectable() +export class RatingsCountKpiDataSource + extends DataSourceService + implements OnDestroy +{ + public static providerId = "RatingsCountKpiDataSource"; + + // Use this subject to communicate the data source's busy state + public busy = new BehaviorSubject(false); + + constructor(private http: HttpClient) { + super(); + } + + public async getFilteredData(): Promise { + this.busy.next(true); + return new Promise((resolve) => { + this.http + .get("https://www.googleapis.com/books/v1/volumes/5MQFrgEACAAJ") + .pipe(finalize(() => this.busy.next(false))) + .subscribe({ + next: (data: any) => { + resolve({ + result: { + value: data.volumeInfo.ratingsCount, + }, + }); + }, + error: (error: HttpErrorResponse) => { + resolve({ + result: null, + error: { + type: error.status, + }, + }); + }, + }); + }); + } + + public ngOnDestroy(): void { + this.outputsSubject.complete(); + } +} + +const widgetConfig: IWidget = { + id: "widget1", + type: "kpi", + pizzagna: { + [PizzagnaLayer.Configuration]: { + [DEFAULT_PIZZAGNA_ROOT]: { + providers: { + [WellKnownProviders.Refresher]: { + properties: { + // Configuring the refresher interval so that our data source is invoked every ten minutes + interval: 60 * 10, + enabled: true, + } as IRefresherProperties, + } as Partial, + }, + }, + header: { + properties: { + title: "Harry Potter and the Sorcerer's Stone", + subtitle: "By J. K. Rowling", + }, + }, + tiles: { + properties: { + nodes: ["kpi1"], + }, + }, + kpi1: { + id: "kpi1", + componentType: KpiComponent.lateLoadKey, + properties: { + widgetData: { + units: "out of 5 Stars", + label: "Average Rating", + }, + }, + providers: { + [WellKnownProviders.DataSource]: { + // Setting the data source providerId for the tile with id "kpi1" + providerId: AverageRatingKpiDataSource.providerId, + } as IProviderConfiguration, + [WellKnownProviders.Adapter]: { + providerId: NOVA_KPI_DATASOURCE_ADAPTER, + properties: { + componentId: "kpi1", + propertyPath: "widgetData", + }, + } as IProviderConfiguration, + }, + }, + }, + }, +}; +\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\`, + "tutorials/persistence-handler-setup/persistence-handler-setup.module.ts": \\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\`// © 2022 SolarWinds Worldwide, LLC. All rights reserved. +// +// Permission is hereby granted, free of charge, to any person obtaining a copy +// of this software and associated documentation files (the "Software"), to +// deal in the Software without restriction, including without limitation the +// rights to use, copy, modify, merge, publish, distribute, sublicense, and/or +// sell copies of the Software, and to permit persons to whom the Software is +// furnished to do so, subject to the following conditions: +// +// The above copyright notice and this permission notice shall be included in +// all copies or substantial portions of the Software. +// +// THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR +// IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, +// FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE +// AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER +// LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, +// OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN +// THE SOFTWARE. + +import { CommonModule } from "@angular/common"; +import { HttpClientModule } from "@angular/common/http"; +import { NgModule } from "@angular/core"; +import { RouterModule } from "@angular/router"; + +import { + NuiButtonModule, + NuiDocsModule, + NuiMessageModule, + NuiSwitchModule, + NuiToastModule, + DEMO_PATH_TOKEN, +} from "@nova-ui/bits"; +import { NuiDashboardsModule } from "@nova-ui/dashboards"; + +import { PersistenceHandlerSetupDocsComponent } from "./persistence-handler-setup-docs.component"; +import { PersistenceHandlerSetupComponent } from "./persistence-handler-setup.component"; +import { getDemoFiles } from "../../../../demo-files-factory"; + +const routes = [ + { + path: "", + component: PersistenceHandlerSetupDocsComponent, + data: { + srlc: { + hideIndicator: true, + }, + showThemeSwitcher: true, + }, + }, + { + path: "example", + component: PersistenceHandlerSetupComponent, + data: { + srlc: { + hideIndicator: true, + }, + }, + }, +]; + +@NgModule({ + imports: [ + CommonModule, + HttpClientModule, + NuiDashboardsModule, + NuiDocsModule, + NuiMessageModule, + NuiSwitchModule, + NuiToastModule, + NuiButtonModule, + RouterModule.forChild(routes), + ], + declarations: [ + PersistenceHandlerSetupDocsComponent, + PersistenceHandlerSetupComponent, + ], + providers: [ + { + provide: DEMO_PATH_TOKEN, + useValue: getDemoFiles("persistence-handler-setup"), + }, + ], +}) +export default class PersistenceHandlerSetupModule {} +\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\`, + "tutorials/tutorials.module.ts": \\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\`// © 2022 SolarWinds Worldwide, LLC. All rights reserved. +// +// Permission is hereby granted, free of charge, to any person obtaining a copy +// of this software and associated documentation files (the "Software"), to +// deal in the Software without restriction, including without limitation the +// rights to use, copy, modify, merge, publish, distribute, sublicense, and/or +// sell copies of the Software, and to permit persons to whom the Software is +// furnished to do so, subject to the following conditions: +// +// The above copyright notice and this permission notice shall be included in +// all copies or substantial portions of the Software. +// +// THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR +// IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, +// FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE +// AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER +// LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, +// OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN +// THE SOFTWARE. + +import { NgModule, Type } from "@angular/core"; +import { RouterModule, Routes } from "@angular/router"; + +import { ConfiguratorHeadingService } from "@nova-ui/dashboards"; + +export enum TutorialsModuleRoute { + HelloDashboards = "hello-dashboards", + DataSource = "data-source-setup", + WidgetEditor = "widget-editor-setup", + SubmitHandler = "persistence-handler-setup", + WidgetCreation = "widget-creation", + Customization = "customization", + WidgetErrorHandling = "widget-error-handling", + DynamicHeaderLinks = "dynamic-header-links", +} + +const routes: Routes = [ + { + path: TutorialsModuleRoute.HelloDashboards, + loadChildren: async () => + import( + "./hello-dashboards/hello-dashboards.module" + ) as object as Promise>, + }, + { + path: TutorialsModuleRoute.DataSource, + loadChildren: async () => + import( + "./data-source-setup/data-source-setup.module" + ) as object as Promise>, + }, + { + path: TutorialsModuleRoute.WidgetEditor, + loadChildren: async () => + import( + "./widget-editor-setup/widget-editor-setup.module" + ) as object as Promise>, + }, + { + path: TutorialsModuleRoute.SubmitHandler, + loadChildren: async () => + import( + "./persistence-handler-setup/persistence-handler-setup.module" + ) as object as Promise>, + }, + { + path: TutorialsModuleRoute.WidgetCreation, + loadChildren: async () => + import( + "./widget-creation/widget-creation.module" + ) as object as Promise>, + }, + { + path: TutorialsModuleRoute.Customization, + loadChildren: async () => + import("./customization/customization.module") as object as Promise< + Type + >, + }, + { + path: TutorialsModuleRoute.WidgetErrorHandling, + loadChildren: async () => + import( + "./widget-error-handling/widget-error-handling.module" + ) as object as Promise>, + }, + { + path: TutorialsModuleRoute.DynamicHeaderLinks, + loadChildren: async () => + import( + "./dynamic-header-links/dynamic-header-links-docs.module" + ) as object as Promise>, + }, +]; + +@NgModule({ + imports: [RouterModule.forChild(routes)], + providers: [ConfiguratorHeadingService], +}) +export default class TutorialsModule {} +\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\`, + "tutorials/widget-creation/widget-creation-docs.component.ts": \\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\`// © 2022 SolarWinds Worldwide, LLC. All rights reserved. +// +// Permission is hereby granted, free of charge, to any person obtaining a copy +// of this software and associated documentation files (the "Software"), to +// deal in the Software without restriction, including without limitation the +// rights to use, copy, modify, merge, publish, distribute, sublicense, and/or +// sell copies of the Software, and to permit persons to whom the Software is +// furnished to do so, subject to the following conditions: +// +// The above copyright notice and this permission notice shall be included in +// all copies or substantial portions of the Software. +// +// THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR +// IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, +// FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE +// AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER +// LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, +// OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN +// THE SOFTWARE. + +import { Component } from "@angular/core"; + +@Component({ + selector: "nui-dashboard-widget-creation-docs", + templateUrl: "./widget-creation-docs.component.html", + standalone: false, +}) +export class WidgetCreationDocsComponent {} +\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\`, + "tutorials/widget-creation/widget-creation.component.ts": \\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\`// © 2022 SolarWinds Worldwide, LLC. All rights reserved. +// +// Permission is hereby granted, free of charge, to any person obtaining a copy +// of this software and associated documentation files (the "Software"), to +// deal in the Software without restriction, including without limitation the +// rights to use, copy, modify, merge, publish, distribute, sublicense, and/or +// sell copies of the Software, and to permit persons to whom the Software is +// furnished to do so, subject to the following conditions: +// +// The above copyright notice and this permission notice shall be included in +// all copies or substantial portions of the Software. +// +// THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR +// IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, +// FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE +// AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER +// LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, +// OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN +// THE SOFTWARE. + +import { HttpClient, HttpErrorResponse } from "@angular/common/http"; +import { + Component, + EventEmitter, + Injectable, + OnDestroy, + OnInit, + Output, + ViewChild, +} from "@angular/core"; +import { GridsterConfig, GridsterItem } from "angular-gridster2"; +import { BehaviorSubject, Observable, Subject } from "rxjs"; +import { finalize, take, takeUntil } from "rxjs/operators"; + +import { + DataSourceService, + IFilteringOutputs, + ToastService, + uuid, +} from "@nova-ui/bits"; +import { + DashboardComponent, + DATA_SOURCE, + DEFAULT_PIZZAGNA_ROOT, + IDashboard, + IDashboardPersistenceHandler, + IDataSourceOutput, + IKpiData, + IProviderConfiguration, + IRefresherProperties, + IWidget, + IWidgets, + IWidgetSelector, + IWidgetTemplateSelector, + KpiComponent, + NOVA_KPI_DATASOURCE_ADAPTER, + PizzagnaLayer, + ProviderRegistryService, + WellKnownPathKey, + WellKnownProviders, + WidgetClonerService, + WidgetTypesService, +} from "@nova-ui/dashboards"; + +// Interface of a widget item +interface IWidgetItem { + name: string; + widget: IWidget; +} + +// This component acts as the first step, or page, in the wizard where the user selects a wizard type to create. +// It's recommended to have this component in a different file. For this tutorial, it's included in the same +// file for simplicity. +@Component({ + selector: "widget-template-selection", + styleUrls: ["./widget-creation.component.less"], + template: \\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\` +
+ + +
+ + +
+
{{ item.name }}
+
+
+ \\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\`, + standalone: false, +}) +export class WidgetTemplateSelectionComponent + implements IWidgetTemplateSelector, OnInit +{ + // This output will notify the wizard that a widget has been selected. + @Output() public widgetSelected = new EventEmitter(); + + public widgetItems: IWidgetItem[] = []; + public widgetSelection: IWidgetItem[]; + + constructor(private widgetTypesService: WidgetTypesService) {} + + public ngOnInit(): void { + // Here we combine the widget structure from the WidgetTypesService with the corresponding widget + // configuration to create an array of widget objects for the itemSource on the repeat component. + this.widgetItems = [ + { + name: "Fully Configured KPI Widget", + widget: this.widgetTypesService.mergeWithWidgetType( + fullKpiWidgetConfig + ), + }, + { + name: "Unconfigured Proportional Widget", + // Note that 'partialPropWidgetConfig' sets 'metadata.needsConfiguration' to true. + // When this widget is selected in the wizard, the 'Create Widget' button will be hidden + // to guide the user to the second step where they can complete the configuration. + widget: this.widgetTypesService.mergeWithWidgetType( + partialPropWidgetConfig + ), + }, + ]; + + // You can optionally auto-select a widget by doing the following + // this.onSelect([this.widgetItems[0]]); + } + + public onSelect(selectedItems: any[]): void { + // We emit the selected widget to communicate the selection to the configurator + this.widgetSelected.emit(selectedItems[0].widget); + this.widgetSelection = selectedItems; + } +} + +/** + * A simple persistence handler that is tied to the widget editor directive + */ +@Injectable() +// The realizer of IDashboardPersistenceHandler may implement a trySubmit and/or a tryRemove method. +export class PersistenceHandler implements IDashboardPersistenceHandler { + // This variable is just to show how to handle error handling. + private persistenceSucceeded: boolean = true; + + // The example uses the toast service to demonstrate the + // invocation of each of the persistence handler callbacks + constructor(private toastService: ToastService) { + // toastService options to let it sit on the page for 2 seconds. + this.toastService.setConfig({ + timeOut: 2000, + }); + } + + // This method will be invoked anytime the widget editor form gets submitted. + public trySubmit = (widget: IWidget): Observable => { + // Since we are working asynchronously, we'll return a subject. So, after the submit attempt + // succeeds or fails, we can let the subscriber know the result. + const subject = new Subject(); + + if (!widget.id) { + // Creates an id if the widget has no id. + // (This step will make more sense in the context of the widget cloning tutorial + // in which we handle the persistence of a newly created widget.) + widget.id = uuid(); + } + + // For this example, we're using a setTimeout to mock an asynchronous persistence request to a backend + setTimeout(() => { + if (this.persistenceSucceeded) { + // Passes along the new widget after one second. + subject.next(widget); + // Toast on the page on success. + this.toastService.success({ + title: $localize\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\`Submit succeeded.\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\`, + }); + } else { + const errorText = $localize\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\`Submit failed.\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\`; + // Toast on the page on failure. + this.toastService.error({ title: errorText }); + // Makes the subject say there is an error. + subject.error(errorText); + } + // Completes the subject so whoever subscribes to it knows its finished. + subject.complete(); + }, 1000); + + // Returns the subject as an observable. + return subject.asObservable(); + }; + + // This method will be invoked anytime there's a widget removal attempt. + public tryRemove = (widgetId: string): Observable => { + const subject = new Subject(); + + setTimeout(() => { + if (this.persistenceSucceeded) { + // Pass through the id of the widget that was removed. + subject.next(widgetId); + this.toastService.success({ + title: $localize\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\`Removal success\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\`, + }); + } else { + const errorText = $localize\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\`Removal failed.\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\`; + this.toastService.error({ title: errorText }); + subject.error(errorText); + } + subject.complete(); + }, 1000); + + return subject.asObservable(); + }; +} + +/** + * A component that instantiates the dashboard + */ +@Component({ + selector: "widget-creation", + templateUrl: "./widget-creation.component.html", + styleUrls: ["./widget-creation.component.less"], + // Here we provide our persistence handler at the component level; this can also be done in the module. + providers: [PersistenceHandler], + standalone: false, +}) +export class WidgetCreationComponent implements OnInit { + // The WidgetClonerService will need this for updating the dashboard + @ViewChild(DashboardComponent, { static: true }) + dashboardComponent: DashboardComponent; + // This variable will hold all the data needed to define the layout and behavior of the widgets. + // Pass this to the dashboard component's dashboard input in the template. + public dashboard: IDashboard; + + // Angular gridster requires a configuration object even if it's empty. + // Pass this to the dashboard component's gridsterConfig input in the template. + public gridsterConfig: GridsterConfig = { + // These values will be used to set the initial widget dimensions on creation. + // If not set, they each default to 6. + defaultItemCols: 3, + defaultItemRows: 5, + }; + + // Boolean the dashboard takes in as an input; if it's set to true + // the dashboard allows you to resize widgets and move them around. + public editMode: boolean = false; + + // Subject used for auto-unsubscribing from subscriptions on component destruction + private readonly destroy$ = new Subject(); + + constructor( + // WidgetTypesService provides the widget's necessary structure information + private widgetTypesService: WidgetTypesService, + + // In general, the ProviderRegistryService is used for making entities available for injection into dynamically loaded components. + private providerRegistry: ProviderRegistryService, + + // Injecting the PersistenceHandler we created and assigning it to a property we use in the template. + public persistenceHandler: PersistenceHandler, + + // Injecting the cloner service which is needed for opening up the cloner wizard. + private widgetClonerService: WidgetClonerService + ) {} + + public ngOnInit(): void { + // Grabbing the widget's default template which will be needed as a parameter for setNode + const kpiTemplate = this.widgetTypesService.getWidgetType("kpi", 1); + const proportionalTemplate = this.widgetTypesService.getWidgetType( + "proportional", + 1 + ); + + // Registering our data sources as dropdown options in the widget editor/configurator + // Note: This could also be done in the parent module's constructor so that + // multiple dashboards could have access to the same widget template modification. + this.widgetTypesService.setNode( + // This is the template we grabbed above with getWidgetType + proportionalTemplate, + // Setting the editor/configurator part of the widget template + "configurator", + // This indicates which node you are changing and we want to change + // the data source providers available for selection in the editor. + WellKnownPathKey.DataSourceProviders, + // Setting the data sources available for selection in the editor + [RandomCitiesProportionalDataSource.providerId] + ); + + // Same as above, but for the KPI data sources + this.widgetTypesService.setNode( + kpiTemplate, + "configurator", + WellKnownPathKey.DataSourceProviders, + [ + AverageRatingKpiDataSource.providerId, + RatingsCountKpiDataSource.providerId, + ] + ); + + // Registering the data sources available for injection into the KPI tiles and proportional widget. + // Note: Each tile of a KPI widget is assigned its own instance of a data source. + this.providerRegistry.setProviders({ + [AverageRatingKpiDataSource.providerId]: { + provide: DATA_SOURCE, + useClass: AverageRatingKpiDataSource, + // Any dependencies that need to be injected into the provider must be listed here + deps: [HttpClient], + }, + [RatingsCountKpiDataSource.providerId]: { + provide: DATA_SOURCE, + useClass: RatingsCountKpiDataSource, + deps: [HttpClient], + }, + [RandomCitiesProportionalDataSource.providerId]: { + provide: DATA_SOURCE, + useClass: RandomCitiesProportionalDataSource, + deps: [], + }, + }); + + this.initializeDashboard(); + } + + public onCreateWidget(): void { + const widgetSelector: IWidgetSelector = { + // Template ref of the dashboard component. + dashboardComponent: this.dashboardComponent, + // A trySubmit function; in this case, we use the trySubmit from the PersistenceHandler created in the previous tutorial. + trySubmit: this.persistenceHandler.trySubmit, + // WidgetTemplateSelectionComponent will act as step one of the wizard to allow the user to select which widget will be cloned. + widgetSelectionComponentType: WidgetTemplateSelectionComponent, + }; + this.widgetClonerService + .open(widgetSelector) + .pipe( + // Auto-unsubscribe after one emission or on component destruction + take(1), + takeUntil(this.destroy$) + ) + .subscribe(); + } + + public initializeDashboard(): void { + // We're using a static configuration object for this example (see widgetConfig at the bottom of the file), + // but this is where the widget's configuration could potentially be populated from a database + const kpiWidget = fullKpiWidgetConfig; + const widgetIndex: IWidgets = { + // Complete the KPI widget with information coming from its type definition + [kpiWidget.id]: + this.widgetTypesService.mergeWithWidgetType(kpiWidget), + }; + + // Setting the widget dimensions and position (this is for gridster) + // Note: If no position is given for a widget the 'defaultItemCols' and 'defaultItemRows' properties + // from the gridsterConfig will be used for the dimensions + const positions: Record = { + [kpiWidget.id]: { + cols: 3, + rows: 5, + y: 0, + x: 0, + }, + }; + + // Finally, assigning the variables we created above to the dashboard + this.dashboard = { + positions, + widgets: widgetIndex, + }; + } +} + +// Interface for each data point in a proportional widget. +interface IProportionalWidgetData { + id: string; + name: string; + data: number[]; + icon: string; + link: string; + value: string; +} + +@Injectable() +export class RandomCitiesProportionalDataSource implements OnDestroy { + public static providerId = "RandomCitiesProportionalDataSource"; + + public outputsSubject = new Subject< + IDataSourceOutput + >(); + + // Every time applyFilters gets ran we are changing the data source. + public applyFilters(): void { + setTimeout(() => { + this.outputsSubject.next({ + result: this.getRandomProportionalWidgetData(), + }); + }, 1000); + } + + public ngOnDestroy(): void { + this.outputsSubject.complete(); + } + + private getRandomProportionalWidgetData(): IProportionalWidgetData[] { + return [ + { + id: "Down", + name: "Down", + data: [Math.round(Math.random() * 100)], + icon: "status_down", + link: "https://en.wikipedia.org/wiki/Brno", + value: "Brno", + }, + { + id: "Critical", + name: "Critical", + data: [Math.round(Math.random() * 100)], + icon: "status_critical", + link: "https://en.wikipedia.org/wiki/Kyiv", + value: "Kyiv", + }, + { + id: "Warning", + name: "Warning", + data: [Math.round(Math.random() * 100)], + icon: "status_warning", + link: "https://en.wikipedia.org/wiki/Austin", + value: "Austin", + }, + { + id: "Unknown", + name: "Unknown", + data: [Math.round(Math.random() * 100)], + icon: "status_unknown", + link: "https://en.wikipedia.org/wiki/Lisbon", + value: "Lisbon", + }, + { + id: "Up", + name: "Up", + data: [Math.round(Math.random() * 100)], + icon: "status_up", + link: "https://en.wikipedia.org/wiki/Sydney", + value: "Sydney", + }, + { + id: "Unmanaged", + name: "Unmanaged", + data: [Math.round(Math.random() * 100)], + icon: "status_unmanaged", + link: "https://en.wikipedia.org/wiki/Nur-Sultan", + value: "Nur-Sultan", + }, + ]; + } +} + +/** + * A simple KPI data source to retrieve the average rating of Harry Potter and the Sorcerer's Stone (book) via googleapis + */ +@Injectable() +export class AverageRatingKpiDataSource + extends DataSourceService + implements OnDestroy +{ + // This is the ID we'll use to identify the provider + public static providerId = "AverageRatingKpiDataSource"; + + // Use this subject to communicate the data source's busy state + public busy = new BehaviorSubject(false); + + constructor(private http: HttpClient) { + super(); + } + + // In this example, getFilteredData is invoked every 10 minutes (Take a look at the refresher + // provider definition in the widget configuration below to see how the interval is set) + public async getFilteredData(): Promise { + this.busy.next(true); + return new Promise((resolve) => { + // *** Make a resource request to an external API (if needed) + this.http + .get("https://www.googleapis.com/books/v1/volumes/5MQFrgEACAAJ") + .pipe(finalize(() => this.busy.next(false))) + .subscribe({ + next: (data: any) => { + resolve({ + result: { + value: data.volumeInfo.averageRating, + }, + }); + }, + error: (error: HttpErrorResponse) => { + resolve({ + result: null, + error: { + type: error.status, + }, + }); + }, + }); + }); + } + + public ngOnDestroy(): void { + this.outputsSubject.complete(); + } +} + +/** + * A simple KPI data source to retrieve the ratings count of Harry Potter and the Sorcerer's Stone (book) via googleapis + */ +@Injectable() +export class RatingsCountKpiDataSource + extends DataSourceService + implements OnDestroy +{ + public static providerId = "RatingsCountKpiDataSource"; + + // Use this subject to communicate the data source's busy state + public busy = new BehaviorSubject(false); + + constructor(private http: HttpClient) { + super(); + } + + public async getFilteredData(): Promise { + this.busy.next(true); + return new Promise((resolve) => { + this.http + .get("https://www.googleapis.com/books/v1/volumes/5MQFrgEACAAJ") + .pipe(finalize(() => this.busy.next(false))) + .subscribe({ + next: (data: any) => { + resolve({ + result: { + value: data.volumeInfo.ratingsCount, + }, + }); + }, + error: (error: HttpErrorResponse) => { + resolve({ + result: null, + error: { + type: error.status, + }, + }); + }, + }); + }); + } + + public ngOnDestroy(): void { + this.outputsSubject.complete(); + } +} + +const fullKpiWidgetConfig: IWidget = { + id: "widget1", + type: "kpi", + pizzagna: { + [PizzagnaLayer.Configuration]: { + [DEFAULT_PIZZAGNA_ROOT]: { + providers: { + [WellKnownProviders.Refresher]: { + properties: { + // Configuring the refresher interval so that our data source is invoked every ten minutes + interval: 60 * 10, + enabled: true, + } as IRefresherProperties, + } as Partial, + }, + }, + header: { + properties: { + title: "Harry Potter and the Sorcerer's Stone", + subtitle: "By J. K. Rowling", + }, + }, + tiles: { + properties: { + nodes: ["kpi1"], + }, + }, + kpi1: { + id: "kpi1", + componentType: KpiComponent.lateLoadKey, + properties: { + widgetData: { + units: \\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\`out of 5 Stars\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\`, + label: \\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\`Average Rating\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\`, + }, + }, + providers: { + [WellKnownProviders.DataSource]: { + // Setting the data source providerId for the tile with id "kpi1" + providerId: AverageRatingKpiDataSource.providerId, + } as IProviderConfiguration, + [WellKnownProviders.Adapter]: { + providerId: NOVA_KPI_DATASOURCE_ADAPTER, + properties: { + componentId: "kpi1", + propertyPath: "widgetData", + }, + } as IProviderConfiguration, + }, + }, + }, + }, +}; + +const partialPropWidgetConfig: IWidget = { + id: "widget2", + type: "proportional", + metadata: { + // Set 'needsConfiguration' to true if the widget needs further configuration before it can be + // placed on the dashboard. The "Create Widget" button will be hidden in the wizard when this + // widget is selected. + needsConfiguration: true, + }, + pizzagna: { + [PizzagnaLayer.Configuration]: { + header: { + properties: { + title: "*New Proportional Widget*", + }, + }, + }, + }, +}; +\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\`, + "tutorials/widget-creation/widget-creation.module.ts": \\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\`// © 2022 SolarWinds Worldwide, LLC. All rights reserved. +// +// Permission is hereby granted, free of charge, to any person obtaining a copy +// of this software and associated documentation files (the "Software"), to +// deal in the Software without restriction, including without limitation the +// rights to use, copy, modify, merge, publish, distribute, sublicense, and/or +// sell copies of the Software, and to permit persons to whom the Software is +// furnished to do so, subject to the following conditions: +// +// The above copyright notice and this permission notice shall be included in +// all copies or substantial portions of the Software. +// +// THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR +// IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, +// FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE +// AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER +// LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, +// OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN +// THE SOFTWARE. + +import { CommonModule } from "@angular/common"; +import { HttpClientModule } from "@angular/common/http"; +import { NgModule } from "@angular/core"; +import { RouterModule } from "@angular/router"; + +import { + NuiButtonModule, + NuiDocsModule, + NuiImageModule, + NuiMessageModule, + NuiRepeatModule, + NuiSwitchModule, + NuiToastModule, + DEMO_PATH_TOKEN, +} from "@nova-ui/bits"; +import { NuiDashboardsModule } from "@nova-ui/dashboards"; + +import { WidgetCreationDocsComponent } from "./widget-creation-docs.component"; +import { + WidgetCreationComponent, + WidgetTemplateSelectionComponent, +} from "./widget-creation.component"; +import { getDemoFiles } from "../../../../demo-files-factory"; + +const routes = [ + { + path: "", + component: WidgetCreationDocsComponent, + data: { + srlc: { + hideIndicator: true, + }, + showThemeSwitcher: true, + }, + }, + { + path: "example", + component: WidgetCreationComponent, + data: { + srlc: { + hideIndicator: true, + }, + }, + }, +]; + +@NgModule({ + imports: [ + CommonModule, + HttpClientModule, + NuiDashboardsModule, + NuiDocsModule, + NuiMessageModule, + NuiSwitchModule, + NuiToastModule, + NuiButtonModule, + NuiRepeatModule, + NuiImageModule, + RouterModule.forChild(routes), + ], + declarations: [ + WidgetCreationDocsComponent, + WidgetCreationComponent, + WidgetTemplateSelectionComponent, + ], + providers: [ + { + provide: DEMO_PATH_TOKEN, + useValue: getDemoFiles("widget-creation"), + }, + ], +}) +export default class WidgetCreationModule {} +\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\`, + "tutorials/widget-editor-setup/widget-editor-setup-docs.component.ts": \\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\`// © 2022 SolarWinds Worldwide, LLC. All rights reserved. +// +// Permission is hereby granted, free of charge, to any person obtaining a copy +// of this software and associated documentation files (the "Software"), to +// deal in the Software without restriction, including without limitation the +// rights to use, copy, modify, merge, publish, distribute, sublicense, and/or +// sell copies of the Software, and to permit persons to whom the Software is +// furnished to do so, subject to the following conditions: +// +// The above copyright notice and this permission notice shall be included in +// all copies or substantial portions of the Software. +// +// THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR +// IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, +// FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE +// AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER +// LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, +// OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN +// THE SOFTWARE. + +import { Component } from "@angular/core"; + +@Component({ + selector: "nui-dashboard-widget-editor-docs", + templateUrl: "./widget-editor-setup-docs.component.html", + standalone: false, +}) +export class WidgetEditorDocsComponent {} +\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\`, + "tutorials/widget-editor-setup/widget-editor-setup.component.ts": \\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\`// © 2022 SolarWinds Worldwide, LLC. All rights reserved. +// +// Permission is hereby granted, free of charge, to any person obtaining a copy +// of this software and associated documentation files (the "Software"), to +// deal in the Software without restriction, including without limitation the +// rights to use, copy, modify, merge, publish, distribute, sublicense, and/or +// sell copies of the Software, and to permit persons to whom the Software is +// furnished to do so, subject to the following conditions: +// +// The above copyright notice and this permission notice shall be included in +// all copies or substantial portions of the Software. +// +// THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR +// IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, +// FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE +// AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER +// LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, +// OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN +// THE SOFTWARE. + +import { HttpClient, HttpErrorResponse } from "@angular/common/http"; +import { + ChangeDetectorRef, + Component, + Injectable, + OnDestroy, + OnInit, +} from "@angular/core"; +import { GridsterConfig, GridsterItem } from "angular-gridster2"; +import { BehaviorSubject } from "rxjs"; +import { finalize } from "rxjs/operators"; + +import { DataSourceService, IFilteringOutputs } from "@nova-ui/bits"; +import { + DATA_SOURCE, + DEFAULT_PIZZAGNA_ROOT, + IDashboard, + IKpiData, + IProviderConfiguration, + IRefresherProperties, + IWidget, + IWidgets, + KpiComponent, + NOVA_KPI_DATASOURCE_ADAPTER, + PizzagnaLayer, + ProviderRegistryService, + WellKnownPathKey, + WellKnownProviders, + WidgetTypesService, +} from "@nova-ui/dashboards"; + +/** + * A simple KPI data source to retrieve the average rating of Harry Potter and the Sorcerer's Stone (book) via googleapis + */ +@Injectable() +export class AverageRatingKpiDataSource + extends DataSourceService + implements OnDestroy +{ + // This is the ID we'll use to identify the provider + public static providerId = "AverageRatingKpiDataSource"; + + // Use this subject to communicate the data source's busy state + public busy = new BehaviorSubject(false); + + constructor(private http: HttpClient) { + super(); + } + + // In this example, getFilteredData is invoked every 10 minutes (Take a look at the refresher + // provider definition in the widget configuration below to see how the interval is set) + public async getFilteredData(): Promise { + this.busy.next(true); + return new Promise((resolve) => { + // *** Make a resource request to an external API (if needed) + this.http + .get("https://www.googleapis.com/books/v1/volumes/5MQFrgEACAAJ") + .pipe(finalize(() => this.busy.next(false))) + .subscribe({ + next: (data: any) => { + resolve({ + result: { + value: data.volumeInfo.averageRating, + }, + }); + }, + error: (error: HttpErrorResponse) => { + resolve({ + result: null, + error: { + type: error.status, + }, + }); + }, + }); + }); + } + + public ngOnDestroy(): void { + this.outputsSubject.complete(); + } +} + +/** + * A simple KPI data source to retrieve the ratings count of Harry Potter and the Sorcerer's Stone (book) via googleapis + */ +@Injectable() +export class RatingsCountKpiDataSource + extends DataSourceService + implements OnDestroy +{ + public static providerId = "RatingsCountKpiDataSource"; + + // Use this subject to communicate the data source's busy state + public busy = new BehaviorSubject(false); + + constructor(private http: HttpClient) { + super(); + } + + public async getFilteredData(): Promise { + this.busy.next(true); + return new Promise((resolve) => { + this.http + .get("https://www.googleapis.com/books/v1/volumes/5MQFrgEACAAJ") + .pipe(finalize(() => this.busy.next(false))) + .subscribe({ + next: (data: any) => { + resolve({ + result: { + value: data.volumeInfo.ratingsCount, + }, + }); + }, + error: (error: HttpErrorResponse) => { + resolve({ + result: null, + error: { + type: error.status, + }, + }); + }, + }); + }); + } + + public ngOnDestroy(): void { + this.outputsSubject.complete(); + } +} + +/** + * A component that instantiates the dashboard + */ +@Component({ + selector: "widget-editor-setup", + templateUrl: "./widget-editor-setup.component.html", + styleUrls: ["./widget-editor-setup.component.less"], + standalone: false, +}) +export class WidgetEditorSetupComponent implements OnInit { + // This variable will hold all the data needed to define the layout and behavior of the widgets. + // Pass this to the dashboard component's dashboard input in the template. + public dashboard: IDashboard | undefined; + + // Angular gridster requires a configuration object even if it's empty. + // Pass this to the dashboard component's gridsterConfig input in the template. + public gridsterConfig: GridsterConfig = {}; + + // Boolean which dashboard takes in as an input if its true it allows you to move widgets around. + public editMode: boolean = false; + + constructor( + // WidgetTypesService provides the widget's necessary structure information + private widgetTypesService: WidgetTypesService, + + // In general, the ProviderRegistryService is used for making entities available for injection into dynamically loaded components. + private providerRegistry: ProviderRegistryService, + private changeDetectorRef: ChangeDetectorRef + ) {} + + public ngOnInit(): void { + // Grabbing the widget's default template which will be needed as a parameter for setNode + const widgetTemplate = this.widgetTypesService.getWidgetType("kpi", 1); + // Registering our data sources as dropdown options in the widget editor/configurator + // Note: This could also be done in the parent module's constructor so that + // multiple dashboards could have access to the same widget template modification. + this.widgetTypesService.setNode( + // This is the template we grabbed above with getWidgetType + widgetTemplate, + // We are setting the editor/configurator part of the widget template + "configurator", + // This indicates which node you are changing and we want to change + // the data source providers available for selection in the editor. + WellKnownPathKey.DataSourceProviders, + // We are setting the data sources available for selection in the editor + [ + AverageRatingKpiDataSource.providerId, + RatingsCountKpiDataSource.providerId, + ] + ); + + // Registering the data sources available for injection into the KPI tiles. + // Note: Each tile of a KPI widget is assigned its own instance of a data source + this.providerRegistry.setProviders({ + [AverageRatingKpiDataSource.providerId]: { + provide: DATA_SOURCE, + useClass: AverageRatingKpiDataSource, + // Any dependencies that need to be injected into the provider must be listed here + deps: [HttpClient], + }, + [RatingsCountKpiDataSource.providerId]: { + provide: DATA_SOURCE, + useClass: RatingsCountKpiDataSource, + deps: [HttpClient], + }, + }); + + this.initializeDashboard(); + } + + /** Used for restoring widgets state */ + public reInitializeDashboard(): void { + // destroys the components and their providers so the dashboard can re init data + this.dashboard = undefined; + this.changeDetectorRef.detectChanges(); + + this.initializeDashboard(); + } + + public initializeDashboard(): void { + // We're using a static configuration object for this example (see widgetConfig at the bottom of the file), + // but this is where the widget's configuration could potentially be populated from a database + const kpiWidget = widgetConfig; + const widgetIndex: IWidgets = { + // Complete the KPI widget with information coming from its type definition + [kpiWidget.id]: + this.widgetTypesService.mergeWithWidgetType(kpiWidget), + }; + + // Setting the widget dimensions and position (this is for gridster) + const positions: Record = { + [kpiWidget.id]: { + cols: 4, + rows: 6, + y: 0, + x: 0, + }, + }; + + // Finally, assigning the variables we created above to the dashboard + this.dashboard = { + positions, + widgets: widgetIndex, + }; + } +} + +const widgetConfig: IWidget = { + id: "widget1", + type: "kpi", + pizzagna: { + [PizzagnaLayer.Configuration]: { + [DEFAULT_PIZZAGNA_ROOT]: { + providers: { + [WellKnownProviders.Refresher]: { + properties: { + // Configuring the refresher interval so that our data source is invoked every ten minutes + interval: 60 * 10, + enabled: true, + } as IRefresherProperties, + } as Partial, + }, + }, + header: { + properties: { + title: "Harry Potter and the Sorcerer's Stone", + subtitle: "By J. K. Rowling", + }, + }, + tiles: { + properties: { + nodes: ["kpi1"], + }, + }, + kpi1: { + id: "kpi1", + componentType: KpiComponent.lateLoadKey, + properties: { + widgetData: { + units: "out of 5 Stars", + label: "Average Rating", + }, + }, + providers: { + [WellKnownProviders.DataSource]: { + // Setting the data source providerId for the tile with id "kpi1" + providerId: AverageRatingKpiDataSource.providerId, + } as IProviderConfiguration, + [WellKnownProviders.Adapter]: { + providerId: NOVA_KPI_DATASOURCE_ADAPTER, + properties: { + componentId: "kpi1", + propertyPath: "widgetData", + }, + } as IProviderConfiguration, + }, + }, + }, + }, +}; +\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\`, + "tutorials/widget-editor-setup/widget-editor-setup.module.ts": \\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\`// © 2022 SolarWinds Worldwide, LLC. All rights reserved. +// +// Permission is hereby granted, free of charge, to any person obtaining a copy +// of this software and associated documentation files (the "Software"), to +// deal in the Software without restriction, including without limitation the +// rights to use, copy, modify, merge, publish, distribute, sublicense, and/or +// sell copies of the Software, and to permit persons to whom the Software is +// furnished to do so, subject to the following conditions: +// +// The above copyright notice and this permission notice shall be included in +// all copies or substantial portions of the Software. +// +// THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR +// IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, +// FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE +// AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER +// LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, +// OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN +// THE SOFTWARE. + +import { CommonModule } from "@angular/common"; +import { HttpClientModule } from "@angular/common/http"; +import { NgModule } from "@angular/core"; +import { RouterModule } from "@angular/router"; + +import { + NuiButtonModule, + NuiDocsModule, + NuiMessageModule, + NuiSwitchModule, + DEMO_PATH_TOKEN, +} from "@nova-ui/bits"; +import { NuiDashboardsModule } from "@nova-ui/dashboards"; + +import { WidgetEditorDocsComponent } from "./widget-editor-setup-docs.component"; +import { WidgetEditorSetupComponent } from "./widget-editor-setup.component"; +import { getDemoFiles } from "../../../../demo-files-factory"; + +const routes = [ + { + path: "", + component: WidgetEditorDocsComponent, + data: { + srlc: { + hideIndicator: true, + }, + showThemeSwitcher: true, + }, + }, + { + path: "example", + component: WidgetEditorSetupComponent, + data: { + srlc: { + hideIndicator: true, + }, + }, + }, +]; + +@NgModule({ + imports: [ + CommonModule, + HttpClientModule, + NuiDashboardsModule, + NuiDocsModule, + NuiMessageModule, + NuiSwitchModule, + NuiButtonModule, + RouterModule.forChild(routes), + ], + declarations: [WidgetEditorDocsComponent, WidgetEditorSetupComponent], + providers: [ + { + provide: DEMO_PATH_TOKEN, + useValue: getDemoFiles("widget-editor-setup"), + }, + ], +}) +export default class WidgetEditorSetupModule {} +\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\`, + "tutorials/widget-error-handling/widget-error-handling-docs.component.ts": \\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\`// © 2022 SolarWinds Worldwide, LLC. All rights reserved. +// +// Permission is hereby granted, free of charge, to any person obtaining a copy +// of this software and associated documentation files (the "Software"), to +// deal in the Software without restriction, including without limitation the +// rights to use, copy, modify, merge, publish, distribute, sublicense, and/or +// sell copies of the Software, and to permit persons to whom the Software is +// furnished to do so, subject to the following conditions: +// +// The above copyright notice and this permission notice shall be included in +// all copies or substantial portions of the Software. +// +// THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR +// IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, +// FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE +// AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER +// LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, +// OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN +// THE SOFTWARE. + +import { Component } from "@angular/core"; + +@Component({ + selector: "nui-widget-error-handling-docs", + templateUrl: "./widget-error-handling-docs.component.html", + standalone: false, +}) +export class WidgetErrorHandlingDocsComponent { + public fallbackAdapter = \\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\` +@Injectable() +export class StatusContentFallbackAdapter implements OnDestroy, IHasComponent { + + protected readonly destroy$ = new Subject(); + protected componentId: string; + + constructor(@Inject(PIZZAGNA_EVENT_BUS) protected eventBus: EventBus, + protected pizzagnaService: PizzagnaService) { + this.eventBus.getStream(DATA_SOURCE_OUTPUT) + .pipe(takeUntil(this.destroy$)).subscribe((event: IEvent>) => { + this.handleDataSourceOutput(event); + }); + } + + public ngOnDestroy(): void { + this.destroy$.next(); + this.destroy$.complete(); + } + + public setComponent(component: any, componentId: string) { + this.componentId = componentId; + } + + protected handleDataSourceOutput(event: IEvent>) { + this.pizzagnaService.setProperty({ + componentId: this.componentId, + propertyPath: ["fallbackKey"], + pizzagnaKey: PizzagnaLayer.Data, + }, typeof event.payload?.error?.type !== "undefined" ? event.payload?.error?.type.toString() : undefined); + } +}\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\`; + public errorsMap = \\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\` +export const ERROR_FALLBACK_MAP: Record = { + [HttpStatusCode.Unknown]: ErrorNodeKey.ErrorUnknown, + [HttpStatusCode.Forbidden]: ErrorNodeKey.ErrorForbidden, + [HttpStatusCode.NotFound]: ErrorNodeKey.ErrorNotFound, +}; +\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\`; + public errorNodes = \\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\` +export const ERROR_NODES: Record = { + [ErrorNodeKey.ErrorUnknown]: { + id: ErrorNodeKey.ErrorUnknown, + componentType: WidgetErrorComponent.lateLoadKey, + properties: { + image: "no-data-to-show", + title: $localize\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\`Whoops, something went wrong\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\`, + description: $localize\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\`There was an unexpected error.\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\`, + } as IWidgetErrorDisplayProperties, + }, + [ErrorNodeKey.ErrorForbidden]: { + id: ErrorNodeKey.ErrorForbidden, + componentType: WidgetErrorComponent.lateLoadKey, + properties: { + image: "no-data-to-show", + title: $localize\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\`403 - Forbidden\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\`, + description: $localize\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\`The requested action was forbidden.\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\`, + } as IWidgetErrorDisplayProperties, + }, + [ErrorNodeKey.ErrorNotFound]: { + id: ErrorNodeKey.ErrorNotFound, + componentType: WidgetErrorComponent.lateLoadKey, + properties: { + image: "no-data-to-show", + title: $localize\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\`404 - Not Found\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\`, + description: $localize\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\`The requested resource could not be found.\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\`, + } as IWidgetErrorDisplayProperties, + }, +};\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\`; + public widgetBodyContentNodesSignature = \\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\` +/** + * Retrieves an index of the basic widget body content nodes including fallback nodes + * + * @param mainContentNodeKey The key corresponding to the main body content node + * @param fallbackAdapterId The id for the adapter responsible for activating fallback content in case of an error + * @param fallbackMap A map of node keys to fallback content definitions + * @param fallbackNodes An index of fallback content definitions + * + * @returns An index of component configurations + */ +export function widgetBodyContentNodes( + mainContentNodeKey: string, + fallbackAdapterId = NOVA_STATUS_CONTENT_FALLBACK_ADAPTER, + fallbackMap: Record = ERROR_FALLBACK_MAP, + fallbackNodes: Record = ERROR_NODES +): Record { ... } +\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\`; +} +\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\`, + "tutorials/widget-error-handling/widget-error-handling.component.ts": \\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\`// © 2022 SolarWinds Worldwide, LLC. All rights reserved. +// +// Permission is hereby granted, free of charge, to any person obtaining a copy +// of this software and associated documentation files (the "Software"), to +// deal in the Software without restriction, including without limitation the +// rights to use, copy, modify, merge, publish, distribute, sublicense, and/or +// sell copies of the Software, and to permit persons to whom the Software is +// furnished to do so, subject to the following conditions: +// +// The above copyright notice and this permission notice shall be included in +// all copies or substantial portions of the Software. +// +// THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR +// IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, +// FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE +// AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER +// LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, +// OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN +// THE SOFTWARE. + +import { HttpClient, HttpErrorResponse } from "@angular/common/http"; +import { + ChangeDetectorRef, + Component, + Injectable, + OnDestroy, + OnInit, +} from "@angular/core"; +import { GridsterConfig, GridsterItem } from "angular-gridster2"; +import { BehaviorSubject } from "rxjs"; +import { finalize } from "rxjs/operators"; + +import { DataSourceService, IFilteringOutputs } from "@nova-ui/bits"; +import { + DATA_SOURCE, + DEFAULT_PIZZAGNA_ROOT, + HttpStatusCode, + IDashboard, + IKpiData, + IProviderConfiguration, + IRefresherProperties, + IWidget, + IWidgets, + KpiComponent, + NOVA_KPI_DATASOURCE_ADAPTER, + PizzagnaLayer, + ProviderRegistryService, + WellKnownPathKey, + WellKnownProviders, + WidgetTypesService, +} from "@nova-ui/dashboards"; + +/** + * A simple KPI data source to retrieve the average rating of Harry Potter and the Sorcerer's Stone (book) via googleapis + */ +@Injectable() +export class AverageRatingKpiDataSource + extends DataSourceService + implements OnDestroy +{ + // This is the ID we'll use to identify the provider + public static providerId = "AverageRatingKpiDataSource"; + + // Use this subject to communicate the data source's busy state + public busy = new BehaviorSubject(false); + + constructor(private http: HttpClient) { + super(); + } + + // In this example, getFilteredData is invoked every 10 minutes (Take a look at the refresher + // provider definition in the widget configuration below to see how the interval is set) + public async getFilteredData(): Promise { + this.busy.next(true); + return new Promise((resolve) => { + // *** Make a resource request to an external API (if needed) + this.http + .get("https://www.googleapis.com/books/v1/volumes/5MQFrgEACAAJ") + .pipe(finalize(() => this.busy.next(false))) + .subscribe({ + next: (data: any) => { + resolve({ + result: { + value: data.volumeInfo.averageRating, + }, + }); + }, + error: (error: HttpErrorResponse) => { + resolve({ + result: null, + error: { + type: error.status, + }, + }); + }, + }); + }); + } + + public ngOnDestroy(): void { + this.outputsSubject.complete(); + } +} + +/** + * A simple KPI data source to retrieve the average rating of Harry Potter and the Sorcerer's Stone (book) via googleapis + */ +@Injectable() +export class ErrorUnknownDataSource + extends DataSourceService + implements OnDestroy +{ + // This is the ID we'll use to identify the provider + public static providerId = "ErrorUnknownDataSource"; + + // Use this subject to communicate the data source's busy state + public busy = new BehaviorSubject(false); + + // In this example, getFilteredData is invoked every 10 minutes (Take a look at the refresher + // provider definition in the widget configuration below to see how the interval is set) + public async getFilteredData(): Promise { + this.busy.next(true); + const mockError = { + result: null, + error: { type: HttpStatusCode.Unknown }, + }; + this.busy.next(false); + return mockError; + } + + public ngOnDestroy(): void { + this.outputsSubject.complete(); + } +} + +/** + * A simple KPI data source to retrieve the ratings count of Harry Potter and the Sorcerer's Stone (book) via googleapis + */ +@Injectable() +export class ErrorForbiddenDataSource + extends DataSourceService + implements OnDestroy +{ + public static providerId = "ErrorForbiddenDataSource"; + + // Use this subject to communicate the data source's busy state + public busy = new BehaviorSubject(false); + + constructor(private http: HttpClient) { + super(); + } + + public async getFilteredData(): Promise { + this.busy.next(true); + // generate a 403 + return new Promise((resolve) => { + this.http + .get( + "http://www.mocky.io/v2/5ecc724a3200000f0023614a?mocky-delay=4000ms" + ) + .pipe(finalize(() => this.busy.next(false))) + .subscribe({ + error: (error: HttpErrorResponse) => { + resolve({ + result: null, + error: { + type: error.status, + }, + }); + }, + }); + }); + } + + public ngOnDestroy(): void { + this.outputsSubject.complete(); + } +} + +/** + * A simple KPI data source to retrieve the ratings count of Harry Potter and the Sorcerer's Stone (book) via googleapis + */ +@Injectable() +export class ErrorNotFoundDataSource + extends DataSourceService + implements OnDestroy +{ + public static providerId = "ErrorNotFoundDataSource"; + + // Use this subject to communicate the data source's busy state + public busy = new BehaviorSubject(false); + + constructor(private http: HttpClient) { + super(); + } + + public async getFilteredData(): Promise { + this.busy.next(true); + // generate a 404 + return new Promise((resolve) => { + this.http + .get( + "http://www.mocky.io/v2/5ec6bfd93200007800d75100?mocky-delay=1000ms" + ) + .pipe(finalize(() => this.busy.next(false))) + .subscribe({ + error: (error: HttpErrorResponse) => { + resolve({ + result: null, + error: { + type: error.status, + }, + }); + }, + }); + }); + } + + public ngOnDestroy(): void { + this.outputsSubject.complete(); + } +} + +/** + * A component that instantiates the dashboard + */ +@Component({ + selector: "widget-error-handling", + templateUrl: "./widget-error-handling.component.html", + styleUrls: ["./widget-error-handling.component.less"], + standalone: false, +}) +export class WidgetErrorHandlingComponent implements OnInit { + // This variable will hold all the data needed to define the layout and behavior of the widgets. + // Pass this to the dashboard component's dashboard input in the template. + public dashboard: IDashboard | undefined; + + // Angular gridster requires a configuration object even if it's empty. + // Pass this to the dashboard component's gridsterConfig input in the template. + public gridsterConfig: GridsterConfig = {}; + + // Boolean which dashboard takes in as an input if its true it allows you to move widgets around. + public editMode: boolean = false; + + constructor( + // WidgetTypesService provides the widget's necessary structure information + private widgetTypesService: WidgetTypesService, + + // In general, the ProviderRegistryService is used for making entities available for injection into dynamically loaded components. + private providerRegistry: ProviderRegistryService, + private changeDetectorRef: ChangeDetectorRef + ) {} + + public ngOnInit(): void { + // Grab the widget's default template which will be needed as a parameter for setNode. + const widgetTemplate = this.widgetTypesService.getWidgetType("kpi", 1); + // Register our data sources as dropdown options in the widget editor/configurator + // Note: This could also be done in the parent module's constructor so that + // multiple dashboards could have access to the same widget template modification. + this.widgetTypesService.setNode( + // This is the template we grabbed above with getWidgetType + widgetTemplate, + // We are setting the editor/configurator part of the widget template + "configurator", + // This indicates which node you are changing and we want to change + // the data source providers available for selection in the editor. + WellKnownPathKey.DataSourceProviders, + // We are setting the data sources available for selection in the editor + [ + ErrorUnknownDataSource.providerId, + ErrorForbiddenDataSource.providerId, + ErrorNotFoundDataSource.providerId, + AverageRatingKpiDataSource.providerId, + ] + ); + + // Register the data sources available for injection into the KPI tiles. + // Note: Each tile of a KPI widget is assigned its own instance of a data source + this.providerRegistry.setProviders({ + [ErrorUnknownDataSource.providerId]: { + provide: DATA_SOURCE, + useClass: ErrorUnknownDataSource, + // Any dependencies that need to be injected into the provider must be listed here + deps: [HttpClient], + }, + [ErrorForbiddenDataSource.providerId]: { + provide: DATA_SOURCE, + useClass: ErrorForbiddenDataSource, + deps: [HttpClient], + }, + [ErrorNotFoundDataSource.providerId]: { + provide: DATA_SOURCE, + useClass: ErrorNotFoundDataSource, + deps: [HttpClient], + }, + [AverageRatingKpiDataSource.providerId]: { + provide: DATA_SOURCE, + useClass: AverageRatingKpiDataSource, + // Any dependencies that need to be injected into the provider must be listed here + deps: [HttpClient], + }, + }); + + this.initializeDashboard(); + } + + /** Used for restoring widgets state */ + public reInitializeDashboard(): void { + // destroys the components and their providers so the dashboard can re init data + this.dashboard = undefined; + this.changeDetectorRef.detectChanges(); + + this.initializeDashboard(); + } + + public initializeDashboard(): void { + // We're using a static configuration object for this example (see widgetConfig at the bottom of the file), + // but this is where the widget's configuration could potentially be populated from a database + const kpiWidget = widgetConfig; + const widgetIndex: IWidgets = { + // Complete the KPI widget with information coming from its type definition + [kpiWidget.id]: + this.widgetTypesService.mergeWithWidgetType(kpiWidget), + }; + + // Setting the widget dimensions and position (this is for gridster) + const positions: Record = { + [kpiWidget.id]: { + cols: 4, + rows: 6, + y: 0, + x: 0, + }, + }; + + // Finally, assigning the variables we created above to the dashboard + this.dashboard = { + positions, + widgets: widgetIndex, + }; + } +} + +const widgetConfig: IWidget = { + id: "widget1", + type: "kpi", + pizzagna: { + [PizzagnaLayer.Configuration]: { + [DEFAULT_PIZZAGNA_ROOT]: { + providers: { + [WellKnownProviders.Refresher]: { + properties: { + // Configuring the refresher interval so that our data source is invoked every ten minutes + interval: 60 * 10, + enabled: true, + } as IRefresherProperties, + } as Partial, + }, + }, + header: { + properties: { + title: "Harry Potter and the Sorcerer's Stone", + subtitle: "By J. K. Rowling", + }, + }, + tiles: { + properties: { + nodes: ["kpi1"], + }, + }, + kpi1: { + id: "kpi1", + componentType: KpiComponent.lateLoadKey, + properties: { + widgetData: { + units: "out of 5 Stars", + label: "Average Rating", + }, + }, + providers: { + [WellKnownProviders.DataSource]: { + // Setting the data source providerId for the tile with id "kpi1" + providerId: ErrorUnknownDataSource.providerId, + } as IProviderConfiguration, + [WellKnownProviders.Adapter]: { + providerId: NOVA_KPI_DATASOURCE_ADAPTER, + properties: { + componentId: "kpi1", + propertyPath: "widgetData", + }, + } as IProviderConfiguration, + }, + }, + }, + }, +}; +\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\`, + "tutorials/widget-error-handling/widget-error-handling.module.ts": \\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\`// © 2022 SolarWinds Worldwide, LLC. All rights reserved. +// +// Permission is hereby granted, free of charge, to any person obtaining a copy +// of this software and associated documentation files (the "Software"), to +// deal in the Software without restriction, including without limitation the +// rights to use, copy, modify, merge, publish, distribute, sublicense, and/or +// sell copies of the Software, and to permit persons to whom the Software is +// furnished to do so, subject to the following conditions: +// +// The above copyright notice and this permission notice shall be included in +// all copies or substantial portions of the Software. +// +// THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR +// IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, +// FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE +// AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER +// LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, +// OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN +// THE SOFTWARE. + +import { CommonModule } from "@angular/common"; +import { HttpClientModule } from "@angular/common/http"; +import { NgModule } from "@angular/core"; +import { ReactiveFormsModule } from "@angular/forms"; +import { RouterModule } from "@angular/router"; + +import { + NuiButtonModule, + NuiDocsModule, + NuiFormFieldModule, + NuiIconModule, + NuiMessageModule, + NuiSwitchModule, + NuiTextboxModule, + DEMO_PATH_TOKEN, +} from "@nova-ui/bits"; +import { + NuiDashboardConfiguratorModule, + NuiDashboardsModule, +} from "@nova-ui/dashboards"; + +import { WidgetErrorHandlingDocsComponent } from "./widget-error-handling-docs.component"; +import { WidgetErrorHandlingComponent } from "./widget-error-handling.component"; +import { getDemoFiles } from "../../../../demo-files-factory"; + +const routes = [ + { + path: "", + component: WidgetErrorHandlingDocsComponent, + data: { + srlc: { + hideIndicator: true, + }, + showThemeSwitcher: true, + }, + }, + { + path: "example", + component: WidgetErrorHandlingComponent, + data: { + srlc: { + hideIndicator: true, + }, + }, + }, +]; + +@NgModule({ + imports: [ + CommonModule, + ReactiveFormsModule, + HttpClientModule, + NuiButtonModule, + NuiDashboardsModule, + NuiDashboardConfiguratorModule, + NuiDocsModule, + NuiFormFieldModule, + NuiIconModule, + NuiMessageModule, + NuiIconModule, + NuiTextboxModule, + NuiIconModule, + NuiSwitchModule, + RouterModule.forChild(routes), + ], + declarations: [ + WidgetErrorHandlingDocsComponent, + WidgetErrorHandlingComponent, + ], + providers: [ + { + provide: DEMO_PATH_TOKEN, + useValue: getDemoFiles("widget-error-handling"), + }, + ], +}) +export default class WidgetErrorHandlingModule {} +\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\`, + "types.ts": \\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\`// © 2022 SolarWinds Worldwide, LLC. All rights reserved. +// +// Permission is hereby granted, free of charge, to any person obtaining a copy +// of this software and associated documentation files (the "Software"), to +// deal in the Software without restriction, including without limitation the +// rights to use, copy, modify, merge, publish, distribute, sublicense, and/or +// sell copies of the Software, and to permit persons to whom the Software is +// furnished to do so, subject to the following conditions: +// +// The above copyright notice and this permission notice shall be included in +// all copies or substantial portions of the Software. +// +// THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR +// IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, +// FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE +// AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER +// LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, +// OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN +// THE SOFTWARE. + +export enum APOLLO_API_NAMESPACE { + COUNTRIES = "countries", +} +\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\`, + "widget-types/drilldown/drilldown-multi-request-widget/drilldown-multi-request-widget-example.component.ts": \\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\`// © 2022 SolarWinds Worldwide, LLC. All rights reserved. +// +// Permission is hereby granted, free of charge, to any person obtaining a copy +// of this software and associated documentation files (the "Software"), to +// deal in the Software without restriction, including without limitation the +// rights to use, copy, modify, merge, publish, distribute, sublicense, and/or +// sell copies of the Software, and to permit persons to whom the Software is +// furnished to do so, subject to the following conditions: +// +// The above copyright notice and this permission notice shall be included in +// all copies or substantial portions of the Software. +// +// THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR +// IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, +// FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE +// AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER +// LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, +// OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN +// THE SOFTWARE. + +import { HttpClient } from "@angular/common/http"; +import { + ChangeDetectorRef, + Component, + Injectable, + OnDestroy, + OnInit, +} from "@angular/core"; +import { GridsterConfig, GridsterItem } from "angular-gridster2"; +import { Apollo, gql } from "apollo-angular"; +import { BehaviorSubject, Observable, of, Subject } from "rxjs"; +// eslint-disable-next-line import/no-deprecated +import { finalize, map, switchMap, tap } from "rxjs/operators"; + +import { + DataSourceService, + IconStatus, + IDataField, + IFilters, + INovaFilters, +} from "@nova-ui/bits"; +import { + DATA_SOURCE, + DEFAULT_PIZZAGNA_ROOT, + IDashboard, + IDrilldownComponentsConfiguration, + IListWidgetConfiguration, + IProviderConfiguration, + IWidget, + IWidgets, + ListGroupItemComponent, + ListLeafItemComponent, + NOVA_DRILLDOWN_DATASOURCE_ADAPTER, + PizzagnaLayer, + ProviderRegistryService, + WellKnownPathKey, + WellKnownProviders, + WidgetTypesService, +} from "@nova-ui/dashboards"; + +import { APOLLO_API_NAMESPACE } from "../../../types"; + +/** + * A simple KPI data source to retrieve the average rating of Harry Potter and the Sorcerer's Stone (book) via googleapis + */ +@Injectable() +export class DrilldownDataSource + extends DataSourceService + implements OnDestroy +{ + // This is the ID we'll use to identify the provider + public static providerId = "DrilldownDataSource"; + + // Use this subject to communicate the data source's busy state + public busy = new BehaviorSubject(false); + + public dataFields: Partial[] = [ + { id: "Region", label: "Region name" }, + { id: "Subregion", label: "Subregion name" }, + ]; + + private drillState: string[] = []; + private groupBy: string[]; + private cache: any; + private lastDrillState: string[] = []; + private leafGroup: string = "country"; + private applyFilters$ = new Subject(); + + constructor(private http: HttpClient, private apollo: Apollo) { + super(); + + // TODO: remove Partial in vNext after marking dataType field as optional - NUI-5838 + ( + this.dataFieldsConfig.dataFields$ as BehaviorSubject< + Partial[] + > + ).next(this.dataFields); + + this.applyFilters$ + // eslint-disable-next-line import/no-deprecated + .pipe(switchMap((filters) => this.getData(filters))) + .subscribe(async (res) => { + this.outputsSubject.next(await this.getFilteredData(res)); + }); + } + + private groupedDataHistory: any[] = []; + + // In this example, getFilteredData is invoked every 10 minutes (Take a look at the refresher + // provider definition in the widget configuration below to see how the interval is set) + public async getFilteredData(data: any): Promise { + return of(data) + .pipe( + map((entries) => { + if (this.isDrillDown()) { + const activeDrillLvl = this.drillState.length; + const group = this.groupBy[activeDrillLvl]; + const lastGroupedValue = + this.getTransformedDataForGroup( + entries, + group, + getLast(this.drillState) + ); + + this.groupedDataHistory.push(lastGroupedValue); + + return lastGroupedValue; + } + + const mapIconsToEntries = entries.map((item: any) => ({ + ...item, + icon: "virtual-host", + icon_status: IconStatus.Up, + })); + this.groupedDataHistory.push(mapIconsToEntries); + const widgetInput = this.getOutput(entries); + + return widgetInput; + }) + ) + .toPromise(); + } + + public ngOnDestroy(): void { + this.outputsSubject.complete(); + } + + // redefine parent method + public async applyFilters(): Promise { + this.applyFilters$.next(this.getFilters()); + } + + private getQuery(key: string, value: string) { + const groupToRequestMap: Record = { + Region: \\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\`{ Region { name } }\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\`, + Subregion: \\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\`{ Subregion(filter: { region: { name: "\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\${value}" } } ) { name } }\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\`, + Country: \\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\`{ Country(filter: { subregion: { name: "\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\${value}" } } ) { name capital } }\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\`, + }; + + return gql\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\` + \\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\${groupToRequestMap[key]} + \\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\`; + } + + private getData(filters: INovaFilters): Observable { + this.drillState = filters.drillstate?.value; + this.groupBy = filters.group?.value; + const group = this.groupBy[this.drillState.length]; + const isDrillUp = this.drillState.length < this.lastDrillState.length; + + this.lastDrillState = [...this.drillState]; + + if (!this.drillState.length) { + this.groupedDataHistory.length = 0; + } + + this.busy.next(true); + + if (this.cache && (isDrillUp || this.isHome())) { + return of(this.cache).pipe( + map((data) => data.data[group]), + finalize(() => this.busy.next(false)) + ); + } else { + return this.apollo + .use(APOLLO_API_NAMESPACE.COUNTRIES) + .query({ + query: this.getQuery( + group || this.leafGroup, + getLast(this.drillState) + ), + }) + .pipe( + tap( + (data) => + (this.cache = { + data: { ...this.cache?.data, ...data?.data }, + }) + ), + map((data) => data.data[group || this.leafGroup]), + finalize(() => this.busy.next(false)) + ); + } + } + + private getTransformedDataForGroup( + data: any, + group: string, + drillStateValue: string + ) { + const fallback: string = \\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\`No \\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\${group} for \\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\${drillStateValue}\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\`; + const dataArr = Object.values(data).map((val: any) => ({ + id: val.name || fallback, + label: val.name || fallback, + statuses: [ + { key: "state_ok", value: val.name?.length }, + { + key: "status_unreachable", + value: generateNumberUpTo(100000), + }, + { key: "status_warning", value: generateNumberUpTo(10000) }, + { key: "status_unknown", value: generateNumberUpTo(1000) }, + ], + })); + + return dataArr; + } + + private isHome(): boolean { + return this.drillState.length === 0; + } + + private isDrillDown(): boolean { + return this.drillState.length !== this.groupBy.length; + } + + private getOutput(data: any) { + if (this.isHome()) { + this.groupedDataHistory.length = 0; + } + + const lastHistoryValue = getLast(this.groupedDataHistory); + + if (!lastHistoryValue) { + return data; + } + + return lastHistoryValue[getLast(this.drillState)] || lastHistoryValue; + } +} + +@Component({ + selector: "drilldown-multi-request-widget-example", + templateUrl: "./drilldown-multi-request-widget-example.component.html", + styleUrls: ["./drilldown-multi-request-widget-example.component.less"], + standalone: false, +}) +export class DrilldownMultiRequestWidgetExampleComponent implements OnInit { + // This variable will hold all the data needed to define the layout and behavior of the widgets. + // Pass this to the dashboard component's dashboard input in the template. + public dashboard: IDashboard | undefined; + + // Angular gridster requires a configuration object even if it's empty. + // Pass this to the dashboard component's gridsterConfig input in the template. + public gridsterConfig: GridsterConfig = {}; + + // Boolean passed as an input to the dashboard. When true, widgets can be moved, resized, removed, or edited + public editMode: boolean = false; + + constructor( + // WidgetTypesService provides the widget's necessary structure information + private widgetTypesService: WidgetTypesService, + private providerRegistry: ProviderRegistryService, + private changeDetectorRef: ChangeDetectorRef + ) {} + + public ngOnInit(): void { + // this.prepareNovaDashboards(); + this.initializeDashboard(); + const widgetTemplate = this.widgetTypesService.getWidgetType( + "drilldown", + 1 + ); + this.widgetTypesService.setNode( + widgetTemplate, + "configurator", + WellKnownPathKey.DataSourceProviders, + [DrilldownDataSource.providerId] + ); + + // Registering the data source for injection into the KPI tile. + // Note: Each tile of a KPI widget is assigned its own instance of the data source + this.providerRegistry.setProviders({ + [DrilldownDataSource.providerId]: { + provide: DATA_SOURCE, + useClass: DrilldownDataSource, + // Any dependencies that need to be injected into the provider must be listed here + deps: [HttpClient, Apollo], + }, + }); + } + + /** Used for restoring widgets state */ + public reInitializeDashboard(): void { + // destroys the components and their providers so the dashboard can re init data + this.dashboard = undefined; + this.changeDetectorRef.detectChanges(); + + this.initializeDashboard(); + } + + public initializeDashboard(): void { + // We're using a static configuration object for this example, but this is where + // the widget's configuration could potentially be populated from a database + const drilldownWidget = widgetConfig; + const widgets: IWidgets = { + // Complete the widget with information coming from its type definition + [drilldownWidget.id]: + this.widgetTypesService.mergeWithWidgetType(drilldownWidget), + }; + + // Setting the widget dimensions and position (this is for gridster) + const positions: Record = { + [drilldownWidget.id]: { + cols: 10, + rows: 10, + y: 0, + x: 0, + }, + }; + + // Finally, assigning the variables we created above to the dashboard + this.dashboard = { positions, widgets }; + } +} + +const widgetConfig: IWidget = { + id: "drilldown", + type: "drilldown", + pizzagna: { + [PizzagnaLayer.Configuration]: { + header: { + properties: { + title: "Drilldown Widget", + subtitle: "Countries BY continent THEN currency", + }, + }, + [DEFAULT_PIZZAGNA_ROOT]: { + providers: { + [WellKnownProviders.DataSource]: { + // Setting the data source providerId for the tile with id "kpi1" + providerId: DrilldownDataSource.providerId, + properties: {}, + } as IProviderConfiguration, + }, + }, + listWidget: { + providers: { + [WellKnownProviders.Adapter]: { + providerId: NOVA_DRILLDOWN_DATASOURCE_ADAPTER, + properties: { + // widget + navigationBarId: "navigationBar", + componentId: "listWidget", + dataPath: "data", + + // adapter props + drillstate: [], + groups: ["Region", "Subregion"], + groupBy: ["Region", "Subregion"], + + // components + componentsConfig: { + group: { + componentType: + ListGroupItemComponent.lateLoadKey, + properties: { + dataFieldIds: { + id: "id", + label: "label", + statuses: "statuses", + }, + }, + itemProperties: { + canNavigate: true, + }, + }, + leaf: { + componentType: + ListLeafItemComponent.lateLoadKey, + properties: { + dataFieldIds: { + icon: "icon", + status: "icon_status", + detailedUrl: "capital", + label: "name", + }, + }, + itemProperties: { + canNavigate: false, + }, + }, + } as IDrilldownComponentsConfiguration, + }, + }, + }, + properties: { + configuration: { + // FORMAT: + // componentType: ListLeafItemComponent.lateLoadKey, + // properties: { + // dataFieldIds: { + // icon: "", + // status: "code", + // detailedUrl: "capital", + // label: "name", + // }, + // }, + // + } as IListWidgetConfiguration, + }, + }, + }, + }, +}; + +const getLast = (arr: any[]) => arr[arr.length - 1]; + +const generateNumberUpTo = (upperLimit: number): number => + Math.floor(Math.random() * upperLimit + 1); +\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\`, + "widget-types/drilldown/drilldown-widget/data-mock.ts": \\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\`// © 2022 SolarWinds Worldwide, LLC. All rights reserved. +// +// Permission is hereby granted, free of charge, to any person obtaining a copy +// of this software and associated documentation files (the "Software"), to +// deal in the Software without restriction, including without limitation the +// rights to use, copy, modify, merge, publish, distribute, sublicense, and/or +// sell copies of the Software, and to permit persons to whom the Software is +// furnished to do so, subject to the following conditions: +// +// The above copyright notice and this permission notice shall be included in +// all copies or substantial portions of the Software. +// +// THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR +// IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, +// FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE +// AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER +// LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, +// OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN +// THE SOFTWARE. + +import { IconStatus } from "@nova-ui/bits"; + +export const GRAPH_DATA_MOCK = { + data: { + countries: [ + { + name: "Andorra", + code: "AD", + icon: "virtual-host", + icon_status: IconStatus.Up, + capital: "Andorra la Vella", + continent: { + name: "Europe", + }, + currency: "EUR", + languages: [ + { + name: "Catalan", + }, + ], + url: "https://en.wikipedia.org/wiki/Andorra", + }, + { + name: "United Arab Emirates", + code: "AE", + icon: "virtual-host", + icon_status: IconStatus.Up, + capital: "Abu Dhabi", + continent: { + name: "Asia", + }, + currency: "AED", + languages: [ + { + name: "Arabic", + }, + ], + url: "https://en.wikipedia.org/wiki/United_Arab_Emirates", + }, + { + name: "Afghanistan", + code: "AF", + icon: "virtual-host", + icon_status: IconStatus.Up, + capital: "Kabul", + continent: { + name: "Asia", + }, + currency: "AFN", + languages: [ + { + name: "Pashto", + }, + { + name: "Uzbek", + }, + { + name: "Turkmen", + }, + ], + url: "https://en.wikipedia.org/wiki/Afghanistan", + }, + { + name: "Antigua and Barbuda", + code: "AG", + icon: "virtual-host", + icon_status: IconStatus.Up, + capital: "Saint John's", + continent: { + name: "North America", + }, + currency: "XCD", + languages: [ + { + name: "English", + }, + ], + url: "https://en.wikipedia.org/wiki/Antigua_and_Barbuda", + }, + { + name: "Anguilla", + code: "AI", + icon: "virtual-host", + icon_status: IconStatus.Up, + capital: "The Valley", + continent: { + name: "North America", + }, + currency: "XCD", + languages: [ + { + name: "English", + }, + ], + url: "https://en.wikipedia.org/wiki/Anguilla", + }, + { + name: "Albania", + code: "AL", + icon: "virtual-host", + icon_status: IconStatus.Up, + capital: "Tirana", + continent: { + name: "Europe", + }, + currency: "ALL", + languages: [ + { + name: "Albanian", + }, + ], + url: "https://en.wikipedia.org/wiki/Albania", + }, + { + name: "Armenia", + code: "AM", + icon: "virtual-host", + icon_status: IconStatus.Up, + capital: "Yerevan", + continent: { + name: "Asia", + }, + currency: "AMD", + languages: [ + { + name: "Armenian", + }, + { + name: "Russian", + }, + ], + url: "https://en.wikipedia.org/wiki/Armenia", + }, + { + name: "Angola", + code: "AO", + icon: "virtual-host", + icon_status: IconStatus.Up, + capital: "Luanda", + continent: { + name: "Africa", + }, + currency: "AOA", + languages: [ + { + name: "Portuguese", + }, + ], + }, + { + name: "Antarctica", + code: "AQ", + icon: "virtual-host", + icon_status: IconStatus.Up, + capital: null, + continent: { + name: "Antarctica", + }, + currency: null, + languages: [], + }, + { + name: "Argentina", + code: "AR", + icon: "virtual-host", + icon_status: IconStatus.Up, + capital: "Buenos Aires", + continent: { + name: "South America", + }, + currency: "ARS", + languages: [ + { + name: "Spanish", + }, + { + name: "Guarani", + }, + ], + }, + { + name: "American Samoa", + code: "AS", + icon: "virtual-host", + icon_status: IconStatus.Up, + capital: "Pago Pago", + continent: { + name: "Oceania", + }, + currency: "USD", + languages: [ + { + name: "English", + }, + { + name: "Samoan", + }, + ], + }, + { + name: "Austria", + code: "AT", + icon: "virtual-host", + icon_status: IconStatus.Up, + capital: "Vienna", + continent: { + name: "Europe", + }, + currency: "EUR", + languages: [ + { + name: "German", + }, + ], + }, + { + name: "Australia", + code: "AU", + icon: "virtual-host", + icon_status: IconStatus.Up, + capital: "Canberra", + continent: { + name: "Oceania", + }, + currency: "AUD", + languages: [ + { + name: "English", + }, + ], + }, + { + name: "Aruba", + code: "AW", + icon: "virtual-host", + icon_status: IconStatus.Up, + capital: "Oranjestad", + continent: { + name: "North America", + }, + currency: "AWG", + languages: [ + { + name: "Dutch", + }, + { + name: "Panjabi / Punjabi", + }, + ], + }, + { + name: "Åland", + code: "AX", + icon: "virtual-host", + icon_status: IconStatus.Up, + capital: "Mariehamn", + continent: { + name: "Europe", + }, + currency: "EUR", + languages: [ + { + name: "Swedish", + }, + ], + }, + { + name: "Azerbaijan", + code: "AZ", + icon: "virtual-host", + icon_status: IconStatus.Up, + capital: "Baku", + continent: { + name: "Asia", + }, + currency: "AZN", + languages: [ + { + name: "Azerbaijani", + }, + ], + }, + { + name: "Bosnia and Herzegovina", + code: "BA", + icon: "virtual-host", + icon_status: IconStatus.Up, + capital: "Sarajevo", + continent: { + name: "Europe", + }, + currency: "BAM", + languages: [ + { + name: "Bosnian", + }, + { + name: "Croatian", + }, + { + name: "Serbian", + }, + ], + }, + { + name: "Barbados", + code: "BB", + icon: "virtual-host", + icon_status: IconStatus.Up, + capital: "Bridgetown", + continent: { + name: "North America", + }, + currency: "BBD", + languages: [ + { + name: "English", + }, + ], + }, + { + name: "Bangladesh", + code: "BD", + icon: "virtual-host", + icon_status: IconStatus.Up, + capital: "Dhaka", + continent: { + name: "Asia", + }, + currency: "BDT", + languages: [ + { + name: "Bengali", + }, + ], + }, + { + name: "Belgium", + code: "BE", + icon: "virtual-host", + icon_status: IconStatus.Up, + capital: "Brussels", + continent: { + name: "Europe", + }, + currency: "EUR", + languages: [ + { + name: "Dutch", + }, + { + name: "French", + }, + { + name: "German", + }, + ], + }, + { + name: "Burkina Faso", + code: "BF", + icon: "virtual-host", + icon_status: IconStatus.Up, + capital: "Ouagadougou", + continent: { + name: "Africa", + }, + currency: "XOF", + languages: [ + { + name: "French", + }, + { + name: "Peul", + }, + ], + }, + { + name: "Bulgaria", + code: "BG", + icon: "virtual-host", + icon_status: IconStatus.Up, + capital: "Sofia", + continent: { + name: "Europe", + }, + currency: "BGN", + languages: [ + { + name: "Bulgarian", + }, + ], + }, + { + name: "Bahrain", + code: "BH", + icon: "virtual-host", + icon_status: IconStatus.Up, + capital: "Manama", + continent: { + name: "Asia", + }, + currency: "BHD", + languages: [ + { + name: "Arabic", + }, + ], + }, + { + name: "Burundi", + code: "BI", + icon: "virtual-host", + icon_status: IconStatus.Up, + capital: "Bujumbura", + continent: { + name: "Africa", + }, + currency: "BIF", + languages: [ + { + name: "French", + }, + { + name: "Kirundi", + }, + ], + }, + { + name: "Benin", + code: "BJ", + icon: "virtual-host", + icon_status: IconStatus.Up, + capital: "Porto-Novo", + continent: { + name: "Africa", + }, + currency: "XOF", + languages: [ + { + name: "French", + }, + ], + }, + { + name: "Saint Barthélemy", + code: "BL", + icon: "virtual-host", + icon_status: IconStatus.Up, + capital: "Gustavia", + continent: { + name: "North America", + }, + currency: "EUR", + languages: [ + { + name: "French", + }, + ], + }, + { + name: "Bermuda", + code: "BM", + icon: "virtual-host", + icon_status: IconStatus.Up, + capital: "Hamilton", + continent: { + name: "North America", + }, + currency: "BMD", + languages: [ + { + name: "English", + }, + ], + }, + { + name: "Brunei", + code: "BN", + icon: "virtual-host", + icon_status: IconStatus.Up, + capital: "Bandar Seri Begawan", + continent: { + name: "Asia", + }, + currency: "BND", + languages: [ + { + name: "Malay", + }, + ], + }, + { + name: "Bolivia", + code: "BO", + icon: "virtual-host", + icon_status: IconStatus.Up, + capital: "Sucre", + continent: { + name: "South America", + }, + currency: "BOB,BOV", + languages: [ + { + name: "Spanish", + }, + { + name: "Aymara", + }, + { + name: "Quechua", + }, + ], + }, + { + name: "Bonaire", + code: "BQ", + icon: "virtual-host", + icon_status: IconStatus.Up, + capital: "Kralendijk", + continent: { + name: "North America", + }, + currency: "USD", + languages: [ + { + name: "Dutch", + }, + ], + }, + { + name: "Brazil", + code: "BR", + icon: "virtual-host", + icon_status: IconStatus.Up, + capital: "Brasília", + continent: { + name: "South America", + }, + currency: "BRL", + languages: [ + { + name: "Portuguese", + }, + ], + }, + { + name: "Bahamas", + code: "BS", + icon: "virtual-host", + icon_status: IconStatus.Up, + capital: "Nassau", + continent: { + name: "North America", + }, + currency: "BSD", + languages: [ + { + name: "English", + }, + ], + }, + { + name: "Bhutan", + code: "BT", + icon: "virtual-host", + icon_status: IconStatus.Up, + capital: "Thimphu", + continent: { + name: "Asia", + }, + currency: "BTN,INR", + languages: [ + { + name: "Dzongkha", + }, + ], + }, + { + name: "Bouvet Island", + code: "BV", + icon: "virtual-host", + icon_status: IconStatus.Up, + capital: null, + continent: { + name: "Antarctica", + }, + currency: "NOK", + languages: [ + { + name: "Norwegian", + }, + { + name: "Norwegian Bokmål", + }, + { + name: "Norwegian Nynorsk", + }, + ], + }, + { + name: "Botswana", + code: "BW", + icon: "virtual-host", + icon_status: IconStatus.Up, + capital: "Gaborone", + continent: { + name: "Africa", + }, + currency: "BWP", + languages: [ + { + name: "English", + }, + { + name: "Tswana", + }, + ], + }, + { + name: "Belarus", + code: "BY", + icon: "virtual-host", + icon_status: IconStatus.Up, + capital: "Minsk", + continent: { + name: "Europe", + }, + currency: "BYN", + languages: [ + { + name: "Belarusian", + }, + { + name: "Russian", + }, + ], + }, + { + name: "Belize", + code: "BZ", + icon: "virtual-host", + icon_status: IconStatus.Up, + capital: "Belmopan", + continent: { + name: "North America", + }, + currency: "BZD", + languages: [ + { + name: "English", + }, + { + name: "Spanish", + }, + ], + }, + { + name: "Canada", + code: "CA", + icon: "virtual-host", + icon_status: IconStatus.Up, + capital: "Ottawa", + continent: { + name: "North America", + }, + currency: "CAD", + languages: [ + { + name: "English", + }, + { + name: "French", + }, + ], + }, + { + name: "Cocos [Keeling] Islands", + code: "CC", + icon: "virtual-host", + icon_status: IconStatus.Up, + capital: "West Island", + continent: { + name: "Asia", + }, + currency: "AUD", + languages: [ + { + name: "English", + }, + ], + }, + { + name: "Democratic Republic of the Congo", + code: "CD", + icon: "virtual-host", + icon_status: IconStatus.Up, + capital: "Kinshasa", + continent: { + name: "Africa", + }, + currency: "CDF", + languages: [ + { + name: "French", + }, + { + name: "Lingala", + }, + { + name: "Kongo", + }, + { + name: "Swahili", + }, + { + name: "Luba-Katanga", + }, + ], + }, + { + name: "Central African Republic", + code: "CF", + icon: "virtual-host", + icon_status: IconStatus.Up, + capital: "Bangui", + continent: { + name: "Africa", + }, + currency: "XAF", + languages: [ + { + name: "French", + }, + { + name: "Sango", + }, + ], + }, + { + name: "Republic of the Congo", + code: "CG", + icon: "virtual-host", + icon_status: IconStatus.Up, + capital: "Brazzaville", + continent: { + name: "Africa", + }, + currency: "XAF", + languages: [ + { + name: "French", + }, + { + name: "Lingala", + }, + ], + }, + { + name: "Switzerland", + code: "CH", + icon: "virtual-host", + icon_status: IconStatus.Up, + capital: "Bern", + continent: { + name: "Europe", + }, + currency: "CHE,CHF,CHW", + languages: [ + { + name: "German", + }, + { + name: "French", + }, + { + name: "Italian", + }, + ], + }, + { + name: "Ivory Coast", + code: "CI", + icon: "virtual-host", + icon_status: IconStatus.Up, + capital: "Yamoussoukro", + continent: { + name: "Africa", + }, + currency: "XOF", + languages: [ + { + name: "French", + }, + ], + }, + { + name: "Cook Islands", + code: "CK", + icon: "virtual-host", + icon_status: IconStatus.Up, + capital: "Avarua", + continent: { + name: "Oceania", + }, + currency: "NZD", + languages: [ + { + name: "English", + }, + ], + }, + { + name: "Chile", + code: "CL", + icon: "virtual-host", + icon_status: IconStatus.Up, + capital: "Santiago", + continent: { + name: "South America", + }, + currency: "CLF,CLP", + languages: [ + { + name: "Spanish", + }, + ], + }, + { + name: "Cameroon", + code: "CM", + icon: "virtual-host", + icon_status: IconStatus.Up, + capital: "Yaoundé", + continent: { + name: "Africa", + }, + currency: "XAF", + languages: [ + { + name: "English", + }, + { + name: "French", + }, + ], + }, + { + name: "China", + code: "CN", + icon: "virtual-host", + icon_status: IconStatus.Up, + capital: "Beijing", + continent: { + name: "Asia", + }, + currency: "CNY", + languages: [ + { + name: "Chinese", + }, + ], + }, + { + name: "Colombia", + code: "CO", + icon: "virtual-host", + icon_status: IconStatus.Up, + capital: "Bogotá", + continent: { + name: "South America", + }, + currency: "COP", + languages: [ + { + name: "Spanish", + }, + ], + }, + { + name: "Costa Rica", + code: "CR", + icon: "virtual-host", + icon_status: IconStatus.Up, + capital: "San José", + continent: { + name: "North America", + }, + currency: "CRC", + languages: [ + { + name: "Spanish", + }, + ], + }, + { + name: "Cuba", + code: "CU", + icon: "virtual-host", + icon_status: IconStatus.Up, + capital: "Havana", + continent: { + name: "North America", + }, + currency: "CUC,CUP", + languages: [ + { + name: "Spanish", + }, + ], + }, + { + name: "Cape Verde", + code: "CV", + icon: "virtual-host", + icon_status: IconStatus.Up, + capital: "Praia", + continent: { + name: "Africa", + }, + currency: "CVE", + languages: [ + { + name: "Portuguese", + }, + ], + }, + { + name: "Curacao", + code: "CW", + icon: "virtual-host", + icon_status: IconStatus.Up, + capital: "Willemstad", + continent: { + name: "North America", + }, + currency: "ANG", + languages: [ + { + name: "Dutch", + }, + { + name: "Panjabi / Punjabi", + }, + { + name: "English", + }, + ], + }, + { + name: "Christmas Island", + code: "CX", + icon: "virtual-host", + icon_status: IconStatus.Up, + capital: "Flying Fish Cove", + continent: { + name: "Asia", + }, + currency: "AUD", + languages: [ + { + name: "English", + }, + ], + }, + { + name: "Cyprus", + code: "CY", + icon: "virtual-host", + icon_status: IconStatus.Up, + capital: "Nicosia", + continent: { + name: "Europe", + }, + currency: "EUR", + languages: [ + { + name: "Greek", + }, + { + name: "Turkish", + }, + { + name: "Armenian", + }, + ], + }, + { + name: "Czech Republic", + code: "CZ", + icon: "virtual-host", + icon_status: IconStatus.Up, + capital: "Prague", + continent: { + name: "Europe", + }, + currency: "CZK", + languages: [ + { + name: "Czech", + }, + { + name: "Slovak", + }, + ], + }, + { + name: "Germany", + code: "DE", + icon: "virtual-host", + icon_status: IconStatus.Up, + capital: "Berlin", + continent: { + name: "Europe", + }, + currency: "EUR", + languages: [ + { + name: "German", + }, + ], + }, + { + name: "Djibouti", + code: "DJ", + icon: "virtual-host", + icon_status: IconStatus.Up, + capital: "Djibouti", + continent: { + name: "Africa", + }, + currency: "DJF", + languages: [ + { + name: "French", + }, + { + name: "Arabic", + }, + ], + }, + { + name: "Denmark", + code: "DK", + icon: "virtual-host", + icon_status: IconStatus.Up, + capital: "Copenhagen", + continent: { + name: "Europe", + }, + currency: "DKK", + languages: [ + { + name: "Danish", + }, + ], + }, + { + name: "Dominica", + code: "DM", + icon: "virtual-host", + icon_status: IconStatus.Up, + capital: "Roseau", + continent: { + name: "North America", + }, + currency: "XCD", + languages: [ + { + name: "English", + }, + ], + }, + { + name: "Dominican Republic", + code: "DO", + icon: "virtual-host", + icon_status: IconStatus.Up, + capital: "Santo Domingo", + continent: { + name: "North America", + }, + currency: "DOP", + languages: [ + { + name: "Spanish", + }, + ], + }, + { + name: "Algeria", + code: "DZ", + icon: "virtual-host", + icon_status: IconStatus.Up, + capital: "Algiers", + continent: { + name: "Africa", + }, + currency: "DZD", + languages: [ + { + name: "Arabic", + }, + ], + }, + { + name: "Ecuador", + code: "EC", + icon: "virtual-host", + icon_status: IconStatus.Up, + capital: "Quito", + continent: { + name: "South America", + }, + currency: "USD", + languages: [ + { + name: "Spanish", + }, + ], + }, + { + name: "Estonia", + code: "EE", + icon: "virtual-host", + icon_status: IconStatus.Up, + capital: "Tallinn", + continent: { + name: "Europe", + }, + currency: "EUR", + languages: [ + { + name: "Estonian", + }, + ], + }, + { + name: "Egypt", + code: "EG", + icon: "virtual-host", + icon_status: IconStatus.Up, + capital: "Cairo", + continent: { + name: "Africa", + }, + currency: "EGP", + languages: [ + { + name: "Arabic", + }, + ], + }, + { + name: "Western Sahara", + code: "EH", + icon: "virtual-host", + icon_status: IconStatus.Up, + capital: "El Aaiún", + continent: { + name: "Africa", + }, + currency: "MAD,DZD,MRU", + languages: [ + { + name: "Spanish", + }, + ], + }, + { + name: "Eritrea", + code: "ER", + icon: "virtual-host", + icon_status: IconStatus.Up, + capital: "Asmara", + continent: { + name: "Africa", + }, + currency: "ERN", + languages: [ + { + name: "Tigrinya", + }, + { + name: "Arabic", + }, + { + name: "English", + }, + ], + }, + { + name: "Spain", + code: "ES", + icon: "virtual-host", + icon_status: IconStatus.Up, + capital: "Madrid", + continent: { + name: "Europe", + }, + currency: "EUR", + languages: [ + { + name: "Spanish", + }, + { + name: "Basque", + }, + { + name: "Catalan", + }, + { + name: "Galician", + }, + { + name: "Occitan", + }, + ], + }, + { + name: "Ethiopia", + code: "ET", + icon: "virtual-host", + icon_status: IconStatus.Up, + capital: "Addis Ababa", + continent: { + name: "Africa", + }, + currency: "ETB", + languages: [ + { + name: "Amharic", + }, + ], + }, + { + name: "Finland", + code: "FI", + icon: "virtual-host", + icon_status: IconStatus.Up, + capital: "Helsinki", + continent: { + name: "Europe", + }, + currency: "EUR", + languages: [ + { + name: "Finnish", + }, + { + name: "Swedish", + }, + ], + }, + { + name: "Fiji", + code: "FJ", + icon: "virtual-host", + icon_status: IconStatus.Up, + capital: "Suva", + continent: { + name: "Oceania", + }, + currency: "FJD", + languages: [ + { + name: "English", + }, + { + name: "Fijian", + }, + { + name: "Hindi", + }, + { + name: "Urdu", + }, + ], + }, + { + name: "Falkland Islands", + code: "FK", + icon: "virtual-host", + icon_status: IconStatus.Up, + capital: "Stanley", + continent: { + name: "South America", + }, + currency: "FKP", + languages: [ + { + name: "English", + }, + ], + }, + { + name: "Micronesia", + code: "FM", + icon: "virtual-host", + icon_status: IconStatus.Up, + capital: "Palikir", + continent: { + name: "Oceania", + }, + currency: "USD", + languages: [ + { + name: "English", + }, + ], + }, + { + name: "Faroe Islands", + code: "FO", + icon: "virtual-host", + icon_status: IconStatus.Up, + capital: "Tórshavn", + continent: { + name: "Europe", + }, + currency: "DKK", + languages: [ + { + name: "Faroese", + }, + ], + }, + { + name: "France", + code: "FR", + icon: "virtual-host", + icon_status: IconStatus.Up, + capital: "Paris", + continent: { + name: "Europe", + }, + currency: "EUR", + languages: [ + { + name: "French", + }, + ], + }, + { + name: "Gabon", + code: "GA", + icon: "virtual-host", + icon_status: IconStatus.Up, + capital: "Libreville", + continent: { + name: "Africa", + }, + currency: "XAF", + languages: [ + { + name: "French", + }, + ], + }, + { + name: "United Kingdom", + code: "GB", + icon: "virtual-host", + icon_status: IconStatus.Up, + capital: "London", + continent: { + name: "Europe", + }, + currency: "GBP", + languages: [ + { + name: "English", + }, + ], + }, + { + name: "Grenada", + code: "GD", + icon: "virtual-host", + icon_status: IconStatus.Up, + capital: "St. George's", + continent: { + name: "North America", + }, + currency: "XCD", + languages: [ + { + name: "English", + }, + ], + }, + { + name: "Georgia", + code: "GE", + icon: "virtual-host", + icon_status: IconStatus.Up, + capital: "Tbilisi", + continent: { + name: "Asia", + }, + currency: "GEL", + languages: [ + { + name: "Georgian", + }, + ], + }, + { + name: "French Guiana", + code: "GF", + icon: "virtual-host", + icon_status: IconStatus.Up, + capital: "Cayenne", + continent: { + name: "South America", + }, + currency: "EUR", + languages: [ + { + name: "French", + }, + ], + }, + { + name: "Guernsey", + code: "GG", + icon: "virtual-host", + icon_status: IconStatus.Up, + capital: "St. Peter Port", + continent: { + name: "Europe", + }, + currency: "GBP", + languages: [ + { + name: "English", + }, + { + name: "French", + }, + ], + }, + { + name: "Ghana", + code: "GH", + icon: "virtual-host", + icon_status: IconStatus.Up, + capital: "Accra", + continent: { + name: "Africa", + }, + currency: "GHS", + languages: [ + { + name: "English", + }, + ], + }, + { + name: "Gibraltar", + code: "GI", + icon: "virtual-host", + icon_status: IconStatus.Up, + capital: "Gibraltar", + continent: { + name: "Europe", + }, + currency: "GIP", + languages: [ + { + name: "English", + }, + ], + }, + { + name: "Greenland", + code: "GL", + icon: "virtual-host", + icon_status: IconStatus.Up, + capital: "Nuuk", + continent: { + name: "North America", + }, + currency: "DKK", + languages: [ + { + name: "Greenlandic", + }, + ], + }, + { + name: "Gambia", + code: "GM", + icon: "virtual-host", + icon_status: IconStatus.Up, + capital: "Banjul", + continent: { + name: "Africa", + }, + currency: "GMD", + languages: [ + { + name: "English", + }, + ], + }, + { + name: "Guinea", + code: "GN", + icon: "virtual-host", + icon_status: IconStatus.Up, + capital: "Conakry", + continent: { + name: "Africa", + }, + currency: "GNF", + languages: [ + { + name: "French", + }, + { + name: "Peul", + }, + ], + }, + { + name: "Guadeloupe", + code: "GP", + icon: "virtual-host", + icon_status: IconStatus.Up, + capital: "Basse-Terre", + continent: { + name: "North America", + }, + currency: "EUR", + languages: [ + { + name: "French", + }, + ], + }, + { + name: "Equatorial Guinea", + code: "GQ", + icon: "virtual-host", + icon_status: IconStatus.Up, + capital: "Malabo", + continent: { + name: "Africa", + }, + currency: "XAF", + languages: [ + { + name: "Spanish", + }, + { + name: "French", + }, + ], + }, + { + name: "Greece", + code: "GR", + icon: "virtual-host", + icon_status: IconStatus.Up, + capital: "Athens", + continent: { + name: "Europe", + }, + currency: "EUR", + languages: [ + { + name: "Greek", + }, + ], + }, + { + name: "South Georgia and the South Sandwich Islands", + code: "GS", + icon: "virtual-host", + icon_status: IconStatus.Up, + capital: "King Edward Point", + continent: { + name: "Antarctica", + }, + currency: "GBP", + languages: [ + { + name: "English", + }, + ], + }, + { + name: "Guatemala", + code: "GT", + icon: "virtual-host", + icon_status: IconStatus.Up, + capital: "Guatemala City", + continent: { + name: "North America", + }, + currency: "GTQ", + languages: [ + { + name: "Spanish", + }, + ], + }, + { + name: "Guam", + code: "GU", + icon: "virtual-host", + icon_status: IconStatus.Up, + capital: "Hagåtña", + continent: { + name: "Oceania", + }, + currency: "USD", + languages: [ + { + name: "English", + }, + { + name: "Chamorro", + }, + { + name: "Spanish", + }, + ], + }, + { + name: "Guinea-Bissau", + code: "GW", + icon: "virtual-host", + icon_status: IconStatus.Up, + capital: "Bissau", + continent: { + name: "Africa", + }, + currency: "XOF", + languages: [ + { + name: "Portuguese", + }, + ], + }, + { + name: "Guyana", + code: "GY", + icon: "virtual-host", + icon_status: IconStatus.Up, + capital: "Georgetown", + continent: { + name: "South America", + }, + currency: "GYD", + languages: [ + { + name: "English", + }, + ], + }, + { + name: "Hong Kong", + code: "HK", + icon: "virtual-host", + icon_status: IconStatus.Up, + capital: "City of Victoria", + continent: { + name: "Asia", + }, + currency: "HKD", + languages: [ + { + name: "Chinese", + }, + { + name: "English", + }, + ], + }, + { + name: "Heard Island and McDonald Islands", + code: "HM", + icon: "virtual-host", + icon_status: IconStatus.Up, + capital: null, + continent: { + name: "Antarctica", + }, + currency: "AUD", + languages: [ + { + name: "English", + }, + ], + }, + { + name: "Honduras", + code: "HN", + icon: "virtual-host", + icon_status: IconStatus.Up, + capital: "Tegucigalpa", + continent: { + name: "North America", + }, + currency: "HNL", + languages: [ + { + name: "Spanish", + }, + ], + }, + { + name: "Croatia", + code: "HR", + icon: "virtual-host", + icon_status: IconStatus.Up, + capital: "Zagreb", + continent: { + name: "Europe", + }, + currency: "HRK", + languages: [ + { + name: "Croatian", + }, + ], + }, + { + name: "Haiti", + code: "HT", + icon: "virtual-host", + icon_status: IconStatus.Up, + capital: "Port-au-Prince", + continent: { + name: "North America", + }, + currency: "HTG,USD", + languages: [ + { + name: "French", + }, + { + name: "Haitian", + }, + ], + }, + { + name: "Hungary", + code: "HU", + icon: "virtual-host", + icon_status: IconStatus.Up, + capital: "Budapest", + continent: { + name: "Europe", + }, + currency: "HUF", + languages: [ + { + name: "Hungarian", + }, + ], + }, + { + name: "Indonesia", + code: "ID", + icon: "virtual-host", + icon_status: IconStatus.Up, + capital: "Jakarta", + continent: { + name: "Asia", + }, + currency: "IDR", + languages: [ + { + name: "Indonesian", + }, + ], + }, + { + name: "Ireland", + code: "IE", + icon: "virtual-host", + icon_status: IconStatus.Up, + capital: "Dublin", + continent: { + name: "Europe", + }, + currency: "EUR", + languages: [ + { + name: "Irish", + }, + { + name: "English", + }, + ], + }, + { + name: "Israel", + code: "IL", + icon: "virtual-host", + icon_status: IconStatus.Up, + capital: "Jerusalem", + continent: { + name: "Asia", + }, + currency: "ILS", + languages: [ + { + name: "Hebrew", + }, + { + name: "Arabic", + }, + ], + }, + { + name: "Isle of Man", + code: "IM", + icon: "virtual-host", + icon_status: IconStatus.Up, + capital: "Douglas", + continent: { + name: "Europe", + }, + currency: "GBP", + languages: [ + { + name: "English", + }, + { + name: "Manx", + }, + ], + }, + { + name: "India", + code: "IN", + icon: "virtual-host", + icon_status: IconStatus.Up, + capital: "New Delhi", + continent: { + name: "Asia", + }, + currency: "INR", + languages: [ + { + name: "Hindi", + }, + { + name: "English", + }, + ], + }, + { + name: "British Indian Ocean Territory", + code: "IO", + icon: "virtual-host", + icon_status: IconStatus.Up, + capital: "Diego Garcia", + continent: { + name: "Asia", + }, + currency: "USD", + languages: [ + { + name: "English", + }, + ], + }, + { + name: "Iraq", + code: "IQ", + icon: "virtual-host", + icon_status: IconStatus.Up, + capital: "Baghdad", + continent: { + name: "Asia", + }, + currency: "IQD", + languages: [ + { + name: "Arabic", + }, + { + name: "Kurdish", + }, + ], + }, + { + name: "Iran", + code: "IR", + icon: "virtual-host", + icon_status: IconStatus.Up, + capital: "Tehran", + continent: { + name: "Asia", + }, + currency: "IRR", + languages: [ + { + name: "Persian", + }, + ], + }, + { + name: "Iceland", + code: "IS", + icon: "virtual-host", + icon_status: IconStatus.Up, + capital: "Reykjavik", + continent: { + name: "Europe", + }, + currency: "ISK", + languages: [ + { + name: "Icelandic", + }, + ], + }, + { + name: "Italy", + code: "IT", + icon: "virtual-host", + icon_status: IconStatus.Up, + capital: "Rome", + continent: { + name: "Europe", + }, + currency: "EUR", + languages: [ + { + name: "Italian", + }, + ], + }, + { + name: "Jersey", + code: "JE", + icon: "virtual-host", + icon_status: IconStatus.Up, + capital: "Saint Helier", + continent: { + name: "Europe", + }, + currency: "GBP", + languages: [ + { + name: "English", + }, + { + name: "French", + }, + ], + }, + { + name: "Jamaica", + code: "JM", + icon: "virtual-host", + icon_status: IconStatus.Up, + capital: "Kingston", + continent: { + name: "North America", + }, + currency: "JMD", + languages: [ + { + name: "English", + }, + ], + }, + { + name: "Jordan", + code: "JO", + icon: "virtual-host", + icon_status: IconStatus.Up, + capital: "Amman", + continent: { + name: "Asia", + }, + currency: "JOD", + languages: [ + { + name: "Arabic", + }, + ], + }, + { + name: "Japan", + code: "JP", + icon: "virtual-host", + icon_status: IconStatus.Up, + capital: "Tokyo", + continent: { + name: "Asia", + }, + currency: "JPY", + languages: [ + { + name: "Japanese", + }, + ], + }, + { + name: "Kenya", + code: "KE", + icon: "virtual-host", + icon_status: IconStatus.Up, + capital: "Nairobi", + continent: { + name: "Africa", + }, + currency: "KES", + languages: [ + { + name: "English", + }, + { + name: "Swahili", + }, + ], + }, + { + name: "Kyrgyzstan", + code: "KG", + icon: "virtual-host", + icon_status: IconStatus.Up, + capital: "Bishkek", + continent: { + name: "Asia", + }, + currency: "KGS", + languages: [ + { + name: "Kirghiz", + }, + { + name: "Russian", + }, + ], + }, + { + name: "Cambodia", + code: "KH", + icon: "virtual-host", + icon_status: IconStatus.Up, + capital: "Phnom Penh", + continent: { + name: "Asia", + }, + currency: "KHR", + languages: [ + { + name: "Cambodian", + }, + ], + }, + { + name: "Kiribati", + code: "KI", + icon: "virtual-host", + icon_status: IconStatus.Up, + capital: "South Tarawa", + continent: { + name: "Oceania", + }, + currency: "AUD", + languages: [ + { + name: "English", + }, + ], + }, + { + name: "Comoros", + code: "KM", + icon: "virtual-host", + icon_status: IconStatus.Up, + capital: "Moroni", + continent: { + name: "Africa", + }, + currency: "KMF", + languages: [ + { + name: "Arabic", + }, + { + name: "French", + }, + ], + }, + { + name: "Saint Kitts and Nevis", + code: "KN", + icon: "virtual-host", + icon_status: IconStatus.Up, + capital: "Basseterre", + continent: { + name: "North America", + }, + currency: "XCD", + languages: [ + { + name: "English", + }, + ], + }, + { + name: "North Korea", + code: "KP", + icon: "virtual-host", + icon_status: IconStatus.Up, + capital: "Pyongyang", + continent: { + name: "Asia", + }, + currency: "KPW", + languages: [ + { + name: "Korean", + }, + ], + }, + { + name: "South Korea", + code: "KR", + icon: "virtual-host", + icon_status: IconStatus.Up, + capital: "Seoul", + continent: { + name: "Asia", + }, + currency: "KRW", + languages: [ + { + name: "Korean", + }, + ], + }, + { + name: "Kuwait", + code: "KW", + icon: "virtual-host", + icon_status: IconStatus.Up, + capital: "Kuwait City", + continent: { + name: "Asia", + }, + currency: "KWD", + languages: [ + { + name: "Arabic", + }, + ], + }, + { + name: "Cayman Islands", + code: "KY", + icon: "virtual-host", + icon_status: IconStatus.Up, + capital: "George Town", + continent: { + name: "North America", + }, + currency: "KYD", + languages: [ + { + name: "English", + }, + ], + }, + { + name: "Kazakhstan", + code: "KZ", + icon: "virtual-host", + icon_status: IconStatus.Up, + capital: "Astana", + continent: { + name: "Asia", + }, + currency: "KZT", + languages: [ + { + name: "Kazakh", + }, + { + name: "Russian", + }, + ], + }, + { + name: "Laos", + code: "LA", + icon: "virtual-host", + icon_status: IconStatus.Up, + capital: "Vientiane", + continent: { + name: "Asia", + }, + currency: "LAK", + languages: [ + { + name: "Laotian", + }, + ], + }, + { + name: "Lebanon", + code: "LB", + icon: "virtual-host", + icon_status: IconStatus.Up, + capital: "Beirut", + continent: { + name: "Asia", + }, + currency: "LBP", + languages: [ + { + name: "Arabic", + }, + { + name: "French", + }, + ], + }, + { + name: "Saint Lucia", + code: "LC", + icon: "virtual-host", + icon_status: IconStatus.Up, + capital: "Castries", + continent: { + name: "North America", + }, + currency: "XCD", + languages: [ + { + name: "English", + }, + ], + }, + { + name: "Liechtenstein", + code: "LI", + icon: "virtual-host", + icon_status: IconStatus.Up, + capital: "Vaduz", + continent: { + name: "Europe", + }, + currency: "CHF", + languages: [ + { + name: "German", + }, + ], + }, + { + name: "Sri Lanka", + code: "LK", + icon: "virtual-host", + icon_status: IconStatus.Up, + capital: "Colombo", + continent: { + name: "Asia", + }, + currency: "LKR", + languages: [ + { + name: "Sinhalese", + }, + { + name: "Tamil", + }, + ], + }, + { + name: "Liberia", + code: "LR", + icon: "virtual-host", + icon_status: IconStatus.Up, + capital: "Monrovia", + continent: { + name: "Africa", + }, + currency: "LRD", + languages: [ + { + name: "English", + }, + ], + }, + { + name: "Lesotho", + code: "LS", + icon: "virtual-host", + icon_status: IconStatus.Up, + capital: "Maseru", + continent: { + name: "Africa", + }, + currency: "LSL,ZAR", + languages: [ + { + name: "English", + }, + { + name: "Southern Sotho", + }, + ], + }, + { + name: "Lithuania", + code: "LT", + icon: "virtual-host", + icon_status: IconStatus.Up, + capital: "Vilnius", + continent: { + name: "Europe", + }, + currency: "EUR", + languages: [ + { + name: "Lithuanian", + }, + ], + }, + { + name: "Luxembourg", + code: "LU", + icon: "virtual-host", + icon_status: IconStatus.Up, + capital: "Luxembourg", + continent: { + name: "Europe", + }, + currency: "EUR", + languages: [ + { + name: "French", + }, + { + name: "German", + }, + { + name: "Luxembourgish", + }, + ], + }, + { + name: "Latvia", + code: "LV", + icon: "virtual-host", + icon_status: IconStatus.Up, + capital: "Riga", + continent: { + name: "Europe", + }, + currency: "EUR", + languages: [ + { + name: "Latvian", + }, + ], + }, + { + name: "Libya", + code: "LY", + icon: "virtual-host", + icon_status: IconStatus.Up, + capital: "Tripoli", + continent: { + name: "Africa", + }, + currency: "LYD", + languages: [ + { + name: "Arabic", + }, + ], + }, + { + name: "Morocco", + code: "MA", + icon: "virtual-host", + icon_status: IconStatus.Up, + capital: "Rabat", + continent: { + name: "Africa", + }, + currency: "MAD", + languages: [ + { + name: "Arabic", + }, + ], + }, + { + name: "Monaco", + code: "MC", + icon: "virtual-host", + icon_status: IconStatus.Up, + capital: "Monaco", + continent: { + name: "Europe", + }, + currency: "EUR", + languages: [ + { + name: "French", + }, + ], + }, + { + name: "Moldova", + code: "MD", + icon: "virtual-host", + icon_status: IconStatus.Up, + capital: "Chișinău", + continent: { + name: "Europe", + }, + currency: "MDL", + languages: [ + { + name: "Romanian", + }, + ], + }, + { + name: "Montenegro", + code: "ME", + icon: "virtual-host", + icon_status: IconStatus.Up, + capital: "Podgorica", + continent: { + name: "Europe", + }, + currency: "EUR", + languages: [ + { + name: "Serbian", + }, + { + name: "Bosnian", + }, + { + name: "Albanian", + }, + { + name: "Croatian", + }, + ], + }, + { + name: "Saint Martin", + code: "MF", + icon: "virtual-host", + icon_status: IconStatus.Up, + capital: "Marigot", + continent: { + name: "North America", + }, + currency: "EUR", + languages: [ + { + name: "English", + }, + { + name: "French", + }, + { + name: "Dutch", + }, + ], + }, + { + name: "Madagascar", + code: "MG", + icon: "virtual-host", + icon_status: IconStatus.Up, + capital: "Antananarivo", + continent: { + name: "Africa", + }, + currency: "MGA", + languages: [ + { + name: "French", + }, + { + name: "Malagasy", + }, + ], + }, + { + name: "Marshall Islands", + code: "MH", + icon: "virtual-host", + icon_status: IconStatus.Up, + capital: "Majuro", + continent: { + name: "Oceania", + }, + currency: "USD", + languages: [ + { + name: "English", + }, + { + name: "Marshallese", + }, + ], + }, + { + name: "North Macedonia", + code: "MK", + icon: "virtual-host", + icon_status: IconStatus.Up, + capital: "Skopje", + continent: { + name: "Europe", + }, + currency: "MKD", + languages: [ + { + name: "Macedonian", + }, + ], + }, + { + name: "Mali", + code: "ML", + icon: "virtual-host", + icon_status: IconStatus.Up, + capital: "Bamako", + continent: { + name: "Africa", + }, + currency: "XOF", + languages: [ + { + name: "French", + }, + ], + }, + { + name: "Myanmar [Burma]", + code: "MM", + icon: "virtual-host", + icon_status: IconStatus.Up, + capital: "Naypyidaw", + continent: { + name: "Asia", + }, + currency: "MMK", + languages: [ + { + name: "Burmese", + }, + ], + }, + { + name: "Mongolia", + code: "MN", + icon: "virtual-host", + icon_status: IconStatus.Up, + capital: "Ulan Bator", + continent: { + name: "Asia", + }, + currency: "MNT", + languages: [ + { + name: "Mongolian", + }, + ], + }, + { + name: "Macao", + code: "MO", + icon: "virtual-host", + icon_status: IconStatus.Up, + capital: null, + continent: { + name: "Asia", + }, + currency: "MOP", + languages: [ + { + name: "Chinese", + }, + { + name: "Portuguese", + }, + ], + }, + { + name: "Northern Mariana Islands", + code: "MP", + icon: "virtual-host", + icon_status: IconStatus.Up, + capital: "Saipan", + continent: { + name: "Oceania", + }, + currency: "USD", + languages: [ + { + name: "English", + }, + { + name: "Chamorro", + }, + ], + }, + { + name: "Martinique", + code: "MQ", + icon: "virtual-host", + icon_status: IconStatus.Up, + capital: "Fort-de-France", + continent: { + name: "North America", + }, + currency: "EUR", + languages: [ + { + name: "French", + }, + ], + }, + { + name: "Mauritania", + code: "MR", + icon: "virtual-host", + icon_status: IconStatus.Up, + capital: "Nouakchott", + continent: { + name: "Africa", + }, + currency: "MRU", + languages: [ + { + name: "Arabic", + }, + ], + }, + { + name: "Montserrat", + code: "MS", + icon: "virtual-host", + icon_status: IconStatus.Up, + capital: "Plymouth", + continent: { + name: "North America", + }, + currency: "XCD", + languages: [ + { + name: "English", + }, + ], + }, + { + name: "Malta", + code: "MT", + icon: "virtual-host", + icon_status: IconStatus.Up, + capital: "Valletta", + continent: { + name: "Europe", + }, + currency: "EUR", + languages: [ + { + name: "Maltese", + }, + { + name: "English", + }, + ], + }, + { + name: "Mauritius", + code: "MU", + icon: "virtual-host", + icon_status: IconStatus.Up, + capital: "Port Louis", + continent: { + name: "Africa", + }, + currency: "MUR", + languages: [ + { + name: "English", + }, + ], + }, + { + name: "Maldives", + code: "MV", + icon: "virtual-host", + icon_status: IconStatus.Up, + capital: "Malé", + continent: { + name: "Asia", + }, + currency: "MVR", + languages: [ + { + name: "Divehi", + }, + ], + }, + { + name: "Malawi", + code: "MW", + icon: "virtual-host", + icon_status: IconStatus.Up, + capital: "Lilongwe", + continent: { + name: "Africa", + }, + currency: "MWK", + languages: [ + { + name: "English", + }, + { + name: "Chichewa", + }, + ], + }, + { + name: "Mexico", + code: "MX", + icon: "virtual-host", + icon_status: IconStatus.Up, + capital: "Mexico City", + continent: { + name: "North America", + }, + currency: "MXN", + languages: [ + { + name: "Spanish", + }, + ], + }, + { + name: "Malaysia", + code: "MY", + icon: "virtual-host", + icon_status: IconStatus.Up, + capital: "Kuala Lumpur", + continent: { + name: "Asia", + }, + currency: "MYR", + languages: [ + { + name: "Malay", + }, + ], + }, + { + name: "Mozambique", + code: "MZ", + icon: "virtual-host", + icon_status: IconStatus.Up, + capital: "Maputo", + continent: { + name: "Africa", + }, + currency: "MZN", + languages: [ + { + name: "Portuguese", + }, + ], + }, + { + name: "Namibia", + code: "NA", + icon: "virtual-host", + icon_status: IconStatus.Up, + capital: "Windhoek", + continent: { + name: "Africa", + }, + currency: "NAD,ZAR", + languages: [ + { + name: "English", + }, + { + name: "Afrikaans", + }, + ], + }, + { + name: "New Caledonia", + code: "NC", + icon: "virtual-host", + icon_status: IconStatus.Up, + capital: "Nouméa", + continent: { + name: "Oceania", + }, + currency: "XPF", + languages: [ + { + name: "French", + }, + ], + }, + { + name: "Niger", + code: "NE", + icon: "virtual-host", + icon_status: IconStatus.Up, + capital: "Niamey", + continent: { + name: "Africa", + }, + currency: "XOF", + languages: [ + { + name: "French", + }, + ], + }, + { + name: "Norfolk Island", + code: "NF", + icon: "virtual-host", + icon_status: IconStatus.Up, + capital: "Kingston", + continent: { + name: "Oceania", + }, + currency: "AUD", + languages: [ + { + name: "English", + }, + ], + }, + { + name: "Nigeria", + code: "NG", + icon: "virtual-host", + icon_status: IconStatus.Up, + capital: "Abuja", + continent: { + name: "Africa", + }, + currency: "NGN", + languages: [ + { + name: "English", + }, + ], + }, + { + name: "Nicaragua", + code: "NI", + icon: "virtual-host", + icon_status: IconStatus.Up, + capital: "Managua", + continent: { + name: "North America", + }, + currency: "NIO", + languages: [ + { + name: "Spanish", + }, + ], + }, + { + name: "Netherlands", + code: "NL", + icon: "virtual-host", + icon_status: IconStatus.Up, + capital: "Amsterdam", + continent: { + name: "Europe", + }, + currency: "EUR", + languages: [ + { + name: "Dutch", + }, + ], + }, + { + name: "Norway", + code: "NO", + icon: "virtual-host", + icon_status: IconStatus.Up, + capital: "Oslo", + continent: { + name: "Europe", + }, + currency: "NOK", + languages: [ + { + name: "Norwegian", + }, + { + name: "Norwegian Bokmål", + }, + { + name: "Norwegian Nynorsk", + }, + ], + }, + { + name: "Nepal", + code: "NP", + icon: "virtual-host", + icon_status: IconStatus.Up, + capital: "Kathmandu", + continent: { + name: "Asia", + }, + currency: "NPR", + languages: [ + { + name: "Nepali", + }, + ], + }, + { + name: "Nauru", + code: "NR", + icon: "virtual-host", + icon_status: IconStatus.Up, + capital: "Yaren", + continent: { + name: "Oceania", + }, + currency: "AUD", + languages: [ + { + name: "English", + }, + { + name: "Nauruan", + }, + ], + }, + { + name: "Niue", + code: "NU", + icon: "virtual-host", + icon_status: IconStatus.Up, + capital: "Alofi", + continent: { + name: "Oceania", + }, + currency: "NZD", + languages: [ + { + name: "English", + }, + ], + }, + { + name: "New Zealand", + code: "NZ", + icon: "virtual-host", + icon_status: IconStatus.Up, + capital: "Wellington", + continent: { + name: "Oceania", + }, + currency: "NZD", + languages: [ + { + name: "English", + }, + { + name: "Maori", + }, + ], + }, + { + name: "Oman", + code: "OM", + icon: "virtual-host", + icon_status: IconStatus.Up, + capital: "Muscat", + continent: { + name: "Asia", + }, + currency: "OMR", + languages: [ + { + name: "Arabic", + }, + ], + }, + { + name: "Panama", + code: "PA", + icon: "virtual-host", + icon_status: IconStatus.Up, + capital: "Panama City", + continent: { + name: "North America", + }, + currency: "PAB,USD", + languages: [ + { + name: "Spanish", + }, + ], + }, + { + name: "Peru", + code: "PE", + icon: "virtual-host", + icon_status: IconStatus.Up, + capital: "Lima", + continent: { + name: "South America", + }, + currency: "PEN", + languages: [ + { + name: "Spanish", + }, + ], + }, + { + name: "French Polynesia", + code: "PF", + icon: "virtual-host", + icon_status: IconStatus.Up, + capital: "Papeetē", + continent: { + name: "Oceania", + }, + currency: "XPF", + languages: [ + { + name: "French", + }, + ], + }, + { + name: "Papua New Guinea", + code: "PG", + icon: "virtual-host", + icon_status: IconStatus.Up, + capital: "Port Moresby", + continent: { + name: "Oceania", + }, + currency: "PGK", + languages: [ + { + name: "English", + }, + ], + }, + { + name: "Philippines", + code: "PH", + icon: "virtual-host", + icon_status: IconStatus.Up, + capital: "Manila", + continent: { + name: "Asia", + }, + currency: "PHP", + languages: [ + { + name: "English", + }, + ], + }, + { + name: "Pakistan", + code: "PK", + icon: "virtual-host", + icon_status: IconStatus.Up, + capital: "Islamabad", + continent: { + name: "Asia", + }, + currency: "PKR", + languages: [ + { + name: "English", + }, + { + name: "Urdu", + }, + ], + }, + { + name: "Poland", + code: "PL", + icon: "virtual-host", + icon_status: IconStatus.Up, + capital: "Warsaw", + continent: { + name: "Europe", + }, + currency: "PLN", + languages: [ + { + name: "Polish", + }, + ], + }, + { + name: "Saint Pierre and Miquelon", + code: "PM", + icon: "virtual-host", + icon_status: IconStatus.Up, + capital: "Saint-Pierre", + continent: { + name: "North America", + }, + currency: "EUR", + languages: [ + { + name: "French", + }, + ], + }, + { + name: "Pitcairn Islands", + code: "PN", + icon: "virtual-host", + icon_status: IconStatus.Up, + capital: "Adamstown", + continent: { + name: "Oceania", + }, + currency: "NZD", + languages: [ + { + name: "English", + }, + ], + }, + { + name: "Puerto Rico", + code: "PR", + icon: "virtual-host", + icon_status: IconStatus.Up, + capital: "San Juan", + continent: { + name: "North America", + }, + currency: "USD", + languages: [ + { + name: "Spanish", + }, + { + name: "English", + }, + ], + }, + { + name: "Palestine", + code: "PS", + icon: "virtual-host", + icon_status: IconStatus.Up, + capital: "Ramallah", + continent: { + name: "Asia", + }, + currency: "ILS", + languages: [ + { + name: "Arabic", + }, + ], + }, + { + name: "Portugal", + code: "PT", + icon: "virtual-host", + icon_status: IconStatus.Up, + capital: "Lisbon", + continent: { + name: "Europe", + }, + currency: "EUR", + languages: [ + { + name: "Portuguese", + }, + ], + }, + { + name: "Palau", + code: "PW", + icon: "virtual-host", + icon_status: IconStatus.Up, + capital: "Ngerulmud", + continent: { + name: "Oceania", + }, + currency: "USD", + languages: [ + { + name: "English", + }, + ], + }, + { + name: "Paraguay", + code: "PY", + icon: "virtual-host", + icon_status: IconStatus.Up, + capital: "Asunción", + continent: { + name: "South America", + }, + currency: "PYG", + languages: [ + { + name: "Spanish", + }, + { + name: "Guarani", + }, + ], + }, + { + name: "Qatar", + code: "QA", + icon: "virtual-host", + icon_status: IconStatus.Up, + capital: "Doha", + continent: { + name: "Asia", + }, + currency: "QAR", + languages: [ + { + name: "Arabic", + }, + ], + }, + { + name: "Réunion", + code: "RE", + icon: "virtual-host", + icon_status: IconStatus.Up, + capital: "Saint-Denis", + continent: { + name: "Africa", + }, + currency: "EUR", + languages: [ + { + name: "French", + }, + ], + }, + { + name: "Romania", + code: "RO", + icon: "virtual-host", + icon_status: IconStatus.Up, + capital: "Bucharest", + continent: { + name: "Europe", + }, + currency: "RON", + languages: [ + { + name: "Romanian", + }, + ], + }, + { + name: "Serbia", + code: "RS", + icon: "virtual-host", + icon_status: IconStatus.Up, + capital: "Belgrade", + continent: { + name: "Europe", + }, + currency: "RSD", + languages: [ + { + name: "Serbian", + }, + ], + }, + { + name: "Russia", + code: "RU", + icon: "virtual-host", + icon_status: IconStatus.Up, + capital: "Moscow", + continent: { + name: "Europe", + }, + currency: "RUB", + languages: [ + { + name: "Russian", + }, + ], + }, + { + name: "Rwanda", + code: "RW", + icon: "virtual-host", + icon_status: IconStatus.Up, + capital: "Kigali", + continent: { + name: "Africa", + }, + currency: "RWF", + languages: [ + { + name: "Rwandi", + }, + { + name: "English", + }, + { + name: "French", + }, + ], + }, + { + name: "Saudi Arabia", + code: "SA", + icon: "virtual-host", + icon_status: IconStatus.Up, + capital: "Riyadh", + continent: { + name: "Asia", + }, + currency: "SAR", + languages: [ + { + name: "Arabic", + }, + ], + }, + { + name: "Solomon Islands", + code: "SB", + icon: "virtual-host", + icon_status: IconStatus.Up, + capital: "Honiara", + continent: { + name: "Oceania", + }, + currency: "SBD", + languages: [ + { + name: "English", + }, + ], + }, + { + name: "Seychelles", + code: "SC", + icon: "virtual-host", + icon_status: IconStatus.Up, + capital: "Victoria", + continent: { + name: "Africa", + }, + currency: "SCR", + languages: [ + { + name: "French", + }, + { + name: "English", + }, + ], + }, + { + name: "Sudan", + code: "SD", + icon: "virtual-host", + icon_status: IconStatus.Up, + capital: "Khartoum", + continent: { + name: "Africa", + }, + currency: "SDG", + languages: [ + { + name: "Arabic", + }, + { + name: "English", + }, + ], + }, + { + name: "Sweden", + code: "SE", + icon: "virtual-host", + icon_status: IconStatus.Up, + capital: "Stockholm", + continent: { + name: "Europe", + }, + currency: "SEK", + languages: [ + { + name: "Swedish", + }, + ], + }, + { + name: "Singapore", + code: "SG", + icon: "virtual-host", + icon_status: IconStatus.Up, + capital: "Singapore", + continent: { + name: "Asia", + }, + currency: "SGD", + languages: [ + { + name: "English", + }, + { + name: "Malay", + }, + { + name: "Tamil", + }, + { + name: "Chinese", + }, + ], + }, + { + name: "Saint Helena", + code: "SH", + icon: "virtual-host", + icon_status: IconStatus.Up, + capital: "Jamestown", + continent: { + name: "Africa", + }, + currency: "SHP", + languages: [ + { + name: "English", + }, + ], + }, + { + name: "Slovenia", + code: "SI", + icon: "virtual-host", + icon_status: IconStatus.Up, + capital: "Ljubljana", + continent: { + name: "Europe", + }, + currency: "EUR", + languages: [ + { + name: "Slovenian", + }, + ], + }, + { + name: "Svalbard and Jan Mayen", + code: "SJ", + icon: "virtual-host", + icon_status: IconStatus.Up, + capital: "Longyearbyen", + continent: { + name: "Europe", + }, + currency: "NOK", + languages: [ + { + name: "Norwegian", + }, + ], + }, + { + name: "Slovakia", + code: "SK", + icon: "virtual-host", + icon_status: IconStatus.Up, + capital: "Bratislava", + continent: { + name: "Europe", + }, + currency: "EUR", + languages: [ + { + name: "Slovak", + }, + ], + }, + { + name: "Sierra Leone", + code: "SL", + icon: "virtual-host", + icon_status: IconStatus.Up, + capital: "Freetown", + continent: { + name: "Africa", + }, + currency: "SLL", + languages: [ + { + name: "English", + }, + ], + }, + { + name: "San Marino", + code: "SM", + icon: "virtual-host", + icon_status: IconStatus.Up, + capital: "City of San Marino", + continent: { + name: "Europe", + }, + currency: "EUR", + languages: [ + { + name: "Italian", + }, + ], + }, + { + name: "Senegal", + code: "SN", + icon: "virtual-host", + icon_status: IconStatus.Up, + capital: "Dakar", + continent: { + name: "Africa", + }, + currency: "XOF", + languages: [ + { + name: "French", + }, + ], + }, + { + name: "Somalia", + code: "SO", + icon: "virtual-host", + icon_status: IconStatus.Up, + capital: "Mogadishu", + continent: { + name: "Africa", + }, + currency: "SOS", + languages: [ + { + name: "Somalia", + }, + { + name: "Arabic", + }, + ], + }, + { + name: "Suriname", + code: "SR", + icon: "virtual-host", + icon_status: IconStatus.Up, + capital: "Paramaribo", + continent: { + name: "South America", + }, + currency: "SRD", + languages: [ + { + name: "Dutch", + }, + ], + }, + { + name: "South Sudan", + code: "SS", + icon: "virtual-host", + icon_status: IconStatus.Up, + capital: "Juba", + continent: { + name: "Africa", + }, + currency: "SSP", + languages: [ + { + name: "English", + }, + ], + }, + { + name: "São Tomé and Príncipe", + code: "ST", + icon: "virtual-host", + icon_status: IconStatus.Up, + capital: "São Tomé", + continent: { + name: "Africa", + }, + currency: "STN", + languages: [ + { + name: "Portuguese", + }, + ], + }, + { + name: "El Salvador", + code: "SV", + icon: "virtual-host", + icon_status: IconStatus.Up, + capital: "San Salvador", + continent: { + name: "North America", + }, + currency: "SVC,USD", + languages: [ + { + name: "Spanish", + }, + ], + }, + { + name: "Sint Maarten", + code: "SX", + icon: "virtual-host", + icon_status: IconStatus.Up, + capital: "Philipsburg", + continent: { + name: "North America", + }, + currency: "ANG", + languages: [ + { + name: "Dutch", + }, + { + name: "English", + }, + ], + }, + { + name: "Syria", + code: "SY", + icon: "virtual-host", + icon_status: IconStatus.Up, + capital: "Damascus", + continent: { + name: "Asia", + }, + currency: "SYP", + languages: [ + { + name: "Arabic", + }, + ], + }, + { + name: "Swaziland", + code: "SZ", + icon: "virtual-host", + icon_status: IconStatus.Up, + capital: "Lobamba", + continent: { + name: "Africa", + }, + currency: "SZL", + languages: [ + { + name: "English", + }, + { + name: "Swati", + }, + ], + }, + { + name: "Turks and Caicos Islands", + code: "TC", + icon: "virtual-host", + icon_status: IconStatus.Up, + capital: "Cockburn Town", + continent: { + name: "North America", + }, + currency: "USD", + languages: [ + { + name: "English", + }, + ], + }, + { + name: "Chad", + code: "TD", + icon: "virtual-host", + icon_status: IconStatus.Up, + capital: "N'Djamena", + continent: { + name: "Africa", + }, + currency: "XAF", + languages: [ + { + name: "French", + }, + { + name: "Arabic", + }, + ], + }, + { + name: "French Southern Territories", + code: "TF", + icon: "virtual-host", + icon_status: IconStatus.Up, + capital: "Port-aux-Français", + continent: { + name: "Antarctica", + }, + currency: "EUR", + languages: [ + { + name: "French", + }, + ], + }, + { + name: "Togo", + code: "TG", + icon: "virtual-host", + icon_status: IconStatus.Up, + capital: "Lomé", + continent: { + name: "Africa", + }, + currency: "XOF", + languages: [ + { + name: "French", + }, + ], + }, + { + name: "Thailand", + code: "TH", + icon: "virtual-host", + icon_status: IconStatus.Up, + capital: "Bangkok", + continent: { + name: "Asia", + }, + currency: "THB", + languages: [ + { + name: "Thai", + }, + ], + }, + { + name: "Tajikistan", + code: "TJ", + icon: "virtual-host", + icon_status: IconStatus.Up, + capital: "Dushanbe", + continent: { + name: "Asia", + }, + currency: "TJS", + languages: [ + { + name: "Tajik", + }, + { + name: "Russian", + }, + ], + }, + { + name: "Tokelau", + code: "TK", + icon: "virtual-host", + icon_status: IconStatus.Up, + capital: "Fakaofo", + continent: { + name: "Oceania", + }, + currency: "NZD", + languages: [ + { + name: "English", + }, + ], + }, + { + name: "East Timor", + code: "TL", + icon: "virtual-host", + icon_status: IconStatus.Up, + capital: "Dili", + continent: { + name: "Oceania", + }, + currency: "USD", + languages: [ + { + name: "Portuguese", + }, + ], + }, + { + name: "Turkmenistan", + code: "TM", + icon: "virtual-host", + icon_status: IconStatus.Up, + capital: "Ashgabat", + continent: { + name: "Asia", + }, + currency: "TMT", + languages: [ + { + name: "Turkmen", + }, + { + name: "Russian", + }, + ], + }, + { + name: "Tunisia", + code: "TN", + icon: "virtual-host", + icon_status: IconStatus.Up, + capital: "Tunis", + continent: { + name: "Africa", + }, + currency: "TND", + languages: [ + { + name: "Arabic", + }, + ], + }, + { + name: "Tonga", + code: "TO", + icon: "virtual-host", + icon_status: IconStatus.Up, + capital: "Nuku'alofa", + continent: { + name: "Oceania", + }, + currency: "TOP", + languages: [ + { + name: "English", + }, + { + name: "Tonga", + }, + ], + }, + { + name: "Turkey", + code: "TR", + icon: "virtual-host", + icon_status: IconStatus.Up, + capital: "Ankara", + continent: { + name: "Asia", + }, + currency: "TRY", + languages: [ + { + name: "Turkish", + }, + ], + }, + { + name: "Trinidad and Tobago", + code: "TT", + icon: "virtual-host", + icon_status: IconStatus.Up, + capital: "Port of Spain", + continent: { + name: "North America", + }, + currency: "TTD", + languages: [ + { + name: "English", + }, + ], + }, + { + name: "Tuvalu", + code: "TV", + icon: "virtual-host", + icon_status: IconStatus.Up, + capital: "Funafuti", + continent: { + name: "Oceania", + }, + currency: "AUD", + languages: [ + { + name: "English", + }, + ], + }, + { + name: "Taiwan", + code: "TW", + icon: "virtual-host", + icon_status: IconStatus.Up, + capital: "Taipei", + continent: { + name: "Asia", + }, + currency: "TWD", + languages: [ + { + name: "Chinese", + }, + ], + }, + { + name: "Tanzania", + code: "TZ", + icon: "virtual-host", + icon_status: IconStatus.Up, + capital: "Dodoma", + continent: { + name: "Africa", + }, + currency: "TZS", + languages: [ + { + name: "Swahili", + }, + { + name: "English", + }, + ], + }, + { + name: "Ukraine", + code: "UA", + icon: "virtual-host", + icon_status: IconStatus.Up, + capital: "Kyiv", + continent: { + name: "Europe", + }, + currency: "UAH", + languages: [ + { + name: "Ukrainian", + }, + ], + }, + { + name: "Uganda", + code: "UG", + icon: "virtual-host", + icon_status: IconStatus.Up, + capital: "Kampala", + continent: { + name: "Africa", + }, + currency: "UGX", + languages: [ + { + name: "English", + }, + { + name: "Swahili", + }, + ], + }, + { + name: "U.S. Minor Outlying Islands", + code: "UM", + icon: "virtual-host", + icon_status: IconStatus.Up, + capital: null, + continent: { + name: "Oceania", + }, + currency: "USD", + languages: [ + { + name: "English", + }, + ], + }, + { + name: "United States", + code: "US", + icon: "virtual-host", + icon_status: IconStatus.Up, + capital: "Washington D.C.", + continent: { + name: "North America", + }, + currency: "USD,USN,USS", + languages: [ + { + name: "English", + }, + ], + }, + { + name: "Uruguay", + code: "UY", + icon: "virtual-host", + icon_status: IconStatus.Up, + capital: "Montevideo", + continent: { + name: "South America", + }, + currency: "UYI,UYU", + languages: [ + { + name: "Spanish", + }, + ], + }, + { + name: "Uzbekistan", + code: "UZ", + icon: "virtual-host", + icon_status: IconStatus.Up, + capital: "Tashkent", + continent: { + name: "Asia", + }, + currency: "UZS", + languages: [ + { + name: "Uzbek", + }, + { + name: "Russian", + }, + ], + }, + { + name: "Vatican City", + code: "VA", + icon: "virtual-host", + icon_status: IconStatus.Up, + capital: "Vatican City", + continent: { + name: "Europe", + }, + currency: "EUR", + languages: [ + { + name: "Italian", + }, + { + name: "Latin", + }, + ], + }, + { + name: "Saint Vincent and the Grenadines", + code: "VC", + icon: "virtual-host", + icon_status: IconStatus.Up, + capital: "Kingstown", + continent: { + name: "North America", + }, + currency: "XCD", + languages: [ + { + name: "English", + }, + ], + }, + { + name: "Venezuela", + code: "VE", + icon: "virtual-host", + icon_status: IconStatus.Up, + capital: "Caracas", + continent: { + name: "South America", + }, + currency: "VES", + languages: [ + { + name: "Spanish", + }, + ], + }, + { + name: "British Virgin Islands", + code: "VG", + icon: "virtual-host", + icon_status: IconStatus.Up, + capital: "Road Town", + continent: { + name: "North America", + }, + currency: "USD", + languages: [ + { + name: "English", + }, + ], + }, + { + name: "U.S. Virgin Islands", + code: "VI", + icon: "virtual-host", + icon_status: IconStatus.Up, + capital: "Charlotte Amalie", + continent: { + name: "North America", + }, + currency: "USD", + languages: [ + { + name: "English", + }, + ], + }, + { + name: "Vietnam", + code: "VN", + icon: "virtual-host", + icon_status: IconStatus.Up, + capital: "Hanoi", + continent: { + name: "Asia", + }, + currency: "VND", + languages: [ + { + name: "Vietnamese", + }, + ], + }, + { + name: "Vanuatu", + code: "VU", + icon: "virtual-host", + icon_status: IconStatus.Up, + capital: "Port Vila", + continent: { + name: "Oceania", + }, + currency: "VUV", + languages: [ + { + name: "Bislama", + }, + { + name: "English", + }, + { + name: "French", + }, + ], + }, + { + name: "Wallis and Futuna", + code: "WF", + icon: "virtual-host", + icon_status: IconStatus.Up, + capital: "Mata-Utu", + continent: { + name: "Oceania", + }, + currency: "XPF", + languages: [ + { + name: "French", + }, + ], + }, + { + name: "Samoa", + code: "WS", + icon: "virtual-host", + icon_status: IconStatus.Up, + capital: "Apia", + continent: { + name: "Oceania", + }, + currency: "WST", + languages: [ + { + name: "Samoan", + }, + { + name: "English", + }, + ], + }, + { + name: "Kosovo", + code: "XK", + icon: "virtual-host", + icon_status: IconStatus.Up, + capital: "Pristina", + continent: { + name: "Europe", + }, + currency: "EUR", + languages: [ + { + name: "Albanian", + }, + { + name: "Serbian", + }, + ], + }, + { + name: "Yemen", + code: "YE", + icon: "virtual-host", + icon_status: IconStatus.Up, + capital: "Sana'a", + continent: { + name: "Asia", + }, + currency: "YER", + languages: [ + { + name: "Arabic", + }, + ], + }, + { + name: "Mayotte", + code: "YT", + icon: "virtual-host", + icon_status: IconStatus.Up, + capital: "Mamoudzou", + continent: { + name: "Africa", + }, + currency: "EUR", + languages: [ + { + name: "French", + }, + ], + }, + { + name: "South Africa", + code: "ZA", + icon: "virtual-host", + icon_status: IconStatus.Up, + capital: "Pretoria", + continent: { + name: "Africa", + }, + currency: "ZAR", + languages: [ + { + name: "Afrikaans", + }, + { + name: "English", + }, + { + name: "South Ndebele", + }, + { + name: "Southern Sotho", + }, + { + name: "Swati", + }, + { + name: "Tswana", + }, + { + name: "Tsonga", + }, + { + name: "Venda", + }, + { + name: "Xhosa", + }, + { + name: "Zulu", + }, + ], + }, + { + name: "Zambia", + code: "ZM", + icon: "virtual-host", + icon_status: IconStatus.Up, + capital: "Lusaka", + continent: { + name: "Africa", + }, + currency: "ZMW", + languages: [ + { + name: "English", + }, + ], + }, + { + name: "Zimbabwe", + code: "ZW", + icon: "virtual-host", + icon_status: IconStatus.Up, + capital: "Harare", + continent: { + name: "Africa", + }, + currency: "USD,ZAR,BWP,GBP,AUD,CNY,INR,JPY", + languages: [ + { + name: "English", + }, + { + name: "Shona", + }, + { + name: "North Ndebele", + }, + ], + }, + ], + }, +}; +\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\`, + "widget-types/drilldown/drilldown-widget/drilldown-widget-example.component.ts": \\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\`// © 2022 SolarWinds Worldwide, LLC. All rights reserved. +// +// Permission is hereby granted, free of charge, to any person obtaining a copy +// of this software and associated documentation files (the "Software"), to +// deal in the Software without restriction, including without limitation the +// rights to use, copy, modify, merge, publish, distribute, sublicense, and/or +// sell copies of the Software, and to permit persons to whom the Software is +// furnished to do so, subject to the following conditions: +// +// The above copyright notice and this permission notice shall be included in +// all copies or substantial portions of the Software. +// +// THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR +// IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, +// FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE +// AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER +// LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, +// OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN +// THE SOFTWARE. + +import { HttpClient } from "@angular/common/http"; +import { + ChangeDetectorRef, + Component, + Injectable, + OnDestroy, + OnInit, +} from "@angular/core"; +import { GridsterConfig, GridsterItem } from "angular-gridster2"; +import { Apollo, gql } from "apollo-angular"; +import groupBy from "lodash/groupBy"; +import { BehaviorSubject, Observable, of } from "rxjs"; +import { catchError, delay, filter, map } from "rxjs/operators"; + +import { + DataSourceFeatures, + IconStatus, + IDataField, + IDataSource, + IDataSourceFeatures, + IDataSourceFeaturesConfiguration, + INovaFilters, + LoggerService, + ServerSideDataSource, + IFilters, +} from "@nova-ui/bits"; +import { + DATA_SOURCE, + DEFAULT_PIZZAGNA_ROOT, + IDashboard, + IDrilldownComponentsConfiguration, + IListWidgetConfiguration, + IProviderConfiguration, + IWidget, + IWidgets, + ListGroupItemComponent, + ListLeafItemComponent, + NOVA_DRILLDOWN_DATASOURCE_ADAPTER, + PizzagnaLayer, + ProviderRegistryService, + WellKnownPathKey, + WellKnownProviders, + WidgetTypesService, +} from "@nova-ui/dashboards"; + +import { DrilldownDataSource } from "./mock-data-source"; + +/** + * A simple KPI data source to retrieve the average rating of Harry Potter and the Sorcerer's Stone (book) via googleapis + */ +@Injectable() +export class DrilldownDataSourceRealApi + extends ServerSideDataSource + implements OnDestroy, IDataSource +{ + // This is the ID we'll use to identify the provider + public static providerId = "DrilldownDataSourceRealApi"; + + // Use this subject to communicate the data source's busy state + public busy = new BehaviorSubject(false); + public dataFields: Partial[] = [ + { id: "regionName", label: "Region name" }, + { id: "subregionName", label: "Subregion name" }, + ]; + + public features: IDataSourceFeaturesConfiguration; + private supportedFeatures: IDataSourceFeatures = { + search: { enabled: true }, + }; + + private drillState: string[] = []; + private groupBy: string[]; + + constructor( + private logger: LoggerService, + private http: HttpClient, + private apollo: Apollo + ) { + super(); + this.features = new DataSourceFeatures(this.supportedFeatures); + // TODO: remove Partial in vNext after marking dataType field as optional - NUI-5838 + ( + this.dataFieldsConfig.dataFields$ as BehaviorSubject< + Partial[] + > + ).next(this.dataFields); + } + + private groupedDataHistory: Array> = []; + + // In this example, getFilteredData is invoked every 10 minutes (Take a look at the refresher + // provider definition in the widget configuration below to see how the interval is set) + public async getFilteredData(data: IFilters): Promise { + return of(data) + .pipe( + filter(() => !!this.drillState), + map((countries) => { + const lastHistory = () => getLast(this.groupedDataHistory); + + if (!this.drillState.length && !this.groupBy.length) { + return countries; + } + + // adding "ROOT" as a root level for drilling + const fullDrillState = ["ROOT", ...this.drillState]; + const activeDrillLvl = fullDrillState.length; + const historyLvl = this.groupedDataHistory.length; + + // checking how many lvls we have to group for drilling, in case some are missed + const drillLvlDiff = activeDrillLvl - historyLvl; + + if (!drillLvlDiff) { + return lastHistory() || countries; + } + + const drillToGroup = fullDrillState.slice( + fullDrillState.length - drillLvlDiff + ); + + for (const drill of drillToGroup) { + const drillIdx = fullDrillState.findIndex( + (v) => v === drill + ); + const group = this.groupBy[drillIdx]; + + if (group) { + const dataToGroup = lastHistory() + ? lastHistory()[drill] + : countries; + const lastGroupedValue = groupBy( + dataToGroup, + group + ); + + this.groupedDataHistory.push(lastGroupedValue); + } + } + + // take last if we have all data grouped + if (this.groupBy.length === this.drillState.length) { + return lastHistory()[getLast(this.drillState)]; + } + + // get groping and transform to raw data format + return this.getGroupsWidgetData(lastHistory()); + }) + ) + .toPromise(); + } + + public ngOnDestroy(): void { + this.outputsSubject.complete(); + } + + // This method is expected to return all data needed for repeat/paginator/filterGroups in order to work. + // In case of custom filtering participants feel free to extend INovaFilteringOutputs. + protected getBackendData(filters: INovaFilters): Observable { + const mainRequest = this.apollo.watchQuery<{ countries: any }>({ + query: this.generateQuery(filters), + }); + + return mainRequest.valueChanges.pipe( + // mock delay + delay(300), + // data mapping, !DS specific! + map((res) => res.data.countries), + // adds mock icons to be displayed on leaf nodes !DS specific! + map((res: any[]) => + res.map((v) => ({ + ...v, + icon: "virtual-host", + icon_status: IconStatus.Up, + subregionName: + v.subregion?.name || "No Subregion Specified", + regionName: + v.subregion?.region?.name || "No Region Specified", + })) + ), + catchError((e) => { + this.logger.error(e); + return of({} as any); + }) + ); + } + + private generateQuery(filters: INovaFilters) { + const { search } = filters; + const searchValue = search?.value ? \\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\`^[\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\${search.value}]*\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\` : ""; + + const queryString = \\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\` + query { + countries(filter: {name: {regex: "\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\${searchValue}"} }) { + name + native + capital + languages { + name + } + currencies + subdivisions { + name + } + } + } + \\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\`; + + return gql\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\` + \\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\${queryString} + \\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\`; + } + + // Overrides default ServerSideDataSource.beforeApplyFilters implementation + // to save some filters that are used internally + // -- !DS specific + protected beforeApplyFilters(filters: INovaFilters): void { + this.busy.next(true); + + this.drillState = filters.drillstate?.value; + this.groupBy = filters.group?.value; + + if (this.isHome()) { + this.groupedDataHistory.length = 0; + } + + if (this.isBack()) { + this.groupedDataHistory.length = this.groupedDataHistory.length - 1; + } + + if (this.getFilters()["search"] && this.filterChanged("search")) { + this.groupedDataHistory.length = 0; + } + } + + private getGroupsWidgetData(groupByObj: Record) { + return Object.keys(groupByObj).map((property) => ({ + id: property, + label: property, + // statuses that will be displayed on group item + statuses: [ + { key: "virtual-host", value: groupByObj[property].length }, + { + key: "acknowledge", + value: this.getPopulation(groupByObj[property]), + }, + ], + })); + } + + private isHome(): boolean { + return this.drillState?.length === 0; + } + + private isBack(): boolean { + return ( + this.groupedDataHistory?.length > this.drillState?.length && + !this.isHome() + ); + } + + /** + * Gets population for the country(ies) + */ + private getPopulation(countries: any[]) { + const totalPopulation = countries.reduce( + (acc, next) => (acc += next.population), + 0 + ); + return \\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\`\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\${totalPopulation * Math.pow(10, -3)} k\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\`; + } +} + +@Component({ + selector: "drilldown-widget-example", + templateUrl: "./drilldown-widget-example.component.html", + styleUrls: ["./drilldown-widget-example.component.less"], + standalone: false, +}) +export class DrilldownWidgetExampleComponent implements OnInit { + // This variable will hold all the data needed to define the layout and behavior of the widgets. + // Pass this to the dashboard component's dashboard input in the template. + public dashboard: IDashboard | undefined; + + // Angular gridster requires a configuration object even if it's empty. + // Pass this to the dashboard component's gridsterConfig input in the template. + public gridsterConfig: GridsterConfig = {}; + + // Boolean passed as an input to the dashboard. When true, widgets can be moved, resized, removed, or edited + public editMode: boolean = false; + + constructor( + // WidgetTypesService provides the widget's necessary structure information + private widgetTypesService: WidgetTypesService, + private providerRegistry: ProviderRegistryService, + private changeDetectorRef: ChangeDetectorRef + ) {} + + public ngOnInit(): void { + // Registering the data source for injection into the KPI tile. + // Note: Each tile of a KPI widget is assigned its own instance of the data source + this.providerRegistry.setProviders({ + [DrilldownDataSource.providerId]: { + provide: DATA_SOURCE, + useClass: DrilldownDataSource, + // Any dependencies that need to be injected into the provider must be listed here + deps: [HttpClient], + }, + [DrilldownDataSourceRealApi.providerId]: { + provide: DATA_SOURCE, + useClass: DrilldownDataSourceRealApi, + // Any dependencies that need to be injected into the provider must be listed here + deps: [LoggerService, HttpClient, Apollo], + }, + }); + + this.initializeDashboard(); + const widgetTemplate = this.widgetTypesService.getWidgetType( + "drilldown", + 1 + ); + this.widgetTypesService.setNode( + widgetTemplate, + "configurator", + WellKnownPathKey.DataSourceProviders, + [ + DrilldownDataSourceRealApi.providerId, + DrilldownDataSource.providerId, + ] + ); + } + + public initializeDashboard(): void { + // We're using a static configuration object for this example, but this is where + // the widget's configuration could potentially be populated from a database + const drilldownWidget = widgetConfig; + const widgets: IWidgets = { + // Complete the widget with information coming from its type definition + [drilldownWidget.id]: + this.widgetTypesService.mergeWithWidgetType(drilldownWidget), + }; + + // Setting the widget dimensions and position (this is for gridster) + const positions: Record = { + [drilldownWidget.id]: { + cols: 10, + rows: 10, + y: 0, + x: 0, + }, + }; + + // Finally, assigning the variables we created above to the dashboard + this.dashboard = { positions, widgets }; + } + + public reInitializeDashboard(): void { + // destroys the components and their providers so the dashboard can re init data + this.dashboard = undefined; + this.changeDetectorRef.detectChanges(); + + const adapterProperties = + widgetConfig.pizzagna[PizzagnaLayer.Configuration].listWidget + .providers?.adapter?.properties; + + if (adapterProperties) { + adapterProperties.drillstate = []; + } + + this.initializeDashboard(); + } +} + +const widgetConfig: IWidget = { + id: "drilldown", + type: "drilldown", + pizzagna: { + [PizzagnaLayer.Configuration]: { + [DEFAULT_PIZZAGNA_ROOT]: { + providers: { + [WellKnownProviders.DataSource]: { + // Setting the data source providerId for the tile with id "kpi1" + providerId: DrilldownDataSourceRealApi.providerId, + properties: {}, + } as IProviderConfiguration, + }, + }, + header: { + properties: { + title: "Drilldown Widget", + subtitle: "Search is case sensitive!", + }, + }, + listWidget: { + providers: { + [WellKnownProviders.Adapter]: { + providerId: NOVA_DRILLDOWN_DATASOURCE_ADAPTER, + properties: { + // widget + navigationBarId: "navigationBar", + componentId: "listWidget", + dataPath: "data", + + // adapter props + drillstate: [], + groupBy: ["regionName", "subregionName"], + groups: ["regionName", "subregionName"], + + // components + componentsConfig: { + group: { + componentType: + ListGroupItemComponent.lateLoadKey, + properties: { + dataFieldIds: { + id: "id", + label: "label", + statuses: "statuses", + }, + }, + itemProperties: { + canNavigate: true, + }, + }, + leaf: { + componentType: + ListLeafItemComponent.lateLoadKey, + properties: { + dataFieldIds: { + icon: "icon", + status: "icon_status", + detailedUrl: "capital", + label: "name", + }, + }, + itemProperties: { + canNavigate: false, + }, + }, + } as IDrilldownComponentsConfiguration, + }, + }, + }, + properties: { + configuration: { + // FORMAT: + // componentType: ListLeafItemComponent.lateLoadKey, + // properties: { + // dataFieldIds: { + // icon: "", + // status: "code", + // detailedUrl: "capital", + // label: "name", + // }, + // }, + // + } as IListWidgetConfiguration, + }, + }, + }, + }, +}; + +const getLast = (arr: any[]) => arr[arr.length - 1]; +\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\`, + "widget-types/drilldown/drilldown-widget/mock-data-source.ts": \\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\`// © 2022 SolarWinds Worldwide, LLC. All rights reserved. +// +// Permission is hereby granted, free of charge, to any person obtaining a copy +// of this software and associated documentation files (the "Software"), to +// deal in the Software without restriction, including without limitation the +// rights to use, copy, modify, merge, publish, distribute, sublicense, and/or +// sell copies of the Software, and to permit persons to whom the Software is +// furnished to do so, subject to the following conditions: +// +// The above copyright notice and this permission notice shall be included in +// all copies or substantial portions of the Software. +// +// THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR +// IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, +// FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE +// AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER +// LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, +// OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN +// THE SOFTWARE. + +import { Injectable, OnDestroy } from "@angular/core"; +import groupBy from "lodash/groupBy"; +import { BehaviorSubject, Observable, of, Subject } from "rxjs"; +import { + catchError, + delay, + finalize, + map, + // eslint-disable-next-line import/no-deprecated + switchMap, + tap, +} from "rxjs/operators"; + +import { + DataSourceService, + IDataField, + IDataSource, + IFilters, + INovaFilters, +} from "@nova-ui/bits"; + +import { GRAPH_DATA_MOCK } from "./data-mock"; + +/** + * A simple KPI data source to retrieve the average rating of Harry Potter and the Sorcerer's Stone (book) via googleapis + */ +@Injectable() +export class DrilldownDataSource + extends DataSourceService + implements IDataSource, OnDestroy +{ + // This is the ID we'll use to identify the provider + public static providerId = "DrilldownDataSource"; + + // Use this subject to communicate the data source's busy state + public busy = new BehaviorSubject(false); + public dataFields: Partial[] = [ + { id: "continent.name", label: "Continent name" }, + { id: "currency", label: "Currency" }, + ]; + + private drillState: string[] = []; + private groupBy: string[]; + private cache: any; + private applyFilters$ = new Subject(); + + constructor() { + super(); + + // TODO: remove Partial in vNext after marking dataType field as optional - NUI-5838 + ( + this.dataFieldsConfig.dataFields$ as BehaviorSubject< + Partial[] + > + ).next(this.dataFields); + + this.applyFilters$ + // eslint-disable-next-line import/no-deprecated + .pipe(switchMap((filters) => this.getData(filters))) + .subscribe(async (res) => { + this.outputsSubject.next(await this.getFilteredData(res)); + }); + } + + private groupedDataHistory: any[] = []; + + // In this example, getFilteredData is invoked every 10 minutes (Take a look at the refresher + // provider definition in the widget configuration below to see how the interval is set) + public async getFilteredData(data: any): Promise { + return of(data) + .pipe( + map((countries) => { + const widgetInput = this.getOutput(countries); + + if (this.isDrillDown()) { + const activeDrillLvl = this.drillState.length; + const group = this.groupBy[activeDrillLvl]; + const [lastGroupedValue, groupedData] = + this.getTransformedDataForGroup(widgetInput, group); + + this.groupedDataHistory.push(lastGroupedValue); + + return groupedData; + } + + return widgetInput; + }) + ) + .toPromise(); + } + + public ngOnDestroy(): void { + this.outputsSubject.complete(); + } + + // redefine parent method + public async applyFilters(): Promise { + this.applyFilters$.next(this.getFilters()); + } + + private getData(filters: INovaFilters): Observable { + this.drillState = filters.drillstate?.value; + this.groupBy = filters.group?.value; + + this.busy.next(true); + + return of(this.cache || GRAPH_DATA_MOCK).pipe( + delay(1000), + tap((data) => (this.cache = data)), + map((data) => data.data.countries), + catchError((e) => of([])), + finalize(() => this.busy.next(false)) + ); + } + + private getTransformedDataForGroup(data: any, groupName: string) { + const groupedDict = groupBy(data, groupName); + const dataArr = Object.keys(groupedDict).map((property) => ({ + id: property, + label: property, + // TODO: apply groups mapping here + statuses: [ + { key: "state_ok", value: groupedDict[property].length }, + { + key: "status_unreachable", + value: generateNumberUpTo(100000), + }, + { key: "status_warning", value: generateNumberUpTo(10000) }, + { key: "status_unknown", value: generateNumberUpTo(1000) }, + ], + })); + + return [groupedDict, dataArr]; + } + + private isHome(): boolean { + return !this.drillState || this.drillState.length === 0; + } + + private isBack(): boolean { + return ( + this.groupedDataHistory.length > this.drillState?.length && + !this.isHome() + ); + } + + private isDrillDown(): boolean { + return this.drillState?.length !== this.groupBy?.length; + } + + private getOutput(data: any) { + if (this.isHome()) { + this.groupedDataHistory.length = 0; + } + + if (this.isBack()) { + this.groupedDataHistory.length = this.groupedDataHistory.length - 1; + } + + const lastHistoryValue = getLast(this.groupedDataHistory); + + if (!lastHistoryValue) { + return data; + } + + return lastHistoryValue[getLast(this.drillState)] || lastHistoryValue; + } +} + +const getLast = (arr: any[]) => arr[arr.length - 1]; +const generateNumberUpTo = (upperLimit: number): number => + Math.floor(Math.random() * upperLimit + 1); +\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\`, + "widget-types/drilldown/drilldown-widget-docs.component.ts": \\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\`// © 2022 SolarWinds Worldwide, LLC. All rights reserved. +// +// Permission is hereby granted, free of charge, to any person obtaining a copy +// of this software and associated documentation files (the "Software"), to +// deal in the Software without restriction, including without limitation the +// rights to use, copy, modify, merge, publish, distribute, sublicense, and/or +// sell copies of the Software, and to permit persons to whom the Software is +// furnished to do so, subject to the following conditions: +// +// The above copyright notice and this permission notice shall be included in +// all copies or substantial portions of the Software. +// +// THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR +// IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, +// FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE +// AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER +// LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, +// OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN +// THE SOFTWARE. + +import { Component, OnInit } from "@angular/core"; + +import { mapContentFile } from "../../../../demo-files-factory"; + +@Component({ + selector: "nui-drilldown-docs", + templateUrl: "./drilldown-widget-docs.component.html", + standalone: false, +}) +export class DrilldownDocsComponent implements OnInit { + public widgetFileText = ""; + public configuratorFileText = ""; + + public predefinedGroping = \\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\` +listWidget: { + providers: { + [WellKnownProviders.Adapter]: { + providerId: NOVA_DRILLDOWN_DATASOURCE_ADAPTER, + properties: { + ... + // adapter props + drillstate: [], + groupBy: ["regionName", "subregionName"], + groups: ["regionName", "subregionName"], + ... + }, + }, + }, +}, +\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\`; + public featuredDeclaredText = \\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\` + private supportedFeatures: IDataSourceFeatures = { + search: { enabled: true }, + };\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\`; + public featuresUsedText = \\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\` + this.features = new DataSourceFeatures(this.supportedFeatures); + \\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\`; + + public async ngOnInit(): Promise { + this.widgetFileText = await import( + "./../../../../../../src/lib/widget-types/drilldown/drilldown-widget" + ).then(mapContentFile); + this.configuratorFileText = await import( + "./../../../../../../src/lib/widget-types/drilldown/drilldown-configurator" + ).then(mapContentFile); + } +} +\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\`, + "widget-types/drilldown/drilldown-widget-docs.module.ts": \\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\`// © 2022 SolarWinds Worldwide, LLC. All rights reserved. +// +// Permission is hereby granted, free of charge, to any person obtaining a copy +// of this software and associated documentation files (the "Software"), to +// deal in the Software without restriction, including without limitation the +// rights to use, copy, modify, merge, publish, distribute, sublicense, and/or +// sell copies of the Software, and to permit persons to whom the Software is +// furnished to do so, subject to the following conditions: +// +// The above copyright notice and this permission notice shall be included in +// all copies or substantial portions of the Software. +// +// THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR +// IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, +// FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE +// AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER +// LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, +// OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN +// THE SOFTWARE. + +import { NgModule } from "@angular/core"; +import { RouterModule, Routes } from "@angular/router"; + +// eslint-disable-next-line max-len +import { + NuiButtonModule, + NuiDocsModule, + NuiMessageModule, + NuiSwitchModule, + DEMO_PATH_TOKEN, +} from "@nova-ui/bits"; +import { NuiDashboardsModule } from "@nova-ui/dashboards"; + +import { DrilldownMultiRequestWidgetExampleComponent } from "./drilldown-multi-request-widget/drilldown-multi-request-widget-example.component"; +import { DrilldownWidgetExampleComponent } from "./drilldown-widget/drilldown-widget-example.component"; +import { DrilldownDocsComponent } from "./drilldown-widget-docs.component"; +import { getDemoFiles } from "../../../../demo-files-factory"; + +const routes: Routes = [ + { + path: "", + component: DrilldownDocsComponent, + data: { + srlc: { + hideIndicator: true, + }, + }, + }, + { + path: "example", + component: DrilldownWidgetExampleComponent, + data: { + srlc: { + hideIndicator: true, + }, + }, + }, + { + path: "multiple-requests", + component: DrilldownMultiRequestWidgetExampleComponent, + data: { + srlc: { + hideIndicator: true, + }, + }, + }, +]; + +@NgModule({ + imports: [ + RouterModule.forChild(routes), + NuiButtonModule, + NuiDocsModule, + NuiMessageModule, + NuiDashboardsModule, + NuiSwitchModule, + ], + declarations: [ + DrilldownDocsComponent, + DrilldownWidgetExampleComponent, + DrilldownMultiRequestWidgetExampleComponent, + ], + providers: [ + { + provide: DEMO_PATH_TOKEN, + useValue: getDemoFiles("drilldown"), + }, + ], +}) +export default class DrilldownDocsModule {} +\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\`, + "widget-types/embedded-content/embedded-content-docs.component.ts": \\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\`// © 2022 SolarWinds Worldwide, LLC. All rights reserved. +// +// Permission is hereby granted, free of charge, to any person obtaining a copy +// of this software and associated documentation files (the "Software"), to +// deal in the Software without restriction, including without limitation the +// rights to use, copy, modify, merge, publish, distribute, sublicense, and/or +// sell copies of the Software, and to permit persons to whom the Software is +// furnished to do so, subject to the following conditions: +// +// The above copyright notice and this permission notice shall be included in +// all copies or substantial portions of the Software. +// +// THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR +// IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, +// FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE +// AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER +// LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, +// OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN +// THE SOFTWARE. + +import { Component, OnInit } from "@angular/core"; + +import { mapContentFile } from "../../../../demo-files-factory"; + +@Component({ + selector: "nui-embedded-content-docs", + templateUrl: "./embedded-content-docs.component.html", + standalone: false, +}) +export class EmbeddedContentDocsComponent implements OnInit { + public embeddedContentWidgetFileText = ""; + public embeddedContentConfiguratorFileText = ""; + + public async ngOnInit(): Promise { + this.embeddedContentWidgetFileText = await import( + "./../../../../../../src/lib/widget-types/embedded-content/embedded-content-widget" + ).then(mapContentFile); + this.embeddedContentWidgetFileText = await import( + "./../../../../../../src/lib/widget-types/embedded-content/embedded-content-configurator" + ).then(mapContentFile); + } +} +\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\`, + "widget-types/embedded-content/embedded-content-docs.module.ts": \\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\`// © 2022 SolarWinds Worldwide, LLC. All rights reserved. +// +// Permission is hereby granted, free of charge, to any person obtaining a copy +// of this software and associated documentation files (the "Software"), to +// deal in the Software without restriction, including without limitation the +// rights to use, copy, modify, merge, publish, distribute, sublicense, and/or +// sell copies of the Software, and to permit persons to whom the Software is +// furnished to do so, subject to the following conditions: +// +// The above copyright notice and this permission notice shall be included in +// all copies or substantial portions of the Software. +// +// THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR +// IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, +// FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE +// AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER +// LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, +// OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN +// THE SOFTWARE. + +import { NgModule } from "@angular/core"; +import { RouterModule, Routes } from "@angular/router"; + +// eslint-disable-next-line max-len +import { + NuiButtonModule, + NuiDocsModule, + NuiMessageModule, + NuiSwitchModule, + DEMO_PATH_TOKEN, +} from "@nova-ui/bits"; +import { NuiDashboardsModule } from "@nova-ui/dashboards"; + +import { EmbeddedContentDocsComponent } from "./embedded-content-docs.component"; +import { EmbeddedContentWidgetExampleComponent } from "./embedded-content-widget-example/embedded-content-widget-example.component"; +import { getDemoFiles } from "../../../../demo-files-factory"; + +const routes: Routes = [ + { + path: "", + component: EmbeddedContentDocsComponent, + data: { + srlc: { + hideIndicator: true, + }, + }, + }, + { + path: "example", + component: EmbeddedContentWidgetExampleComponent, + data: { + srlc: { + hideIndicator: true, + }, + }, + }, +]; + +@NgModule({ + imports: [ + RouterModule.forChild(routes), + NuiButtonModule, + NuiDocsModule, + NuiMessageModule, + NuiDashboardsModule, + NuiSwitchModule, + ], + declarations: [ + EmbeddedContentDocsComponent, + EmbeddedContentWidgetExampleComponent, + ], + providers: [ + { + provide: DEMO_PATH_TOKEN, + useValue: getDemoFiles("embedded-content"), + }, + ], +}) +export default class EmbeddedContentDocsModule {} +\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\`, + "widget-types/embedded-content/embedded-content-widget-example/embedded-content-widget-example.component.ts": \\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\`// © 2022 SolarWinds Worldwide, LLC. All rights reserved. +// +// Permission is hereby granted, free of charge, to any person obtaining a copy +// of this software and associated documentation files (the "Software"), to +// deal in the Software without restriction, including without limitation the +// rights to use, copy, modify, merge, publish, distribute, sublicense, and/or +// sell copies of the Software, and to permit persons to whom the Software is +// furnished to do so, subject to the following conditions: +// +// The above copyright notice and this permission notice shall be included in +// all copies or substantial portions of the Software. +// +// THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR +// IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, +// FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE +// AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER +// LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, +// OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN +// THE SOFTWARE. + +import { ChangeDetectorRef, Component, OnInit } from "@angular/core"; +import { GridsterConfig, GridsterItem } from "angular-gridster2"; + +import { + ComponentRegistryService, + EmbeddedContentComponent, + EmbeddedContentConfigurationComponent, + EmbeddedContentMode, + IDashboard, + IWidget, + IWidgets, + PizzagnaLayer, + WidgetTypesService, +} from "@nova-ui/dashboards"; + +@Component({ + selector: "embedded-content-widget-example", + templateUrl: "./embedded-content-widget-example.component.html", + styleUrls: ["./embedded-content-widget-example.component.less"], + standalone: false, +}) +export class EmbeddedContentWidgetExampleComponent implements OnInit { + // This variable will hold all the data needed to define the layout and behavior of the widgets. + // Pass this to the dashboard component's dashboard input in the template. + public dashboard: IDashboard | undefined; + + // Angular gridster requires a configuration object even if it's empty. + // Pass this to the dashboard component's gridsterConfig input in the template. + public gridsterConfig: GridsterConfig = {}; + + // Boolean passed as an input to the dashboard. When true, widgets can be moved, resized, removed, or edited + public editMode: boolean = false; + + constructor( + // WidgetTypesService provides the widget's necessary structure information + private widgetTypesService: WidgetTypesService, + + private componentRegistry: ComponentRegistryService, + private changeDetectorRef: ChangeDetectorRef + ) {} + + public ngOnInit(): void { + this.prepareNovaDashboards(); + this.initializeDashboard(); + } + + /** Used for restoring widgets state */ + public reInitializeDashboard(): void { + // destroys the components and their providers so the dashboard can re init data + this.dashboard = undefined; + this.changeDetectorRef.detectChanges(); + + this.initializeDashboard(); + } + + public initializeDashboard(): void { + // We're using a static configuration object for this example, but this is where + // the widget's configuration could potentially be populated from a database + const embeddedContentWidget = widgetConfig; + const widgets: IWidgets = { + // Complete the widget with information coming from its type definition + [embeddedContentWidget.id]: + this.widgetTypesService.mergeWithWidgetType( + embeddedContentWidget + ), + }; + + // Setting the widget dimensions and position (this is for gridster) + const positions: Record = { + [embeddedContentWidget.id]: { + cols: 10, + rows: 10, + y: 0, + x: 0, + }, + }; + + // Finally, assigning the variables we created above to the dashboard + this.dashboard = { positions, widgets }; + } + + private prepareNovaDashboards() { + this.componentRegistry.registerByLateLoadKey(EmbeddedContentComponent); + this.componentRegistry.registerByLateLoadKey( + EmbeddedContentConfigurationComponent + ); + } +} + +const widgetConfig: IWidget = { + id: "embeddedContentWidgetId", + type: "embedded-content", + pizzagna: { + [PizzagnaLayer.Configuration]: { + header: { + properties: { + title: "Embedded Content Widget", + subtitle: "", + }, + }, + mainContent: { + properties: { + sanitized: true, + mode: EmbeddedContentMode.URL, + customEmbeddedContent: "https://www.ventusky.com/", + }, + }, + }, + }, +}; +\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\`, + "widget-types/kpi/kpi-docs.component.ts": \\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\`// © 2022 SolarWinds Worldwide, LLC. All rights reserved. +// +// Permission is hereby granted, free of charge, to any person obtaining a copy +// of this software and associated documentation files (the "Software"), to +// deal in the Software without restriction, including without limitation the +// rights to use, copy, modify, merge, publish, distribute, sublicense, and/or +// sell copies of the Software, and to permit persons to whom the Software is +// furnished to do so, subject to the following conditions: +// +// The above copyright notice and this permission notice shall be included in +// all copies or substantial portions of the Software. +// +// THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR +// IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, +// FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE +// AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER +// LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, +// OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN +// THE SOFTWARE. + +import { Component, OnInit } from "@angular/core"; + +import { mapContentFile } from "../../../../demo-files-factory"; + +@Component({ + selector: "nui-kpi-docs", + templateUrl: "./kpi-docs.component.html", + standalone: false, +}) +export class KpiDocsComponent implements OnInit { + public kpiWidgetFileText = ""; + public kpiConfiguratorFileText = ""; + + public async ngOnInit(): Promise { + this.kpiWidgetFileText = await import( + "./../../../../../../src/lib/widget-types/kpi/kpi-widget" + ).then(mapContentFile); + this.kpiConfiguratorFileText = await import( + "./../../../../../../src/lib/widget-types/kpi/kpi-configurator" + ).then(mapContentFile); + } +} +\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\`, + "widget-types/kpi/kpi-docs.module.ts": \\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\`// © 2022 SolarWinds Worldwide, LLC. All rights reserved. +// +// Permission is hereby granted, free of charge, to any person obtaining a copy +// of this software and associated documentation files (the "Software"), to +// deal in the Software without restriction, including without limitation the +// rights to use, copy, modify, merge, publish, distribute, sublicense, and/or +// sell copies of the Software, and to permit persons to whom the Software is +// furnished to do so, subject to the following conditions: +// +// The above copyright notice and this permission notice shall be included in +// all copies or substantial portions of the Software. +// +// THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR +// IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, +// FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE +// AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER +// LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, +// OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN +// THE SOFTWARE. + +import { NgModule } from "@angular/core"; +import { RouterModule, Routes } from "@angular/router"; + +import { + NuiButtonModule, + NuiDocsModule, + NuiMessageModule, + NuiSwitchModule, + DEMO_PATH_TOKEN, +} from "@nova-ui/bits"; +import { + KpiColorComparatorsRegistryService, + NuiDashboardsModule, +} from "@nova-ui/dashboards"; + +import { KpiDocsComponent } from "./kpi-docs.component"; +import { KpiSyncBrokerExampleComponent } from "./kpi-sync-broker/kpi-sync-broker-example.component"; +import { KpiSyncBrokerDocsComponent } from "./kpi-sync-broker-docs.component"; +import { KpiSyncBrokerForAllTilesExampleComponent } from "./kpi-sync-broker-for-all-tiles/kpi-sync-broker-for-all-tiles-example.component"; +import { KpiWidgetExampleComponent } from "./kpi-widget/kpi-widget-example.component"; +import { KpiWidgetBackgroundColorExampleComponent } from "./kpi-widget-background-color/kpi-widget-background-color-example.component"; +import { KpiWidgetBackgroundColorDocsComponent } from "./kpi-widget-background-color-docs.component"; +import { KpiWidgetInteractiveExampleComponent } from "./kpi-widget-interactive/kpi-widget-interactive-example.component"; +import { getDemoFiles } from "../../../../demo-files-factory"; + +const routes: Routes = [ + { + path: "", + component: KpiDocsComponent, + data: { + srlc: { + hideIndicator: true, + }, + showThemeSwitcher: true, + }, + }, + { + path: "example", + component: KpiWidgetExampleComponent, + data: { + srlc: { + hideIndicator: true, + }, + }, + }, + { + path: "background-color", + component: KpiWidgetBackgroundColorDocsComponent, + data: { + srlc: { + hideIndicator: true, + }, + }, + }, + { + path: "sync-broker", + component: KpiSyncBrokerDocsComponent, + data: { + srlc: { + hideIndicator: true, + }, + }, + }, +]; + +@NgModule({ + imports: [ + RouterModule.forChild(routes), + NuiButtonModule, + NuiDocsModule, + NuiMessageModule, + NuiDashboardsModule, + NuiSwitchModule, + ], + declarations: [ + KpiDocsComponent, + KpiWidgetExampleComponent, + KpiWidgetInteractiveExampleComponent, + KpiWidgetBackgroundColorDocsComponent, + KpiWidgetBackgroundColorExampleComponent, + KpiSyncBrokerDocsComponent, + KpiSyncBrokerExampleComponent, + KpiSyncBrokerForAllTilesExampleComponent, + ], + providers: [ + KpiColorComparatorsRegistryService, + { + provide: DEMO_PATH_TOKEN, + useValue: getDemoFiles("kpi"), + }, + ], +}) +export default class KpiDocsModule { + constructor( + private comparatorsRegistry: KpiColorComparatorsRegistryService + ) { + this.backgroundColorDocsSetup(); + } + + private backgroundColorDocsSetup() { + this.comparatorsRegistry.registerComparators({ + "!=": { + comparatorFn: (actual: any, reference: any) => + // eslint-disable-next-line eqeqeq + actual != reference, + label: "Not equal", + }, + }); + } +} +\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\`, + "widget-types/kpi/kpi-sync-broker/kpi-sync-broker-example.component.ts": \\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\`// © 2022 SolarWinds Worldwide, LLC. All rights reserved. +// +// Permission is hereby granted, free of charge, to any person obtaining a copy +// of this software and associated documentation files (the "Software"), to +// deal in the Software without restriction, including without limitation the +// rights to use, copy, modify, merge, publish, distribute, sublicense, and/or +// sell copies of the Software, and to permit persons to whom the Software is +// furnished to do so, subject to the following conditions: +// +// The above copyright notice and this permission notice shall be included in +// all copies or substantial portions of the Software. +// +// THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR +// IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, +// FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE +// AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER +// LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, +// OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN +// THE SOFTWARE. + +import { HttpClient, HttpErrorResponse } from "@angular/common/http"; +import { + ChangeDetectorRef, + Component, + Injectable, + OnDestroy, + OnInit, +} from "@angular/core"; +import { GridsterConfig, GridsterItem } from "angular-gridster2"; +import keyBy from "lodash/keyBy"; +import { BehaviorSubject, of } from "rxjs"; +import { delay, finalize, take } from "rxjs/operators"; + +import { DataSourceService, IFilteringOutputs } from "@nova-ui/bits"; +import { + DATA_SOURCE, + IDashboard, + IKpiData, + IProviderConfiguration, + IWidget, + KpiComponent, + NOVA_KPI_DATASOURCE_ADAPTER, + NOVA_KPI_SCALE_SYNC_BROKER, + PizzagnaLayer, + ProviderRegistryService, + WellKnownPathKey, + WellKnownProviders, + WidgetTypesService, +} from "@nova-ui/dashboards"; + +/** + * A simple KPI data source to retrieve the average rating of Harry Potter and the Sorcerer's Stone (book) via googleapis + */ +@Injectable() +export class AverageRatingKpiDataSource + extends DataSourceService + implements OnDestroy +{ + public static providerId = "AverageRatingKpiDataSource"; + public busy = new BehaviorSubject(false); + + constructor(private http: HttpClient) { + super(); + } + + public async getFilteredData(): Promise { + this.busy.next(true); + return new Promise((resolve) => { + // *** Make a resource request to an external API (if needed) + this.http + .get("https://www.googleapis.com/books/v1/volumes/5MQFrgEACAAJ") + .pipe(finalize(() => this.busy.next(false))) + .subscribe({ + next: (data: any) => { + resolve({ + result: { + value: data.volumeInfo.averageRating, + }, + }); + }, + error: (error: HttpErrorResponse) => { + resolve({ + result: null, + error: { + type: error.status, + }, + }); + }, + }); + }); + } + + public ngOnDestroy(): void { + this.outputsSubject.complete(); + } +} + +/** + * A simple KPI data source to retrieve the ratings count of Harry Potter and the Sorcerer's Stone (book) via googleapis + */ +@Injectable() +export class RatingsCountKpiDataSource + extends DataSourceService + implements OnDestroy +{ + public static providerId = "RatingsCountKpiDataSource"; + + // Use this subject to communicate the data source's busy state + public busy = new BehaviorSubject(false); + + constructor(private http: HttpClient) { + super(); + } + + public async getFilteredData(): Promise { + this.busy.next(true); + return new Promise((resolve) => { + this.http + .get("https://www.googleapis.com/books/v1/volumes/5MQFrgEACAAJ") + .pipe( + delay(2000), + finalize(() => this.busy.next(false)) + ) + .subscribe({ + next: (data: any) => { + resolve({ + result: { + value: data.volumeInfo.ratingsCount, + }, + }); + }, + error: (error: HttpErrorResponse) => { + resolve({ + result: null, + error: { + type: error.status, + }, + }); + }, + }); + }); + } + + public ngOnDestroy(): void { + this.outputsSubject.complete(); + } +} +/** + * A simple KPI data source to retrieve the ratings count of Harry Potter and the Sorcerer's Stone (book) via googleapis + */ +@Injectable() +export class MockKpiDataSource + extends DataSourceService + implements OnDestroy +{ + public static providerId = "MockKpiDataSource"; + + // Use this subject to communicate the data source's busy state + public busy = new BehaviorSubject(false); + + constructor() { + super(); + } + + public async getFilteredData(): Promise { + this.busy.next(true); + return new Promise((resolve) => { + of(3381342) + .pipe( + delay(5000), + take(1), + finalize(() => this.busy.next(false)) + ) + .subscribe({ + next: (data: any) => { + resolve({ + result: { + value: data, + }, + }); + }, + }); + }); + } + + public ngOnDestroy(): void { + this.outputsSubject.complete(); + } +} + +/** + * A component that instantiates the dashboard + */ +@Component({ + selector: "kpi-sync-broker-example", + templateUrl: "./kpi-sync-broker-example.component.html", + styleUrls: ["./kpi-sync-broker-example.component.less"], + standalone: false, +}) +export class KpiSyncBrokerExampleComponent implements OnInit { + public dashboard: IDashboard | undefined; + public gridsterConfig: GridsterConfig = {}; + public editMode: boolean = false; + + constructor( + private widgetTypesService: WidgetTypesService, + private providerRegistry: ProviderRegistryService, + private changeDetectorRef: ChangeDetectorRef + ) {} + + public ngOnInit(): void { + this.setupDashboard(); + + this.initializeDashboard(); + } + + private setupDashboard() { + const widgetTemplate = this.widgetTypesService.getWidgetType("kpi", 1); + + this.widgetTypesService.setNode( + widgetTemplate, + "configurator", + WellKnownPathKey.DataSourceProviders, + [ + AverageRatingKpiDataSource.providerId, + RatingsCountKpiDataSource.providerId, + MockKpiDataSource.providerId, + ] + ); + + this.providerRegistry.setProviders({ + [AverageRatingKpiDataSource.providerId]: { + provide: DATA_SOURCE, + useClass: AverageRatingKpiDataSource, + deps: [HttpClient], + }, + [RatingsCountKpiDataSource.providerId]: { + provide: DATA_SOURCE, + useClass: RatingsCountKpiDataSource, + deps: [HttpClient], + }, + [MockKpiDataSource.providerId]: { + provide: DATA_SOURCE, + useClass: MockKpiDataSource, + deps: [], + }, + }); + } + + /** Used for restoring widgets state */ + public reInitializeDashboard(): void { + // destroys the components and their providers so the dashboard can re init data + this.dashboard = undefined; + this.changeDetectorRef.detectChanges(); + + this.initializeDashboard(); + } + + private initializeDashboard(): void { + const widgetsWithStructure = widgetsConfig.map((w) => + this.widgetTypesService.mergeWithWidgetType(w) + ); + const widgetsIndex = keyBy(widgetsWithStructure, (w: IWidget) => w.id); + + const positions: Record = { + kpiWidgetId: { + cols: 3, + rows: 6, + y: 0, + x: 0, + }, + kpiWidgetId2: { + cols: 3, + rows: 6, + y: 0, + x: 0, + }, + }; + + this.dashboard = { + positions, + widgets: widgetsIndex, + }; + } +} + +const widgetsConfig: IWidget[] = [ + { + id: "kpiWidgetId", + type: "kpi", + pizzagna: { + [PizzagnaLayer.Configuration]: { + header: { + properties: { + title: "NO Sync Broker", + subtitle: "Values sizes are being not synced", + }, + }, + tiles: { + properties: { + nodes: ["kpi1", "kpi2", "kpi3"], + }, + }, + kpi1: { + id: "kpi1", + componentType: KpiComponent.lateLoadKey, + properties: { + widgetData: { + units: \\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\`out of 5 Stars\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\`, + label: \\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\`Average Rating\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\`, + backgroundColor: "lightpink", + }, + }, + providers: { + [WellKnownProviders.DataSource]: { + providerId: AverageRatingKpiDataSource.providerId, + } as IProviderConfiguration, + [WellKnownProviders.Adapter]: { + providerId: NOVA_KPI_DATASOURCE_ADAPTER, + properties: { + componentId: "kpi1", + propertyPath: "widgetData", + }, + } as IProviderConfiguration, + }, + }, + kpi2: { + id: "kpi2", + componentType: KpiComponent.lateLoadKey, + properties: { + widgetData: { + label: \\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\`Another label which might be a pretty long one\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\`, + units: \\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\`Which comes from somewhere\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\`, + backgroundColor: "skyblue", + }, + }, + providers: { + [WellKnownProviders.DataSource]: { + providerId: RatingsCountKpiDataSource.providerId, + } as IProviderConfiguration, + [WellKnownProviders.Adapter]: { + providerId: NOVA_KPI_DATASOURCE_ADAPTER, + properties: { + componentId: "kpi2", + propertyPath: "widgetData", + }, + } as IProviderConfiguration, + }, + }, + kpi3: { + id: "kpi3", + componentType: KpiComponent.lateLoadKey, + properties: { + widgetData: { + label: \\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\`Random\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\`, + units: \\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\`Data\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\`, + }, + }, + providers: { + [WellKnownProviders.DataSource]: { + providerId: MockKpiDataSource.providerId, + } as IProviderConfiguration, + [WellKnownProviders.Adapter]: { + providerId: NOVA_KPI_DATASOURCE_ADAPTER, + properties: { + componentId: "kpi3", + propertyPath: "widgetData", + }, + } as IProviderConfiguration, + }, + }, + }, + }, + }, + { + id: "kpiWidgetId2", + type: "kpi", + pizzagna: { + [PizzagnaLayer.Configuration]: { + header: { + properties: { + title: "WITH Sync Broker", + subtitle: + "Now the values of label, units, and value are being synced", + }, + }, + tiles: { + properties: { + nodes: ["kpi4", "kpi5", "kpi6"], + }, + providers: { + // This is where and how you set the sync broker provider + kpiScaleSyncBroker: { + providerId: NOVA_KPI_SCALE_SYNC_BROKER, + properties: { + scaleSyncConfig: [ + // You can decide which values to keep in sync. For instance, you can leave only 'label' id in the array below + { id: "value" }, + { id: "label" }, + { id: "units" }, + ], + }, + }, + }, + }, + kpi4: { + id: "kpi4", + componentType: KpiComponent.lateLoadKey, + properties: { + widgetData: { + units: \\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\`out of 5 Stars\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\`, + label: \\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\`Average Rating\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\`, + backgroundColor: "lightpink", + }, + }, + providers: { + [WellKnownProviders.DataSource]: { + providerId: AverageRatingKpiDataSource.providerId, + } as IProviderConfiguration, + [WellKnownProviders.Adapter]: { + providerId: NOVA_KPI_DATASOURCE_ADAPTER, + properties: { + componentId: "kpi4", + propertyPath: "widgetData", + }, + } as IProviderConfiguration, + }, + }, + kpi5: { + id: "kpi5", + componentType: KpiComponent.lateLoadKey, + properties: { + widgetData: { + label: \\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\`Another label which might be a pretty long one\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\`, + units: \\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\`Which comes from somewhere\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\`, + backgroundColor: "skyblue", + }, + }, + providers: { + [WellKnownProviders.DataSource]: { + providerId: RatingsCountKpiDataSource.providerId, + } as IProviderConfiguration, + [WellKnownProviders.Adapter]: { + providerId: NOVA_KPI_DATASOURCE_ADAPTER, + properties: { + componentId: "kpi5", + propertyPath: "widgetData", + }, + } as IProviderConfiguration, + }, + }, + kpi6: { + id: "kpi6", + componentType: KpiComponent.lateLoadKey, + properties: { + widgetData: { + label: \\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\`Random\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\`, + units: \\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\`Data\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\`, + }, + }, + providers: { + [WellKnownProviders.DataSource]: { + providerId: MockKpiDataSource.providerId, + } as IProviderConfiguration, + [WellKnownProviders.Adapter]: { + providerId: NOVA_KPI_DATASOURCE_ADAPTER, + properties: { + componentId: "kpi6", + propertyPath: "widgetData", + }, + } as IProviderConfiguration, + }, + }, + }, + }, + }, +]; +\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\`, + "widget-types/kpi/kpi-sync-broker-docs.component.ts": \\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\`// © 2022 SolarWinds Worldwide, LLC. All rights reserved. +// +// Permission is hereby granted, free of charge, to any person obtaining a copy +// of this software and associated documentation files (the "Software"), to +// deal in the Software without restriction, including without limitation the +// rights to use, copy, modify, merge, publish, distribute, sublicense, and/or +// sell copies of the Software, and to permit persons to whom the Software is +// furnished to do so, subject to the following conditions: +// +// The above copyright notice and this permission notice shall be included in +// all copies or substantial portions of the Software. +// +// THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR +// IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, +// FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE +// AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER +// LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, +// OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN +// THE SOFTWARE. + +import { Component } from "@angular/core"; + +@Component({ + selector: "kpi-sync-broker-docs", + templateUrl: "./kpi-sync-broker-docs.component.html", + standalone: false, +}) +export class KpiSyncBrokerDocsComponent { + public kpiScaleSyncBroker = \\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\` +"tiles": { + "providers": { + kpiScaleSyncBroker: { + providerId: NOVA_KPI_SCALE_SYNC_BROKER, + properties: { + scaleSyncConfig: [ + { id: "value" }, + { id: "label" }, + { id: "units" }, + ], + }, + }, + }, +}, +\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\`; + + public defineScaleBrokerOnDashboardSetup = \\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\` +// To add the sync broker globally to all the kpi tiles you may start with setting up the broker config +// Here you define which values to keep in sync +const brokerConfig = { + providerId: NOVA_KPI_SCALE_SYNC_BROKER, + properties: { + scaleSyncConfig: [ + { id: "value" }, + { id: "label" }, + { id: "units" }, + ], + }, + }; + +// And here is how you set the sync broker for every KPI widget in the dashboard. +// Later, you will be able to override this setting for each separate KPI widget in the configuration (just like it is shown in the third +// width of the example with the 'kpiWidgetId3') +this.widgetTypesService.setNode( + widgetTemplate, + "widget", + "tiles.providers.kpiScaleSyncBroker", + brokerConfig +); +\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\`; +} +\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\`, + "widget-types/kpi/kpi-sync-broker-for-all-tiles/kpi-sync-broker-for-all-tiles-example.component.ts": \\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\`// © 2022 SolarWinds Worldwide, LLC. All rights reserved. +// +// Permission is hereby granted, free of charge, to any person obtaining a copy +// of this software and associated documentation files (the "Software"), to +// deal in the Software without restriction, including without limitation the +// rights to use, copy, modify, merge, publish, distribute, sublicense, and/or +// sell copies of the Software, and to permit persons to whom the Software is +// furnished to do so, subject to the following conditions: +// +// The above copyright notice and this permission notice shall be included in +// all copies or substantial portions of the Software. +// +// THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR +// IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, +// FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE +// AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER +// LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, +// OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN +// THE SOFTWARE. + +import { HttpClient, HttpErrorResponse } from "@angular/common/http"; +import { + ChangeDetectorRef, + Component, + Injectable, + OnDestroy, + OnInit, +} from "@angular/core"; +import { GridsterConfig, GridsterItem } from "angular-gridster2"; +import keyBy from "lodash/keyBy"; +import { BehaviorSubject, of } from "rxjs"; +import { delay, finalize, take } from "rxjs/operators"; + +import { DataSourceService, IFilteringOutputs } from "@nova-ui/bits"; +import { + DATA_SOURCE, + IDashboard, + IKpiData, + IProviderConfiguration, + IWidget, + KpiComponent, + NOVA_KPI_DATASOURCE_ADAPTER, + NOVA_KPI_SCALE_SYNC_BROKER, + PizzagnaLayer, + ProviderRegistryService, + WellKnownPathKey, + WellKnownProviders, + WidgetTypesService, +} from "@nova-ui/dashboards"; + +/** + * A simple KPI data source to retrieve the average rating of Harry Potter and the Sorcerer's Stone (book) via googleapis + */ +@Injectable() +export class AverageRatingKpiDataSource + extends DataSourceService + implements OnDestroy +{ + public static providerId = "AverageRatingKpiDataSource"; + public busy = new BehaviorSubject(false); + + constructor(private http: HttpClient) { + super(); + } + + public async getFilteredData(): Promise { + this.busy.next(true); + return new Promise((resolve) => { + // *** Make a resource request to an external API (if needed) + this.http + .get("https://www.googleapis.com/books/v1/volumes/5MQFrgEACAAJ") + .pipe(finalize(() => this.busy.next(false))) + .subscribe({ + next: (data: any) => { + resolve({ + result: { + value: data.volumeInfo.averageRating, + }, + }); + }, + error: (error: HttpErrorResponse) => { + resolve({ + result: null, + error: { + type: error.status, + }, + }); + }, + }); + }); + } + + public ngOnDestroy(): void { + this.outputsSubject.complete(); + } +} + +/** + * A simple KPI data source to retrieve the ratings count of Harry Potter and the Sorcerer's Stone (book) via googleapis + */ +@Injectable() +export class RatingsCountKpiDataSource + extends DataSourceService + implements OnDestroy +{ + public static providerId = "RatingsCountKpiDataSource"; + + // Use this subject to communicate the data source's busy state + public busy = new BehaviorSubject(false); + + constructor(private http: HttpClient) { + super(); + } + + public async getFilteredData(): Promise { + this.busy.next(true); + return new Promise((resolve) => { + this.http + .get("https://www.googleapis.com/books/v1/volumes/5MQFrgEACAAJ") + .pipe( + delay(2000), + finalize(() => this.busy.next(false)) + ) + .subscribe({ + next: (data: any) => { + resolve({ + result: { + value: data.volumeInfo.ratingsCount, + }, + }); + }, + error: (error: HttpErrorResponse) => { + resolve({ + result: null, + error: { + type: error.status, + }, + }); + }, + }); + }); + } + + public ngOnDestroy(): void { + this.outputsSubject.complete(); + } +} +/** + * A simple KPI data source to retrieve the ratings count of Harry Potter and the Sorcerer's Stone (book) via googleapis + */ +@Injectable() +export class MockKpiDataSource + extends DataSourceService + implements OnDestroy +{ + public static providerId = "MockKpiDataSource"; + + // Use this subject to communicate the data source's busy state + public busy = new BehaviorSubject(false); + public value: number = 3381342; + + constructor() { + super(); + } + + public async getFilteredData(): Promise { + this.busy.next(true); + return new Promise((resolve) => { + of(this.value) + .pipe( + delay(5000), + take(1), + finalize(() => this.busy.next(false)) + ) + .subscribe({ + next: (data: any) => { + resolve({ + result: { + value: data, + }, + }); + }, + }); + }); + } + + public ngOnDestroy(): void { + this.outputsSubject.complete(); + } +} + +/** + * A component that instantiates the dashboard + */ +@Component({ + selector: "kpi-sync-broker-for-all-tiles-example", + templateUrl: "./kpi-sync-broker-for-all-tiles-example.component.html", + styleUrls: ["./kpi-sync-broker-for-all-tiles-example.component.less"], + standalone: false, +}) +export class KpiSyncBrokerForAllTilesExampleComponent implements OnInit { + public dashboard: IDashboard | undefined; + public gridsterConfig: GridsterConfig = {}; + public editMode: boolean = false; + + constructor( + private widgetTypesService: WidgetTypesService, + private providerRegistry: ProviderRegistryService, + private changeDetectorRef: ChangeDetectorRef + ) {} + + public ngOnInit(): void { + this.setupDashboard(); + + this.initializeDashboard(); + } + + /** Used for restoring widgets state */ + public reInitializeDashboard(): void { + // destroys the components and their providers so the dashboard can re init data + this.dashboard = undefined; + this.changeDetectorRef.detectChanges(); + + this.initializeDashboard(); + } + + private setupDashboard() { + // To add the sync broker globally to all the kpi tiles you may start with setting up the broker config + // Here you define which values to keep in sync + const brokerConfig = { + providerId: NOVA_KPI_SCALE_SYNC_BROKER, + properties: { + scaleSyncConfig: [ + { id: "value" }, + { id: "label" }, + { id: "units" }, + ], + }, + }; + const widgetTemplate = this.widgetTypesService.getWidgetType("kpi", 1); + + this.widgetTypesService.setNode( + widgetTemplate, + "configurator", + WellKnownPathKey.DataSourceProviders, + [ + AverageRatingKpiDataSource.providerId, + RatingsCountKpiDataSource.providerId, + MockKpiDataSource.providerId, + ] + ); + + // And here is how you set the sync broker for every KPI widget in the dashboard. + // Later, you will be able to override this setting for each separate KPI widget in the configuration (just like it is shown in the third + // width of the example with the 'kpiWidgetId3') + this.widgetTypesService.setNode( + widgetTemplate, + "widget", + "tiles.providers.kpiScaleSyncBroker", + brokerConfig + ); + + this.providerRegistry.setProviders({ + [AverageRatingKpiDataSource.providerId]: { + provide: DATA_SOURCE, + useClass: AverageRatingKpiDataSource, + deps: [HttpClient], + }, + [RatingsCountKpiDataSource.providerId]: { + provide: DATA_SOURCE, + useClass: RatingsCountKpiDataSource, + deps: [HttpClient], + }, + [MockKpiDataSource.providerId]: { + provide: DATA_SOURCE, + useClass: MockKpiDataSource, + deps: [], + }, + }); + } + + private initializeDashboard(): void { + const widgetsWithStructure = widgetsConfig.map((w) => + this.widgetTypesService.mergeWithWidgetType(w) + ); + const widgetsIndex = keyBy(widgetsWithStructure, (w: IWidget) => w.id); + + const positions: Record = { + kpiWidgetId: { + cols: 3, + rows: 6, + y: 0, + x: 0, + }, + kpiWidgetId2: { + cols: 3, + rows: 6, + y: 0, + x: 3, + }, + kpiWidgetId3: { + cols: 3, + rows: 6, + y: 0, + x: 6, + }, + }; + + this.dashboard = { + positions, + widgets: widgetsIndex, + }; + } +} + +const widgetsConfig: IWidget[] = [ + { + id: "kpiWidgetId", + type: "kpi", + pizzagna: { + [PizzagnaLayer.Configuration]: { + header: { + properties: { + title: "Sync Broker Applied for ALL Widgets", + subtitle: "Values are being synced", + }, + }, + tiles: { + properties: { + nodes: ["kpi1", "kpi2", "kpi3"], + }, + }, + kpi1: { + id: "kpi1", + componentType: KpiComponent.lateLoadKey, + properties: { + widgetData: { + units: \\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\`out of 5 Stars\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\`, + label: \\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\`Average Rating\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\`, + backgroundColor: "lightpink", + }, + }, + providers: { + [WellKnownProviders.DataSource]: { + providerId: AverageRatingKpiDataSource.providerId, + } as IProviderConfiguration, + [WellKnownProviders.Adapter]: { + providerId: NOVA_KPI_DATASOURCE_ADAPTER, + properties: { + componentId: "kpi1", + propertyPath: "widgetData", + }, + } as IProviderConfiguration, + }, + }, + kpi2: { + id: "kpi2", + componentType: KpiComponent.lateLoadKey, + properties: { + widgetData: { + label: \\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\`Another label which might be a pretty long one\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\`, + units: \\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\`Which comes from somewhere\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\`, + backgroundColor: "skyblue", + }, + }, + providers: { + [WellKnownProviders.DataSource]: { + providerId: RatingsCountKpiDataSource.providerId, + } as IProviderConfiguration, + [WellKnownProviders.Adapter]: { + providerId: NOVA_KPI_DATASOURCE_ADAPTER, + properties: { + componentId: "kpi2", + propertyPath: "widgetData", + }, + } as IProviderConfiguration, + }, + }, + kpi3: { + id: "kpi3", + componentType: KpiComponent.lateLoadKey, + properties: { + widgetData: { + label: \\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\`Random\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\`, + units: \\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\`Data\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\`, + }, + }, + providers: { + [WellKnownProviders.DataSource]: { + providerId: MockKpiDataSource.providerId, + } as IProviderConfiguration, + [WellKnownProviders.Adapter]: { + providerId: NOVA_KPI_DATASOURCE_ADAPTER, + properties: { + componentId: "kpi3", + propertyPath: "widgetData", + }, + } as IProviderConfiguration, + }, + }, + }, + }, + }, + { + id: "kpiWidgetId2", + type: "kpi", + pizzagna: { + [PizzagnaLayer.Configuration]: { + header: { + properties: { + title: "Sync Broker Applied for ALL Widgets", + subtitle: + "Now the values of label, units, and value are being synced", + }, + }, + tiles: { + properties: { + nodes: ["kpi1", "kpi2", "kpi3"], + }, + }, + kpi1: { + id: "kpi1", + componentType: KpiComponent.lateLoadKey, + properties: { + widgetData: { + units: \\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\`out of 5 Stars\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\`, + label: \\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\`Average Rating\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\`, + backgroundColor: "lightpink", + }, + }, + providers: { + [WellKnownProviders.DataSource]: { + providerId: AverageRatingKpiDataSource.providerId, + } as IProviderConfiguration, + [WellKnownProviders.Adapter]: { + providerId: NOVA_KPI_DATASOURCE_ADAPTER, + properties: { + componentId: "kpi1", + propertyPath: "widgetData", + }, + } as IProviderConfiguration, + }, + }, + kpi2: { + id: "kpi2", + componentType: KpiComponent.lateLoadKey, + properties: { + widgetData: { + label: \\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\`Another label which might be a pretty long one\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\`, + units: \\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\`Which comes from somewhere\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\`, + backgroundColor: "skyblue", + }, + }, + providers: { + [WellKnownProviders.DataSource]: { + providerId: RatingsCountKpiDataSource.providerId, + } as IProviderConfiguration, + [WellKnownProviders.Adapter]: { + providerId: NOVA_KPI_DATASOURCE_ADAPTER, + properties: { + componentId: "kpi2", + propertyPath: "widgetData", + }, + } as IProviderConfiguration, + }, + }, + kpi3: { + id: "kpi3", + componentType: KpiComponent.lateLoadKey, + properties: { + widgetData: { + label: \\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\`Random\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\`, + units: \\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\`Data\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\`, + }, + }, + providers: { + [WellKnownProviders.DataSource]: { + providerId: MockKpiDataSource.providerId, + } as IProviderConfiguration, + [WellKnownProviders.Adapter]: { + providerId: NOVA_KPI_DATASOURCE_ADAPTER, + properties: { + componentId: "kpi3", + propertyPath: "widgetData", + }, + } as IProviderConfiguration, + }, + }, + }, + }, + }, + { + id: "kpiWidgetId3", + type: "kpi", + pizzagna: { + [PizzagnaLayer.Configuration]: { + header: { + properties: { + title: "Here We Sync Only Labels and Units", + subtitle: + "Now only the label, and units are being synced", + }, + }, + tiles: { + properties: { + nodes: ["kpi1", "kpi2", "kpi3"], + }, + providers: { + // This is where and how you can override the globally set broker config + kpiScaleSyncBroker: { + providerId: NOVA_KPI_SCALE_SYNC_BROKER, + properties: { + scaleSyncConfig: [ + { id: "label" }, + { id: "units" }, + ], + }, + }, + }, + }, + kpi1: { + id: "kpi1", + componentType: KpiComponent.lateLoadKey, + properties: { + widgetData: { + units: \\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\`out of 5 Stars\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\`, + label: \\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\`Average Rating\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\`, + backgroundColor: "lightpink", + }, + }, + providers: { + [WellKnownProviders.DataSource]: { + providerId: AverageRatingKpiDataSource.providerId, + } as IProviderConfiguration, + [WellKnownProviders.Adapter]: { + providerId: NOVA_KPI_DATASOURCE_ADAPTER, + properties: { + componentId: "kpi1", + propertyPath: "widgetData", + }, + } as IProviderConfiguration, + }, + }, + kpi2: { + id: "kpi2", + componentType: KpiComponent.lateLoadKey, + properties: { + widgetData: { + label: \\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\`Another label which might be a pretty long one\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\`, + units: \\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\`Which comes from somewhere\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\`, + backgroundColor: "skyblue", + }, + }, + providers: { + [WellKnownProviders.DataSource]: { + providerId: RatingsCountKpiDataSource.providerId, + } as IProviderConfiguration, + [WellKnownProviders.Adapter]: { + providerId: NOVA_KPI_DATASOURCE_ADAPTER, + properties: { + componentId: "kpi2", + propertyPath: "widgetData", + }, + } as IProviderConfiguration, + }, + }, + kpi3: { + id: "kpi3", + componentType: KpiComponent.lateLoadKey, + properties: { + widgetData: { + label: \\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\`Random\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\`, + units: \\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\`Data\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\`, + }, + }, + providers: { + [WellKnownProviders.DataSource]: { + providerId: MockKpiDataSource.providerId, + } as IProviderConfiguration, + [WellKnownProviders.Adapter]: { + providerId: NOVA_KPI_DATASOURCE_ADAPTER, + properties: { + componentId: "kpi3", + propertyPath: "widgetData", + }, + } as IProviderConfiguration, + }, + }, + }, + }, + }, +]; +\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\`, + "widget-types/kpi/kpi-widget/kpi-widget-example.component.ts": \\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\`// © 2022 SolarWinds Worldwide, LLC. All rights reserved. +// +// Permission is hereby granted, free of charge, to any person obtaining a copy +// of this software and associated documentation files (the "Software"), to +// deal in the Software without restriction, including without limitation the +// rights to use, copy, modify, merge, publish, distribute, sublicense, and/or +// sell copies of the Software, and to permit persons to whom the Software is +// furnished to do so, subject to the following conditions: +// +// The above copyright notice and this permission notice shall be included in +// all copies or substantial portions of the Software. +// +// THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR +// IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, +// FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE +// AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER +// LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, +// OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN +// THE SOFTWARE. + +import { HttpClient, HttpErrorResponse } from "@angular/common/http"; +import { Component, Injectable, OnDestroy, OnInit } from "@angular/core"; +import { GridsterConfig, GridsterItem } from "angular-gridster2"; +import { BehaviorSubject } from "rxjs"; +import { finalize } from "rxjs/operators"; + +import { DataSourceService, IFilteringOutputs } from "@nova-ui/bits"; +import { + DATA_SOURCE, + DEFAULT_PIZZAGNA_ROOT, + IDashboard, + IKpiData, + IProviderConfiguration, + IRefresherProperties, + IWidget, + IWidgets, + KpiComponent, + NOVA_KPI_DATASOURCE_ADAPTER, + PizzagnaLayer, + ProviderRegistryService, + WellKnownPathKey, + WellKnownProviders, + WidgetTypesService, +} from "@nova-ui/dashboards"; + +/** + * A simple KPI data source to retrieve the average rating of Harry Potter and the Sorcerer's Stone (book) via googleapis + */ +@Injectable() +export class AverageRatingKpiDataSource + extends DataSourceService + implements OnDestroy +{ + // This is the ID we'll use to identify the provider + public static providerId = "AverageRatingKpiDataSource"; + + // Use this subject to communicate the data source's busy state + public busy = new BehaviorSubject(false); + + constructor(private http: HttpClient) { + super(); + } + + // In this example, getFilteredData is invoked every 10 minutes (Take a look at the refresher + // provider definition in the widget configuration below to see how the interval is set) + public async getFilteredData(): Promise { + this.busy.next(true); + return new Promise((resolve) => { + // *** Make a resource request to an external API (if needed) + this.http + .get("https://www.googleapis.com/books/v1/volumes/5MQFrgEACAAJ") + .pipe(finalize(() => this.busy.next(false))) + .subscribe({ + next: (data: any) => { + resolve({ + result: { + value: data.volumeInfo.averageRating, + }, + }); + }, + error: (error: HttpErrorResponse) => { + resolve({ + result: null, + error: { + type: error.status, + }, + }); + }, + }); + }); + } + + public ngOnDestroy(): void { + this.outputsSubject.complete(); + } +} + +/** + * A component that instantiates the dashboard + */ +@Component({ + selector: "kpi-widget-example", + templateUrl: "./kpi-widget-example.component.html", + styleUrls: ["./kpi-widget-example.component.less"], + standalone: false, +}) +export class KpiWidgetExampleComponent implements OnInit { + // This variable will hold all the data needed to define the layout and behavior of the widgets. + // Pass this to the dashboard component's dashboard input in the template. + public dashboard: IDashboard; + + // Angular gridster requires a configuration object even if it's empty. + // Pass this to the dashboard component's gridsterConfig input in the template. + public gridsterConfig: GridsterConfig = {}; + + // Boolean passed as an input to the dashboard. When true, widgets can be moved, resized, removed, or edited + public editMode: boolean = false; + + constructor( + // WidgetTypesService provides the widget's necessary structure information + private widgetTypesService: WidgetTypesService, + + // In general, the ProviderRegistryService is used for making entities available for injection into dynamically loaded components. + private providerRegistry: ProviderRegistryService + ) {} + + public ngOnInit(): void { + // Grabbing the widget's default template which will be needed as a parameter for setNode + const widgetTemplate = this.widgetTypesService.getWidgetType("kpi", 1); + // Registering our data sources as dropdown options in the widget editor/configurator + // Note: This could also be done in the parent module's constructor so that + // multiple dashboards could have access to the same widget template modification. + this.widgetTypesService.setNode( + // This is the template we grabbed above with getWidgetType + widgetTemplate, + // We are setting the editor/configurator part of the widget template + "configurator", + // This indicates which node you are changing and we want to change + // the data source providers available for selection in the editor. + WellKnownPathKey.DataSourceProviders, + // We are setting the data sources available for selection in the editor + [AverageRatingKpiDataSource.providerId] + ); + + // Registering the data source for injection into the KPI tile. + // Note: Each tile of a KPI widget is assigned its own instance of the data source + this.providerRegistry.setProviders({ + [AverageRatingKpiDataSource.providerId]: { + provide: DATA_SOURCE, + useClass: AverageRatingKpiDataSource, + // Any dependencies that need to be injected into the provider must be listed here + deps: [HttpClient], + }, + }); + + this.initializeDashboard(); + } + + public initializeDashboard(): void { + // We're using a static configuration object for this example, but this is where + // the widget's configuration could potentially be populated from a database + const kpiWidget = widgetConfig; + const widgetIndex: IWidgets = { + // Complete the KPI widget with information coming from its type definition + [kpiWidget.id]: + this.widgetTypesService.mergeWithWidgetType(kpiWidget), + }; + + // Setting the widget dimensions and position (this is for gridster) + const positions: Record = { + [kpiWidget.id]: { + cols: 4, + rows: 6, + y: 0, + x: 0, + }, + }; + + // Finally, assigning the variables we created above to the dashboard + this.dashboard = { + positions, + widgets: widgetIndex, + }; + } +} + +const widgetConfig: IWidget = { + id: "kpiWidgetId", + type: "kpi", + pizzagna: { + [PizzagnaLayer.Configuration]: { + [DEFAULT_PIZZAGNA_ROOT]: { + providers: { + [WellKnownProviders.Refresher]: { + properties: { + // Configuring the refresher interval so that our data source is invoked every ten minutes + interval: 60 * 10, + enabled: true, + } as IRefresherProperties, + } as Partial, + }, + }, + header: { + properties: { + title: "Harry Potter and the Sorcerer's Stone", + subtitle: "By J. K. Rowling", + }, + }, + tiles: { + properties: { + nodes: ["kpi1"], + }, + }, + kpi1: { + id: "kpi1", + componentType: KpiComponent.lateLoadKey, + properties: { + widgetData: { + units: \\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\`out of 5 Stars\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\`, + label: \\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\`Average Rating\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\`, + }, + }, + providers: { + [WellKnownProviders.DataSource]: { + // Setting the data source providerId for the tile with id "kpi1" + providerId: AverageRatingKpiDataSource.providerId, + } as IProviderConfiguration, + [WellKnownProviders.Adapter]: { + providerId: NOVA_KPI_DATASOURCE_ADAPTER, + properties: { + componentId: "kpi1", + propertyPath: "widgetData", + }, + } as IProviderConfiguration, + }, + }, + }, + }, +}; +\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\`, + "widget-types/kpi/kpi-widget-background-color/kpi-widget-background-color-example.component.ts": \\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\`// © 2022 SolarWinds Worldwide, LLC. All rights reserved. +// +// Permission is hereby granted, free of charge, to any person obtaining a copy +// of this software and associated documentation files (the "Software"), to +// deal in the Software without restriction, including without limitation the +// rights to use, copy, modify, merge, publish, distribute, sublicense, and/or +// sell copies of the Software, and to permit persons to whom the Software is +// furnished to do so, subject to the following conditions: +// +// The above copyright notice and this permission notice shall be included in +// all copies or substantial portions of the Software. +// +// THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR +// IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, +// FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE +// AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER +// LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, +// OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN +// THE SOFTWARE. + +import { HttpClient, HttpErrorResponse } from "@angular/common/http"; +import { + ChangeDetectorRef, + Component, + Injectable, + OnDestroy, + OnInit, +} from "@angular/core"; +import { GridsterConfig, GridsterItem } from "angular-gridster2"; +import { BehaviorSubject } from "rxjs"; +import { finalize } from "rxjs/operators"; + +import { DataSourceService, IFilteringOutputs } from "@nova-ui/bits"; +import { + DATA_SOURCE, + DEFAULT_KPI_BACKGROUND_COLORS, + IDashboard, + IKpiColorRules, + IKpiData, + IProviderConfiguration, + IWidget, + IWidgets, + KpiComponent, + NOVA_KPI_COLOR_PRIORITIZER, + NOVA_KPI_DATASOURCE_ADAPTER, + PizzagnaLayer, + ProviderRegistryService, + WellKnownPathKey, + WellKnownProviders, + WidgetTypesService, +} from "@nova-ui/dashboards"; + +/** + * A simple KPI data source to retrieve the average rating of Harry Potter and the Sorcerer's Stone (book) via googleapis + */ +@Injectable() +export class AverageRatingKpiDataSource + extends DataSourceService + implements OnDestroy +{ + public static providerId = "AverageRatingKpiDataSource"; + public busy = new BehaviorSubject(false); + + constructor(private http: HttpClient) { + super(); + } + + public async getFilteredData(): Promise { + this.busy.next(true); + return new Promise((resolve) => { + // *** Make a resource request to an external API (if needed) + this.http + .get("https://www.googleapis.com/books/v1/volumes/5MQFrgEACAAJ") + .pipe(finalize(() => this.busy.next(false))) + .subscribe({ + next: (data: any) => { + resolve({ + result: { + value: data.volumeInfo.averageRating, + // setting the color on the dataSource "Sea Green", + // uncomment to get the background color update from the "Data" layer + // backgroundColor: "var(--nui-color-chart-three)", + }, + }); + }, + error: (error: HttpErrorResponse) => { + resolve({ + result: null, + error: { + type: error.status, + }, + }); + }, + }); + }); + } + + public ngOnDestroy(): void { + this.outputsSubject.complete(); + } +} + +/** + * A component that instantiates the dashboard + */ +@Component({ + selector: "kpi-widget-background-color-example", + templateUrl: "./kpi-widget-background-color-example.component.html", + styleUrls: ["./kpi-widget-background-color-example.component.less"], + standalone: false, +}) +export class KpiWidgetBackgroundColorExampleComponent implements OnInit { + public dashboard: IDashboard | undefined; + public gridsterConfig: GridsterConfig = {}; + public editMode: boolean = false; + + constructor( + private widgetTypesService: WidgetTypesService, + private providerRegistry: ProviderRegistryService, + private changeDetectorRef: ChangeDetectorRef + ) {} + + public ngOnInit(): void { + this.setupDashboard(); + + // KPI tile default color setup + this.setupDefaultColorStructure(); + + // Sets the custom pallette to the 'Description' section + this.setupCustomPalletteDescription(); + + // Sets the custom pallette to the 'Background color rules' section + this.setupCustomPalletteRules(); + + this.initializeDashboard(); + } + + /** Used for restoring widgets state */ + public reInitializeDashboard(): void { + // destroys the components and their providers so the dashboard can re init data + this.dashboard = undefined; + this.changeDetectorRef.detectChanges(); + + this.initializeDashboard(); + } + + private setupCustomPalletteDescription() { + const kpiWidgetTemplate = this.widgetTypesService.getWidgetType( + "kpi", + 1 + ); + this.widgetTypesService.setNode( + kpiWidgetTemplate, + "configurator", + WellKnownPathKey.TileDescriptionBackgroundColors, + [ + { color: "var(--nui-color-chart-one)", label: "Blue" }, + { + color: "var(--nui-color-chart-one-light)", + label: "Blue Light", + }, + { + color: "var(--nui-color-chart-one-dark)", + label: "Blue Dark", + }, + ] + ); + } + + private setupCustomPalletteRules() { + const kpiWidgetTemplate = this.widgetTypesService.getWidgetType( + "kpi", + 1 + ); + this.widgetTypesService.setNode( + kpiWidgetTemplate, + "configurator", + WellKnownPathKey.TileBackgroundColorRulesBackgroundColors, + [ + { color: "red", label: "Native Red" }, + ...DEFAULT_KPI_BACKGROUND_COLORS, + ] + ); + } + + private setupDefaultColorStructure() { + const widgetTemplate = this.widgetTypesService.getWidgetType("kpi", 1); + this.widgetTypesService.setNode( + widgetTemplate, + "widget", + "tiles.properties.template.properties.widgetData.backgroundColor", + "red" + ); + } + + private setupDashboard() { + const widgetTemplate = this.widgetTypesService.getWidgetType("kpi", 1); + this.widgetTypesService.setNode( + widgetTemplate, + "configurator", + WellKnownPathKey.DataSourceProviders, + [AverageRatingKpiDataSource.providerId] + ); + + this.providerRegistry.setProviders({ + [AverageRatingKpiDataSource.providerId]: { + provide: DATA_SOURCE, + useClass: AverageRatingKpiDataSource, + deps: [HttpClient], + }, + }); + } + + private initializeDashboard(): void { + const kpiWidget = widgetConfig; + const widgetIndex: IWidgets = { + [kpiWidget.id]: + this.widgetTypesService.mergeWithWidgetType(kpiWidget), + }; + + const positions: Record = { + [kpiWidget.id]: { + cols: 4, + rows: 6, + y: 0, + x: 0, + }, + }; + + this.dashboard = { + positions, + widgets: widgetIndex, + }; + } +} + +const widgetConfig: IWidget = { + id: "kpiWidgetId", + type: "kpi", + pizzagna: { + [PizzagnaLayer.Configuration]: { + header: { + properties: { + title: "Harry Potter and the Sorcerer's Stone", + subtitle: "By J. K. Rowling", + }, + }, + tiles: { + properties: { + nodes: ["kpi1"], + }, + }, + kpi1: { + id: "kpi1", + componentType: KpiComponent.lateLoadKey, + properties: { + widgetData: { + units: \\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\`out of 5 Stars\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\`, + label: \\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\`Average Rating\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\`, + // Configuration color "Blue" + backgroundColor: "var(--nui-color-chart-one)", + }, + }, + providers: { + [WellKnownProviders.DataSource]: { + providerId: AverageRatingKpiDataSource.providerId, + } as IProviderConfiguration, + [WellKnownProviders.Adapter]: { + providerId: NOVA_KPI_DATASOURCE_ADAPTER, + properties: { + componentId: "kpi1", + propertyPath: "widgetData", + }, + } as IProviderConfiguration, + [WellKnownProviders.KpiColorPrioritizer]: { + providerId: NOVA_KPI_COLOR_PRIORITIZER, + properties: { + // Color Prioritizer Rules + // settings rules - if the value is more than "2" display "Violet" color + rules: [ + { + comparisonType: ">", + value: 2, + color: "var(--nui-color-chart-four)", + }, + ] as IKpiColorRules[], + }, + } as IProviderConfiguration, + }, + }, + }, + }, +}; +\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\`, + "widget-types/kpi/kpi-widget-background-color-docs.component.ts": \\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\`// © 2022 SolarWinds Worldwide, LLC. All rights reserved. +// +// Permission is hereby granted, free of charge, to any person obtaining a copy +// of this software and associated documentation files (the "Software"), to +// deal in the Software without restriction, including without limitation the +// rights to use, copy, modify, merge, publish, distribute, sublicense, and/or +// sell copies of the Software, and to permit persons to whom the Software is +// furnished to do so, subject to the following conditions: +// +// The above copyright notice and this permission notice shall be included in +// all copies or substantial portions of the Software. +// +// THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR +// IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, +// FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE +// AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER +// LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, +// OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN +// THE SOFTWARE. + +import { Component } from "@angular/core"; + +@Component({ + selector: "nui-kpi-background-color-docs", + templateUrl: "./kpi-widget-background-color-docs.component.html", + standalone: false, +}) +export class KpiWidgetBackgroundColorDocsComponent { + public comparatorsRegistryCode = \\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\` + this.comparatorsRegistry.registerComparators({ + "!=": { + comparatorFn: (actual: any, reference: any) => actual != reference, + label: "Not equal", + }, + }); + \\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\`; +} +\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\`, + "widget-types/kpi/kpi-widget-interactive/kpi-widget-interactive-example.component.ts": \\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\`// © 2022 SolarWinds Worldwide, LLC. All rights reserved. +// +// Permission is hereby granted, free of charge, to any person obtaining a copy +// of this software and associated documentation files (the "Software"), to +// deal in the Software without restriction, including without limitation the +// rights to use, copy, modify, merge, publish, distribute, sublicense, and/or +// sell copies of the Software, and to permit persons to whom the Software is +// furnished to do so, subject to the following conditions: +// +// The above copyright notice and this permission notice shall be included in +// all copies or substantial portions of the Software. +// +// THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR +// IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, +// FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE +// AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER +// LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, +// OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN +// THE SOFTWARE. + +import { HttpClient, HttpErrorResponse } from "@angular/common/http"; +import { Component, Injectable, OnDestroy, OnInit } from "@angular/core"; +import { GridsterConfig, GridsterItem } from "angular-gridster2"; +import { BehaviorSubject } from "rxjs"; +import { finalize } from "rxjs/operators"; + +import { DataSourceService, IFilteringOutputs } from "@nova-ui/bits"; +import { + DATA_SOURCE, + IDashboard, + IKpiData, + IProviderConfiguration, + IWidget, + IWidgets, + KpiComponent, + NOVA_KPI_DATASOURCE_ADAPTER, + NOVA_URL_INTERACTION_HANDLER, + PizzagnaLayer, + ProviderRegistryService, + WellKnownPathKey, + WellKnownProviders, + WidgetTypesService, +} from "@nova-ui/dashboards"; + +/** + * A simple KPI data source to retrieve the average rating of Harry Potter and the Sorcerer's Stone (book) via googleapis + */ +@Injectable() +export class BookRatingDataSource + extends DataSourceService + implements OnDestroy +{ + // This is the ID we'll use to identify the provider + public static providerId = "BookRatingDataSource"; + + // Use this subject to communicate the data source's busy state + public busy = new BehaviorSubject(false); + + constructor(private http: HttpClient) { + super(); + } + + // In this example, getFilteredData is invoked every 10 minutes (Take a look at the refresher + // provider definition in the widget configuration below to see how the interval is set) + public async getFilteredData(): Promise { + this.busy.next(true); + return new Promise((resolve) => { + // *** Make a resource request to an external API (if needed) + this.http + .get("https://www.googleapis.com/books/v1/volumes/zpvysRGsBlwC") + .pipe(finalize(() => this.busy.next(false))) + .subscribe({ + next: (data: any) => { + resolve({ + result: { + value: data.volumeInfo.averageRating, + link: data.volumeInfo.infoLink, + }, + }); + }, + error: (error: HttpErrorResponse) => { + resolve({ + result: null, + error: { + type: error.status, + }, + }); + }, + }); + }); + } + + public ngOnDestroy(): void { + this.outputsSubject.complete(); + } +} + +/** + * A component that instantiates the dashboard + */ +@Component({ + selector: "kpi-widget-interactive-example", + templateUrl: "./kpi-widget-interactive-example.component.html", + styleUrls: ["./kpi-widget-interactive-example.component.less"], + standalone: false, +}) +export class KpiWidgetInteractiveExampleComponent implements OnInit { + // This variable will hold all the data needed to define the layout and behavior of the widgets. + // Pass this to the dashboard component's dashboard input in the template. + public dashboard: IDashboard; + + // Angular gridster requires a configuration object even if it's empty. + // Pass this to the dashboard component's gridsterConfig input in the template. + public gridsterConfig: GridsterConfig = {}; + + // Boolean passed as an input to the dashboard. When true, widgets can be moved, resized, removed, or edited + public editMode: boolean = false; + + constructor( + // WidgetTypesService provides the widget's necessary structure information + private widgetTypesService: WidgetTypesService, + + // In general, the ProviderRegistryService is used for making entities available for injection into dynamically loaded components. + private providerRegistry: ProviderRegistryService + ) {} + + public ngOnInit(): void { + // Grabbing the widget's default template which will be needed as a parameter for setNode + const widgetTemplate = this.widgetTypesService.getWidgetType("kpi", 1); + // Registering our data sources as dropdown options in the widget editor/configurator + // Note: This could also be done in the parent module's constructor so that + // multiple dashboards could have access to the same widget template modification. + this.widgetTypesService.setNode( + // This is the template we grabbed above with getWidgetType + widgetTemplate, + // We are setting the editor/configurator part of the widget template + "configurator", + // This indicates which node you are changing and we want to change + // the data source providers available for selection in the editor. + WellKnownPathKey.DataSourceProviders, + // We are setting the data sources available for selection in the editor + [BookRatingDataSource.providerId] + ); + + // Registering the data source for injection into the KPI tile. + // Note: Each tile of a KPI widget is assigned its own instance of the data source + this.providerRegistry.setProviders({ + [BookRatingDataSource.providerId]: { + provide: DATA_SOURCE, + useClass: BookRatingDataSource, + // Any dependencies that need to be injected into the provider must be listed here + deps: [HttpClient], + }, + }); + + this.initializeDashboard(); + } + + public initializeDashboard(): void { + // We're using a static configuration object for this example, but this is where + // the widget's configuration could potentially be populated from a database + const kpiWidget = widgetConfig; + const widgetIndex: IWidgets = { + // Complete the KPI widget with information coming from its type definition + [kpiWidget.id]: + this.widgetTypesService.mergeWithWidgetType(kpiWidget), + }; + + // Setting the widget dimensions and position (this is for gridster) + const positions: Record = { + [kpiWidget.id]: { + cols: 4, + rows: 6, + y: 0, + x: 0, + }, + }; + + // Finally, assigning the variables we created above to the dashboard + this.dashboard = { + positions, + widgets: widgetIndex, + }; + } +} + +const widgetConfig: IWidget = { + id: "kpiWidgetId", + type: "kpi", + pizzagna: { + [PizzagnaLayer.Configuration]: { + header: { + properties: { + title: "Harry Potter and the Order of the Phoenix", + subtitle: "By: J. K. Rowling", + }, + }, + tiles: { + providers: { + interaction: { + // Configuring the UrlInteractionHandler for interactions on the tiles + providerId: NOVA_URL_INTERACTION_HANDLER, + properties: { + // the 'url' property tells the handler what link to use when interaction occurs on the series + url: "\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\${data.link}", + }, + }, + }, + properties: { + nodes: ["kpi1"], + }, + }, + kpi1: { + id: "kpi1", + componentType: KpiComponent.lateLoadKey, + properties: { + widgetData: { + units: \\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\`out of 5 stars\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\`, + label: \\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\`Average Rating\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\`, + value: 0, + // the link property that is passed to the UrlInteractionHandler when the title is clicked + // this will be updated in BookRatingDataSource's 'getFilteredData' call. + link: "http://www.google.com", + }, + }, + providers: { + [WellKnownProviders.DataSource]: { + // Setting the data source providerId for the tile with id "kpi1" + providerId: BookRatingDataSource.providerId, + } as IProviderConfiguration, + [WellKnownProviders.Adapter]: { + providerId: NOVA_KPI_DATASOURCE_ADAPTER, + properties: { + componentId: "kpi1", + propertyPath: "widgetData", + }, + } as IProviderConfiguration, + }, + }, + }, + }, +}; +\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\`, + "widget-types/proportional/models.ts": \\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\`export interface IMockBeerReview { + id: string; + name: string; + data: number[]; + icon: string; + link?: string; + value: string; + color?: string; +} +\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\`, + "widget-types/proportional/proportional-docs.component.ts": \\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\`// © 2022 SolarWinds Worldwide, LLC. All rights reserved. +// +// Permission is hereby granted, free of charge, to any person obtaining a copy +// of this software and associated documentation files (the "Software"), to +// deal in the Software without restriction, including without limitation the +// rights to use, copy, modify, merge, publish, distribute, sublicense, and/or +// sell copies of the Software, and to permit persons to whom the Software is +// furnished to do so, subject to the following conditions: +// +// The above copyright notice and this permission notice shall be included in +// all copies or substantial portions of the Software. +// +// THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR +// IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, +// FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE +// AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER +// LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, +// OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN +// THE SOFTWARE. + +import { Component, OnInit } from "@angular/core"; + +import { mapContentFile } from "../../../../demo-files-factory"; + +@Component({ + selector: "nui-proportional-docs", + templateUrl: "./proportional-docs.component.html", + standalone: false, +}) +export class ProportionalDocsComponent implements OnInit { + public proportionalWidgetFileText = ""; + public proportionalConfiguratorFileText = ""; + + public async ngOnInit(): Promise { + this.proportionalWidgetFileText = await import( + "./../../../../../../src/lib/widget-types/proportional/proportional-widget" + ).then(mapContentFile); + this.proportionalConfiguratorFileText = await import( + "./../../../../../../src/lib/widget-types/proportional/proportional-configurator" + ).then(mapContentFile); + } +} +\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\`, + "widget-types/proportional/proportional-docs.module.ts": \\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\`// © 2022 SolarWinds Worldwide, LLC. All rights reserved. +// +// Permission is hereby granted, free of charge, to any person obtaining a copy +// of this software and associated documentation files (the "Software"), to +// deal in the Software without restriction, including without limitation the +// rights to use, copy, modify, merge, publish, distribute, sublicense, and/or +// sell copies of the Software, and to permit persons to whom the Software is +// furnished to do so, subject to the following conditions: +// +// The above copyright notice and this permission notice shall be included in +// all copies or substantial portions of the Software. +// +// THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR +// IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, +// FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE +// AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER +// LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, +// OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN +// THE SOFTWARE. + +import { NgModule } from "@angular/core"; +import { RouterModule, Routes } from "@angular/router"; + +import { + NuiButtonModule, + NuiDocsModule, + NuiMessageModule, + NuiSwitchModule, + DEMO_PATH_TOKEN, +} from "@nova-ui/bits"; +import { NuiDashboardsModule } from "@nova-ui/dashboards"; + +import { ProportionalDocsComponent } from "./proportional-docs.component"; +import { ProportionalDonutContentDocsComponent } from "./proportional-donut-content-docs.component"; +import { ProportionalWidgetDonutContentFormattersExampleComponent } from "./proportional-donut-content-formatters/proportional-donut-content-formatters-example.component"; +import { ProportionalWidgetExampleComponent } from "./proportional-widget/proportional-widget-example.component"; +import { ProportionalWidgetInteractiveExampleComponent } from "./proportional-widget-interactive/proportional-widget-interactive-example.component"; +import { getDemoFiles } from "../../../../demo-files-factory"; + +const routes: Routes = [ + { + path: "", + component: ProportionalDocsComponent, + data: { + srlc: { + hideIndicator: true, + }, + showThemeSwitcher: true, + }, + }, + { + path: "example", + component: ProportionalWidgetExampleComponent, + data: { + srlc: { + hideIndicator: true, + }, + }, + }, + { + path: "donut-content-formatters", + component: ProportionalDonutContentDocsComponent, + data: { + srlc: { + hideIndicator: true, + }, + }, + }, + { + path: "donut-content-formatters-example", + component: ProportionalWidgetDonutContentFormattersExampleComponent, + data: { + srlc: { + hideIndicator: true, + }, + }, + }, + { + path: "proportional-widget-interactive-example", + component: ProportionalWidgetInteractiveExampleComponent, + data: { + srlc: { + hideIndicator: true, + }, + }, + }, +]; + +@NgModule({ + imports: [ + RouterModule.forChild(routes), + NuiButtonModule, + NuiDocsModule, + NuiDashboardsModule, + NuiMessageModule, + NuiSwitchModule, + ], + declarations: [ + ProportionalDocsComponent, + ProportionalWidgetExampleComponent, + ProportionalWidgetInteractiveExampleComponent, + ProportionalWidgetDonutContentFormattersExampleComponent, + ProportionalDonutContentDocsComponent, + ], + providers: [ + { + provide: DEMO_PATH_TOKEN, + useValue: getDemoFiles("proportional"), + }, + ], +}) +export default class ProportionalDocsModule {} +\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\`, + "widget-types/proportional/proportional-donut-content-docs.component.ts": \\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\`// © 2022 SolarWinds Worldwide, LLC. All rights reserved. +// +// Permission is hereby granted, free of charge, to any person obtaining a copy +// of this software and associated documentation files (the "Software"), to +// deal in the Software without restriction, including without limitation the +// rights to use, copy, modify, merge, publish, distribute, sublicense, and/or +// sell copies of the Software, and to permit persons to whom the Software is +// furnished to do so, subject to the following conditions: +// +// The above copyright notice and this permission notice shall be included in +// all copies or substantial portions of the Software. +// +// THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR +// IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, +// FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE +// AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER +// LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, +// OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN +// THE SOFTWARE. + +import { Component } from "@angular/core"; + +@Component({ + selector: "nui-proportional-donut-content-docs", + templateUrl: "./proportional-donut-content-docs.component.html", + standalone: false, +}) +export class ProportionalDonutContentDocsComponent { + public dataSourceDataFieldsConfig = \\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\` +public dataFieldsConfig: IProportionalDataFieldsConfig = { + dataFields$: new BehaviorSubject(this.dataFields), + chartSeriesDataFields$: new BehaviorSubject(this.chartSeriesDataFields), +}; + \\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\`; + + public widgetConfigSlice = \\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\` +"properties": { + "configuration": { + "chartOptions": { + donutContentConfig: { + formatter: { + componentType: SiUnitsFormatterComponent.lateLoadKey, + }, + aggregator: { + aggregatorType: sumAggregator.aggregatorType, + }, + }, + } + } +} + + \\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\`; +} +\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\`, + "widget-types/proportional/proportional-donut-content-formatters/proportional-donut-content-formatters-example.component.ts": \\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\`// © 2022 SolarWinds Worldwide, LLC. All rights reserved. +// +// Permission is hereby granted, free of charge, to any person obtaining a copy +// of this software and associated documentation files (the "Software"), to +// deal in the Software without restriction, including without limitation the +// rights to use, copy, modify, merge, publish, distribute, sublicense, and/or +// sell copies of the Software, and to permit persons to whom the Software is +// furnished to do so, subject to the following conditions: +// +// The above copyright notice and this permission notice shall be included in +// all copies or substantial portions of the Software. +// +// THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR +// IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, +// FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE +// AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER +// LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, +// OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN +// THE SOFTWARE. + +import { + ChangeDetectorRef, + Component, + Injectable, + OnDestroy, + OnInit, +} from "@angular/core"; +import { GridsterConfig, GridsterItem } from "angular-gridster2"; +import { BehaviorSubject } from "rxjs"; + +import { + DataSourceService, + IDataField, + IDataSource, + IFilteringOutputs, +} from "@nova-ui/bits"; +import { IAccessors, IChartAssistSeries } from "@nova-ui/charts"; +import { + DATA_SOURCE, + DEFAULT_LEGEND_FORMATTERS, + DEFAULT_PIZZAGNA_ROOT, + DEFAULT_PROPORTIONAL_CONTENT_AGGREGATORS, + DEFAULT_PROPORTIONAL_CONTENT_FORMATTERS, + DONUT_CONTENT_CONFIGURATION_SLICE, + IDashboard, + IDonutContentConfig, + IProportionalDataFieldsConfig, + IProportionalWidgetChartOptions, + IProportionalWidgetConfig, + IProviderConfiguration, + IWidget, + IWidgets, + LegendPlacement, + PizzagnaLayer, + ProportionalContentAggregatorsRegistryService, + ProportionalDonutContentFormattersRegistryService, + ProportionalLegendFormattersRegistryService, + ProportionalWidgetChartTypes, + ProviderRegistryService, + SiUnitsFormatterComponent, + sumAggregator, + WellKnownPathKey, + WellKnownProviders, + WidgetTypesService, +} from "@nova-ui/dashboards"; + +import { IMockBeerReview } from "../models"; + +/** + * A simple proportional data source to retrieve beer review counts by city + */ +@Injectable() +export class BeerReviewCountsByCityMockDataSource + extends DataSourceService> + implements IDataSource>, OnDestroy +{ + public static providerId = "BeerReviewCountsByCityMockDataSource"; + public busy = new BehaviorSubject(false); + + protected dataFields: IDataField[] = [ + { + id: "Brno", + label: "Brno", + // @ts-ignore + dataType: null, + }, + { + id: "kyiv", + label: "Kyiv", + // @ts-ignore + dataType: null, + }, + { + id: "austin", + label: "Austin", + // @ts-ignore + dataType: null, + }, + { + id: "lisbon", + label: "Lisbon", + // @ts-ignore + dataType: null, + }, + { + id: "sydney", + label: "Sydney", + // @ts-ignore + dataType: null, + }, + { + id: "nur-sultan", + label: "Nur-Sultan", + // @ts-ignore + dataType: null, + }, + ]; + protected chartSeriesDataFields: IDataField[] = [ + // default field in the chart series that is used for the aggregation + { + id: "data[0]", + label: "data", + // @ts-ignore + dataType: null, + }, + // any custom field in the chart series that is used for the aggregation + { + id: "customDonutContent", + label: "Custom Donut Content", + // @ts-ignore + dataType: null, + }, + ]; + + /** + * DataSource needs to implement the "IDataFieldsConfig" for this scenario. + * + * It's necessary to provide the "chartSeriesDataFields", + * that's why proportional widget dataSource has it's own interface for that - IProportionalDataFieldsConfig. + * + * dataFields$ - stands for possible series fields + * chartSeriesDataFields$ - stands for the fields IN the series + * + * see declaration of "dataFields" and "chartSeriesDataFields" for the example. + */ + public dataFieldsConfig: IProportionalDataFieldsConfig = { + dataFields$: new BehaviorSubject(this.dataFields), + chartSeriesDataFields$: new BehaviorSubject( + this.chartSeriesDataFields + ), + }; + + public async getFilteredData(): Promise { + this.busy.next(true); + return new Promise((resolve) => { + setTimeout(() => { + this.outputsSubject.next({ + result: getMockBeerReviewCountsByCity(), + }); + this.busy.next(false); + }, 300); + }); + } + + public ngOnDestroy(): void { + this.outputsSubject.complete(); + } +} + +/** + * A component that instantiates the dashboard + */ +@Component({ + selector: "proportional-widget-donut-content-formatters-example", + templateUrl: "./proportional-donut-content-formatters-example.component.html", + styleUrls: [ + "./proportional-donut-content-formatters-example.component.less", + ], + standalone: false, +}) +export class ProportionalWidgetDonutContentFormattersExampleComponent + implements OnInit +{ + public dashboard: IDashboard | undefined; + + // Angular gridster requires a configuration object even if it's empty. + // Pass this to the dashboard component's gridsterConfig input in the template. + public gridsterConfig: GridsterConfig = {}; + + // Boolean passed as an input to the dashboard. When true, widgets can be moved, resized, removed, or edited + public editMode: boolean = false; + + constructor( + // WidgetTypesService provides the widget's necessary structure information + private widgetTypesService: WidgetTypesService, + // In general, the ProviderRegistryService is used for making entities available for injection into dynamically loaded components. + private providerRegistry: ProviderRegistryService, + // registry for adding the formatter for donut content + contentFormattersRegistry: ProportionalDonutContentFormattersRegistryService, + // registry for adding the formatter for proportional legend + legendFormattersRegistry: ProportionalLegendFormattersRegistryService, + // registry for adding the aggregators for donut content + aggregatorRegistry: ProportionalContentAggregatorsRegistryService, + private changeDetectorRef: ChangeDetectorRef + ) { + // on the dashboard startup, it's necessary to add possible content formatters, legend formatters and content aggregators to the registry. + // using registry is a way for setting the available formatters. + legendFormattersRegistry.addItems(DEFAULT_LEGEND_FORMATTERS); + contentFormattersRegistry.addItems( + DEFAULT_PROPORTIONAL_CONTENT_FORMATTERS + ); + aggregatorRegistry.addItems(DEFAULT_PROPORTIONAL_CONTENT_AGGREGATORS); + } + + public ngOnInit(): void { + // Grabbing the widget's default template which will be needed as a parameter for setNode + const widgetTemplate = this.widgetTypesService.getWidgetType( + "proportional", + 1 + ); + + // Registering our data sources as dropdown options in the widget editor/configurator + // Note: This could also be done in the parent module's constructor so that + // multiple dashboards could have access to the same widget template modification. + this.widgetTypesService.setNode( + // This is the template we grabbed above with getWidgetType + widgetTemplate, + // We are setting the editor/configurator part of the widget template + "configurator", + // This indicates which node you are changing and we want to change + // the data source providers available for selection in the editor. + WellKnownPathKey.DataSourceProviders, + // We are setting the data sources available for selection in the editor + [BeerReviewCountsByCityMockDataSource.providerId] + ); + + // Setup of the configurator is done here + this.setupConfigurator(); + + // Registering the data source for injection into the Proportional widget. + this.providerRegistry.setProviders({ + [BeerReviewCountsByCityMockDataSource.providerId]: { + provide: DATA_SOURCE, + useClass: BeerReviewCountsByCityMockDataSource, + deps: [], + }, + }); + + this.initializeDashboard(); + } + + /** Used for restoring widgets state */ + public reInitializeDashboard(): void { + // destroys the components and their providers so the dashboard can re init data + this.dashboard = undefined; + this.changeDetectorRef.detectChanges(); + + this.initializeDashboard(); + } + + private initializeDashboard(): void { + // We're using a static configuration object for this example, but this is where + // the widget's configuration could potentially be populated from a database + const widgetIndex: IWidgets = { + // Complete the proportional widget with information coming from its type definition + [widgetConfig.id]: + this.widgetTypesService.mergeWithWidgetType(widgetConfig), + }; + + // Setting the widget dimensions and position (this is for gridster) + const positions: Record = { + [widgetConfig.id]: { + cols: 6, + rows: 6, + y: 0, + x: 0, + }, + }; + + // Finally, assigning the variables we created above to the dashboard + this.dashboard = { + positions, + widgets: widgetIndex, + }; + } + + /** + * Sets up the configurator sections for proportional donut + */ + private setupConfigurator() { + const widgetTemplate = this.widgetTypesService.getWidgetType( + "proportional", + 1 + ); + + // remove old "presentation", "chartOptionsEditor" and "donutContentConfiguration" sections from the configurator + delete widgetTemplate.configurator?.structure?.presentation; + delete widgetTemplate.configurator?.structure?.chartOptionsEditor; + delete widgetTemplate.configurator?.structure + ?.donutContentConfiguration; + + // add new "presentation" section + this.widgetTypesService.setNode( + widgetTemplate, + "configurator", + "presentation", + DONUT_CONTENT_CONFIGURATION_SLICE.presentation + ); + // add new "chartOptionsEditor" section + this.widgetTypesService.setNode( + widgetTemplate, + "configurator", + "chartOptionsEditor", + DONUT_CONTENT_CONFIGURATION_SLICE.chartOptionsEditor + ); + // add new "donutContentConfiguration" section + this.widgetTypesService.setNode( + widgetTemplate, + "configurator", + "donutContentConfiguration", + DONUT_CONTENT_CONFIGURATION_SLICE.donutContentConfiguration + ); + } +} + +const widgetConfig: IWidget = { + id: "proportionalWidgetId", + type: "proportional", + pizzagna: { + [PizzagnaLayer.Configuration]: { + [DEFAULT_PIZZAGNA_ROOT]: { + providers: {}, + }, + header: { + properties: { + title: "Beer Review Tally by City", + subtitle: "These People Love Beer", + }, + }, + chart: { + providers: { + [WellKnownProviders.DataSource]: { + // Setting the data source providerId for the chart + providerId: + BeerReviewCountsByCityMockDataSource.providerId, + } as IProviderConfiguration, + }, + properties: { + configuration: { + chartOptions: { + type: ProportionalWidgetChartTypes.DonutChart, + legendPlacement: LegendPlacement.Right, + // old configuration looks like this + // contentFormatter: { + // componentType: DonutContentSumFormatterComponent.lateLoadKey, + // }, + + // NEW configuration looks like this + donutContentConfig: { + formatter: { + componentType: + SiUnitsFormatterComponent.lateLoadKey, + }, + aggregator: { + aggregatorType: + sumAggregator.aggregatorType, + properties: { + // example of a default metric to be used for the percentage calculation + // activeMetricId: "austin", + }, + }, + } as IDonutContentConfig, + } as IProportionalWidgetChartOptions, + } as IProportionalWidgetConfig, + }, + }, + }, + }, +}; + +export function getMockBeerReviewCountsByCity(): IMockBeerReview[] { + return [ + { + id: "Brno", + name: "Brno", + data: [Math.round(Math.random() * 1000000)], + icon: "status_down", + link: "https://en.wikipedia.org/wiki/Brno", + value: "Brno", + customDonutContent: "Custom Brno", + }, + { + id: "kyiv", + name: "Kyiv", + data: [Math.round(Math.random() * 1000000)], + icon: "status_critical", + link: "https://en.wikipedia.org/wiki/Kyiv", + value: "Kyiv", + customDonutContent: "Custom Kyiv", + }, + { + id: "austin", + name: "Austin", + data: [Math.round(Math.random() * 1000000)], + icon: "status_warning", + link: "https://en.wikipedia.org/wiki/Austin", + value: "Austin", + customDonutContent: "Custom Austin", + }, + { + id: "lisbon", + name: "Lisbon", + data: [Math.round(Math.random() * 1000000)], + icon: "status_unknown", + link: "https://en.wikipedia.org/wiki/Lisbon", + value: "Lisbon", + customDonutContent: "Custom Lisbon", + }, + { + id: "sydney", + name: "Sydney", + data: [Math.round(Math.random() * 1000000)], + icon: "status_up", + link: "https://en.wikipedia.org/wiki/Sydney", + value: "Sydney", + customDonutContent: "Custom Sydney", + }, + { + id: "nur-sultan", + name: "Nur-Sultan", + data: [Math.round(Math.random() * 1000000)], + icon: "status_unmanaged", + link: "https://en.wikipedia.org/wiki/Nur-Sultan", + value: "Nur-Sultan", + customDonutContent: "Custom Nur-Sultan", + }, + ].sort((a, b) => a.data[0] - b.data[0]); +} +\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\`, + "widget-types/proportional/proportional-widget/proportional-widget-example.component.ts": \\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\`// © 2022 SolarWinds Worldwide, LLC. All rights reserved. +// +// Permission is hereby granted, free of charge, to any person obtaining a copy +// of this software and associated documentation files (the "Software"), to +// deal in the Software without restriction, including without limitation the +// rights to use, copy, modify, merge, publish, distribute, sublicense, and/or +// sell copies of the Software, and to permit persons to whom the Software is +// furnished to do so, subject to the following conditions: +// +// The above copyright notice and this permission notice shall be included in +// all copies or substantial portions of the Software. +// +// THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR +// IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, +// FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE +// AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER +// LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, +// OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN +// THE SOFTWARE. + +import { + ChangeDetectorRef, + Component, + Injectable, + OnDestroy, + OnInit, +} from "@angular/core"; +import { GridsterConfig, GridsterItem } from "angular-gridster2"; +import { BehaviorSubject } from "rxjs"; + +import { + DataSourceService, + IDataSource, + IFilteringOutputs, +} from "@nova-ui/bits"; +import { + DATA_SOURCE, + DEFAULT_PIZZAGNA_ROOT, + IDashboard, + IProportionalWidgetChartOptions, + IProportionalWidgetConfig, + IProportionalWidgetData, + IProviderConfiguration, + IRefresherProperties, + IWidget, + IWidgets, + LegendPlacement, + PizzagnaLayer, + ProportionalWidgetChartTypes, + ProviderRegistryService, + WellKnownPathKey, + WellKnownProviders, + WidgetTypesService, +} from "@nova-ui/dashboards"; + +import { IMockBeerReview } from "../models"; + +/** + * A simple proportional data source to retrieve beer review counts by city + */ +@Injectable() +export class BeerReviewCountsByCityMockDataSource + extends DataSourceService + implements IDataSource, OnDestroy +{ + // This is the ID we'll use to identify the provider + public static providerId = "BeerReviewCountsByCityMockDataSource"; + public busy = new BehaviorSubject(false); + + public async getFilteredData(): Promise { + this.busy.next(true); + return new Promise((resolve) => { + setTimeout(() => { + this.outputsSubject.next({ + result: getMockBeerReviewCountsByCity(), + }); + this.busy.next(false); + }, 300); + }); + } + + public ngOnDestroy(): void { + this.outputsSubject.complete(); + } +} + +/** + * A component that instantiates the dashboard + */ +@Component({ + selector: "proportional-widget-example", + templateUrl: "./proportional-widget-example.component.html", + styleUrls: ["./proportional-widget-example.component.less"], + standalone: false, +}) +export class ProportionalWidgetExampleComponent implements OnInit { + // This variable will hold all the data needed to define the layout and behavior of the widgets. + // Pass this to the dashboard component's dashboard input in the template. + public dashboard: IDashboard | undefined; + + // Angular gridster requires a configuration object even if it's empty. + // Pass this to the dashboard component's gridsterConfig input in the template. + public gridsterConfig: GridsterConfig = {}; + + // Boolean passed as an input to the dashboard. When true, widgets can be moved, resized, removed, or edited + public editMode: boolean = false; + + constructor( + // WidgetTypesService provides the widget's necessary structure information + private widgetTypesService: WidgetTypesService, + // In general, the ProviderRegistryService is used for making entities available for injection into dynamically loaded components. + private providerRegistry: ProviderRegistryService, + private changeDetectorRef: ChangeDetectorRef + ) {} + + public ngOnInit(): void { + // Grabbing the widget's default template which will be needed as a parameter for setNode + const widgetTemplate = this.widgetTypesService.getWidgetType( + "proportional", + 1 + ); + + // Registering our data sources as dropdown options in the widget editor/configurator + // Note: This could also be done in the parent module's constructor so that + // multiple dashboards could have access to the same widget template modification. + this.widgetTypesService.setNode( + // This is the template we grabbed above with getWidgetType + widgetTemplate, + // We are setting the editor/configurator part of the widget template + "configurator", + // This indicates which node you are changing and we want to change + // the data source providers available for selection in the editor. + WellKnownPathKey.DataSourceProviders, + // We are setting the data sources available for selection in the editor + [BeerReviewCountsByCityMockDataSource.providerId] + ); + + // Registering the data source for injection into the Proportional widget. + this.providerRegistry.setProviders({ + [BeerReviewCountsByCityMockDataSource.providerId]: { + provide: DATA_SOURCE, + useClass: BeerReviewCountsByCityMockDataSource, + deps: [], + }, + }); + + this.initializeDashboard(); + } + + /** Used for restoring widgets state */ + public reInitializeDashboard(): void { + // destroys the components and their providers so the dashboard can re init data + this.dashboard = undefined; + this.changeDetectorRef.detectChanges(); + + this.initializeDashboard(); + } + + public initializeDashboard(): void { + // We're using a static configuration object for this example, but this is where + // the widget's configuration could potentially be populated from a database + const widgetIndex: IWidgets = { + // Complete the proportional widget with information coming from its type definition + [widgetConfig.id]: + this.widgetTypesService.mergeWithWidgetType(widgetConfig), + }; + + // Setting the widget dimensions and position (this is for gridster) + const positions: Record = { + [widgetConfig.id]: { + cols: 5, + rows: 6, + y: 0, + x: 0, + }, + }; + + // Finally, assigning the variables we created above to the dashboard + this.dashboard = { + positions, + widgets: widgetIndex, + }; + } +} + +const widgetConfig: IWidget = { + id: "proportionalWidgetId", + type: "proportional", + pizzagna: { + [PizzagnaLayer.Configuration]: { + [DEFAULT_PIZZAGNA_ROOT]: { + providers: { + [WellKnownProviders.Refresher]: { + properties: { + // Configuring the refresher interval so that our data source is invoked every ten minutes + interval: 60 * 10, + enabled: true, + } as IRefresherProperties, + } as Partial, + }, + }, + header: { + properties: { + title: "Beer Review Tally by City", + subtitle: "These People Love Beer", + }, + }, + chart: { + providers: { + [WellKnownProviders.DataSource]: { + // Setting the data source providerId for the chart + providerId: + BeerReviewCountsByCityMockDataSource.providerId, + } as IProviderConfiguration, + }, + properties: { + configuration: { + chartOptions: { + type: ProportionalWidgetChartTypes.DonutChart, + legendPlacement: LegendPlacement.Right, + } as IProportionalWidgetChartOptions, + // You can optionally define custom colors for the chart by setting the 'chartColors' configuration property + // "chartColors": [ + // "var(--nui-color-chart-five)", + // "var(--nui-color-chart-six)", + // "var(--nui-color-chart-seven)", + // "var(--nui-color-chart-eight)", + // "var(--nui-color-chart-nine)", + // "var(--nui-color-chart-ten)", + // ], + // or use-mapped structure + chartColors: { + Brno: "var(--nui-color-chart-five)", + kyiv: "var(--nui-color-chart-six)", + austin: "var(--nui-color-chart-seven)", + lisbon: "var(--nui-color-chart-eight)", + sydney: "var(--nui-color-chart-nine)", + "nur-sultan": "var(--nui-color-chart-ten)", + }, + prioritizeWidgetColors: false, + } as IProportionalWidgetConfig, + }, + }, + }, + }, +}; + +export function getMockBeerReviewCountsByCity(): IMockBeerReview[] { + return [ + { + id: "Brno", + name: "Brno", + data: [Math.round(Math.random() * 100000)], + icon: "status_down", + link: "https://en.wikipedia.org/wiki/Brno", + value: "Brno", + color: "var(--nui-color-chart-one)", + }, + { + id: "kyiv", + name: "Kyiv", + data: [Math.round(Math.random() * 100000)], + icon: "status_critical", + link: "https://en.wikipedia.org/wiki/Kyiv", + value: "Kyiv", + color: "var(--nui-color-chart-two)", + }, + { + id: "austin", + name: "Austin", + data: [Math.round(Math.random() * 100000)], + icon: "status_warning", + link: "https://en.wikipedia.org/wiki/Austin", + value: "Austin", + color: "var(--nui-color-chart-three)", + }, + { + id: "lisbon", + name: "Lisbon", + data: [Math.round(Math.random() * 100000)], + icon: "status_unknown", + link: "https://en.wikipedia.org/wiki/Lisbon", + value: "Lisbon", + color: "var(--nui-color-chart-four)", + }, + { + id: "sydney", + name: "Sydney", + data: [Math.round(Math.random() * 100000)], + icon: "status_up", + link: "https://en.wikipedia.org/wiki/Sydney", + value: "Sydney", + color: "var(--nui-color-chart-five)", + }, + { + id: "nur-sultan", + name: "Nur-Sultan", + data: [Math.round(Math.random() * 100000)], + icon: "status_unmanaged", + link: "https://en.wikipedia.org/wiki/Nur-Sultan", + value: "Nur-Sultan", + color: "var(--nui-color-chart-six)", + }, + ].sort((a, b) => a.data[0] - b.data[0]); +} +\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\`, + "widget-types/proportional/proportional-widget-interactive/proportional-widget-interactive-example.component.ts": \\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\`// © 2022 SolarWinds Worldwide, LLC. All rights reserved. +// +// Permission is hereby granted, free of charge, to any person obtaining a copy +// of this software and associated documentation files (the "Software"), to +// deal in the Software without restriction, including without limitation the +// rights to use, copy, modify, merge, publish, distribute, sublicense, and/or +// sell copies of the Software, and to permit persons to whom the Software is +// furnished to do so, subject to the following conditions: +// +// The above copyright notice and this permission notice shall be included in +// all copies or substantial portions of the Software. +// +// THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR +// IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, +// FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE +// AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER +// LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, +// OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN +// THE SOFTWARE. + +import { + ChangeDetectorRef, + Component, + Injectable, + OnDestroy, + OnInit, +} from "@angular/core"; +import { GridsterConfig, GridsterItem } from "angular-gridster2"; +import keyBy from "lodash/keyBy"; +import { BehaviorSubject } from "rxjs"; + +import { + DataSourceService, + IDataSource, + IFilteringOutputs, +} from "@nova-ui/bits"; +import { + DATA_SOURCE, + DEFAULT_PIZZAGNA_ROOT, + IDashboard, + IProportionalWidgetChartOptions, + IProportionalWidgetConfig, + IProportionalWidgetData, + IProviderConfiguration, + IWidget, + LegendPlacement, + NOVA_URL_INTERACTION_HANDLER, + PizzagnaLayer, + ProportionalWidgetChartTypes, + ProviderRegistryService, + WellKnownPathKey, + WellKnownProviders, + WidgetTypesService, +} from "@nova-ui/dashboards"; + +import { IMockBeerReview } from "../models"; + +/** + * A simple proportional data source to retrieve beer review counts by city + */ +@Injectable() +export class ReviewCountsByCityMockDataSource + extends DataSourceService + implements IDataSource, OnDestroy +{ + // This is the ID we'll use to identify the provider + public static providerId = "ReviewCountsByCityMockDataSource"; + public busy = new BehaviorSubject(false); + + public async getFilteredData(): Promise { + this.busy.next(true); + return new Promise((resolve) => { + setTimeout(() => { + this.outputsSubject.next({ + result: getMockBeerReviewCountsByCity(), + }); + this.busy.next(false); + }, 300); + }); + } + + public ngOnDestroy(): void { + this.outputsSubject.complete(); + } +} + +/** + * A component that instantiates the dashboard + */ +@Component({ + selector: "proportional-widget-interactive-example", + templateUrl: "./proportional-widget-interactive-example.component.html", + styleUrls: ["./proportional-widget-interactive-example.component.less"], + standalone: false, +}) +export class ProportionalWidgetInteractiveExampleComponent implements OnInit { + // This variable will hold all the data needed to define the layout and behavior of the widgets. + // Pass this to the dashboard component's dashboard input in the template. + public dashboard: IDashboard | undefined; + + // Angular gridster requires a configuration object even if it's empty. + // Pass this to the dashboard component's gridsterConfig input in the template. + public gridsterConfig: GridsterConfig = {}; + + // Boolean passed as an input to the dashboard. When true, widgets can be moved, resized, removed, or edited + public editMode: boolean = false; + + constructor( + // WidgetTypesService provides the widget's necessary structure information + private widgetTypesService: WidgetTypesService, + // In general, the ProviderRegistryService is used for making entities available for injection into dynamically loaded components. + private providerRegistry: ProviderRegistryService, + private changeDetectorRef: ChangeDetectorRef + ) {} + + public ngOnInit(): void { + // Grabbing the widget's default template which will be needed as a parameter for setNode + const widgetTemplate = this.widgetTypesService.getWidgetType( + "proportional", + 1 + ); + + // Registering our data sources as dropdown options in the widget editor/configurator + // Note: This could also be done in the parent module's constructor so that + // multiple dashboards could have access to the same widget template modification. + this.widgetTypesService.setNode( + // This is the template we grabbed above with getWidgetType + widgetTemplate, + // We are setting the editor/configurator part of the widget template + "configurator", + // This indicates which node you are changing and we want to change + // the data source providers available for selection in the editor. + WellKnownPathKey.DataSourceProviders, + // We are setting the data sources available for selection in the editor + [ReviewCountsByCityMockDataSource.providerId] + ); + + // Registering the data source for injection into the Proportional widget. + this.providerRegistry.setProviders({ + [ReviewCountsByCityMockDataSource.providerId]: { + provide: DATA_SOURCE, + useClass: ReviewCountsByCityMockDataSource, + deps: [], + }, + }); + + this.initializeDashboard(); + } + + /** Used for restoring widgets state */ + public reInitializeDashboard(): void { + // destroys the components and their providers so the dashboard can re init data + this.dashboard = undefined; + this.changeDetectorRef.detectChanges(); + + this.initializeDashboard(); + } + + public initializeDashboard(): void { + // We're using a static configuration object for this example, but this is where + // the widget's configuration could potentially be populated from a database + const widgetsWithStructure = widgetConfigs.map((w) => + this.widgetTypesService.mergeWithWidgetType(w) + ); + const widgetsIndex = keyBy(widgetsWithStructure, (w: IWidget) => w.id); + + // Setting the widget dimensions and position (this is for gridster) + const positions: Record = { + [widgetConfigs[0].id]: { + cols: 6, + rows: 6, + y: 0, + x: 0, + }, + [widgetConfigs[1].id]: { + cols: 6, + rows: 6, + y: 0, + x: 6, + }, + }; + + // Finally, assigning the variables we created above to the dashboard + this.dashboard = { + positions, + widgets: widgetsIndex, + }; + } +} + +const widgetConfigs: IWidget[] = [ + { + id: "widget1", + type: "proportional", + pizzagna: { + [PizzagnaLayer.Configuration]: { + [DEFAULT_PIZZAGNA_ROOT]: { + providers: { + // Configuring the UrlInteractionHandler to handle interactions + [WellKnownProviders.InteractionHandler]: { + providerId: NOVA_URL_INTERACTION_HANDLER, + properties: { + // the 'url' property tells the handler what link to use when interaction occurs on the series + // if the series does not have a link we are passing one to the handler + url: "\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\${data.link || 'https://en.wikipedia.org/wiki/'+data.id}", + // by default the link is opened in the current window, set 'newWindow' to true to open in a new tab instead + // newWindow: true, + }, + }, + }, + }, + header: { + properties: { + title: "Proportional Widget", + subtitle: "With interaction handler", + }, + }, + chart: { + providers: { + [WellKnownProviders.DataSource]: { + // Setting the data source providerId for the chart + providerId: + ReviewCountsByCityMockDataSource.providerId, + } as IProviderConfiguration, + }, + properties: { + configuration: { + // Setting the interactive to true + interactive: true, + chartOptions: { + type: ProportionalWidgetChartTypes.VerticalBarChart, + legendPlacement: LegendPlacement.Bottom, + } as IProportionalWidgetChartOptions, + prioritizeWidgetColors: false, + } as IProportionalWidgetConfig, + }, + }, + }, + }, + }, + { + id: "widget2", + type: "proportional", + pizzagna: { + [PizzagnaLayer.Configuration]: { + header: { + properties: { + title: "Proportional Widget", + subtitle: "Without interaction handler", + }, + }, + chart: { + providers: { + [WellKnownProviders.DataSource]: { + // Setting the data source providerId for the chart + providerId: + ReviewCountsByCityMockDataSource.providerId, + } as IProviderConfiguration, + }, + properties: { + configuration: { + // interactive set to false so series without links are not styled like a link + interactive: false, + chartOptions: { + type: ProportionalWidgetChartTypes.HorizontalBarChart, + legendPlacement: LegendPlacement.Bottom, + } as IProportionalWidgetChartOptions, + prioritizeWidgetColors: false, + } as IProportionalWidgetConfig, + }, + }, + }, + }, + }, +]; + +export function getMockBeerReviewCountsByCity(): IMockBeerReview[] { + return [ + { + id: "Brno", + name: "Brno", + data: [Math.round(Math.random() * 100000)], + icon: "status_down", + link: "https://en.wikipedia.org/wiki/Brno", + value: "Brno", + color: "var(--nui-color-chart-one)", + }, + { + id: "kyiv", + name: "Kyiv", + data: [Math.round(Math.random() * 100000)], + icon: "status_critical", + link: "https://en.wikipedia.org/wiki/Kyiv", + value: "Kyiv", + color: "var(--nui-color-chart-two)", + }, + { + id: "austin", + name: "Austin", + data: [Math.round(Math.random() * 100000)], + icon: "status_warning", + value: "Austin", + color: "var(--nui-color-chart-three)", + }, + { + id: "lisbon", + name: "Lisbon", + data: [Math.round(Math.random() * 100000)], + icon: "status_unknown", + link: "https://en.wikipedia.org/wiki/Lisbon", + value: "Lisbon", + color: "var(--nui-color-chart-four)", + }, + { + id: "sydney", + name: "Sydney", + data: [Math.round(Math.random() * 100000)], + icon: "status_up", + value: "Sydney", + color: "var(--nui-color-chart-five)", + }, + { + id: "nur-sultan", + name: "Nur-Sultan", + data: [Math.round(Math.random() * 100000)], + icon: "status_unmanaged", + value: "Nur-Sultan", + color: "var(--nui-color-chart-six)", + }, + ].sort((a, b) => a.data[0] - b.data[0]); +} +\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\`, + "widget-types/risk-score/risk-score-docs.component.ts": \\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\`// © 2023 SolarWinds Worldwide, LLC. All rights reserved. +// +// Permission is hereby granted, free of charge, to any person obtaining a copy +// of this software and associated documentation files (the "Software"), to +// deal in the Software without restriction, including without limitation the +// rights to use, copy, modify, merge, publish, distribute, sublicense, and/or +// sell copies of the Software, and to permit persons to whom the Software is +// furnished to do so, subject to the following conditions: +// +// The above copyright notice and this permission notice shall be included in +// all copies or substantial portions of the Software. +// +// THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR +// IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, +// FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE +// AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER +// LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, +// OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN +// THE SOFTWARE. + +import { Component, OnInit } from "@angular/core"; + +import { mapContentFile } from "../../../../demo-files-factory"; + +@Component({ + selector: "nui-risk-score-docs", + templateUrl: "./risk-score-docs.component.html", + standalone: false, +}) +export class RiskScoreDocsComponent implements OnInit { + public riskScoreWidgetFileText = ""; + public riskScoreConfiguratorFileText = ""; + + public async ngOnInit(): Promise { + this.riskScoreWidgetFileText = await import( + "./../../../../../../src/lib/widget-types/risk-score/risk-score-widget" + ).then(mapContentFile); + this.riskScoreConfiguratorFileText = await import( + "./../../../../../../src/lib/widget-types/risk-score/risk-score-configurator" + ).then(mapContentFile); + } +} +\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\`, + "widget-types/risk-score/risk-score-docs.module.ts": \\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\`// © 2023 SolarWinds Worldwide, LLC. All rights reserved. +// +// Permission is hereby granted, free of charge, to any person obtaining a copy +// of this software and associated documentation files (the "Software"), to +// deal in the Software without restriction, including without limitation the +// rights to use, copy, modify, merge, publish, distribute, sublicense, and/or +// sell copies of the Software, and to permit persons to whom the Software is +// furnished to do so, subject to the following conditions: +// +// The above copyright notice and this permission notice shall be included in +// all copies or substantial portions of the Software. +// +// THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR +// IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, +// FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE +// AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER +// LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, +// OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN +// THE SOFTWARE. + +import { NgModule } from "@angular/core"; +import { RouterModule, Routes } from "@angular/router"; + +import { + DEMO_PATH_TOKEN, + NuiButtonModule, + NuiDocsModule, + NuiMessageModule, + NuiSwitchModule, +} from "@nova-ui/bits"; +import { NuiDashboardsModule } from "@nova-ui/dashboards"; + +import { RiskScoreDocsComponent } from "./risk-score-docs.component"; +import { RiskScoreWidgetExampleComponent } from "./risk-score-widget-example/risk-score-widget-example.component"; +import { getDemoFiles } from "../../../../demo-files-factory"; + +const routes: Routes = [ + { + path: "", + component: RiskScoreDocsComponent, + data: { + srlc: { + hideIndicator: true, + }, + showThemeSwitcher: true, + }, + }, + { + path: "example", + component: RiskScoreWidgetExampleComponent, + data: { + srlc: { + hideIndicator: true, + }, + }, + }, +]; + +@NgModule({ + imports: [ + RouterModule.forChild(routes), + NuiButtonModule, + NuiDocsModule, + NuiMessageModule, + NuiDashboardsModule, + NuiSwitchModule, + ], + declarations: [RiskScoreDocsComponent, RiskScoreWidgetExampleComponent], + providers: [ + { + provide: DEMO_PATH_TOKEN, + useValue: getDemoFiles("risk-score"), + }, + ], +}) +export default class RiskScoreDocsModule {} +\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\`, + "widget-types/risk-score/risk-score-widget-example/risk-score-widget-example.component.ts": \\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\`// © 2023 SolarWinds Worldwide, LLC. All rights reserved. +// +// Permission is hereby granted, free of charge, to any person obtaining a copy +// of this software and associated documentation files (the "Software"), to +// deal in the Software without restriction, including without limitation the +// rights to use, copy, modify, merge, publish, distribute, sublicense, and/or +// sell copies of the Software, and to permit persons to whom the Software is +// furnished to do so, subject to the following conditions: +// +// The above copyright notice and this permission notice shall be included in +// all copies or substantial portions of the Software. +// +// THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR +// IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, +// FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE +// AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER +// LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, +// OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN +// THE SOFTWARE. + +import { HttpClient, HttpErrorResponse } from "@angular/common/http"; +import { Component, Injectable, OnDestroy, OnInit } from "@angular/core"; +import { GridsterConfig, GridsterItem } from "angular-gridster2"; +import { BehaviorSubject } from "rxjs"; +import { finalize } from "rxjs/operators"; + +import { DataSourceService, IFilteringOutputs } from "@nova-ui/bits"; +import { + DATA_SOURCE, + DEFAULT_PIZZAGNA_ROOT, + IDashboard, + IRiskScoreData, + IProviderConfiguration, + IRefresherProperties, + IWidget, + IWidgets, + RiskScoreTileComponent, + NOVA_KPI_DATASOURCE_ADAPTER, + PizzagnaLayer, + ProviderRegistryService, + WellKnownPathKey, + WellKnownProviders, + WidgetTypesService, +} from "@nova-ui/dashboards"; + +/** + * A simple KPI data source to retrieve the average rating of Harry Potter and the Sorcerer's Stone (book) via googleapis + */ +@Injectable() +export class AverageRatingRiskScoreDataSource + extends DataSourceService + implements OnDestroy +{ + // This is the ID we'll use to identify the provider + public static providerId = "AverageRatingRiskScoreDataSource"; + + // Use this subject to communicate the data source's busy state + public busy = new BehaviorSubject(false); + + constructor(private http: HttpClient) { + super(); + } + + // In this example, getFilteredData is invoked every 10 minutes (Take a look at the refresher + // provider definition in the widget configuration below to see how the interval is set) + public async getFilteredData(): Promise { + this.busy.next(true); + return new Promise((resolve) => { + // *** Make a resource request to an external API (if needed) + this.http + .get("https://www.googleapis.com/books/v1/volumes/5MQFrgEACAAJ") + .pipe(finalize(() => this.busy.next(false))) + .subscribe({ + next: (data: any) => { + resolve({ + result: { + value: data.volumeInfo.averageRating, + }, + }); + }, + error: (error: HttpErrorResponse) => { + resolve({ + result: null, + error: { + type: error.status, + }, + }); + }, + }); + }); + } + + public ngOnDestroy(): void { + this.outputsSubject.complete(); + } +} + +/** + * A component that instantiates the dashboard + */ +@Component({ + selector: "risk-score-widget-example", + templateUrl: "./risk-score-widget-example.component.html", + styleUrls: ["./risk-score-widget-example.component.less"], + standalone: false, +}) +export class RiskScoreWidgetExampleComponent implements OnInit { + // This variable will hold all the data needed to define the layout and behavior of the widgets. + // Pass this to the dashboard component's dashboard input in the template. + public dashboard: IDashboard; + + // Angular gridster requires a configuration object even if it's empty. + // Pass this to the dashboard component's gridsterConfig input in the template. + public gridsterConfig: GridsterConfig = {}; + + // Boolean passed as an input to the dashboard. When true, widgets can be moved, resized, removed, or edited + public editMode: boolean = false; + + constructor( + // WidgetTypesService provides the widget's necessary structure information + private widgetTypesService: WidgetTypesService, + + // In general, the ProviderRegistryService is used for making entities available for injection into dynamically loaded components. + private providerRegistry: ProviderRegistryService + ) {} + + public ngOnInit(): void { + // Grabbing the widget's default template which will be needed as a parameter for setNode + const widgetTemplate = this.widgetTypesService.getWidgetType( + "risk-score", + 1 + ); + // Registering our data sources as dropdown options in the widget editor/configurator + // Note: This could also be done in the parent module's constructor so that + // multiple dashboards could have access to the same widget template modification. + this.widgetTypesService.setNode( + // This is the template we grabbed above with getWidgetType + widgetTemplate, + // We are setting the editor/configurator part of the widget template + "configurator", + // This indicates which node you are changing and we want to change + // the data source providers available for selection in the editor. + WellKnownPathKey.DataSourceProviders, + // We are setting the data sources available for selection in the editor + [AverageRatingRiskScoreDataSource.providerId] + ); + + // Registering the data source for injection into the KPI tile. + // Note: Each tile of a KPI widget is assigned its own instance of the data source + this.providerRegistry.setProviders({ + [AverageRatingRiskScoreDataSource.providerId]: { + provide: DATA_SOURCE, + useClass: AverageRatingRiskScoreDataSource, + // Any dependencies that need to be injected into the provider must be listed here + deps: [HttpClient], + }, + }); + + this.initializeDashboard(); + } + + public initializeDashboard(): void { + // We're using a static configuration object for this example, but this is where + // the widget's configuration could potentially be populated from a database + const riskScoreWidget = widgetConfig; + const widgetIndex: IWidgets = { + // Complete the KPI widget with information coming from its type definition + [riskScoreWidget.id]: + this.widgetTypesService.mergeWithWidgetType(riskScoreWidget), + }; + + // Setting the widget dimensions and position (this is for gridster) + const positions: Record = { + [riskScoreWidget.id]: { + cols: 4, + rows: 6, + y: 0, + x: 0, + }, + }; + + // Finally, assigning the variables we created above to the dashboard + this.dashboard = { + positions, + widgets: widgetIndex, + }; + } +} + +const widgetConfig: IWidget = { + id: "riskScoreWidgetId", + type: "risk-score", + pizzagna: { + [PizzagnaLayer.Configuration]: { + [DEFAULT_PIZZAGNA_ROOT]: { + providers: { + [WellKnownProviders.Refresher]: { + properties: { + // Configuring the refresher interval so that our data source is invoked every ten minutes + interval: 60 * 10, + enabled: true, + } as IRefresherProperties, + } as Partial, + }, + }, + header: { + properties: { + title: "Harry Potter and the Sorcerer's Stone", + subtitle: "By J. K. Rowling", + }, + }, + tiles: { + properties: { + nodes: ["riskScore1"], + }, + }, + riskScore1: { + id: "riskScore1", + componentType: RiskScoreTileComponent.lateLoadKey, + properties: { + widgetData: { + minValue: 0, + maxValue: 5, + useStaticLabel: false, + staticLabel: undefined, + label: \\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\`Average Rating\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\`, + description: \\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\`Harry Potter and the Sorcerer's Stone By J. K. Rowling Average Rating Risk Score\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\`, + }, + }, + providers: { + [WellKnownProviders.DataSource]: { + // Setting the data source providerId for the tile with id "kpi1" + providerId: AverageRatingRiskScoreDataSource.providerId, + } as IProviderConfiguration, + [WellKnownProviders.Adapter]: { + providerId: NOVA_KPI_DATASOURCE_ADAPTER, + properties: { + componentId: "riskScore1", + propertyPath: "widgetData", + }, + } as IProviderConfiguration, + }, + }, + }, + }, +}; +\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\`, + "widget-types/table/table-docs.component.ts": \\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\`// © 2022 SolarWinds Worldwide, LLC. All rights reserved. +// +// Permission is hereby granted, free of charge, to any person obtaining a copy +// of this software and associated documentation files (the "Software"), to +// deal in the Software without restriction, including without limitation the +// rights to use, copy, modify, merge, publish, distribute, sublicense, and/or +// sell copies of the Software, and to permit persons to whom the Software is +// furnished to do so, subject to the following conditions: +// +// The above copyright notice and this permission notice shall be included in +// all copies or substantial portions of the Software. +// +// THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR +// IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, +// FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE +// AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER +// LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, +// OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN +// THE SOFTWARE. + +import { Component, OnInit } from "@angular/core"; + +import { mapContentFile } from "../../../../demo-files-factory"; + +@Component({ + selector: "nui-table-docs", + templateUrl: "./table-docs.component.html", + standalone: false, +}) +export class TableDocsComponent implements OnInit { + public widgetFileText = ""; + public configuratorFileText = ""; + + public async ngOnInit(): Promise { + this.widgetFileText = await import( + "./../../../../../../src/lib/widget-types/table/table-widget" + ).then(mapContentFile); + this.configuratorFileText = await import( + "./../../../../../../src/lib/widget-types/table/table-configurator" + ).then(mapContentFile); + } +} +\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\`, + "widget-types/table/table-docs.module.ts": \\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\`// © 2022 SolarWinds Worldwide, LLC. All rights reserved. +// +// Permission is hereby granted, free of charge, to any person obtaining a copy +// of this software and associated documentation files (the "Software"), to +// deal in the Software without restriction, including without limitation the +// rights to use, copy, modify, merge, publish, distribute, sublicense, and/or +// sell copies of the Software, and to permit persons to whom the Software is +// furnished to do so, subject to the following conditions: +// +// The above copyright notice and this permission notice shall be included in +// all copies or substantial portions of the Software. +// +// THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR +// IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, +// FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE +// AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER +// LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, +// OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN +// THE SOFTWARE. + +import { NgModule } from "@angular/core"; +import { RouterModule, Routes } from "@angular/router"; + +import { DEMO_PATH_TOKEN } from "@nova-ui/bits"; +import { + NuiButtonModule, + NuiDocsModule, + NuiMessageModule, + NuiSwitchModule, +} from "@nova-ui/bits"; +import { + NuiDashboardsModule, + TableFormatterRegistryService, +} from "@nova-ui/dashboards"; + +import { TableDocsComponent } from "./table-docs.component"; +import { TablePaginatorDocsComponent } from "./table-paginator-docs.component"; +import { TableSelectableDocsComponent } from "./table-selectable-docs.component"; +import { TableWidgetExampleComponent } from "./table-widget/table-widget-example.component"; +import { TableWidgetInteractiveExampleComponent } from "./table-widget-interactive/table-widget-interactive-example.component"; +import { TableWidgetPaginatorExampleComponent } from "./table-widget-paginator/table-widget-paginator-example.component"; +import { TableWidgetSearchExampleComponent } from "./table-widget-search/table-widget-search-example.component"; +import { TableSearchDocsComponent } from "./table-widget-search-docs.component"; +import { TableWidgetSelectableMultiExampleComponent } from "./table-widget-selectable/table-widget-selectable-multi/table-widget-selectable-multi.example.component"; +import { TableWidgetSelectableRadioExampleComponent } from "./table-widget-selectable/table-widget-selectable-radio/table-widget-selectable-radio.example.component"; +import { TableWidgetSelectableSingleExampleComponent } from "./table-widget-selectable/table-widget-selectable-single/table-widget-selectable-single.example.component"; +import { TableWidgetSelectableExampleComponent } from "./table-widget-selectable/table-widget-selectable.example.component"; +import { DEFAULT_TABLE_FORMATTERS } from "../../../../../../src/lib/widget-types/table/default-table-formatters"; +import { getDemoFiles } from "../../../../demo-files-factory"; + +const routes: Routes = [ + { + path: "", + component: TableDocsComponent, + data: { + srlc: { + hideIndicator: true, + }, + showThemeSwitcher: true, + }, + }, + { + path: "example", + component: TableWidgetExampleComponent, + data: { + srlc: { + hideIndicator: true, + }, + }, + }, + { + path: "table-search", + component: TableSearchDocsComponent, + data: { + srlc: { + hideIndicator: true, + }, + showThemeSwitcher: true, + }, + }, + { + path: "table-paginator", + component: TablePaginatorDocsComponent, + data: { + srlc: { + hideIndicator: true, + }, + showThemeSwitcher: true, + }, + }, + { + path: "table-select", + component: TableSelectableDocsComponent, + data: { + srlc: { + hideIndicator: true, + }, + showThemeSwitcher: true, + }, + }, +]; + +@NgModule({ + imports: [ + RouterModule.forChild(routes), + NuiButtonModule, + NuiDocsModule, + NuiMessageModule, + NuiSwitchModule, + NuiDashboardsModule, + ], + declarations: [ + TableDocsComponent, + TableSearchDocsComponent, + TablePaginatorDocsComponent, + TableWidgetPaginatorExampleComponent, + TableSelectableDocsComponent, + TableWidgetInteractiveExampleComponent, + TableWidgetExampleComponent, + TableWidgetSearchExampleComponent, + TableWidgetSelectableExampleComponent, + TableWidgetSelectableMultiExampleComponent, + TableWidgetSelectableSingleExampleComponent, + TableWidgetSelectableRadioExampleComponent, + ], + providers: [ + { + provide: DEMO_PATH_TOKEN, + useValue: getDemoFiles("table"), + }, + ], +}) +export default class TableDocsModule { + constructor(tableFormattersRegistryService: TableFormatterRegistryService) { + tableFormattersRegistryService.addItems(DEFAULT_TABLE_FORMATTERS); + } +} +\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\`, + "widget-types/table/table-paginator-docs.component.ts": \\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\`// © 2022 SolarWinds Worldwide, LLC. All rights reserved. +// +// Permission is hereby granted, free of charge, to any person obtaining a copy +// of this software and associated documentation files (the "Software"), to +// deal in the Software without restriction, including without limitation the +// rights to use, copy, modify, merge, publish, distribute, sublicense, and/or +// sell copies of the Software, and to permit persons to whom the Software is +// furnished to do so, subject to the following conditions: +// +// The above copyright notice and this permission notice shall be included in +// all copies or substantial portions of the Software. +// +// THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR +// IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, +// FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE +// AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER +// LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, +// OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN +// THE SOFTWARE. + +import { Component } from "@angular/core"; + +@Component({ + selector: "nui-table-paginator-docs", + templateUrl: "./table-paginator-docs.component.html", + standalone: false, +}) +export class TablePaginatorDocsComponent { + public tableConfigurationText = \\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\` + "table": { + ... + properties: { + configuration: { + // define paginator configuration here + scrollType: ScrollType.paginator, + paginatorConfiguration: { + pageSize: 10, // Value have to be one of pageSizeSet values + pageSizeSet: [10, 20, 30], + }, + // If not specified, default is set to + // pageSize: 10, + // pageSizeSet: [10, 20, 50], + hasVirtualScroll: false, // Has to be speciefied because of backward compatibility + } as ITableWidgetConfig, + }, + }, + \\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\`; +} +\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\`, + "widget-types/table/table-selectable-docs.component.ts": \\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\`// © 2022 SolarWinds Worldwide, LLC. All rights reserved. +// +// Permission is hereby granted, free of charge, to any person obtaining a copy +// of this software and associated documentation files (the "Software"), to +// deal in the Software without restriction, including without limitation the +// rights to use, copy, modify, merge, publish, distribute, sublicense, and/or +// sell copies of the Software, and to permit persons to whom the Software is +// furnished to do so, subject to the following conditions: +// +// The above copyright notice and this permission notice shall be included in +// all copies or substantial portions of the Software. +// +// THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR +// IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, +// FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE +// AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER +// LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, +// OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN +// THE SOFTWARE. + +import { Component } from "@angular/core"; + +@Component({ + selector: "nui-table-selectable-docs", + templateUrl: "./table-selectable-docs.component.html", + standalone: false, +}) +export class TableSelectableDocsComponent { + public tableConfigurationText = \\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\` + "table": { + ... + properties: { + // enabling selection here + selectionConfiguration: { + // whether the selection is enabled or disabled + enabled: true, + // can be Multi | Radio | Single + selectionMode: TableSelectionMode.Multi, + // property that uniquely identifies row in a table + trackByProperty: "id", + // whether clicking on row should select it + clickableRow: true, + }, + }, + }, + \\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\`; + + public eventSubscriptionText = \\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\` +... +constructor(Inject(PIZZAGNA_EVENT_BUS) eventBus: EventBus) { + eventBus + .getStream(SELECTION) + // don't forget to unsubscribe! + .subscribe((selection: ISelection) => ...) +} +... + \\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\`; +} +\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\`, + "widget-types/table/table-widget/table-widget-example.component.ts": \\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\`// © 2022 SolarWinds Worldwide, LLC. All rights reserved. +// +// Permission is hereby granted, free of charge, to any person obtaining a copy +// of this software and associated documentation files (the "Software"), to +// deal in the Software without restriction, including without limitation the +// rights to use, copy, modify, merge, publish, distribute, sublicense, and/or +// sell copies of the Software, and to permit persons to whom the Software is +// furnished to do so, subject to the following conditions: +// +// The above copyright notice and this permission notice shall be included in +// all copies or substantial portions of the Software. +// +// THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR +// IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, +// FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE +// AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER +// LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, +// OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN +// THE SOFTWARE. + +import { ChangeDetectorRef, Component, OnInit } from "@angular/core"; +import { GridsterConfig, GridsterItem } from "angular-gridster2"; +import orderBy from "lodash/orderBy"; +import { BehaviorSubject, firstValueFrom, from } from "rxjs"; +import { map, tap } from "rxjs/operators"; + +import { + DataSourceService, + IDataField, + INovaFilteringOutputs, + INovaFilters, + nameof, +} from "@nova-ui/bits"; +import { + DATA_SOURCE, + IDashboard, + ITableWidgetColumnConfig, + IWidget, + IWidgets, + ProviderRegistryService, + WellKnownPathKey, + WellKnownProviders, + WidgetTypesService, +} from "@nova-ui/dashboards"; + +export const BREW_API_URL = "https://api.punkapi.com/v2/beers"; + +export interface IBrewInfo { + id: string; + name: string; + tagline: string; + first_brewed: string; + description: string; + brewers_tips: string; +} + +export interface IBrewDatasourceResponse { + brewInfo: IBrewInfo[]; + total: number; +} + +export class BeerDataSource extends DataSourceService { + public static providerId = "BeerDataSource"; + + private cache: IBrewInfo[] = []; + + public busy = new BehaviorSubject(false); + + public dataFields: Array = [ + { + id: nameof("id"), + label: "No", + dataType: "number", + sortable: true, + }, + // To indicate that a column should not be sortable, set the optional IDataField 'sortable' property to false + { + id: nameof("name"), + label: "Name", + dataType: "string", + sortable: true, + }, + { + id: nameof("tagline"), + label: "Tagline", + dataType: "string", + sortable: true, + }, + { + id: nameof("first_brewed"), + label: "First Brewed", + dataType: "string", + sortable: true, + }, + { + id: nameof("description"), + label: "Description", + dataType: "string", + sortable: false, + }, + { + id: nameof("brewers_tips"), + label: "Brewer's Tips", + dataType: "string", + sortable: false, + }, + ]; + + public async getFilteredData( + filters: INovaFilters + ): Promise { + const start = filters.virtualScroll?.value?.start ?? 0; + const end = filters.virtualScroll?.value?.end ?? 0; + + // Resetting cache on first page request + if (start === 0) { + this.cache = []; + } + + // extract sorter settings to send to the backend + // filters.sorterValue.sortBy; filters.sorterValue.direction + return firstValueFrom( + from(this.fetch(start, end)).pipe( + tap((response: IBrewDatasourceResponse | undefined) => { + if (!response) { + return; + } + this.cache = this.sortData( + this.cache.concat(response.brewInfo), + filters + ); + this.dataSubject.next(this.cache); + }), + map(() => ({ + repeat: { itemsSource: this.cache }, + dataFields: this.dataFields, + })) + ) + ); + } + + public async fetch( + start: number, + end: number + ): Promise { + const delta: number = end - start; + const currentPage: number = end / delta || 0; + const response: object | Array = await ( + await fetch( + \\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\`\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\${BREW_API_URL}/?page=\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\${currentPage}&per_page=\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\${delta}\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\` + ) + ).json(); + + // Note: In case request fails we should not proceed with mapping + if (!Array.isArray(response)) { + return undefined; + } + + return { + brewInfo: response.map((result: IBrewInfo) => ({ + id: result.id, + name: result.name, + tagline: result.tagline, + first_brewed: result.first_brewed, + description: result.description, + brewers_tips: result.brewers_tips, + })), + total: response.length, + }; + } + + private sortData(data: IBrewInfo[], filters: INovaFilters): IBrewInfo[] { + return orderBy( + data, + filters.sorter?.value?.sortBy, + filters.sorter?.value?.direction as "desc" | "asc" + ); + } +} + +/** + * A component that instantiates the dashboard + */ +@Component({ + selector: "table-widget-example", + templateUrl: "./table-widget-example.component.html", + styleUrls: ["./table-widget-example.component.less"], + standalone: false, +}) +export class TableWidgetExampleComponent implements OnInit { + // This variable will hold all the data needed to define the layout and behavior of the widgets. + // Pass this to the dashboard component's dashboard input in the template. + public dashboard: IDashboard | undefined; + + // Angular gridster requires a configuration object even if it's empty. + // Pass this to the dashboard component's gridsterConfig input in the template. + public gridsterConfig: GridsterConfig = {}; + + // Boolean passed as an input to the dashboard. When true, widgets can be moved, resized, removed, or edited + public editMode: boolean = false; + + constructor( + // WidgetTypesService provides the widget's necessary structure information + private widgetTypesService: WidgetTypesService, + // In general, the ProviderRegistryService is used for making entities available for injection into dynamically loaded components. + private providerRegistry: ProviderRegistryService, + private changeDetectorRef: ChangeDetectorRef + ) {} + + public ngOnInit(): void { + // Grabbing the widget's default template which will be needed as a parameter for setNode + const widgetTemplate = this.widgetTypesService.getWidgetType( + "table", + 1 + ); + + // Registering our data sources as dropdown options in the widget editor/configurator + // Note: This could also be done in the parent module's constructor so that + // multiple dashboards could have access to the same widget template modification. + this.widgetTypesService.setNode( + // This is the template we grabbed above with getWidgetType + widgetTemplate, + // We are setting the editor/configurator part of the widget template + "configurator", + // This indicates which node you are changing and we want to change + // the data source providers available for selection in the editor. + WellKnownPathKey.DataSourceProviders, + // We are setting the data sources available for selection in the editor + [BeerDataSource.providerId] + ); + + // Registering the data source for injection into the widget. + this.providerRegistry.setProviders({ + [BeerDataSource.providerId]: { + provide: DATA_SOURCE, + useClass: BeerDataSource, + // Any dependencies that need to be injected into the provider must be listed here + deps: [], + }, + }); + + this.initializeDashboard(); + } + + /** Used for restoring widgets state */ + public reInitializeDashboard(): void { + // destroys the components and their providers so the dashboard can re init data + this.dashboard = undefined; + this.changeDetectorRef.detectChanges(); + + this.initializeDashboard(); + } + + public initializeDashboard(): void { + // We're using a static configuration object for this example, but this is where + // the widget's configuration could potentially be populated from a database + const tableWidget = widgetConfig; + const widgetIndex: IWidgets = { + // Enhance the widget with information coming from it's type definition + [tableWidget.id]: + this.widgetTypesService.mergeWithWidgetType(tableWidget), + }; + + // Setting the widget dimensions and position (this is for gridster) + const positions: Record = { + [tableWidget.id]: { + cols: 12, + rows: 6, + y: 0, + x: 0, + }, + }; + + // Finally, assigning the variables we created above to the dashboard + this.dashboard = { + positions, + widgets: widgetIndex, + }; + } +} + +const TABLE_COLUMNS: ITableWidgetColumnConfig[] = [ + { + id: "column1", + label: $localize\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\`Beer Name\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\`, + isActive: true, + width: 185, + formatter: { + componentType: "RawFormatterComponent", + properties: { + dataFieldIds: { + value: "name", + }, + }, + }, + }, + { + id: "column2", + label: $localize\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\`Tagline\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\`, + isActive: true, + width: 250, + formatter: { + componentType: "RawFormatterComponent", + properties: { + dataFieldIds: { + value: "tagline", + }, + }, + }, + }, + { + id: "column3", + label: $localize\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\`First Brewed\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\`, + isActive: true, + width: 100, + formatter: { + componentType: "RawFormatterComponent", + properties: { + dataFieldIds: { + value: "first_brewed", + }, + }, + }, + }, + { + id: "column4", + label: $localize\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\`Description\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\`, + isActive: true, + formatter: { + componentType: "RawFormatterComponent", + properties: { + dataFieldIds: { + value: "description", + }, + }, + }, + }, +]; + +export const widgetConfig: IWidget = { + id: "tableWidgetId", + type: "table", + pizzagna: { + configuration: { + header: { + properties: { + title: "Stupendous Suds", + subtitle: "Try These Brilliant Brews", + }, + }, + table: { + providers: { + [WellKnownProviders.DataSource]: { + providerId: BeerDataSource.providerId, + }, + }, + properties: { + configuration: { + columns: TABLE_COLUMNS, + sortable: true, + sorterConfiguration: { + descendantSorting: false, + sortBy: "", + }, + hasVirtualScroll: true, + }, + }, + }, + }, + }, +}; +\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\`, + "widget-types/table/table-widget-interactive/table-widget-interactive-example.component.ts": \\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\`// © 2022 SolarWinds Worldwide, LLC. All rights reserved. +// +// Permission is hereby granted, free of charge, to any person obtaining a copy +// of this software and associated documentation files (the "Software"), to +// deal in the Software without restriction, including without limitation the +// rights to use, copy, modify, merge, publish, distribute, sublicense, and/or +// sell copies of the Software, and to permit persons to whom the Software is +// furnished to do so, subject to the following conditions: +// +// The above copyright notice and this permission notice shall be included in +// all copies or substantial portions of the Software. +// +// THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR +// IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, +// FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE +// AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER +// LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, +// OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN +// THE SOFTWARE. + +import { ChangeDetectorRef, Component, OnInit } from "@angular/core"; +import { GridsterConfig, GridsterItem } from "angular-gridster2"; +import orderBy from "lodash/orderBy"; +import { BehaviorSubject, firstValueFrom, from } from "rxjs"; +import { map, tap } from "rxjs/operators"; + +import { + DataSourceService, + IDataField, + INovaFilteringOutputs, + INovaFilters, + nameof, +} from "@nova-ui/bits"; +import { + DATA_SOURCE, + DEFAULT_PIZZAGNA_ROOT, + IDashboard, + ITableWidgetColumnConfig, + IWidget, + IWidgets, + NOVA_URL_INTERACTION_HANDLER, + ProviderRegistryService, + WellKnownPathKey, + WellKnownProviders, + WidgetTypesService, +} from "@nova-ui/dashboards"; + +export const BREW_API_URL = "https://api.punkapi.com/v2/beers"; + +export interface IBrewInfo { + id: string; + name: string; + tagline: string; + first_brewed: string; + description: string; + brewers_tips: string; +} + +export interface IBrewDatasourceResponse { + brewInfo: IBrewInfo[]; + total: number; +} + +export class MockBeerDataSource extends DataSourceService { + public static providerId = "MockBeerDataSource"; + + private cache: IBrewInfo[] = []; + + public busy = new BehaviorSubject(false); + + public dataFields: Array = [ + { + id: nameof("id"), + label: "No", + dataType: "number", + sortable: true, + }, + // To indicate that a column should not be sortable, set the optional IDataField 'sortable' property to false + { + id: nameof("name"), + label: "Name", + dataType: "string", + sortable: true, + }, + { + id: nameof("tagline"), + label: "Tagline", + dataType: "string", + sortable: true, + }, + { + id: nameof("first_brewed"), + label: "First Brewed", + dataType: "string", + sortable: true, + }, + { + id: nameof("description"), + label: "Description", + dataType: "string", + sortable: false, + }, + { + id: nameof("brewers_tips"), + label: "Brewer's Tips", + dataType: "string", + sortable: false, + }, + ]; + + public async getFilteredData( + filters: INovaFilters + ): Promise { + const start = filters.virtualScroll?.value?.start ?? 0; + const end = filters.virtualScroll?.value?.end ?? 0; + + // Resetting cache on first page request + if (start === 0) { + this.cache = []; + } + + // extract sorter settings to send to the backend + // filters.sorterValue.sortBy; filters.sorterValue.direction + return firstValueFrom( + from(this.fetch(start, end)).pipe( + tap((response) => { + if (!response) { + return; + } + this.cache = this.sortData( + this.cache.concat(response.brewInfo), + filters + ); + this.dataSubject.next(this.cache); + }), + map(() => ({ + repeat: { itemsSource: this.cache }, + dataFields: this.dataFields, + })) + ) + ); + } + + public async fetch( + start: number, + end: number + ): Promise { + const delta: number = end - start; + const currentPage: number = end / delta || 0; + const response: object | Array = await ( + await fetch( + \\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\`\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\${BREW_API_URL}/?page=\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\${currentPage}&per_page=\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\${delta}\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\` + ) + ).json(); + console.log( + "📘 table-widget-interactive-example.component: 85# -> response:", + response + ); + + // Note: In case request fails we should not proceed with mapping + if (!Array.isArray(response)) { + return undefined; + } + + return { + brewInfo: response.map((result: IBrewInfo) => ({ + id: result.id, + name: result.name, + tagline: result.tagline, + first_brewed: result.first_brewed, + description: result.description, + brewers_tips: result.brewers_tips, + })), + total: response.length, + }; + } + + private sortData(data: IBrewInfo[], filters: INovaFilters): IBrewInfo[] { + return orderBy( + data, + filters.sorter?.value?.sortBy, + filters.sorter?.value?.direction as "desc" | "asc" + ); + } +} + +/** + * A component that instantiates the dashboard + */ +@Component({ + selector: "table-widget-interactive-example", + templateUrl: "./table-widget-interactive-example.component.html", + styleUrls: ["./table-widget-interactive-example.component.less"], + standalone: false, +}) +export class TableWidgetInteractiveExampleComponent implements OnInit { + // This variable will hold all the data needed to define the layout and behavior of the widgets. + // Pass this to the dashboard component's dashboard input in the template. + public dashboard: IDashboard | undefined; + + // Angular gridster requires a configuration object even if it's empty. + // Pass this to the dashboard component's gridsterConfig input in the template. + public gridsterConfig: GridsterConfig = {}; + + // Boolean passed as an input to the dashboard. When true, widgets can be moved, resized, removed, or edited + public editMode: boolean = false; + + constructor( + // WidgetTypesService provides the widget's necessary structure information + private widgetTypesService: WidgetTypesService, + // In general, the ProviderRegistryService is used for making entities available for injection into dynamically loaded components. + private providerRegistry: ProviderRegistryService, + private changeDetectorRef: ChangeDetectorRef + ) {} + + public ngOnInit(): void { + // Grabbing the widget's default template which will be needed as a parameter for setNode + const widgetTemplate = this.widgetTypesService.getWidgetType( + "table", + 1 + ); + + // Registering our data sources as dropdown options in the widget editor/configurator + // Note: This could also be done in the parent module's constructor so that + // multiple dashboards could have access to the same widget template modification. + this.widgetTypesService.setNode( + // This is the template we grabbed above with getWidgetType + widgetTemplate, + // We are setting the editor/configurator part of the widget template + "configurator", + // This indicates which node you are changing and we want to change + // the data source providers available for selection in the editor. + WellKnownPathKey.DataSourceProviders, + // We are setting the data sources available for selection in the editor + [MockBeerDataSource.providerId] + ); + + // Registering the data source for injection into the widget. + this.providerRegistry.setProviders({ + [MockBeerDataSource.providerId]: { + provide: DATA_SOURCE, + useClass: MockBeerDataSource, + // Any dependencies that need to be injected into the provider must be listed here + deps: [], + }, + }); + + this.initializeDashboard(); + } + + /** Used for restoring widgets state */ + public reInitializeDashboard(): void { + // destroys the components and their providers so the dashboard can re init data + this.dashboard = undefined; + this.changeDetectorRef.detectChanges(); + + this.initializeDashboard(); + } + + public initializeDashboard(): void { + // We're using a static configuration object for this example, but this is where + // the widget's configuration could potentially be populated from a database + const tableWidget = widgetConfig; + const widgetIndex: IWidgets = { + // Enhance the widget with information coming from it's type definition + [tableWidget.id]: + this.widgetTypesService.mergeWithWidgetType(tableWidget), + }; + + // Setting the widget dimensions and position (this is for gridster) + const positions: Record = { + [tableWidget.id]: { + cols: 12, + rows: 6, + y: 0, + x: 0, + }, + }; + + // Finally, assigning the variables we created above to the dashboard + this.dashboard = { + positions, + widgets: widgetIndex, + }; + } +} + +const TABLE_COLUMNS: ITableWidgetColumnConfig[] = [ + { + id: "column1", + label: $localize\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\`Beer Name\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\`, + isActive: true, + width: 185, + formatter: { + componentType: "RawFormatterComponent", + properties: { + dataFieldIds: { + value: "name", + }, + }, + }, + }, + { + id: "column2", + label: $localize\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\`Tagline\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\`, + isActive: true, + width: 250, + formatter: { + componentType: "RawFormatterComponent", + properties: { + dataFieldIds: { + value: "tagline", + }, + }, + }, + }, + { + id: "column3", + label: $localize\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\`First Brewed\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\`, + isActive: true, + width: 100, + formatter: { + componentType: "RawFormatterComponent", + properties: { + dataFieldIds: { + value: "first_brewed", + }, + }, + }, + }, + { + id: "column4", + label: $localize\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\`Description\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\`, + isActive: true, + formatter: { + componentType: "RawFormatterComponent", + properties: { + dataFieldIds: { + value: "description", + }, + }, + }, + }, +]; + +export const widgetConfig: IWidget = { + id: "tableWidgetId", + type: "table", + pizzagna: { + configuration: { + [DEFAULT_PIZZAGNA_ROOT]: { + providers: { + [WellKnownProviders.InteractionHandler]: { + // Configuring the UrlInteractionHandler to handle interactions + providerId: NOVA_URL_INTERACTION_HANDLER, + properties: { + // the 'url' property tells the handler what link to use when interaction occurs on the series + url: "\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\${'https://untappd.com/search?q='+data.name}", + // by default the link is opened in the current window, set 'newWindow' to true to open in a new tab instead + newWindow: true, + }, + }, + }, + }, + header: { + properties: { + title: "Stupendous Suds", + subtitle: "Try These Brilliant Brews", + }, + }, + table: { + providers: { + [WellKnownProviders.DataSource]: { + providerId: MockBeerDataSource.providerId, + }, + }, + properties: { + configuration: { + // set interactions to true on the table + interactive: true, + columns: TABLE_COLUMNS, + sortable: true, + sorterConfiguration: { + descendantSorting: false, + sortBy: "", + }, + hasVirtualScroll: true, + }, + }, + }, + }, + }, +}; +\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\`, + "widget-types/table/table-widget-paginator/table-widget-paginator-example.component.ts": \\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\`// © 2022 SolarWinds Worldwide, LLC. All rights reserved. +// +// Permission is hereby granted, free of charge, to any person obtaining a copy +// of this software and associated documentation files (the "Software"), to +// deal in the Software without restriction, including without limitation the +// rights to use, copy, modify, merge, publish, distribute, sublicense, and/or +// sell copies of the Software, and to permit persons to whom the Software is +// furnished to do so, subject to the following conditions: +// +// The above copyright notice and this permission notice shall be included in +// all copies or substantial portions of the Software. +// +// THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR +// IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, +// FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE +// AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER +// LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, +// OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN +// THE SOFTWARE. + +import { HttpClient } from "@angular/common/http"; +import { ChangeDetectorRef, Component, OnInit } from "@angular/core"; +import { GridsterConfig, GridsterItem } from "angular-gridster2"; + +import { LoggerService } from "@nova-ui/bits"; +import { + DATA_SOURCE, + DEFAULT_PIZZAGNA_ROOT, + IDashboard, + IProviderConfiguration, + ITableWidgetConfig, + IWidget, + IWidgets, + NOVA_URL_INTERACTION_HANDLER, + PizzagnaLayer, + ProviderRegistryService, + RawFormatterComponent, + ScrollType, + WellKnownPathKey, + WellKnownProviders, + WidgetTypesService, +} from "@nova-ui/dashboards"; + +import { AcmeTableMockDataSource } from "../../../../prototypes/data/table/acme-table-mock-data-source.service"; + +/** + * A component that instantiates the dashboard + */ +@Component({ + selector: "table-widget-paginator-example", + templateUrl: "./table-widget-paginator-example.component.html", + styleUrls: ["./table-widget-paginator-example.component.less"], + standalone: false, +}) +export class TableWidgetPaginatorExampleComponent implements OnInit { + public dashboard: IDashboard | undefined; + public gridsterConfig: GridsterConfig = {}; + public editMode: boolean = false; + + constructor( + private widgetTypesService: WidgetTypesService, + private providerRegistry: ProviderRegistryService, + private changeDetectorRef: ChangeDetectorRef + ) {} + + public ngOnInit(): void { + const widgetTemplate = this.widgetTypesService.getWidgetType( + "table", + 1 + ); + this.widgetTypesService.setNode( + widgetTemplate, + "configurator", + WellKnownPathKey.DataSourceProviders, + [AcmeTableMockDataSource.providerId] + ); + + this.providerRegistry.setProviders({ + [AcmeTableMockDataSource.providerId]: { + provide: DATA_SOURCE, + useClass: AcmeTableMockDataSource, + deps: [LoggerService, HttpClient], + }, + }); + + this.initializeDashboard(); + } + + /** Used for restoring widgets state */ + public reInitializeDashboard(): void { + // destroys the components and their providers so the dashboard can re init data + this.dashboard = undefined; + this.changeDetectorRef.detectChanges(); + + this.initializeDashboard(); + } + + public initializeDashboard(): void { + const tableWithPaginator = tableWidgetWithPaginator; + const tableWithVirtualScroll = tableWidgetWithVirtualScroll; + + const widgetIndex: IWidgets = { + [tableWithPaginator.id]: + this.widgetTypesService.mergeWithWidgetType(tableWithPaginator), + [tableWithVirtualScroll.id]: + this.widgetTypesService.mergeWithWidgetType( + tableWithVirtualScroll + ), + }; + + const positions: Record = { + [tableWithPaginator.id]: { + cols: 6, + rows: 6, + y: 0, + x: 0, + }, + [tableWithVirtualScroll.id]: { + cols: 6, + rows: 6, + y: 0, + x: 0, + }, + }; + + this.dashboard = { + positions, + widgets: widgetIndex, + }; + } +} + +export const tableWidgetWithPaginator: IWidget = { + id: "widget1", + type: "table", + pizzagna: { + [PizzagnaLayer.Configuration]: { + [DEFAULT_PIZZAGNA_ROOT]: { + providers: { + [WellKnownProviders.InteractionHandler]: { + providerId: NOVA_URL_INTERACTION_HANDLER, + }, + }, + }, + header: { + properties: { + title: "Table Widget with paginator!", + subtitle: "Basic table widget", + collapsible: true, + }, + }, + table: { + providers: { + [WellKnownProviders.DataSource]: { + providerId: AcmeTableMockDataSource.providerId, + } as IProviderConfiguration, + }, + properties: { + configuration: { + interactive: true, + columns: [ + { + id: "column1", + label: "No.", + isActive: true, + formatter: { + componentType: + RawFormatterComponent.lateLoadKey, + properties: { + dataFieldIds: { + value: "position", + }, + }, + }, + }, + { + id: "column2", + label: "Name", + isActive: true, + formatter: { + componentType: + RawFormatterComponent.lateLoadKey, + properties: { + dataFieldIds: { + value: "name", + }, + }, + }, + }, + { + id: "column3", + label: "Status", + isActive: true, + formatter: { + componentType: + RawFormatterComponent.lateLoadKey, + properties: { + dataFieldIds: { + value: "status", + }, + }, + }, + }, + ], + sorterConfiguration: { + descendantSorting: false, + sortBy: "column1", + }, + scrollType: ScrollType.paginator, + paginatorConfiguration: { + pageSize: 5, + pageSizeSet: [5, 10, 20, 30], + }, + hasVirtualScroll: false, + searchConfiguration: { + enabled: true, + }, + } as ITableWidgetConfig, + }, + }, + }, + }, +}; + +export const tableWidgetWithVirtualScroll: IWidget = { + id: "widget2", + type: "table", + pizzagna: { + [PizzagnaLayer.Configuration]: { + [DEFAULT_PIZZAGNA_ROOT]: { + providers: { + [WellKnownProviders.InteractionHandler]: { + providerId: NOVA_URL_INTERACTION_HANDLER, + }, + }, + }, + header: { + properties: { + title: "Table Widget with virtual scroll!", + subtitle: "Basic table widget", + collapsible: true, + }, + }, + table: { + providers: { + [WellKnownProviders.DataSource]: { + providerId: AcmeTableMockDataSource.providerId, + } as IProviderConfiguration, + }, + properties: { + configuration: { + interactive: true, + columns: [ + { + id: "column1", + label: "No.", + isActive: true, + formatter: { + componentType: + RawFormatterComponent.lateLoadKey, + properties: { + dataFieldIds: { + value: "position", + }, + }, + }, + }, + { + id: "column2", + label: "Name", + isActive: true, + formatter: { + componentType: + RawFormatterComponent.lateLoadKey, + properties: { + dataFieldIds: { + value: "name", + }, + }, + }, + }, + { + id: "column3", + label: "Status", + isActive: true, + formatter: { + componentType: + RawFormatterComponent.lateLoadKey, + properties: { + dataFieldIds: { + value: "status", + }, + }, + }, + }, + ], + sorterConfiguration: { + descendantSorting: false, + sortBy: "column1", + }, + hasVirtualScroll: true, + searchConfiguration: { + enabled: true, + }, + } as ITableWidgetConfig, + }, + }, + }, + }, +}; +\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\`, + "widget-types/table/table-widget-search/table-widget-search-example.component.ts": \\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\`// © 2022 SolarWinds Worldwide, LLC. All rights reserved. +// +// Permission is hereby granted, free of charge, to any person obtaining a copy +// of this software and associated documentation files (the "Software"), to +// deal in the Software without restriction, including without limitation the +// rights to use, copy, modify, merge, publish, distribute, sublicense, and/or +// sell copies of the Software, and to permit persons to whom the Software is +// furnished to do so, subject to the following conditions: +// +// The above copyright notice and this permission notice shall be included in +// all copies or substantial portions of the Software. +// +// THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR +// IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, +// FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE +// AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER +// LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, +// OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN +// THE SOFTWARE. + +import { HttpClient } from "@angular/common/http"; +import { + ChangeDetectorRef, + Component, + Injectable, + OnInit, +} from "@angular/core"; +import { GridsterConfig, GridsterItem } from "angular-gridster2"; +import isEqual from "lodash/isEqual"; +import isNil from "lodash/isNil"; +import { BehaviorSubject, firstValueFrom, Observable, of, Subject } from "rxjs"; +import { + catchError, + delay, + finalize, + map, + // eslint-disable-next-line import/no-deprecated + switchMap, + tap, +} from "rxjs/operators"; + +import { + DataSourceFeatures, + DataSourceService, + IDataField, + IDataSource, + IDataSourceFeatures, + IDataSourceFeaturesConfiguration, + IDataSourceOutput, + IFilter, + IFilters, + INovaFilteringOutputs, + INovaFilters, + LoggerService, +} from "@nova-ui/bits"; +import { + DATA_SOURCE, + IDashboard, + ITableWidgetConfig, + IWidget, + IWidgets, + ProviderRegistryService, + WellKnownPathKey, + WellKnownProviders, + WidgetTypesService, +} from "@nova-ui/dashboards"; + +import { GBOOKS_API_URL } from "../../../../prototypes/data/table/constants"; + +interface IGBooksApiResponse { + kind: string; + totalItems: number; + items: IGBooksItemModel[]; + [key: string]: any; +} + +interface IGBooksItemModel { + id: string; + volumeInfo: { + title: string; + subtitle: string; + authors: string[]; + [key: string]: any; + }; + accessInfo: { [key: string]: any }; + saleInfo: { [key: string]: any }; +} + +interface IGBooksData { + books: IGBooksVolume[]; + totalItems: number; +} + +interface IGBooksVolume { + title: string; + authors: string; +} + +type searchableColumnType = "title" | "authors"; + +@Injectable() +export class AcmeTableGBooksDataSource + extends DataSourceService + implements IDataSource +{ + public static providerId = "AcmeTableGBooksDataSource"; + public static mockError = false; + + public searchableColumn: searchableColumnType = "title"; + + public page: number = 1; + public busy = new BehaviorSubject(false); + public features: IDataSourceFeaturesConfiguration; + + private cache = Array.from({ length: 0 }); + private previousFilters: INovaFilters; + // DataSource Features declared + private supportedFeatures: IDataSourceFeatures = { + search: { enabled: true }, + pagination: { enabled: true }, + }; + private columnToQueryParamMap: { [k in searchableColumnType]: string } = { + title: "intitle", + authors: "inauthor", + }; + + private applyFilters$ = new Subject(); + + public dataFields: Array = [ + { + id: "title", + label: $localize\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\`Title\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\`, + dataType: "string", + sortable: false, + }, + { + id: "authors", + label: $localize\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\`Authors\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\`, + dataType: "string", + sortable: false, + }, + ]; + + constructor(private logger: LoggerService, private http: HttpClient) { + super(); + // Using Nova DataSourceFeatures implementation for the features + this.features = new DataSourceFeatures(this.supportedFeatures); + + this.applyFilters$ + // eslint-disable-next-line import/no-deprecated + .pipe(switchMap((filters) => this.getData(filters))) + .subscribe(async (res) => { + this.outputsSubject.next(await this.getFilteredData(res)); + }); + } + + public async getFilteredData( + booksData: IGBooksData + ): Promise> { + return firstValueFrom( + of(booksData).pipe( + tap((response) => { + this.cache = this.cache.concat(response.books); + }), + map((response) => ({ + result: { + repeat: { itemsSource: this.cache }, + paginator: { total: response.totalItems }, + dataFields: this.dataFields, + }, + })) + ) + ); + } + + private getData(filters: INovaFilters): Observable { + if ( + this.isNewSearchTerm(filters.search) && + filters.virtualScroll?.value.start === 0 + ) { + this.cache = []; + } + + return this.http + .get(this.getComposedUrl(filters)) + .pipe( + tap(() => this.busy.next(true)), + delay(300), // mock + map((response) => ({ + books: + response.items?.map((volume) => ({ + title: volume.volumeInfo.title, + authors: + volume.volumeInfo.authors?.join(", ") || "", + })) || [], + totalItems: response.totalItems, + })), + catchError((e) => { + this.logger.error(e); + return of({ + books: [], + totalItems: 0, + }); + }), + finalize(() => { + this.busy.next(false); + this.previousFilters = filters; + }) + ); + } + + private getComposedUrl(filters: INovaFilters) { + const initialUrl = \\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\`\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\${GBOOKS_API_URL}?q=\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\`; + const maxResults = \\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\`maxResults=\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\${ + (filters.virtualScroll?.value.end || 0) - + (filters.virtualScroll?.value.start || 0) + }\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\`; + + const virtualScrollPart = filters.virtualScroll + ? \\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\`startIndex=\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\${filters.virtualScroll.value.start}\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\` + : ""; + + const searchQueryParam = + this.columnToQueryParamMap[this.searchableColumn]; + const searchPart = filters.search + ? \\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\`\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\${searchQueryParam}:\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\${filters.search.value}\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\` + : "_"; // google books api requires some criteria to do the search + + return \\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\`\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\${initialUrl}\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\${searchPart}&\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\${maxResults}&\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\${virtualScrollPart}&filter=full\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\`; + } + + private isNewSearchTerm(search: IFilter | undefined) { + return ( + !isNil(search?.value) && + !isEqual(search?.value, this.previousFilters?.search?.value) + ); + } + + // redefine parent method + public async applyFilters(): Promise { + this.applyFilters$.next(this.getFilters()); + } +} + +/** + * A component that instantiates the dashboard + */ +@Component({ + selector: "table-widget-search-example", + templateUrl: "./table-widget-search-example.component.html", + styleUrls: ["./table-widget-search-example.component.less"], + standalone: false, +}) +export class TableWidgetSearchExampleComponent implements OnInit { + public dashboard: IDashboard | undefined; + public gridsterConfig: GridsterConfig = {}; + public editMode: boolean = false; + + constructor( + private widgetTypesService: WidgetTypesService, + private providerRegistry: ProviderRegistryService, + private changeDetectorRef: ChangeDetectorRef + ) {} + + public ngOnInit(): void { + const widgetTemplate = this.widgetTypesService.getWidgetType( + "table", + 1 + ); + this.widgetTypesService.setNode( + widgetTemplate, + "configurator", + WellKnownPathKey.DataSourceProviders, + [AcmeTableGBooksDataSource.providerId] + ); + + this.providerRegistry.setProviders({ + [AcmeTableGBooksDataSource.providerId]: { + provide: DATA_SOURCE, + useClass: AcmeTableGBooksDataSource, + deps: [LoggerService, HttpClient], + }, + }); + + this.initializeDashboard(); + } + + /** Used for restoring widgets state */ + public reInitializeDashboard(): void { + // destroys the components and their providers so the dashboard can re init data + this.dashboard = undefined; + this.changeDetectorRef.detectChanges(); + + this.initializeDashboard(); + } + + public initializeDashboard(): void { + const tableWidget = widgetConfig; + const widgetIndex: IWidgets = { + [tableWidget.id]: + this.widgetTypesService.mergeWithWidgetType(tableWidget), + }; + + const positions: Record = { + [tableWidget.id]: { + cols: 12, + rows: 6, + y: 0, + x: 0, + }, + }; + + this.dashboard = { + positions, + widgets: widgetIndex, + }; + } +} + +export const widgetConfig: IWidget = { + id: "tableWidgetId", + type: "table", + pizzagna: { + configuration: { + header: { + properties: { + title: "Google Books", + }, + }, + table: { + providers: { + [WellKnownProviders.DataSource]: { + providerId: AcmeTableGBooksDataSource.providerId, + }, + }, + properties: { + configuration: { + columns: [ + { + id: "column1", + label: $localize\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\`Title\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\`, + isActive: true, + formatter: { + componentType: "RawFormatterComponent", + properties: { + dataFieldIds: { + value: "title", + }, + }, + }, + }, + { + id: "column2", + label: $localize\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\`Author\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\`, + isActive: true, + formatter: { + componentType: "RawFormatterComponent", + properties: { + dataFieldIds: { + value: "authors", + }, + }, + }, + }, + ], + sortable: false, + // define search configuration here + searchConfiguration: { + enabled: true, + // following properties below can be configured as well + // searchTerm: "search criteria here", + // searchDebounce: 300, + }, + hasVirtualScroll: true, + } as ITableWidgetConfig, + }, + }, + }, + }, +}; +\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\`, + "widget-types/table/table-widget-search-docs.component.ts": \\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\`// © 2022 SolarWinds Worldwide, LLC. All rights reserved. +// +// Permission is hereby granted, free of charge, to any person obtaining a copy +// of this software and associated documentation files (the "Software"), to +// deal in the Software without restriction, including without limitation the +// rights to use, copy, modify, merge, publish, distribute, sublicense, and/or +// sell copies of the Software, and to permit persons to whom the Software is +// furnished to do so, subject to the following conditions: +// +// The above copyright notice and this permission notice shall be included in +// all copies or substantial portions of the Software. +// +// THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR +// IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, +// FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE +// AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER +// LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, +// OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN +// THE SOFTWARE. + +import { Component } from "@angular/core"; + +@Component({ + selector: "nui-table-search-docs", + templateUrl: "./table-widget-search-docs.component.html", + standalone: false, +}) +export class TableSearchDocsComponent { + public featuredDeclaredText = \\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\` + private supportedFeatures: IDataSourceFeatures = { + search: { enabled: true }, + pagination: { enabled: true }, + };\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\`; + public featuresUsedText = \\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\` + this.features = new DataSourceFeatures(this.supportedFeatures); + \\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\`; + public tableConfigurationText = \\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\` + "table": { + ... + properties: { + configuration: { + // define search configuration here + searchConfiguration: { + enabled: true, + // following optional properties below can be configured as well + // searchTerm: "search criteria here", + // searchDebounce: 300, + }, + } as ITableWidgetConfig, + }, + }, + \\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\`; +} +\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\`, + "widget-types/table/table-widget-selectable/table-widget-selectable-multi/table-widget-selectable-multi.example.component.ts": \\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\`// © 2022 SolarWinds Worldwide, LLC. All rights reserved. +// +// Permission is hereby granted, free of charge, to any person obtaining a copy +// of this software and associated documentation files (the "Software"), to +// deal in the Software without restriction, including without limitation the +// rights to use, copy, modify, merge, publish, distribute, sublicense, and/or +// sell copies of the Software, and to permit persons to whom the Software is +// furnished to do so, subject to the following conditions: +// +// The above copyright notice and this permission notice shall be included in +// all copies or substantial portions of the Software. +// +// THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR +// IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, +// FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE +// AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER +// LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, +// OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN +// THE SOFTWARE. + +import { Component } from "@angular/core"; + +import { TableSelectionMode } from "@nova-ui/bits"; +import { TableWidgetSelectionConfig } from "@nova-ui/dashboards"; + +/** + * A component that instantiates the dashboard + */ +@Component({ + selector: "table-widget-selectable-multi-example", + templateUrl: "./table-widget-selectable-multi.example.component.html", + styleUrls: ["./table-widget-selectable-multi.example.component.less"], + standalone: false, +}) +export class TableWidgetSelectableMultiExampleComponent { + public selectionConfiguration: TableWidgetSelectionConfig = { + enabled: true, + selectionMode: TableSelectionMode.Multi, + trackByProperty: "id", + clickableRow: true, + }; +} +\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\`, + "widget-types/table/table-widget-selectable/table-widget-selectable-radio/table-widget-selectable-radio.example.component.ts": \\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\`// © 2022 SolarWinds Worldwide, LLC. All rights reserved. +// +// Permission is hereby granted, free of charge, to any person obtaining a copy +// of this software and associated documentation files (the "Software"), to +// deal in the Software without restriction, including without limitation the +// rights to use, copy, modify, merge, publish, distribute, sublicense, and/or +// sell copies of the Software, and to permit persons to whom the Software is +// furnished to do so, subject to the following conditions: +// +// The above copyright notice and this permission notice shall be included in +// all copies or substantial portions of the Software. +// +// THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR +// IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, +// FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE +// AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER +// LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, +// OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN +// THE SOFTWARE. + +import { Component } from "@angular/core"; + +import { TableSelectionMode } from "@nova-ui/bits"; +import { TableWidgetSelectionConfig } from "@nova-ui/dashboards"; + +/** + * A component that instantiates the dashboard + */ +@Component({ + selector: "table-widget-selectable-radio-example", + templateUrl: "./table-widget-selectable-radio.example.component.html", + styleUrls: ["./table-widget-selectable-radio.example.component.less"], + standalone: false, +}) +export class TableWidgetSelectableRadioExampleComponent { + public selectionConfiguration: TableWidgetSelectionConfig = { + enabled: true, + selectionMode: TableSelectionMode.Radio, + trackByProperty: "id", + clickableRow: true, + }; +} +\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\`, + "widget-types/table/table-widget-selectable/table-widget-selectable-single/table-widget-selectable-single.example.component.ts": \\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\`// © 2022 SolarWinds Worldwide, LLC. All rights reserved. +// +// Permission is hereby granted, free of charge, to any person obtaining a copy +// of this software and associated documentation files (the "Software"), to +// deal in the Software without restriction, including without limitation the +// rights to use, copy, modify, merge, publish, distribute, sublicense, and/or +// sell copies of the Software, and to permit persons to whom the Software is +// furnished to do so, subject to the following conditions: +// +// The above copyright notice and this permission notice shall be included in +// all copies or substantial portions of the Software. +// +// THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR +// IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, +// FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE +// AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER +// LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, +// OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN +// THE SOFTWARE. + +import { Component } from "@angular/core"; + +import { TableSelectionMode } from "@nova-ui/bits"; +import { TableWidgetSelectionConfig } from "@nova-ui/dashboards"; + +/** + * A component that instantiates the dashboard + */ +@Component({ + selector: "table-widget-selectable-single-example", + templateUrl: "./table-widget-selectable-single.example.component.html", + styleUrls: ["./table-widget-selectable-single.example.component.less"], + standalone: false, +}) +export class TableWidgetSelectableSingleExampleComponent { + public selectionConfiguration: TableWidgetSelectionConfig = { + enabled: true, + selectionMode: TableSelectionMode.Single, + trackByProperty: "id", + }; +} +\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\`, + "widget-types/table/table-widget-selectable/table-widget-selectable.example.component.ts": \\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\`// © 2022 SolarWinds Worldwide, LLC. All rights reserved. +// +// Permission is hereby granted, free of charge, to any person obtaining a copy +// of this software and associated documentation files (the "Software"), to +// deal in the Software without restriction, including without limitation the +// rights to use, copy, modify, merge, publish, distribute, sublicense, and/or +// sell copies of the Software, and to permit persons to whom the Software is +// furnished to do so, subject to the following conditions: +// +// The above copyright notice and this permission notice shall be included in +// all copies or substantial portions of the Software. +// +// THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR +// IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, +// FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE +// AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER +// LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, +// OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN +// THE SOFTWARE. + +import { HttpClient } from "@angular/common/http"; +import { ChangeDetectorRef, Component, Input, OnInit } from "@angular/core"; +import { GridsterConfig, GridsterItem } from "angular-gridster2"; + +import { LoggerService, TableSelectionMode } from "@nova-ui/bits"; +import { + DATA_SOURCE, + DEFAULT_PIZZAGNA_ROOT, + IDashboard, + IProviderConfiguration, + ITableWidgetConfig, + IWidget, + IWidgets, + NOVA_URL_INTERACTION_HANDLER, + PizzagnaLayer, + ProviderRegistryService, + RawFormatterComponent, + TableWidgetSelectionConfig, + WellKnownPathKey, + WellKnownProviders, + WidgetTypesService, +} from "@nova-ui/dashboards"; + +import { AcmeTableMockDataSource } from "../../../../prototypes/data/table/acme-table-mock-data-source.service"; + +/** + * A component that instantiates the dashboard + */ +@Component({ + selector: "table-widget-selectable-example", + templateUrl: "./table-widget-selectable.example.component.html", + styleUrls: ["./table-widget-selectable.example.component.less"], + standalone: false, +}) +export class TableWidgetSelectableExampleComponent implements OnInit { + public dashboard: IDashboard | undefined; + public gridsterConfig: GridsterConfig = {}; + public editMode: boolean = false; + + @Input() public selectionConfiguration: TableWidgetSelectionConfig = { + enabled: false, + selectionMode: TableSelectionMode.None, + }; + + constructor( + private widgetTypesService: WidgetTypesService, + private providerRegistry: ProviderRegistryService, + private changeDetectorRef: ChangeDetectorRef + ) {} + + public ngOnInit(): void { + const widgetTemplate = this.widgetTypesService.getWidgetType( + "table", + 1 + ); + this.widgetTypesService.setNode( + widgetTemplate, + "configurator", + WellKnownPathKey.DataSourceProviders, + [AcmeTableMockDataSource.providerId] + ); + + this.providerRegistry.setProviders({ + [AcmeTableMockDataSource.providerId]: { + provide: DATA_SOURCE, + useClass: AcmeTableMockDataSource, + deps: [LoggerService, HttpClient], + }, + }); + + this.initializeDashboard(); + } + + /** Used for restoring widgets state */ + public reInitializeDashboard(): void { + // destroys the components and their providers so the dashboard can re init data + this.dashboard = undefined; + this.changeDetectorRef.detectChanges(); + + this.initializeDashboard(); + } + + public initializeDashboard(): void { + const tableWidget = this.widgetConfig; + const widgetIndex: IWidgets = { + [tableWidget.id]: + this.widgetTypesService.mergeWithWidgetType(tableWidget), + }; + + const positions: Record = { + [tableWidget.id]: { + cols: 12, + rows: 6, + y: 0, + x: 0, + }, + }; + + this.dashboard = { + positions, + widgets: widgetIndex, + }; + } + + private get widgetConfig(): IWidget { + return { + id: "widget1", + type: "table", + pizzagna: { + [PizzagnaLayer.Configuration]: { + [DEFAULT_PIZZAGNA_ROOT]: { + providers: { + [WellKnownProviders.InteractionHandler]: { + providerId: NOVA_URL_INTERACTION_HANDLER, + }, + }, + }, + header: { + properties: { + title: "Table Widget with Selection!", + subtitle: "Basic table widget", + collapsible: true, + }, + }, + table: { + providers: { + [WellKnownProviders.DataSource]: { + providerId: AcmeTableMockDataSource.providerId, + } as IProviderConfiguration, + }, + properties: { + configuration: { + // enabling selection here + selectionConfiguration: + this.selectionConfiguration, + columns: [ + { + id: "column1", + label: "No.", + isActive: true, + formatter: { + componentType: + RawFormatterComponent.lateLoadKey, + properties: { + dataFieldIds: { + value: "position", + }, + }, + }, + }, + { + id: "column2", + label: "Name", + isActive: true, + formatter: { + componentType: + RawFormatterComponent.lateLoadKey, + properties: { + dataFieldIds: { + value: "name", + }, + }, + }, + }, + { + id: "column3", + label: "Status", + isActive: true, + formatter: { + componentType: + RawFormatterComponent.lateLoadKey, + properties: { + dataFieldIds: { + value: "status", + }, + }, + }, + }, + ], + } as ITableWidgetConfig, + }, + }, + }, + }, + }; + } +} +\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\`, + "widget-types/timeseries/timeseries-docs.component.ts": \\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\`// © 2022 SolarWinds Worldwide, LLC. All rights reserved. +// +// Permission is hereby granted, free of charge, to any person obtaining a copy +// of this software and associated documentation files (the "Software"), to +// deal in the Software without restriction, including without limitation the +// rights to use, copy, modify, merge, publish, distribute, sublicense, and/or +// sell copies of the Software, and to permit persons to whom the Software is +// furnished to do so, subject to the following conditions: +// +// The above copyright notice and this permission notice shall be included in +// all copies or substantial portions of the Software. +// +// THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR +// IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, +// FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE +// AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER +// LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, +// OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN +// THE SOFTWARE. + +import { Component, OnInit } from "@angular/core"; + +import { mapContentFile } from "../../../../demo-files-factory"; + +@Component({ + selector: "nui-timeseries-docs", + templateUrl: "./timeseries-docs.component.html", + standalone: false, +}) +export class TimeseriesDocsComponent implements OnInit { + public timeseriesWidgetFileText = ""; + public timeseriesConfiguratorFileText = ""; + + async ngOnInit(): Promise { + this.timeseriesWidgetFileText = await import( + "./../../../../../../src/lib/widget-types/timeseries/timeseries-widget" + ).then(mapContentFile); + this.timeseriesConfiguratorFileText = await import( + "./../../../../../../src/lib/widget-types/timeseries/timeseries-configurator" + ).then(mapContentFile); + } +} +\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\`, + "widget-types/timeseries/timeseries-docs.module.ts": \\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\`// © 2022 SolarWinds Worldwide, LLC. All rights reserved. +// +// Permission is hereby granted, free of charge, to any person obtaining a copy +// of this software and associated documentation files (the "Software"), to +// deal in the Software without restriction, including without limitation the +// rights to use, copy, modify, merge, publish, distribute, sublicense, and/or +// sell copies of the Software, and to permit persons to whom the Software is +// furnished to do so, subject to the following conditions: +// +// The above copyright notice and this permission notice shall be included in +// all copies or substantial portions of the Software. +// +// THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR +// IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, +// FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE +// AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER +// LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, +// OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN +// THE SOFTWARE. + +import { NgModule } from "@angular/core"; +import { RouterModule, Routes } from "@angular/router"; + +import { DEMO_PATH_TOKEN } from "@nova-ui/bits"; +import { + NuiButtonModule, + NuiDocsModule, + NuiMessageModule, + NuiSwitchModule, +} from "@nova-ui/bits"; +import { NuiDashboardsModule } from "@nova-ui/dashboards"; + +import { TimeseriesDocsComponent } from "./timeseries-docs.component"; +import { TimeseriesWidgetExampleComponent } from "./timeseries-widget-example/timeseries-widget-example.component"; +import { TimeseriesWidgetInteractiveExampleComponent } from "./timeseries-widget-interactive-example/timeseries-widget-interactive-example.component"; +import { TimeseriesWidgetStatusBarExampleComponent } from "./timeseries-widget-status-bar-example/timeseries-widget-status-bar-example.component"; +import { getDemoFiles } from "../../../../demo-files-factory"; + +const routes: Routes = [ + { + path: "", + component: TimeseriesDocsComponent, + data: { + srlc: { + hideIndicator: true, + }, + showThemeSwitcher: true, + }, + }, + { + path: "example", + component: TimeseriesWidgetExampleComponent, + data: { + srlc: { + hideIndicator: true, + }, + }, + }, +]; + +@NgModule({ + imports: [ + RouterModule.forChild(routes), + NuiButtonModule, + NuiDocsModule, + NuiMessageModule, + NuiSwitchModule, + NuiDashboardsModule, + ], + declarations: [ + TimeseriesDocsComponent, + TimeseriesWidgetExampleComponent, + TimeseriesWidgetInteractiveExampleComponent, + TimeseriesWidgetStatusBarExampleComponent, + ], + providers: [ + { + provide: DEMO_PATH_TOKEN, + useValue: getDemoFiles("timeseries"), + }, + ], +}) +export default class TimeseriesDocsModule {} +\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\`, + "widget-types/timeseries/timeseries-widget-example/timeseries-widget-example.component.ts": \\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\`// © 2022 SolarWinds Worldwide, LLC. All rights reserved. +// +// Permission is hereby granted, free of charge, to any person obtaining a copy +// of this software and associated documentation files (the "Software"), to +// deal in the Software without restriction, including without limitation the +// rights to use, copy, modify, merge, publish, distribute, sublicense, and/or +// sell copies of the Software, and to permit persons to whom the Software is +// furnished to do so, subject to the following conditions: +// +// The above copyright notice and this permission notice shall be included in +// all copies or substantial portions of the Software. +// +// THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR +// IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, +// FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE +// AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER +// LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, +// OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN +// THE SOFTWARE. + +import { + ChangeDetectorRef, + Component, + Injectable, + OnInit, +} from "@angular/core"; +import { GridsterConfig, GridsterItem } from "angular-gridster2"; +import cloneDeep from "lodash/cloneDeep"; +import keyBy from "lodash/keyBy"; +import moment, { Moment } from "moment/moment"; +import { BehaviorSubject } from "rxjs"; + +import { + DataSourceService, + IDataSource, + INovaFilters, + ITimeframe, +} from "@nova-ui/bits"; +import { + DATA_SOURCE, + DEFAULT_PIZZAGNA_ROOT, + IDashboard, + IDataSourceOutput, + IProviderConfiguration, + ISerializableTimeframe, + ITimeseriesItemConfiguration, + ITimeseriesOutput, + ITimeseriesScaleConfig, + ITimeseriesWidgetConfig, + ITimeseriesWidgetData, + ITimeseriesWidgetSeriesData, + IWidget, + LegendPlacement, + PizzagnaLayer, + ProviderRegistryService, + TimeseriesChartPreset, + TimeseriesScaleType, + WellKnownPathKey, + WellKnownProviders, + WidgetTypesService, +} from "@nova-ui/dashboards"; + +/** + * A simple Timeseries data source implementation + */ +@Injectable() +export class BeerVsReadingMockDataSource + extends DataSourceService + implements IDataSource +{ + public static providerId = "BeerVsReadingMockDataSource"; + + public busy = new BehaviorSubject(false); + + public async getFilteredData( + filters: INovaFilters + ): Promise> { + // In this example we're using some static mock data located at the bottom of this file. In a real-world + // scenario, the data for the chart would likely be retrieved via an asynchronous backend call. + let filteredData = getData(); + + this.busy.next(true); + + // Filtering using the filter registered by the TimeFrameBar + const timeframeFilter = filters.timeframe?.value as ITimeframe; + if (timeframeFilter) { + filteredData = filteredData.map((item: ITimeseriesWidgetData) => ({ + id: item.id, + name: item.name, + description: item.description, + data: item.data.filter( + (seriesData: ITimeseriesWidgetSeriesData) => + filterDates( + seriesData.x, + timeframeFilter.startDatetime, + timeframeFilter.endDatetime + ) + ), + })); + } + + this.busy.next(false); + + return { result: { series: filteredData } }; + } +} + +function filterDates(dateToCheck: Date, startDate: Moment, endDate: Moment) { + const mom = moment(dateToCheck); + return ( + mom.isBetween(startDate, endDate) || + mom.isSame(startDate) || + mom.isSame(endDate) + ); +} + +/** + * A component that instantiates the dashboard + */ +@Component({ + selector: "timeseries-widget-example", + templateUrl: "./timeseries-widget-example.component.html", + styleUrls: ["./timeseries-widget-example.component.less"], + standalone: false, +}) +export class TimeseriesWidgetExampleComponent implements OnInit { + // This variable will hold all the data needed to define the layout and behavior of the widgets. + // Pass this to the dashboard component's dashboard input in the template. + public dashboard: IDashboard | undefined; + + // Angular gridster requires a configuration object even if it's empty. + // Pass this to the dashboard component's gridsterConfig input in the template. + public gridsterConfig: GridsterConfig = {}; + + // Boolean passed as an input to the dashboard. When true, widgets can be moved, resized, removed, or edited + public editMode: boolean = false; + + constructor( + // WidgetTypesService provides the widget's necessary structure information + private widgetTypesService: WidgetTypesService, + + // In general, the ProviderRegistryService is used for making entities available for injection into dynamically loaded components. + private providerRegistry: ProviderRegistryService, + + // Angular's ChangeDetectorRef + private changeDetectorRef: ChangeDetectorRef + ) {} + + public ngOnInit(): void { + // Grabbing the widget's default template which will be needed as a parameter for setNode + const widgetTemplate = this.widgetTypesService.getWidgetType( + "timeseries", + 1 + ); + // Registering our data sources as dropdown options in the widget editor/configurator + // Note: This could also be done in the parent module's constructor so that + // multiple dashboards could have access to the same widget template modification. + this.widgetTypesService.setNode( + // This is the template we grabbed above with getWidgetType + widgetTemplate, + // We are setting the editor/configurator part of the widget template + "configurator", + // This indicates which node you are changing and we want to change + // the data source providers available for selection in the editor. + WellKnownPathKey.DataSourceProviders, + // We are setting the data sources available for selection in the editor + [BeerVsReadingMockDataSource.providerId] + ); + + // Registering the data source for injection into the widget. + this.providerRegistry.setProviders({ + [BeerVsReadingMockDataSource.providerId]: { + provide: DATA_SOURCE, + useClass: BeerVsReadingMockDataSource, + deps: [], + }, + }); + + this.initializeDashboard(); + } + + public initializeDashboard(): void { + // We're using a static configuration object for this example, but this is where + // the widget's configuration could potentially be populated from a database + const widgetsWithStructure = widgetConfigs.map((w) => + this.widgetTypesService.mergeWithWidgetType(w) + ); + const widgetsIndex = keyBy(widgetsWithStructure, (w: IWidget) => w.id); + + // Finally, assigning the variables we created above to the dashboard + this.dashboard = { + positions: cloneDeep(positions), + widgets: widgetsIndex, + }; + } + + /** Used for restoring widgets state */ + public reInitializeDashboard(): void { + // destroys the components and their providers so the dashboard can re init data + this.dashboard = undefined; + this.changeDetectorRef.detectChanges(); + + this.initializeDashboard(); + } +} + +const widgetConfigs: IWidget[] = [ + { + id: "lineWidgetId", + type: "timeseries", + pizzagna: { + [PizzagnaLayer.Configuration]: { + [DEFAULT_PIZZAGNA_ROOT]: { + providers: { + [WellKnownProviders.DataSource]: { + // Setting the initially selected data source providerId + providerId: BeerVsReadingMockDataSource.providerId, + } as IProviderConfiguration, + }, + }, + header: { + properties: { + title: "Line Chart", + subtitle: "Survey of 1000 Solarians", + }, + }, + chart: { + providers: { + [WellKnownProviders.Adapter]: { + properties: { + // Setting the series and corresponding labels to initially display on the chart + series: [ + { + id: "series-1", + label: "Beer Tasting", + selectedSeriesId: "series-1", + }, + { + id: "series-2", + label: "Reading", + selectedSeriesId: "series-2", + }, + ] as ITimeseriesItemConfiguration[], + }, + } as Partial, + }, + properties: { + // Setting the general chart configuration + configuration: { + legendPlacement: LegendPlacement.Right, + enableZoom: true, + leftAxisLabel: "Solarians (%)", + // You can optionally define custom colors for the chart by setting the 'chartColors' configuration property + // "chartColors": [ + // "var(--nui-color-chart-eight)", + // "var(--nui-color-chart-nine)", + // "var(--nui-color-chart-ten)", + // ], + } as ITimeseriesWidgetConfig, + }, + }, + timeframeSelection: { + properties: { + // Setting the initial timeframe selected in the timeframe bar + timeframe: { + selectedPresetId: "last7Days", + } as ISerializableTimeframe, + minDate: moment().subtract(60, "days").format(), + maxDate: moment().format(), + }, + }, + }, + }, + }, + { + id: "stackedAreaWidgetId", + type: "timeseries", + pizzagna: { + [PizzagnaLayer.Configuration]: { + [DEFAULT_PIZZAGNA_ROOT]: { + providers: { + [WellKnownProviders.DataSource]: { + // Setting the initially selected data source providerId + providerId: BeerVsReadingMockDataSource.providerId, + } as IProviderConfiguration, + }, + }, + header: { + properties: { + title: "Stacked Area Chart", + subtitle: "Survey of 1000 Solarians", + }, + }, + chart: { + providers: { + [WellKnownProviders.Adapter]: { + properties: { + // Setting the series and corresponding labels to initially display on the chart + series: [ + { + id: "series-1", + label: "Beer Tasting", + selectedSeriesId: "series-1", + }, + { + id: "series-2", + label: "Reading", + selectedSeriesId: "series-2", + }, + ] as ITimeseriesItemConfiguration[], + }, + } as Partial, + }, + properties: { + // Setting the general chart configuration + configuration: { + legendPlacement: LegendPlacement.Right, + enableZoom: true, + // Setting the preset to stacked area + preset: TimeseriesChartPreset.StackedArea, + leftAxisLabel: "Solarians (%)", + // You can optionally define custom colors for the chart by setting the 'chartColors' configuration property + // "chartColors": [ + // "var(--nui-color-chart-eight)", + // "var(--nui-color-chart-nine)", + // "var(--nui-color-chart-ten)", + // ], + } as ITimeseriesWidgetConfig, + }, + }, + timeframeSelection: { + properties: { + // Setting the initial timeframe selected in the timeframe bar + timeframe: { + selectedPresetId: "last7Days", + } as ISerializableTimeframe, + minDate: moment().subtract(60, "days").format(), + maxDate: moment().format(), + }, + }, + }, + }, + }, + { + id: "stackedPercentageAreaWidgetId", + type: "timeseries", + pizzagna: { + [PizzagnaLayer.Configuration]: { + [DEFAULT_PIZZAGNA_ROOT]: { + providers: { + [WellKnownProviders.DataSource]: { + // Setting the initially selected data source providerId + providerId: BeerVsReadingMockDataSource.providerId, + } as IProviderConfiguration, + }, + }, + header: { + properties: { + title: "Stacked Percentage Area Chart", + subtitle: "Survey of 1000 Solarians", + }, + }, + chart: { + providers: { + [WellKnownProviders.Adapter]: { + properties: { + // Setting the series and corresponding labels to initially display on the chart + series: [ + { + id: "series-1", + label: "Beer Tasting", + selectedSeriesId: "series-1", + }, + { + id: "series-2", + label: "Reading", + selectedSeriesId: "series-2", + }, + ] as ITimeseriesItemConfiguration[], + }, + } as Partial, + }, + properties: { + // Setting the general chart configuration + configuration: { + legendPlacement: LegendPlacement.Right, + enableZoom: true, + // Setting the preset to stacked percentage area + preset: TimeseriesChartPreset.StackedPercentageArea, + leftAxisLabel: "Solarians (%)", + // You can optionally define custom colors for the chart by setting the 'chartColors' configuration property + // "chartColors": [ + // "var(--nui-color-chart-eight)", + // "var(--nui-color-chart-nine)", + // "var(--nui-color-chart-ten)", + // ], + } as ITimeseriesWidgetConfig, + }, + }, + timeframeSelection: { + properties: { + // Setting the initial timeframe selected in the timeframe bar + timeframe: { + selectedPresetId: "last7Days", + } as ISerializableTimeframe, + minDate: moment().subtract(60, "days").format(), + maxDate: moment().format(), + }, + }, + }, + }, + }, + { + id: "stackedBarWidgetId", + type: "timeseries", + pizzagna: { + [PizzagnaLayer.Configuration]: { + [DEFAULT_PIZZAGNA_ROOT]: { + providers: { + [WellKnownProviders.DataSource]: { + // Setting the initially selected data source providerId + providerId: BeerVsReadingMockDataSource.providerId, + } as IProviderConfiguration, + }, + }, + header: { + properties: { + title: "Stacked Bar Chart", + subtitle: "Survey of 1000 Solarians", + }, + }, + chart: { + providers: { + [WellKnownProviders.Adapter]: { + properties: { + // Setting the series and corresponding labels to initially display on the chart + series: [ + { + id: "series-1", + label: "Beer Tasting", + selectedSeriesId: "series-1", + }, + { + id: "series-2", + label: "Reading", + selectedSeriesId: "series-2", + }, + ] as ITimeseriesItemConfiguration[], + }, + } as Partial, + }, + properties: { + configuration: { + legendPlacement: LegendPlacement.Right, + enableZoom: true, + leftAxisLabel: "Solarians (%)", + // Setting the preset to stacked bar + preset: TimeseriesChartPreset.StackedBar, + scales: { + x: { + type: TimeseriesScaleType.TimeInterval, + properties: { + interval: 24 * 60 * 60, + }, + } as ITimeseriesScaleConfig, + }, + } as ITimeseriesWidgetConfig, + }, + }, + timeframeSelection: { + properties: { + // Setting the initial timeframe selected in the timeframe bar + timeframe: { + selectedPresetId: "last7Days", + } as ISerializableTimeframe, + minDate: moment().subtract(60, "days").format(), + maxDate: moment().format(), + }, + }, + }, + }, + }, +]; + +// using startOf("day") so that each band for the bar chart corresponds to a calendar day +const now = moment().startOf("day"); + +export const getData = (): ITimeseriesWidgetData[] => [ + { + id: "series-1", + name: "Beer Tasting", + description: "Havin' some suds", + data: [ + { x: now.clone().subtract(20, "day").toDate(), y: 30 }, + { x: now.clone().subtract(19, "day").toDate(), y: 35 }, + { x: now.clone().subtract(18, "day").toDate(), y: 33 }, + { x: now.clone().subtract(17, "day").toDate(), y: 40 }, + { x: now.clone().subtract(16, "day").toDate(), y: 35 }, + { x: now.clone().subtract(15, "day").toDate(), y: 30 }, + { x: now.clone().subtract(14, "day").toDate(), y: 35 }, + { x: now.clone().subtract(13, "day").toDate(), y: 15 }, + { x: now.clone().subtract(12, "day").toDate(), y: 30 }, + { x: now.clone().subtract(11, "day").toDate(), y: 45 }, + { x: now.clone().subtract(10, "day").toDate(), y: 60 }, + { x: now.clone().subtract(9, "day").toDate(), y: 54 }, + { x: now.clone().subtract(8, "day").toDate(), y: 42 }, + { x: now.clone().subtract(7, "day").toDate(), y: 44 }, + { x: now.clone().subtract(6, "day").toDate(), y: 54 }, + { x: now.clone().subtract(5, "day").toDate(), y: 43 }, + { x: now.clone().subtract(4, "day").toDate(), y: 76 }, + { x: now.clone().subtract(3, "day").toDate(), y: 54 }, + { x: now.clone().subtract(2, "day").toDate(), y: 42 }, + { x: now.clone().subtract(1, "day").toDate(), y: 34 }, + ], + }, + { + id: "series-2", + name: "Reading", + description: "Hittin' the books", + data: [ + { x: now.clone().subtract(20, "day").toDate(), y: 60 }, + { x: now.clone().subtract(19, "day").toDate(), y: 64 }, + { x: now.clone().subtract(18, "day").toDate(), y: 70 }, + { x: now.clone().subtract(17, "day").toDate(), y: 55 }, + { x: now.clone().subtract(16, "day").toDate(), y: 55 }, + { x: now.clone().subtract(15, "day").toDate(), y: 45 }, + { x: now.clone().subtract(14, "day").toDate(), y: 60 }, + { x: now.clone().subtract(13, "day").toDate(), y: 65 }, + { x: now.clone().subtract(12, "day").toDate(), y: 63 }, + { x: now.clone().subtract(11, "day").toDate(), y: 60 }, + { x: now.clone().subtract(10, "day").toDate(), y: 61 }, + { x: now.clone().subtract(9, "day").toDate(), y: 65 }, + { x: now.clone().subtract(8, "day").toDate(), y: 63 }, + { x: now.clone().subtract(7, "day").toDate(), y: 58 }, + { x: now.clone().subtract(6, "day").toDate(), y: 64 }, + { x: now.clone().subtract(5, "day").toDate(), y: 63 }, + { x: now.clone().subtract(4, "day").toDate(), y: 60 }, + { x: now.clone().subtract(3, "day").toDate(), y: 62 }, + { x: now.clone().subtract(2, "day").toDate(), y: 61 }, + { x: now.clone().subtract(1, "day").toDate(), y: 62 }, + ], + }, +]; + +// Setting the widget dimensions and position (this is for gridster) +const positions: Record = { + [widgetConfigs[0].id]: { + cols: 6, + rows: 6, + y: 0, + x: 0, + }, + [widgetConfigs[1].id]: { + cols: 6, + rows: 6, + y: 0, + x: 6, + }, + [widgetConfigs[3].id]: { + cols: 6, + rows: 6, + y: 6, + x: 0, + }, + [widgetConfigs[2].id]: { + cols: 6, + rows: 6, + y: 6, + x: 6, + }, +}; +\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\`, + "widget-types/timeseries/timeseries-widget-interactive-example/timeseries-widget-interactive-example.component.ts": \\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\`// © 2022 SolarWinds Worldwide, LLC. All rights reserved. +// +// Permission is hereby granted, free of charge, to any person obtaining a copy +// of this software and associated documentation files (the "Software"), to +// deal in the Software without restriction, including without limitation the +// rights to use, copy, modify, merge, publish, distribute, sublicense, and/or +// sell copies of the Software, and to permit persons to whom the Software is +// furnished to do so, subject to the following conditions: +// +// The above copyright notice and this permission notice shall be included in +// all copies or substantial portions of the Software. +// +// THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR +// IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, +// FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE +// AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER +// LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, +// OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN +// THE SOFTWARE. + +import { + ChangeDetectorRef, + Component, + Injectable, + OnInit, +} from "@angular/core"; +import { GridsterConfig, GridsterItem } from "angular-gridster2"; +import cloneDeep from "lodash/cloneDeep"; +import keyBy from "lodash/keyBy"; +import moment, { Moment } from "moment/moment"; +import { BehaviorSubject } from "rxjs"; + +import { + DataSourceService, + IDataSource, + INovaFilters, + ITimeframe, +} from "@nova-ui/bits"; +import { + DATA_SOURCE, + DEFAULT_PIZZAGNA_ROOT, + IDashboard, + IDataSourceOutput, + IProviderConfiguration, + ISerializableTimeframe, + ITimeseriesItemConfiguration, + ITimeseriesOutput, + ITimeseriesScaleConfig, + ITimeseriesWidgetConfig, + ITimeseriesWidgetData, + ITimeseriesWidgetSeriesData, + IWidget, + NOVA_URL_INTERACTION_HANDLER, + LegendPlacement, + PizzagnaLayer, + ProviderRegistryService, + TimeseriesChartPreset, + TimeseriesScaleType, + WellKnownPathKey, + WellKnownProviders, + WidgetTypesService, +} from "@nova-ui/dashboards"; + +/** + * A simple Timeseries data source implementation + */ +@Injectable() +export class TimeseriesMockDataSource + extends DataSourceService + implements IDataSource +{ + public static providerId = "TimeseriesMockDataSource"; + + public busy = new BehaviorSubject(false); + + public async getFilteredData( + filters: INovaFilters + ): Promise> { + // In this example we're using some static mock data located at the bottom of this file. In a real-world + // scenario, the data for the chart would likely be retrieved via an asynchronous backend call. + let filteredData = getData(); + + this.busy.next(true); + + // Filtering using the filter registered by the TimeFrameBar + const timeframeFilter = filters.timeframe?.value as ITimeframe; + if (timeframeFilter) { + filteredData = filteredData.map((item: ITimeseriesWidgetData) => ({ + id: item.id, + name: item.name, + description: item.description, + // the filtered data should return the provided links if they are set. + link: item?.link, + secondaryLink: item?.secondaryLink, + data: item.data.filter( + (seriesData: ITimeseriesWidgetSeriesData) => + filterDates( + seriesData.x, + timeframeFilter.startDatetime, + timeframeFilter.endDatetime + ) + ), + })); + } + + this.busy.next(false); + + return { result: { series: filteredData } }; + } +} + +function filterDates(dateToCheck: Date, startDate: Moment, endDate: Moment) { + const mom = moment(dateToCheck); + return ( + mom.isBetween(startDate, endDate) || + mom.isSame(startDate) || + mom.isSame(endDate) + ); +} + +/** + * A component that instantiates the dashboard + */ +@Component({ + selector: "timeseries-widget-interactive-example", + templateUrl: "./timeseries-widget-interactive-example.component.html", + styleUrls: ["./timeseries-widget-interactive-example.component.less"], + standalone: false, +}) +export class TimeseriesWidgetInteractiveExampleComponent implements OnInit { + // This variable will hold all the data needed to define the layout and behavior of the widgets. + // Pass this to the dashboard component's dashboard input in the template. + public dashboard: IDashboard | undefined; + + // Angular gridster requires a configuration object even if it's empty. + // Pass this to the dashboard component's gridsterConfig input in the template. + public gridsterConfig: GridsterConfig = {}; + + // Boolean passed as an input to the dashboard. When true, widgets can be moved, resized, removed, or edited + public editMode: boolean = false; + + constructor( + // WidgetTypesService provides the widget's necessary structure information + private widgetTypesService: WidgetTypesService, + + // In general, the ProviderRegistryService is used for making entities available for injection into dynamically loaded components. + private providerRegistry: ProviderRegistryService, + + // Angular's ChangeDetectorRef + private changeDetectorRef: ChangeDetectorRef + ) {} + + public ngOnInit(): void { + // Grabbing the widget's default template which will be needed as a parameter for setNode + const widgetTemplate = this.widgetTypesService.getWidgetType( + "timeseries", + 1 + ); + // Registering our data sources as dropdown options in the widget editor/configurator + // Note: This could also be done in the parent module's constructor so that + // multiple dashboards could have access to the same widget template modification. + this.widgetTypesService.setNode( + // This is the template we grabbed above with getWidgetType + widgetTemplate, + // We are setting the editor/configurator part of the widget template + "configurator", + // This indicates which node you are changing and we want to change + // the data source providers available for selection in the editor. + WellKnownPathKey.DataSourceProviders, + // We are setting the data sources available for selection in the editor + [TimeseriesMockDataSource.providerId] + ); + + // Registering the data source for injection into the widget. + this.providerRegistry.setProviders({ + [TimeseriesMockDataSource.providerId]: { + provide: DATA_SOURCE, + useClass: TimeseriesMockDataSource, + deps: [], + }, + }); + + this.initializeDashboard(); + } + + public initializeDashboard(): void { + // We're using a static configuration object for this example, but this is where + // the widget's configuration could potentially be populated from a database + const widgetsWithStructure = widgetConfigs.map((w) => + this.widgetTypesService.mergeWithWidgetType(w) + ); + const widgetsIndex = keyBy(widgetsWithStructure, (w: IWidget) => w.id); + + // Finally, assigning the variables we created above to the dashboard + this.dashboard = { + positions: cloneDeep(positions), + widgets: widgetsIndex, + }; + } + + /** Used for restoring widgets state */ + public reInitializeDashboard(): void { + // destroys the components and their providers so the dashboard can re init data + this.dashboard = undefined; + this.changeDetectorRef.detectChanges(); + + this.initializeDashboard(); + } +} + +const widgetConfigs: IWidget[] = [ + { + id: "lineWidgetId", + type: "timeseries", + pizzagna: { + [PizzagnaLayer.Configuration]: { + [DEFAULT_PIZZAGNA_ROOT]: { + providers: { + [WellKnownProviders.DataSource]: { + // Setting the initially selected data source providerId + providerId: TimeseriesMockDataSource.providerId, + } as IProviderConfiguration, + [WellKnownProviders.InteractionHandler]: { + // Setting the UrlInteractionHandler as an interactionHandler + providerId: NOVA_URL_INTERACTION_HANDLER, + properties: { + // the 'url' property tells the handler what link to use when interaction occurs on the series + url: "\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\${data.link || 'https://en.wikipedia.org/wiki/'+data.legendDescriptionPrimary}", + // by default the link is opened in the current window, set 'newWindow' to true to open in a new tab instead + // newWindow: true, + }, + }, + }, + }, + header: { + properties: { + title: "Line Chart", + subtitle: "Basic Timeseries with Interaction", + }, + }, + chart: { + providers: { + [WellKnownProviders.Adapter]: { + properties: { + // Setting the series and corresponding labels to initially display on the chart + series: [ + { + id: "series-1", + label: "Nur-Sultan", + selectedSeriesId: "series-1", + }, + { + id: "series-2", + label: "Brno", + selectedSeriesId: "series-2", + }, + { + id: "series-3", + label: "Lisbon", + selectedSeriesId: "series-3", + }, + { + id: "series-4", + label: "Austin", + selectedSeriesId: "series-4", + }, + ] as ITimeseriesItemConfiguration[], + }, + } as Partial, + }, + properties: { + // Setting the general chart configuration + configuration: { + // setting interaction to 'series' will make all series in the chart interactable + interaction: "series", + legendPlacement: LegendPlacement.Right, + enableZoom: true, + } as ITimeseriesWidgetConfig, + }, + }, + timeframeSelection: { + properties: { + timeframe: { + selectedPresetId: "last7Days", + } as ISerializableTimeframe, + minDate: moment().subtract(60, "days").format(), + maxDate: moment().format(), + }, + }, + }, + }, + }, + { + id: "stackedBarWidgetId", + type: "timeseries", + pizzagna: { + [PizzagnaLayer.Configuration]: { + [DEFAULT_PIZZAGNA_ROOT]: { + providers: { + [WellKnownProviders.DataSource]: { + // Setting the initially selected data source providerId + providerId: TimeseriesMockDataSource.providerId, + } as IProviderConfiguration, + }, + }, + header: { + properties: { + title: "Stacked Bar Chart", + subtitle: + "Basic Timeseries without Interaction Handler", + }, + }, + chart: { + providers: { + [WellKnownProviders.Adapter]: { + properties: { + // Setting the series and corresponding labels to initially display on the chart + series: [ + { + id: "series-1", + label: "Nur-Sultan", + selectedSeriesId: "series-1", + }, + { + id: "series-2", + label: "Brno", + selectedSeriesId: "series-2", + }, + { + id: "series-3", + label: "Lisbon", + selectedSeriesId: "series-3", + }, + { + id: "series-4", + label: "Austin", + selectedSeriesId: "series-4", + }, + ] as ITimeseriesItemConfiguration[], + }, + } as Partial, + }, + properties: { + // Setting the general chart configuration + configuration: { + legendPlacement: LegendPlacement.Right, + enableZoom: true, + // Setting the preset to stacked bar + preset: TimeseriesChartPreset.StackedBar, + scales: { + x: { + type: TimeseriesScaleType.TimeInterval, + properties: { + interval: 24 * 60 * 60, + }, + } as ITimeseriesScaleConfig, + }, + } as ITimeseriesWidgetConfig, + }, + }, + timeframeSelection: { + properties: { + // Setting the initial timeframe selected in the timeframe bar + timeframe: { + selectedPresetId: "last7Days", + } as ISerializableTimeframe, + minDate: moment().subtract(60, "days").format(), + maxDate: moment().format(), + }, + }, + }, + }, + }, +]; + +// using startOf("day") so that each band for the bar chart corresponds to a calendar day +const startOfToday = moment().startOf("day").toDate(); + +export const getData = (): ITimeseriesWidgetData[] => [ + { + id: "series-1", + name: "Nur-Sultan", + description: "'link' only", + link: "https://en.wikipedia.org/wiki/Nur-Sultan", + data: [ + { x: moment(startOfToday).subtract(59, "day").toDate(), y: 35 }, + { x: moment(startOfToday).subtract(58, "day").toDate(), y: 33 }, + { x: moment(startOfToday).subtract(57, "day").toDate(), y: 40 }, + { x: moment(startOfToday).subtract(56, "day").toDate(), y: 35 }, + { x: moment(startOfToday).subtract(55, "day").toDate(), y: 30 }, + { x: moment(startOfToday).subtract(54, "day").toDate(), y: 35 }, + { x: moment(startOfToday).subtract(53, "day").toDate(), y: 15 }, + { x: moment(startOfToday).subtract(52, "day").toDate(), y: 30 }, + { x: moment(startOfToday).subtract(51, "day").toDate(), y: 35 }, + { x: moment(startOfToday).subtract(50, "day").toDate(), y: 34 }, + { x: moment(startOfToday).subtract(49, "day").toDate(), y: 35 }, + { x: moment(startOfToday).subtract(48, "day").toDate(), y: 33 }, + { x: moment(startOfToday).subtract(47, "day").toDate(), y: 40 }, + { x: moment(startOfToday).subtract(46, "day").toDate(), y: 35 }, + { x: moment(startOfToday).subtract(45, "day").toDate(), y: 30 }, + { x: moment(startOfToday).subtract(44, "day").toDate(), y: 35 }, + { x: moment(startOfToday).subtract(43, "day").toDate(), y: 15 }, + { x: moment(startOfToday).subtract(42, "day").toDate(), y: 30 }, + { x: moment(startOfToday).subtract(41, "day").toDate(), y: 35 }, + { x: moment(startOfToday).subtract(40, "day").toDate(), y: 34 }, + { x: moment(startOfToday).subtract(39, "day").toDate(), y: 35 }, + { x: moment(startOfToday).subtract(38, "day").toDate(), y: 33 }, + { x: moment(startOfToday).subtract(37, "day").toDate(), y: 40 }, + { x: moment(startOfToday).subtract(36, "day").toDate(), y: 35 }, + { x: moment(startOfToday).subtract(35, "day").toDate(), y: 30 }, + { x: moment(startOfToday).subtract(34, "day").toDate(), y: 35 }, + { x: moment(startOfToday).subtract(33, "day").toDate(), y: 15 }, + { x: moment(startOfToday).subtract(32, "day").toDate(), y: 30 }, + { x: moment(startOfToday).subtract(31, "day").toDate(), y: 35 }, + { x: moment(startOfToday).subtract(30, "day").toDate(), y: 34 }, + { x: moment(startOfToday).subtract(29, "day").toDate(), y: 35 }, + { x: moment(startOfToday).subtract(28, "day").toDate(), y: 33 }, + { x: moment(startOfToday).subtract(27, "day").toDate(), y: 40 }, + { x: moment(startOfToday).subtract(26, "day").toDate(), y: 35 }, + { x: moment(startOfToday).subtract(25, "day").toDate(), y: 30 }, + { x: moment(startOfToday).subtract(24, "day").toDate(), y: 35 }, + { x: moment(startOfToday).subtract(23, "day").toDate(), y: 15 }, + { x: moment(startOfToday).subtract(22, "day").toDate(), y: 30 }, + { x: moment(startOfToday).subtract(21, "day").toDate(), y: 35 }, + { x: moment(startOfToday).subtract(20, "day").toDate(), y: 34 }, + { x: moment(startOfToday).subtract(19, "day").toDate(), y: 35 }, + { x: moment(startOfToday).subtract(18, "day").toDate(), y: 33 }, + { x: moment(startOfToday).subtract(17, "day").toDate(), y: 40 }, + { x: moment(startOfToday).subtract(16, "day").toDate(), y: 35 }, + { x: moment(startOfToday).subtract(15, "day").toDate(), y: 30 }, + { x: moment(startOfToday).subtract(14, "day").toDate(), y: 35 }, + { x: moment(startOfToday).subtract(13, "day").toDate(), y: 15 }, + { x: moment(startOfToday).subtract(12, "day").toDate(), y: 30 }, + { x: moment(startOfToday).subtract(11, "day").toDate(), y: 35 }, + { x: moment(startOfToday).subtract(10, "day").toDate(), y: 34 }, + { x: moment(startOfToday).subtract(9, "day").toDate(), y: 33 }, + { x: moment(startOfToday).subtract(8, "day").toDate(), y: 35 }, + { x: moment(startOfToday).subtract(7, "day").toDate(), y: 36 }, + { x: moment(startOfToday).subtract(6, "day").toDate(), y: 34 }, + { x: moment(startOfToday).subtract(5, "day").toDate(), y: 33 }, + { x: moment(startOfToday).subtract(4, "day").toDate(), y: 30 }, + { x: moment(startOfToday).subtract(3, "day").toDate(), y: 32 }, + { x: moment(startOfToday).subtract(2, "day").toDate(), y: 31 }, + { x: moment(startOfToday).subtract(1, "day").toDate(), y: 34 }, + { x: moment(startOfToday).toDate(), y: 25 }, + ], + }, + { + id: "series-2", + name: "Brno", + description: "'link' and 'secondaryLink'", + link: "https://en.wikipedia.org/wiki/Brno", + secondaryLink: "https://en.wikipedia.org/wiki/Europe", + data: [ + { x: moment(startOfToday).subtract(59, "day").toDate(), y: 35 }, + { x: moment(startOfToday).subtract(58, "day").toDate(), y: 33 }, + { x: moment(startOfToday).subtract(57, "day").toDate(), y: 40 }, + { x: moment(startOfToday).subtract(56, "day").toDate(), y: 35 }, + { x: moment(startOfToday).subtract(55, "day").toDate(), y: 30 }, + { x: moment(startOfToday).subtract(54, "day").toDate(), y: 35 }, + { x: moment(startOfToday).subtract(53, "day").toDate(), y: 15 }, + { x: moment(startOfToday).subtract(52, "day").toDate(), y: 30 }, + { x: moment(startOfToday).subtract(51, "day").toDate(), y: 35 }, + { x: moment(startOfToday).subtract(50, "day").toDate(), y: 34 }, + { x: moment(startOfToday).subtract(49, "day").toDate(), y: 35 }, + { x: moment(startOfToday).subtract(48, "day").toDate(), y: 33 }, + { x: moment(startOfToday).subtract(47, "day").toDate(), y: 40 }, + { x: moment(startOfToday).subtract(46, "day").toDate(), y: 35 }, + { x: moment(startOfToday).subtract(45, "day").toDate(), y: 30 }, + { x: moment(startOfToday).subtract(44, "day").toDate(), y: 35 }, + { x: moment(startOfToday).subtract(43, "day").toDate(), y: 15 }, + { x: moment(startOfToday).subtract(42, "day").toDate(), y: 30 }, + { x: moment(startOfToday).subtract(41, "day").toDate(), y: 35 }, + { x: moment(startOfToday).subtract(40, "day").toDate(), y: 34 }, + { x: moment(startOfToday).subtract(39, "day").toDate(), y: 35 }, + { x: moment(startOfToday).subtract(38, "day").toDate(), y: 33 }, + { x: moment(startOfToday).subtract(37, "day").toDate(), y: 40 }, + { x: moment(startOfToday).subtract(36, "day").toDate(), y: 35 }, + { x: moment(startOfToday).subtract(35, "day").toDate(), y: 30 }, + { x: moment(startOfToday).subtract(34, "day").toDate(), y: 35 }, + { x: moment(startOfToday).subtract(33, "day").toDate(), y: 15 }, + { x: moment(startOfToday).subtract(32, "day").toDate(), y: 30 }, + { x: moment(startOfToday).subtract(31, "day").toDate(), y: 35 }, + { x: moment(startOfToday).subtract(30, "day").toDate(), y: 34 }, + { x: moment(startOfToday).subtract(29, "day").toDate(), y: 35 }, + { x: moment(startOfToday).subtract(28, "day").toDate(), y: 33 }, + { x: moment(startOfToday).subtract(27, "day").toDate(), y: 40 }, + { x: moment(startOfToday).subtract(26, "day").toDate(), y: 35 }, + { x: moment(startOfToday).subtract(25, "day").toDate(), y: 30 }, + { x: moment(startOfToday).subtract(24, "day").toDate(), y: 35 }, + { x: moment(startOfToday).subtract(23, "day").toDate(), y: 15 }, + { x: moment(startOfToday).subtract(22, "day").toDate(), y: 30 }, + { x: moment(startOfToday).subtract(21, "day").toDate(), y: 35 }, + { x: moment(startOfToday).subtract(20, "day").toDate(), y: 34 }, + { x: moment(startOfToday).subtract(19, "day").toDate(), y: 64 }, + { x: moment(startOfToday).subtract(18, "day").toDate(), y: 70 }, + { x: moment(startOfToday).subtract(17, "day").toDate(), y: 55 }, + { x: moment(startOfToday).subtract(16, "day").toDate(), y: 55 }, + { x: moment(startOfToday).subtract(15, "day").toDate(), y: 45 }, + { x: moment(startOfToday).subtract(14, "day").toDate(), y: 10 }, + { x: moment(startOfToday).subtract(13, "day").toDate(), y: 65 }, + { x: moment(startOfToday).subtract(12, "day").toDate(), y: 35 }, + { x: moment(startOfToday).subtract(11, "day").toDate(), y: 60 }, + { x: moment(startOfToday).subtract(10, "day").toDate(), y: 61 }, + { x: moment(startOfToday).subtract(9, "day").toDate(), y: 65 }, + { x: moment(startOfToday).subtract(8, "day").toDate(), y: 63 }, + { x: moment(startOfToday).subtract(7, "day").toDate(), y: 58 }, + { x: moment(startOfToday).subtract(6, "day").toDate(), y: 64 }, + { x: moment(startOfToday).subtract(5, "day").toDate(), y: 63 }, + { x: moment(startOfToday).subtract(4, "day").toDate(), y: 60 }, + { x: moment(startOfToday).subtract(3, "day").toDate(), y: 62 }, + { x: moment(startOfToday).subtract(2, "day").toDate(), y: 61 }, + { x: moment(startOfToday).subtract(1, "day").toDate(), y: 62 }, + { x: moment(startOfToday).toDate(), y: 55 }, + ], + }, + { + id: "series-3", + name: "Lisbon", + description: "'secondaryLink' only", + secondaryLink: "https://en.wikipedia.org/wiki/Lisbon", + data: [ + { x: moment(startOfToday).subtract(59, "day").toDate(), y: 35 }, + { x: moment(startOfToday).subtract(58, "day").toDate(), y: 33 }, + { x: moment(startOfToday).subtract(57, "day").toDate(), y: 40 }, + { x: moment(startOfToday).subtract(56, "day").toDate(), y: 35 }, + { x: moment(startOfToday).subtract(55, "day").toDate(), y: 30 }, + { x: moment(startOfToday).subtract(54, "day").toDate(), y: 35 }, + { x: moment(startOfToday).subtract(53, "day").toDate(), y: 15 }, + { x: moment(startOfToday).subtract(52, "day").toDate(), y: 30 }, + { x: moment(startOfToday).subtract(51, "day").toDate(), y: 35 }, + { x: moment(startOfToday).subtract(50, "day").toDate(), y: 34 }, + { x: moment(startOfToday).subtract(49, "day").toDate(), y: 35 }, + { x: moment(startOfToday).subtract(48, "day").toDate(), y: 33 }, + { x: moment(startOfToday).subtract(47, "day").toDate(), y: 40 }, + { x: moment(startOfToday).subtract(46, "day").toDate(), y: 35 }, + { x: moment(startOfToday).subtract(45, "day").toDate(), y: 30 }, + { x: moment(startOfToday).subtract(44, "day").toDate(), y: 35 }, + { x: moment(startOfToday).subtract(43, "day").toDate(), y: 15 }, + { x: moment(startOfToday).subtract(42, "day").toDate(), y: 30 }, + { x: moment(startOfToday).subtract(41, "day").toDate(), y: 35 }, + { x: moment(startOfToday).subtract(40, "day").toDate(), y: 34 }, + { x: moment(startOfToday).subtract(39, "day").toDate(), y: 35 }, + { x: moment(startOfToday).subtract(38, "day").toDate(), y: 33 }, + { x: moment(startOfToday).subtract(37, "day").toDate(), y: 40 }, + { x: moment(startOfToday).subtract(36, "day").toDate(), y: 35 }, + { x: moment(startOfToday).subtract(35, "day").toDate(), y: 30 }, + { x: moment(startOfToday).subtract(34, "day").toDate(), y: 35 }, + { x: moment(startOfToday).subtract(33, "day").toDate(), y: 15 }, + { x: moment(startOfToday).subtract(32, "day").toDate(), y: 30 }, + { x: moment(startOfToday).subtract(31, "day").toDate(), y: 35 }, + { x: moment(startOfToday).subtract(30, "day").toDate(), y: 34 }, + { x: moment(startOfToday).subtract(29, "day").toDate(), y: 35 }, + { x: moment(startOfToday).subtract(28, "day").toDate(), y: 33 }, + { x: moment(startOfToday).subtract(27, "day").toDate(), y: 40 }, + { x: moment(startOfToday).subtract(26, "day").toDate(), y: 35 }, + { x: moment(startOfToday).subtract(25, "day").toDate(), y: 30 }, + { x: moment(startOfToday).subtract(24, "day").toDate(), y: 35 }, + { x: moment(startOfToday).subtract(23, "day").toDate(), y: 15 }, + { x: moment(startOfToday).subtract(22, "day").toDate(), y: 30 }, + { x: moment(startOfToday).subtract(21, "day").toDate(), y: 35 }, + { x: moment(startOfToday).subtract(20, "day").toDate(), y: 34 }, + { x: moment(startOfToday).subtract(19, "day").toDate(), y: 80 }, + { x: moment(startOfToday).subtract(18, "day").toDate(), y: 70 }, + { x: moment(startOfToday).subtract(17, "day").toDate(), y: 95 }, + { x: moment(startOfToday).subtract(16, "day").toDate(), y: 90 }, + { x: moment(startOfToday).subtract(15, "day").toDate(), y: 85 }, + { x: moment(startOfToday).subtract(14, "day").toDate(), y: 70 }, + { x: moment(startOfToday).subtract(13, "day").toDate(), y: 75 }, + { x: moment(startOfToday).subtract(12, "day").toDate(), y: 69 }, + { x: moment(startOfToday).subtract(11, "day").toDate(), y: 75 }, + { x: moment(startOfToday).subtract(10, "day").toDate(), y: 81 }, + { x: moment(startOfToday).subtract(9, "day").toDate(), y: 93 }, + { x: moment(startOfToday).subtract(8, "day").toDate(), y: 83 }, + { x: moment(startOfToday).subtract(7, "day").toDate(), y: 70 }, + { x: moment(startOfToday).subtract(6, "day").toDate(), y: 74 }, + { x: moment(startOfToday).subtract(5, "day").toDate(), y: 73 }, + { x: moment(startOfToday).subtract(4, "day").toDate(), y: 68 }, + { x: moment(startOfToday).subtract(3, "day").toDate(), y: 72 }, + { x: moment(startOfToday).subtract(2, "day").toDate(), y: 61 }, + { x: moment(startOfToday).subtract(1, "day").toDate(), y: 69 }, + { x: moment(startOfToday).toDate(), y: 60 }, + ], + }, + { + id: "series-4", + name: "Austin", + description: "No links", + data: [ + { x: moment(startOfToday).subtract(59, "day").toDate(), y: 25 }, + { x: moment(startOfToday).subtract(58, "day").toDate(), y: 43 }, + { x: moment(startOfToday).subtract(57, "day").toDate(), y: 40 }, + { x: moment(startOfToday).subtract(56, "day").toDate(), y: 65 }, + { x: moment(startOfToday).subtract(55, "day").toDate(), y: 30 }, + { x: moment(startOfToday).subtract(54, "day").toDate(), y: 25 }, + { x: moment(startOfToday).subtract(53, "day").toDate(), y: 45 }, + { x: moment(startOfToday).subtract(52, "day").toDate(), y: 30 }, + { x: moment(startOfToday).subtract(51, "day").toDate(), y: 85 }, + { x: moment(startOfToday).subtract(50, "day").toDate(), y: 74 }, + { x: moment(startOfToday).subtract(49, "day").toDate(), y: 55 }, + { x: moment(startOfToday).subtract(48, "day").toDate(), y: 23 }, + { x: moment(startOfToday).subtract(47, "day").toDate(), y: 40 }, + { x: moment(startOfToday).subtract(46, "day").toDate(), y: 15 }, + { x: moment(startOfToday).subtract(45, "day").toDate(), y: 20 }, + { x: moment(startOfToday).subtract(44, "day").toDate(), y: 65 }, + { x: moment(startOfToday).subtract(43, "day").toDate(), y: 25 }, + { x: moment(startOfToday).subtract(42, "day").toDate(), y: 40 }, + { x: moment(startOfToday).subtract(41, "day").toDate(), y: 25 }, + { x: moment(startOfToday).subtract(40, "day").toDate(), y: 54 }, + { x: moment(startOfToday).subtract(39, "day").toDate(), y: 65 }, + { x: moment(startOfToday).subtract(38, "day").toDate(), y: 33 }, + { x: moment(startOfToday).subtract(37, "day").toDate(), y: 50 }, + { x: moment(startOfToday).subtract(36, "day").toDate(), y: 45 }, + { x: moment(startOfToday).subtract(35, "day").toDate(), y: 20 }, + { x: moment(startOfToday).subtract(34, "day").toDate(), y: 25 }, + { x: moment(startOfToday).subtract(33, "day").toDate(), y: 35 }, + { x: moment(startOfToday).subtract(32, "day").toDate(), y: 20 }, + { x: moment(startOfToday).subtract(31, "day").toDate(), y: 35 }, + { x: moment(startOfToday).subtract(30, "day").toDate(), y: 14 }, + { x: moment(startOfToday).subtract(29, "day").toDate(), y: 55 }, + { x: moment(startOfToday).subtract(28, "day").toDate(), y: 23 }, + { x: moment(startOfToday).subtract(27, "day").toDate(), y: 10 }, + { x: moment(startOfToday).subtract(26, "day").toDate(), y: 5 }, + { x: moment(startOfToday).subtract(25, "day").toDate(), y: 20 }, + { x: moment(startOfToday).subtract(24, "day").toDate(), y: 35 }, + { x: moment(startOfToday).subtract(23, "day").toDate(), y: 15 }, + { x: moment(startOfToday).subtract(22, "day").toDate(), y: 30 }, + { x: moment(startOfToday).subtract(21, "day").toDate(), y: 35 }, + { x: moment(startOfToday).subtract(20, "day").toDate(), y: 34 }, + { x: moment(startOfToday).subtract(19, "day").toDate(), y: 50 }, + { x: moment(startOfToday).subtract(18, "day").toDate(), y: 60 }, + { x: moment(startOfToday).subtract(17, "day").toDate(), y: 95 }, + { x: moment(startOfToday).subtract(16, "day").toDate(), y: 80 }, + { x: moment(startOfToday).subtract(15, "day").toDate(), y: 65 }, + { x: moment(startOfToday).subtract(14, "day").toDate(), y: 80 }, + { x: moment(startOfToday).subtract(13, "day").toDate(), y: 85 }, + { x: moment(startOfToday).subtract(12, "day").toDate(), y: 69 }, + { x: moment(startOfToday).subtract(11, "day").toDate(), y: 65 }, + { x: moment(startOfToday).subtract(10, "day").toDate(), y: 71 }, + { x: moment(startOfToday).subtract(9, "day").toDate(), y: 73 }, + { x: moment(startOfToday).subtract(8, "day").toDate(), y: 43 }, + { x: moment(startOfToday).subtract(7, "day").toDate(), y: 70 }, + { x: moment(startOfToday).subtract(6, "day").toDate(), y: 84 }, + { x: moment(startOfToday).subtract(5, "day").toDate(), y: 73 }, + { x: moment(startOfToday).subtract(4, "day").toDate(), y: 38 }, + { x: moment(startOfToday).subtract(3, "day").toDate(), y: 72 }, + { x: moment(startOfToday).subtract(2, "day").toDate(), y: 81 }, + { x: moment(startOfToday).subtract(1, "day").toDate(), y: 59 }, + { x: moment(startOfToday).toDate(), y: 60 }, + ], + }, +]; +// Setting the widget dimensions and position (this is for gridster) +const positions: Record = { + [widgetConfigs[0].id]: { + cols: 6, + rows: 6, + y: 0, + x: 0, + }, + [widgetConfigs[1].id]: { + cols: 6, + rows: 6, + y: 0, + x: 6, + }, +}; +\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\`, + "widget-types/timeseries/timeseries-widget-status-bar-example/timeseries-widget-status-bar-example.component.ts": \\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\`// © 2022 SolarWinds Worldwide, LLC. All rights reserved. +// +// Permission is hereby granted, free of charge, to any person obtaining a copy +// of this software and associated documentation files (the "Software"), to +// deal in the Software without restriction, including without limitation the +// rights to use, copy, modify, merge, publish, distribute, sublicense, and/or +// sell copies of the Software, and to permit persons to whom the Software is +// furnished to do so, subject to the following conditions: +// +// The above copyright notice and this permission notice shall be included in +// all copies or substantial portions of the Software. +// +// THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR +// IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, +// FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE +// AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER +// LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, +// OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN +// THE SOFTWARE. + +import { + ChangeDetectorRef, + Component, + Injectable, + OnInit, +} from "@angular/core"; +import { GridsterConfig, GridsterItem } from "angular-gridster2"; +import keyBy from "lodash/keyBy"; +import moment, { Moment } from "moment/moment"; +import { BehaviorSubject } from "rxjs"; + +import { + DataSourceService, + IDataSource, + IDataSourceOutput, + INovaFilters, + ITimeframe, +} from "@nova-ui/bits"; +import { CHART_PALETTE_CS_S_EXTENDED } from "@nova-ui/charts"; +import { + applyStatusEndpoints, + DATA_SOURCE, + DEFAULT_PIZZAGNA_ROOT, + IDashboard, + IProviderConfiguration, + ISerializableTimeframe, + ITimeseriesItemConfiguration, + ITimeseriesOutput, + ITimeseriesScaleConfig, + ITimeseriesWidgetConfig, + ITimeseriesWidgetData, + ITimeseriesWidgetSeriesData, + ITimeseriesWidgetStatusData, + IWidget, + LegendPlacement, + PizzagnaLayer, + ProviderRegistryService, + TimeseriesChartPreset, + TimeseriesScaleType, + WellKnownPathKey, + WellKnownProviders, + WidgetTypesService, +} from "@nova-ui/dashboards"; + +/** + * A simple Timeseries data source implementation with continuous (non-interval-based) output + */ +@Injectable() +export class TimeseriesStatusContinuousDataSource + extends DataSourceService + implements IDataSource> +{ + public static providerId = "TimeseriesStatusContinuousDataSource"; + + public busy = new BehaviorSubject(false); + + public async getFilteredData( + filters: INovaFilters + ): Promise< + IDataSourceOutput> + > { + // In this example we're using some static mock data located at the bottom of this file. In a real-world + // scenario, the data for the chart would likely be retrieved via an asynchronous backend call. + const data = getContinuousData(); + let filteredData = data; + + this.busy.next(true); + + // Filtering using the filter registered by the TimeFrameBar + const timeframeFilter = filters.timeframe?.value as ITimeframe; + if (timeframeFilter) { + filteredData = filteredData.map((item: ITimeseriesWidgetData) => ({ + id: item.id, + name: item.name, + description: item.description, + data: item.data.filter( + (seriesData: ITimeseriesWidgetSeriesData) => + filterDates( + seriesData.x, + timeframeFilter.startDatetime, + timeframeFilter.endDatetime + ) + ), + })); + + // apply endpoints on the filtered status data so that when the status chart is zoomed (filtered), + // each status visualizations is ensured to have valid start and end values + filteredData = applyStatusEndpoints( + timeframeFilter, + filteredData, + data + ); + } + + this.busy.next(false); + return { result: { series: filteredData } }; + } +} + +/** + * A simple Timeseries data source implementation with interval-based output + */ +@Injectable() +export class TimeseriesStatusIntervalDataSource + extends DataSourceService + implements IDataSource> +{ + public static providerId = "TimeseriesStatusIntervalDataSource"; + + public busy = new BehaviorSubject(false); + + public async getFilteredData( + filters: INovaFilters + ): Promise< + IDataSourceOutput> + > { + // In this example we're using some static mock data located at the bottom of this file. In a real-world + // scenario, the data for the chart would likely be retrieved via an asynchronous backend call. + const data = getIntervalData(); + let filteredData = data; + + this.busy.next(true); + + // Filtering using the filter registered by the TimeFrameBar + const timeframeFilter = filters.timeframe?.value as ITimeframe; + if (timeframeFilter) { + filteredData = filteredData.map((item: ITimeseriesWidgetData) => ({ + id: item.id, + name: item.name, + description: item.description, + data: item.data.filter( + (seriesData: ITimeseriesWidgetSeriesData) => + filterDates( + seriesData.x, + timeframeFilter.startDatetime, + timeframeFilter.endDatetime + ) + ), + })); + + // Note: There's no need to apply filter endpoints to the status data in this case since we know it's visualized in regular intervals + } + + this.busy.next(false); + return { result: { series: filteredData } }; + } +} + +function filterDates(dateToCheck: Date, startDate: Moment, endDate: Moment) { + const mom = moment(dateToCheck); + return ( + mom.isBetween(startDate, endDate) || + mom.isSame(startDate) || + mom.isSame(endDate) + ); +} + +/** + * A component that instantiates the dashboard + */ +@Component({ + selector: "timeseries-widget-status-bar-example", + templateUrl: "./timeseries-widget-status-bar-example.component.html", + styleUrls: ["./timeseries-widget-status-bar-example.component.less"], + standalone: false, +}) +export class TimeseriesWidgetStatusBarExampleComponent implements OnInit { + // This variable will hold all the data needed to define the layout and behavior of the widgets. + // Pass this to the dashboard component's dashboard input in the template. + public dashboard: IDashboard | undefined; + + // Angular gridster requires a configuration object even if it's empty. + // Pass this to the dashboard component's gridsterConfig input in the template. + public gridsterConfig: GridsterConfig = {}; + + // Boolean passed as an input to the dashboard. When true, widgets can be moved, resized, removed, or edited + public editMode: boolean = false; + + constructor( + // WidgetTypesService provides the widget's necessary structure information + private widgetTypesService: WidgetTypesService, + + // In general, the ProviderRegistryService is used for making entities available for injection into dynamically loaded components. + private providerRegistry: ProviderRegistryService, + private changeDetectorRef: ChangeDetectorRef + ) {} + + public ngOnInit(): void { + // Grabbing the widget's default template which will be needed as a parameter for setNode + const widgetTemplate = this.widgetTypesService.getWidgetType( + "timeseries", + 1 + ); + // Registering our data sources as dropdown options in the widget editor/configurator + // Note: This could also be done in the parent module's constructor so that + // multiple dashboards could have access to the same widget template modification. + this.widgetTypesService.setNode( + // This is the template we grabbed above with getWidgetType + widgetTemplate, + // We are setting the editor/configurator part of the widget template + "configurator", + // This indicates which node you are changing and we want to change + // the data source providers available for selection in the editor. + WellKnownPathKey.DataSourceProviders, + // We are setting the data sources available for selection in the editor + [ + TimeseriesStatusContinuousDataSource.providerId, + TimeseriesStatusIntervalDataSource.providerId, + ] + ); + + // Registering the data source for injection into the widget. + this.providerRegistry.setProviders({ + [TimeseriesStatusContinuousDataSource.providerId]: { + provide: DATA_SOURCE, + useClass: TimeseriesStatusContinuousDataSource, + deps: [], + }, + [TimeseriesStatusIntervalDataSource.providerId]: { + provide: DATA_SOURCE, + useClass: TimeseriesStatusIntervalDataSource, + deps: [], + }, + }); + + this.initializeDashboard(); + } + + /** Used for restoring widgets state */ + public reInitializeDashboard(): void { + // destroys the components and their providers so the dashboard can re init data + this.dashboard = undefined; + this.changeDetectorRef.detectChanges(); + + this.initializeDashboard(); + } + + public initializeDashboard(): void { + // We're using a static configuration object for this example, but this is where + // the widget's configuration could potentially be populated from a database + const widgetsWithStructure = widgetConfigs.map((w) => + this.widgetTypesService.mergeWithWidgetType(w) + ); + const widgetsIndex = keyBy(widgetsWithStructure, (w: IWidget) => w.id); + + // Finally, assigning the variables we created above to the dashboard + this.dashboard = { + positions, + widgets: widgetsIndex, + }; + } +} + +const widgetConfigs: IWidget[] = [ + { + id: "statusChartWidgetId", + type: "timeseries", + pizzagna: { + [PizzagnaLayer.Configuration]: { + [DEFAULT_PIZZAGNA_ROOT]: { + providers: { + [WellKnownProviders.DataSource]: { + // Setting the initially selected data source providerId + providerId: + TimeseriesStatusContinuousDataSource.providerId, + } as IProviderConfiguration, + }, + }, + header: { + properties: { + title: "Status Bar Chart with Continuous (Non-Interval) Scale", + subtitle: "Basic Timeseries Widget", + }, + }, + chart: { + providers: { + [WellKnownProviders.Adapter]: { + properties: { + // Setting the series and corresponding labels to initially display on the chart + series: [ + { + id: "series-1", + label: "Node Status", + selectedSeriesId: "series-1", + }, + { + id: "series-2", + label: "Node Status", + selectedSeriesId: "series-2", + }, + ] as ITimeseriesItemConfiguration[], + }, + } as Partial, + }, + properties: { + configuration: { + legendPlacement: LegendPlacement.Right, + enableZoom: true, + // Setting the preset to status bar + preset: TimeseriesChartPreset.StatusBar, + } as ITimeseriesWidgetConfig, + }, + }, + timeframeSelection: { + properties: { + // Setting the initial timeframe selected in the timeframe bar + timeframe: { + selectedPresetId: "last7Days", + } as ISerializableTimeframe, + maxDate: moment().format(), + }, + }, + }, + }, + }, + { + id: "statusIntervalChartWidgetId", + type: "timeseries", + pizzagna: { + [PizzagnaLayer.Configuration]: { + [DEFAULT_PIZZAGNA_ROOT]: { + providers: { + [WellKnownProviders.DataSource]: { + // Setting the initially selected data source providerId + providerId: + TimeseriesStatusIntervalDataSource.providerId, + } as IProviderConfiguration, + }, + }, + header: { + properties: { + title: "Status Bar Chart with Interval Scale", + subtitle: "Basic Timeseries Widget", + }, + }, + chart: { + providers: { + [WellKnownProviders.Adapter]: { + properties: { + // Setting the series and corresponding labels to initially display on the chart + series: [ + { + id: "series-1", + label: "Node Status", + selectedSeriesId: "series-1", + }, + { + id: "series-2", + label: "Node Status", + selectedSeriesId: "series-2", + }, + ] as ITimeseriesItemConfiguration[], + }, + } as Partial, + }, + properties: { + configuration: { + legendPlacement: LegendPlacement.Right, + enableZoom: true, + // Setting the preset to status bar + preset: TimeseriesChartPreset.StatusBar, + scales: { + x: { + type: TimeseriesScaleType.TimeInterval, + properties: { + // one-day interval in seconds + interval: 24 * 60 * 60, + }, + } as ITimeseriesScaleConfig, + }, + } as ITimeseriesWidgetConfig, + }, + }, + timeframeSelection: { + properties: { + // Setting the initial timeframe selected in the timeframe bar + timeframe: { + selectedPresetId: "last7Days", + } as ISerializableTimeframe, + maxDate: moment().format(), + }, + }, + }, + }, + }, +]; + +export const startOfToday = (): Moment => moment().startOf("day"); + +export const getContinuousData = + (): ITimeseriesWidgetData[] => { + const series: ITimeseriesWidgetData[] = [ + { + id: "series-1", + name: "Node Status", + description: "lastchance.demo.lab", + data: [ + // the 'x' value is set to the time and 'y' to the status at that given time + { + x: startOfToday().subtract(20, "day").toDate(), + y: Status.Warning, + }, + { + x: startOfToday().subtract(19, "day").toDate(), + y: Status.Down, + }, + { + x: startOfToday().subtract(17, "day").toDate(), + y: Status.Critical, + }, + { + x: startOfToday().subtract(16, "day").toDate(), + y: Status.Warning, + }, + { + x: startOfToday().subtract(15, "day").toDate(), + y: Status.Down, + }, + { + x: startOfToday().subtract(14, "day").toDate(), + y: Status.Critical, + }, + { + x: startOfToday().subtract(12, "day").toDate(), + y: Status.Unknown, + }, + { + x: startOfToday().subtract(10, "day").toDate(), + y: Status.Up, + }, + { + x: startOfToday().subtract(9, "day").toDate(), + y: Status.Critical, + }, + { + x: startOfToday().subtract(6, "day").toDate(), + y: Status.Up, + }, + { + x: startOfToday().subtract(3, "day").toDate(), + y: Status.Warning, + }, + { + x: startOfToday().subtract(2, "day").toDate(), + y: Status.Critical, + }, + { + x: startOfToday().subtract(1, "day").toDate(), + y: Status.Up, + }, + // This data point will be ignored and is only here to provide an endpoint for the previous status. + { x: moment().toDate(), y: Status.Up }, + ], + }, + { + id: "series-2", + name: "Node Status", + description: "newhope.demo.lab", + data: [ + { + x: startOfToday().subtract(19, "day").toDate(), + y: Status.Critical, + }, + { + x: startOfToday().subtract(18, "day").toDate(), + y: Status.Unknown, + }, + { + x: startOfToday().subtract(17, "day").toDate(), + y: Status.Warning, + }, + { + x: startOfToday().subtract(15, "day").toDate(), + y: Status.Down, + }, + { + x: startOfToday().subtract(8, "day").toDate(), + y: Status.Critical, + }, + { + x: startOfToday().subtract(7, "day").toDate(), + y: Status.Down, + }, + { + x: startOfToday().subtract(6, "day").toDate(), + y: Status.Up, + }, + { + x: startOfToday().subtract(5, "day").toDate(), + y: Status.Critical, + }, + { + x: startOfToday().subtract(4, "day").toDate(), + y: Status.Up, + }, + { + x: startOfToday().subtract(3, "day").toDate(), + y: Status.Warning, + }, + { + x: startOfToday().subtract(2, "day").toDate(), + y: Status.Up, + }, + { + x: startOfToday().subtract(1, "day").toDate(), + y: Status.Down, + }, + // This data point will be ignored and is only here to provide an endpoint for the previous status. + { x: moment().toDate(), y: Status.Down }, + ], + }, + ]; + + for (const s of series) { + // here are we setting the color and icon associated to the status for each data point + s.data = s.data.map((d: any, i: number) => ({ + ...d, + color: statusColors[d.y as Status], + // The thickness of the line is dependant on the status. If the status equals 'Up' then 'thick' is set to false. + thick: d.y !== Status.Up, + icon: "status_" + d.y, + })); + } + + return series; + }; + +// Note that the output of this function is spaced evenly at one-day intervals +export const getIntervalData = + (): ITimeseriesWidgetData[] => { + const series: ITimeseriesWidgetData[] = [ + { + id: "series-1", + name: "Node Status", + description: "lastchance.demo.lab", + data: [ + // the 'x' value is set to the time and 'y' to the status at that given time + { + x: startOfToday().subtract(20, "day").toDate(), + y: Status.Up, + }, + { + x: startOfToday().subtract(19, "day").toDate(), + y: Status.Critical, + }, + { + x: startOfToday().subtract(18, "day").toDate(), + y: Status.Warning, + }, + { + x: startOfToday().subtract(17, "day").toDate(), + y: Status.Down, + }, + { + x: startOfToday().subtract(16, "day").toDate(), + y: Status.Critical, + }, + { + x: startOfToday().subtract(15, "day").toDate(), + y: Status.Down, + }, + { + x: startOfToday().subtract(14, "day").toDate(), + y: Status.Up, + }, + { + x: startOfToday().subtract(13, "day").toDate(), + y: Status.Warning, + }, + { + x: startOfToday().subtract(12, "day").toDate(), + y: Status.Up, + }, + { + x: startOfToday().subtract(11, "day").toDate(), + y: Status.Critical, + }, + { + x: startOfToday().subtract(10, "day").toDate(), + y: Status.Up, + }, + { + x: startOfToday().subtract(9, "day").toDate(), + y: Status.Down, + }, + { + x: startOfToday().subtract(8, "day").toDate(), + y: Status.Critical, + }, + { + x: startOfToday().subtract(7, "day").toDate(), + y: Status.Warning, + }, + { + x: startOfToday().subtract(6, "day").toDate(), + y: Status.Down, + }, + { + x: startOfToday().subtract(5, "day").toDate(), + y: Status.Critical, + }, + { + x: startOfToday().subtract(4, "day").toDate(), + y: Status.Down, + }, + { + x: startOfToday().subtract(3, "day").toDate(), + y: Status.Up, + }, + { + x: startOfToday().subtract(2, "day").toDate(), + y: Status.Warning, + }, + { + x: startOfToday().subtract(1, "day").toDate(), + y: Status.Critical, + }, + { x: startOfToday().toDate(), y: Status.Up }, + ], + }, + { + id: "series-2", + name: "Node Status", + description: "newhope.demo.lab", + data: [ + { + x: startOfToday().subtract(20, "day").toDate(), + y: Status.Up, + }, + { + x: startOfToday().subtract(19, "day").toDate(), + y: Status.Up, + }, + { + x: startOfToday().subtract(18, "day").toDate(), + y: Status.Down, + }, + { + x: startOfToday().subtract(17, "day").toDate(), + y: Status.Critical, + }, + { + x: startOfToday().subtract(16, "day").toDate(), + y: Status.Down, + }, + { + x: startOfToday().subtract(15, "day").toDate(), + y: Status.Up, + }, + { + x: startOfToday().subtract(14, "day").toDate(), + y: Status.Critical, + }, + { + x: startOfToday().subtract(13, "day").toDate(), + y: Status.Up, + }, + { + x: startOfToday().subtract(12, "day").toDate(), + y: Status.Critical, + }, + { + x: startOfToday().subtract(11, "day").toDate(), + y: Status.Warning, + }, + { + x: startOfToday().subtract(10, "day").toDate(), + y: Status.Up, + }, + { + x: startOfToday().subtract(9, "day").toDate(), + y: Status.Down, + }, + { + x: startOfToday().subtract(8, "day").toDate(), + y: Status.Up, + }, + { + x: startOfToday().subtract(7, "day").toDate(), + y: Status.Down, + }, + { + x: startOfToday().subtract(6, "day").toDate(), + y: Status.Critical, + }, + { + x: startOfToday().subtract(5, "day").toDate(), + y: Status.Down, + }, + { + x: startOfToday().subtract(4, "day").toDate(), + y: Status.Up, + }, + { + x: startOfToday().subtract(3, "day").toDate(), + y: Status.Critical, + }, + { + x: startOfToday().subtract(2, "day").toDate(), + y: Status.Up, + }, + { + x: startOfToday().subtract(1, "day").toDate(), + y: Status.Warning, + }, + { x: startOfToday().toDate(), y: Status.Critical }, + ], + }, + ]; + + for (const s of series) { + // here are we setting the color and icon associated to the status for each data point + s.data = s.data.map((d: any, i: number) => ({ + ...d, + color: statusColors[d.y as Status], + // The thickness of the line is dependant on the status. If the status equals 'Up' then 'thick' is set to false. + thick: d.y !== Status.Up, + icon: "status_" + d.y, + })); + } + + return series; + }; + +// An enumeration of statuses +enum Status { + Unknown = "unknown", + Up = "up", + Warning = "warning", + Down = "down", + Critical = "critical", +} + +// This is the map used for setting the color of each status bar +const statusColors: Record = { + [Status.Unknown]: CHART_PALETTE_CS_S_EXTENDED[6], + [Status.Up]: CHART_PALETTE_CS_S_EXTENDED[8], + [Status.Warning]: CHART_PALETTE_CS_S_EXTENDED[4], + [Status.Down]: CHART_PALETTE_CS_S_EXTENDED[0], + [Status.Critical]: CHART_PALETTE_CS_S_EXTENDED[2], +}; + +// Setting the widget dimensions and position (this is for gridster) +const positions: Record = { + [widgetConfigs[0].id]: { + cols: 12, + rows: 4, + y: 0, + x: 0, + }, + [widgetConfigs[1].id]: { + cols: 12, + rows: 4, + y: 4, + x: 0, + }, +}; +\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\`, + "widget-types/view-components/kpi-tile-view-basic/kpi-tile-view-basic-example.component.ts": \\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\`// © 2022 SolarWinds Worldwide, LLC. All rights reserved. +// +// Permission is hereby granted, free of charge, to any person obtaining a copy +// of this software and associated documentation files (the "Software"), to +// deal in the Software without restriction, including without limitation the +// rights to use, copy, modify, merge, publish, distribute, sublicense, and/or +// sell copies of the Software, and to permit persons to whom the Software is +// furnished to do so, subject to the following conditions: +// +// The above copyright notice and this permission notice shall be included in +// all copies or substantial portions of the Software. +// +// THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR +// IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, +// FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE +// AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER +// LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, +// OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN +// THE SOFTWARE. + +import { Component } from "@angular/core"; + +type KpiTileState = "normal" | "loading" | "empty"; + +/** + * KPI Tile View - Playground example. + * Switch between all visual states (normal / loading / empty) and toggle + * interactivity to explore every variant the standalone tile supports. + */ +@Component({ + selector: "kpi-tile-view-basic-example", + templateUrl: "./kpi-tile-view-basic-example.component.html", + standalone: false, +}) +export class KpiTileViewBasicExampleComponent { + public state: KpiTileState = "normal"; + public interactive = false; + public lastClicked = ""; + + public readonly stateOptions: KpiTileState[] = ["normal", "loading", "empty"]; + + public onTileClick(label: string): void { + this.lastClicked = label; + } +} +\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\`, + "widget-types/view-components/kpi-tile-view-interactive/kpi-tile-view-interactive-example.component.ts": \\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\`// © 2022 SolarWinds Worldwide, LLC. All rights reserved. +// +// Permission is hereby granted, free of charge, to any person obtaining a copy +// of this software and associated documentation files (the "Software"), to +// deal in the Software without restriction, including without limitation the +// rights to use, copy, modify, merge, publish, distribute, sublicense, and/or +// sell copies of the Software, and to permit persons to whom the Software is +// furnished to do so, subject to the following conditions: +// +// The above copyright notice and this permission notice shall be included in +// all copies or substantial portions of the Software. +// +// THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR +// IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, +// FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE +// AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER +// LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, +// OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN +// THE SOFTWARE. + +import { Component, TemplateRef, ViewChild } from "@angular/core"; + +/** + * Interactive KPI Tile View example with custom value formatting + * and click event handling. + */ +@Component({ + selector: "kpi-tile-view-interactive-example", + templateUrl: "./kpi-tile-view-interactive-example.component.html", + standalone: false, +}) +export class KpiTileViewInteractiveExampleComponent { + public currentValue = 1_247; + public lastClickedTile = ""; + + @ViewChild("customValueTpl", { static: true }) + public customValueTpl: TemplateRef; + + public onTileClick(): void { + this.lastClickedTile = "Active Sessions"; + } + + public onUptimeClick(): void { + this.lastClickedTile = "Uptime"; + } +} +\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\`, + "widget-types/view-components/proportional-chart-view-playground/proportional-chart-view-playground-example.component.ts": \\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\`// © 2022 SolarWinds Worldwide, LLC. All rights reserved. +// +// Permission is hereby granted, free of charge, to any person obtaining a copy +// of this software and associated documentation files (the "Software"), to +// deal in the Software without restriction, including without limitation the +// rights to use, copy, modify, merge, publish, distribute, sublicense, and/or +// sell copies of the Software, and to permit persons to whom the Software is +// furnished to do so, subject to the following conditions: +// +// The above copyright notice and this permission notice shall be included in +// all copies or substantial portions of the Software. +// +// THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR +// IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, +// FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE +// AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER +// LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, +// OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN +// THE SOFTWARE. + +import { Component } from "@angular/core"; + +import { IProportionalDataItem } from "@nova-ui/dashboards"; + +type ProportionalChartType = "donut" | "pie" | "verticalBar" | "horizontalBar"; +type LegendPlacement = "right" | "bottom" | "none"; + +/** + * Proportional Chart View - Playground example. + * Lets you switch between all supported chart types and legend placements + * to see every visual variant the standalone view component provides. + */ +@Component({ + selector: "proportional-chart-view-playground-example", + templateUrl: "./proportional-chart-view-playground-example.component.html", + standalone: false, +}) +export class ProportionalChartViewPlaygroundExampleComponent { + public chartType: ProportionalChartType = "donut"; + public legendPlacement: LegendPlacement = "right"; + + public readonly chartTypeOptions: ProportionalChartType[] = [ + "donut", + "pie", + "verticalBar", + "horizontalBar", + ]; + public readonly legendPlacementOptions: LegendPlacement[] = [ + "right", + "bottom", + "none", + ]; + + public colors: Record = { + down: "#dc3545", + up: "#2cc079", + warning: "#f3a002", + unknown: "#707070", + }; + + public chartData: Array = [ + { id: "up", name: "Up", value: 78 }, + { id: "down", name: "Down", value: 8 }, + { id: "warning", name: "Warning", value: 12 }, + { id: "unknown", name: "Unknown", value: 2 }, + ]; +} +\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\`, + "widget-types/view-components/view-components-docs.component.ts": \\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\`// © 2022 SolarWinds Worldwide, LLC. All rights reserved. +// +// Permission is hereby granted, free of charge, to any person obtaining a copy +// of this software and associated documentation files (the "Software"), to +// deal in the Software without restriction, including without limitation the +// rights to use, copy, modify, merge, publish, distribute, sublicense, and/or +// sell copies of the Software, and to permit persons to whom the Software is +// furnished to do so, subject to the following conditions: +// +// The above copyright notice and this permission notice shall be included in +// all copies or substantial portions of the Software. +// +// THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR +// IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, +// FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE +// AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER +// LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, +// OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN +// THE SOFTWARE. + +import { Component } from "@angular/core"; + +@Component({ + selector: "nui-view-components-docs", + templateUrl: "./view-components-docs.component.html", + standalone: false, +}) +export class ViewComponentsDocsComponent {} +\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\`, + "widget-types/view-components/view-components-docs.module.ts": \\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\`// © 2022 SolarWinds Worldwide, LLC. All rights reserved. +// +// Permission is hereby granted, free of charge, to any person obtaining a copy +// of this software and associated documentation files (the "Software"), to +// deal in the Software without restriction, including without limitation the +// rights to use, copy, modify, merge, publish, distribute, sublicense, and/or +// sell copies of the Software, and to permit persons to whom the Software is +// furnished to do so, subject to the following conditions: +// +// The above copyright notice and this permission notice shall be included in +// all copies or substantial portions of the Software. +// +// THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR +// IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, +// FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE +// AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER +// LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, +// OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN +// THE SOFTWARE. + +import { CommonModule } from "@angular/common"; +import { NgModule } from "@angular/core"; +import { RouterModule, Routes } from "@angular/router"; + +import { + NuiDocsModule, + NuiIconModule, + NuiMessageModule, + NuiFormFieldModule, + NuiSelectV2Module, + NuiSwitchModule, + DEMO_PATH_TOKEN, +} from "@nova-ui/bits"; +import { NuiDashboardViewsModule } from "@nova-ui/dashboards"; + +import { getDemoFiles } from "../../../../demo-files-factory"; +import { KpiTileViewBasicExampleComponent } from "./kpi-tile-view-basic/kpi-tile-view-basic-example.component"; +import { KpiTileViewInteractiveExampleComponent } from "./kpi-tile-view-interactive/kpi-tile-view-interactive-example.component"; +import { ProportionalChartViewPlaygroundExampleComponent } from "./proportional-chart-view-playground/proportional-chart-view-playground-example.component"; +import { ViewComponentsDocsComponent } from "./view-components-docs.component"; + +const routes: Routes = [ + { + path: "", + component: ViewComponentsDocsComponent, + data: { + srlc: { + hideIndicator: true, + }, + showThemeSwitcher: true, + }, + }, + { + path: "kpi-tile-view-basic", + component: KpiTileViewBasicExampleComponent, + data: { + srlc: { + hideIndicator: true, + }, + }, + }, + { + path: "proportional-chart-view-playground", + component: ProportionalChartViewPlaygroundExampleComponent, + data: { + srlc: { + hideIndicator: true, + }, + }, + }, +]; + +@NgModule({ + imports: [ + CommonModule, + RouterModule.forChild(routes), + NuiDocsModule, + NuiMessageModule, + NuiIconModule, + NuiFormFieldModule, + NuiSelectV2Module, + NuiSwitchModule, + NuiDashboardViewsModule, + ], + declarations: [ + ViewComponentsDocsComponent, + KpiTileViewBasicExampleComponent, + KpiTileViewInteractiveExampleComponent, + ProportionalChartViewPlaygroundExampleComponent, + ], + providers: [ + { + provide: DEMO_PATH_TOKEN, + useValue: getDemoFiles("view-components"), + }, + ], +}) +export default class ViewComponentsDocsModule {} +\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\`, + "widget-types/widget-types.module.ts": \\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\`// © 2022 SolarWinds Worldwide, LLC. All rights reserved. +// +// Permission is hereby granted, free of charge, to any person obtaining a copy +// of this software and associated documentation files (the "Software"), to +// deal in the Software without restriction, including without limitation the +// rights to use, copy, modify, merge, publish, distribute, sublicense, and/or +// sell copies of the Software, and to permit persons to whom the Software is +// furnished to do so, subject to the following conditions: +// +// The above copyright notice and this permission notice shall be included in +// all copies or substantial portions of the Software. +// +// THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR +// IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, +// FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE +// AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER +// LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, +// OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN +// THE SOFTWARE. + +import { NgModule, Type } from "@angular/core"; +import { RouterModule, Routes } from "@angular/router"; + +import { NuiDocsModule } from "@nova-ui/bits"; +import { + ConfiguratorHeadingService, + NuiDashboardsModule, +} from "@nova-ui/dashboards"; + +export enum WidgetTypesRoute { + kpi = "kpi", + riskScore = "risk-score", + timeseries = "timeseries", + table = "table", + proportional = "proportional", + embedded = "embedded", + drilldown = "drilldown", + viewComponents = "view-components", +} + +const routes: Routes = [ + { + path: WidgetTypesRoute.kpi, + loadChildren: async () => + import("./kpi/kpi-docs.module") as object as Promise>, + data: { + srlc: { + hideIndicator: true, + }, + }, + }, + { + path: WidgetTypesRoute.riskScore, + loadChildren: async () => + import("./risk-score/risk-score-docs.module") as object as Promise< + Type + >, + data: { + srlc: { + hideIndicator: true, + }, + }, + }, + { + path: WidgetTypesRoute.timeseries, + loadChildren: async () => + import("./timeseries/timeseries-docs.module") as object as Promise< + Type + >, + data: { + srlc: { + hideIndicator: true, + }, + }, + }, + { + path: WidgetTypesRoute.table, + loadChildren: async () => + import("./table/table-docs.module") as object as Promise>, + data: { + srlc: { + hideIndicator: true, + }, + }, + }, + { + path: WidgetTypesRoute.proportional, + loadChildren: async () => + import( + "./proportional/proportional-docs.module" + ) as object as Promise>, + data: { + srlc: { + hideIndicator: true, + }, + }, + }, + { + path: WidgetTypesRoute.embedded, + loadChildren: async () => + import( + "./embedded-content/embedded-content-docs.module" + ) as object as Promise>, + data: { + srlc: { + hideIndicator: true, + }, + }, + }, + { + path: WidgetTypesRoute.drilldown, + loadChildren: async () => + import( + "./drilldown/drilldown-widget-docs.module" + ) as object as Promise>, + data: { + srlc: { + hideIndicator: true, + }, + }, + }, + { + path: WidgetTypesRoute.viewComponents, + loadChildren: async () => + import( + "./view-components/view-components-docs.module" + ) as object as Promise>, + data: { + srlc: { + hideIndicator: true, + }, + }, + }, +]; + +@NgModule({ + imports: [ + RouterModule.forChild(routes), + NuiDocsModule, + NuiDashboardsModule, + ], + providers: [ConfiguratorHeadingService], +}) +export default class WidgetTypesModule {} +\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\`, +}; +\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\`, + "overview/hero/dashboard/hero-dashboard.component.ts": \\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\`// © 2022 SolarWinds Worldwide, LLC. All rights reserved. +// +// Permission is hereby granted, free of charge, to any person obtaining a copy +// of this software and associated documentation files (the "Software"), to +// deal in the Software without restriction, including without limitation the +// rights to use, copy, modify, merge, publish, distribute, sublicense, and/or +// sell copies of the Software, and to permit persons to whom the Software is +// furnished to do so, subject to the following conditions: +// +// The above copyright notice and this permission notice shall be included in +// all copies or substantial portions of the Software. +// +// THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR +// IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, +// FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE +// AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER +// LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, +// OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN +// THE SOFTWARE. + +import { HttpClient } from "@angular/common/http"; +import { + ChangeDetectionStrategy, + Component, + OnInit, + ViewEncapsulation, +} from "@angular/core"; +import keyBy from "lodash/keyBy"; + +import { LoggerService, ThemeSwitchService } from "@nova-ui/bits"; +import { + DATA_SOURCE, + IDashboard, + IWidget, + ProviderRegistryService, + WidgetTypesService, +} from "@nova-ui/dashboards"; + +import { positions, widgets } from "./widget-configs"; +import { + HarryPotterAverageRatingDataSource, + HarryPotterRatingsCountDataSource, +} from "../data/kpi-datasources"; +import { + BeerReviewCountsByCityMockDataSource, + BeerReviewCountsByCityMockDataSource2, +} from "../data/proportional-datasources"; +import { BeerDataSource } from "../data/table/beer-data-source"; +import { RandomUserDataSource } from "../data/table/random-user-data-source"; +import { + BeerVsReadingMockDataSource, + LoungingVsFrisbeeGolfMockDataSource, +} from "../data/timeseries-data-sources"; + +/** + * A component that instantiates the dashboard + */ +@Component({ + selector: "hero-dashboard", + templateUrl: "./hero-dashboard.component.html", + styleUrls: ["./hero-dashboard.component.less"], + encapsulation: ViewEncapsulation.Emulated, + changeDetection: ChangeDetectionStrategy.Default, + standalone: false, +}) +export class HeroDashboardComponent implements OnInit { + public dashboard: IDashboard = { + positions: {}, + widgets: {}, + }; + + public gridsterConfig = {}; + public editMode = false; + + constructor( + private providerRegistry: ProviderRegistryService, + public themeSwitcherService: ThemeSwitchService, + private widgetTypesService: WidgetTypesService + ) { + this.providerRegistry.setProviders({ + [HarryPotterAverageRatingDataSource.providerId]: { + provide: DATA_SOURCE, + useClass: HarryPotterAverageRatingDataSource, + deps: [HttpClient], + }, + [HarryPotterRatingsCountDataSource.providerId]: { + provide: DATA_SOURCE, + useClass: HarryPotterRatingsCountDataSource, + deps: [HttpClient], + }, + [BeerReviewCountsByCityMockDataSource.providerId]: { + provide: DATA_SOURCE, + useClass: BeerReviewCountsByCityMockDataSource, + deps: [], + }, + [BeerReviewCountsByCityMockDataSource2.providerId]: { + provide: DATA_SOURCE, + useClass: BeerReviewCountsByCityMockDataSource2, + deps: [], + }, + [RandomUserDataSource.providerId]: { + provide: DATA_SOURCE, + useClass: RandomUserDataSource, + deps: [LoggerService], + }, + [BeerDataSource.providerId]: { + provide: DATA_SOURCE, + useClass: BeerDataSource, + deps: [LoggerService], + }, + [BeerVsReadingMockDataSource.providerId]: { + provide: DATA_SOURCE, + useClass: BeerVsReadingMockDataSource, + deps: [], + }, + [LoungingVsFrisbeeGolfMockDataSource.providerId]: { + provide: DATA_SOURCE, + useClass: LoungingVsFrisbeeGolfMockDataSource, + deps: [], + }, + }); + } + + public ngOnInit(): void { + const widgetsWithStructure = widgets.map((w) => ({ + ...w, + pizzagna: { + ...this.widgetTypesService.getWidgetType(w.type, w.version) + .widget, + ...w.pizzagna, + }, + })); + const widgetsIndex = keyBy(widgetsWithStructure, (w: IWidget) => w.id); + + this.dashboard = { + positions: positions, + widgets: widgetsIndex, + }; + } +} +\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\`, + "overview/hero/dashboard/widget-configs.ts": \\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\`// © 2022 SolarWinds Worldwide, LLC. All rights reserved. +// +// Permission is hereby granted, free of charge, to any person obtaining a copy +// of this software and associated documentation files (the "Software"), to +// deal in the Software without restriction, including without limitation the +// rights to use, copy, modify, merge, publish, distribute, sublicense, and/or +// sell copies of the Software, and to permit persons to whom the Software is +// furnished to do so, subject to the following conditions: +// +// The above copyright notice and this permission notice shall be included in +// all copies or substantial portions of the Software. +// +// THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR +// IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, +// FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE +// AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER +// LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, +// OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN +// THE SOFTWARE. + +import { GridsterItem } from "angular-gridster2"; + +import { IWidget } from "@nova-ui/dashboards"; + +import { kpiConfig } from "../widget-configs/kpi"; +import { proportionalConfig } from "../widget-configs/proportional"; +import { tableConfig } from "../widget-configs/table"; +import { timeseriesConfig } from "../widget-configs/timeseries"; + +export const positions: Record = { + [tableConfig.id]: { + cols: 7, + rows: 7, + y: 0, + x: 0, + }, + [proportionalConfig.id]: { + cols: 5, + rows: 7, + y: 0, + x: 7, + }, + [kpiConfig.id]: { + cols: 6, + rows: 7, + y: 7, + x: 0, + }, + [timeseriesConfig.id]: { + cols: 6, + rows: 7, + y: 7, + x: 6, + }, +}; + +export const widgets: IWidget[] = [ + { + ...tableConfig, + }, + { + ...proportionalConfig, + }, + { + ...kpiConfig, + }, + { + ...timeseriesConfig, + }, +]; +\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\`, + "overview/hero/data/kpi-datasources.ts": \\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\`// © 2022 SolarWinds Worldwide, LLC. All rights reserved. +// +// Permission is hereby granted, free of charge, to any person obtaining a copy +// of this software and associated documentation files (the "Software"), to +// deal in the Software without restriction, including without limitation the +// rights to use, copy, modify, merge, publish, distribute, sublicense, and/or +// sell copies of the Software, and to permit persons to whom the Software is +// furnished to do so, subject to the following conditions: +// +// The above copyright notice and this permission notice shall be included in +// all copies or substantial portions of the Software. +// +// THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR +// IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, +// FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE +// AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER +// LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, +// OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN +// THE SOFTWARE. + +import { HttpClient, HttpErrorResponse } from "@angular/common/http"; +import { Injectable, OnDestroy } from "@angular/core"; +import { BehaviorSubject } from "rxjs"; +import { finalize } from "rxjs/operators"; + +import { DataSourceService, IFilteringOutputs } from "@nova-ui/bits"; +import { IKpiData } from "@nova-ui/dashboards"; + +import { GOOGLE_BOOKS_URL } from "./table/constants"; + +@Injectable() +export class HarryPotterAverageRatingDataSource + extends DataSourceService + implements OnDestroy +{ + public static providerId = "HarryPotterAverageRatingDataSource"; + + public busy = new BehaviorSubject(false); + + constructor(private http: HttpClient) { + super(); + } + + public async getFilteredData(): Promise { + this.busy.next(true); + return new Promise((resolve) => { + // *** Make a resource request to an external API (if needed) + this.http + .get(\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\`\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\${GOOGLE_BOOKS_URL}/5MQFrgEACAAJ\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\`) + .pipe(finalize(() => this.busy.next(false))) + .subscribe({ + next: (data: any) => { + resolve({ + result: { + value: data.volumeInfo.averageRating, + }, + }); + }, + error: (error: HttpErrorResponse) => { + resolve({ + result: null, + error: { + type: error.status, + }, + }); + }, + }); + }); + } + + public ngOnDestroy(): void { + this.outputsSubject.complete(); + } +} + +@Injectable() +export class HarryPotterRatingsCountDataSource + extends DataSourceService + implements OnDestroy +{ + public static providerId = "HarryPotterRatingsCountDataSource"; + + public busy = new BehaviorSubject(false); + + constructor(private http: HttpClient) { + super(); + } + + public async getFilteredData(): Promise { + this.busy.next(true); + return new Promise((resolve) => { + this.http + .get(\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\`\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\${GOOGLE_BOOKS_URL}/5MQFrgEACAAJ\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\`) + .pipe(finalize(() => this.busy.next(false))) + .subscribe({ + next: (data: any) => { + resolve({ + result: { + value: data.volumeInfo.ratingsCount, + }, + }); + }, + error: (error: HttpErrorResponse) => { + resolve({ + result: null, + error: { + type: error.status, + }, + }); + }, + }); + }); + } + + public ngOnDestroy(): void { + this.outputsSubject.complete(); + } +} +\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\`, + "overview/hero/data/proportional-datasources.ts": \\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\`// © 2022 SolarWinds Worldwide, LLC. All rights reserved. +// +// Permission is hereby granted, free of charge, to any person obtaining a copy +// of this software and associated documentation files (the "Software"), to +// deal in the Software without restriction, including without limitation the +// rights to use, copy, modify, merge, publish, distribute, sublicense, and/or +// sell copies of the Software, and to permit persons to whom the Software is +// furnished to do so, subject to the following conditions: +// +// The above copyright notice and this permission notice shall be included in +// all copies or substantial portions of the Software. +// +// THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR +// IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, +// FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE +// AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER +// LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, +// OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN +// THE SOFTWARE. + +import { Injectable, OnDestroy } from "@angular/core"; +import { BehaviorSubject } from "rxjs"; + +import { + DataSourceService, + IDataSource, + IFilteringOutputs, +} from "@nova-ui/bits"; + +import { + getMockBeerReviewCountsByCity, + getMockBeerReviewCountsByCity2, + IProportionalWidgetData, +} from "./widget-data"; + +@Injectable() +export class BeerReviewCountsByCityMockDataSource + extends DataSourceService + implements IDataSource, OnDestroy +{ + // This is the ID we'll use to identify the provider + public static providerId = "BeerReviewCountsByCityMockDataSource"; + public busy = new BehaviorSubject(false); + + public async getFilteredData(): Promise { + this.busy.next(true); + return new Promise((resolve) => { + setTimeout(() => { + this.outputsSubject.next({ + result: getMockBeerReviewCountsByCity(), + }); + this.busy.next(false); + }, 300); + }); + } + + public ngOnDestroy(): void { + this.outputsSubject.complete(); + } +} + +@Injectable() +export class BeerReviewCountsByCityMockDataSource2 + extends DataSourceService + implements IDataSource, OnDestroy +{ + // This is the ID we'll use to identify the provider + public static providerId = "BeerReviewCountsByCityMockDataSource2"; + public busy = new BehaviorSubject(false); + + public async getFilteredData(): Promise { + this.busy.next(true); + return new Promise((resolve) => { + setTimeout(() => { + this.outputsSubject.next({ + result: getMockBeerReviewCountsByCity2(), + }); + this.busy.next(false); + }, 1500); + }); + } + + public ngOnDestroy(): void { + this.outputsSubject.complete(); + } +} +\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\`, + "overview/hero/data/table/beer-data-source.ts": \\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\`// © 2022 SolarWinds Worldwide, LLC. All rights reserved. +// +// Permission is hereby granted, free of charge, to any person obtaining a copy +// of this software and associated documentation files (the "Software"), to +// deal in the Software without restriction, including without limitation the +// rights to use, copy, modify, merge, publish, distribute, sublicense, and/or +// sell copies of the Software, and to permit persons to whom the Software is +// furnished to do so, subject to the following conditions: +// +// The above copyright notice and this permission notice shall be included in +// all copies or substantial portions of the Software. +// +// THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR +// IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, +// FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE +// AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER +// LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, +// OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN +// THE SOFTWARE. + +import { ListRange } from "@angular/cdk/collections"; +import { Injectable } from "@angular/core"; +import isEqual from "lodash/isEqual"; +import orderBy from "lodash/orderBy"; +import { BehaviorSubject } from "rxjs"; + +import { + DataSourceService, + IDataField, + INovaFilteringOutputs, + INovaFilters, + ISorterFilter, + LoggerService, +} from "@nova-ui/bits"; + +import { IBrewDatasourceResponse, IBrewInfo } from "../types"; +import { BREW_API_URL } from "./constants"; + +@Injectable() +export class BeerDataSource extends DataSourceService { + public static providerId = "BeerDataSource"; + + private cache = Array.from({ length: 0 }); + private lastSortValue?: ISorterFilter; + private lastVirtualScroll?: ListRange; + private totalItems: number = 325; + + public page: number = 1; + public busy = new BehaviorSubject(false); + + public dataFields: Array = [ + { id: "id", label: "No", dataType: "number" }, + { id: "name", label: "Name", dataType: "string" }, + { id: "tagline", label: "Tagline", dataType: "string" }, + { id: "first_brewed", label: "First Brewed", dataType: "string" }, + { id: "description", label: "Description", dataType: "string" }, + { id: "brewers_tips", label: "Brewer's Tips", dataType: "string" }, + ]; + + constructor(private logger: LoggerService) { + super(); + } + + public async getFilteredData( + filters: INovaFilters + ): Promise { + const start = filters.virtualScroll?.value?.start ?? 0; + const end = filters.virtualScroll?.value?.end ?? 0; + const delta = end - start; + + // This condition handles sorting. We want to sort columns without fetching another chunk of data. + // Since the data is being fetched when scrolled, we compare virtual scroll indexes here in the condition as well. + if (filters.sorter?.value) { + if ( + !isEqual(this.lastSortValue, filters.sorter.value) && + isEqual(this.lastVirtualScroll, filters.virtualScroll?.value) + ) { + const totalPages = Math.ceil( + delta ? this.totalItems / delta : 1 + ); + const itemsPerPage: number = Math.max( + delta < 80 ? delta : 80, + 1 + ); + let response: Array | null = null; + let map: IBrewDatasourceResponse; + + if (filters.sorter?.value?.direction === "desc") { + this.cache = []; + for (let i = 0; i < this.page; ++i) { + response = await ( + await fetch( + \\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\`\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\${BREW_API_URL}/?page=\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\${ + totalPages - i || 1 + }&per_page=\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\${itemsPerPage}\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\` + ) + ).json(); + + // since the last page contains only 5 items we need to fetch another page to give virtual scroll enough space to work + if (response && response.length < itemsPerPage) { + this.page++; + } + map = { + brewInfo: response?.map((result: IBrewInfo) => ({ + id: result.id, + name: result.name, + tagline: result.tagline, + first_brewed: result.first_brewed, + description: result.description, + brewers_tips: result.brewers_tips, + })), + total: response?.length, + } as IBrewDatasourceResponse; + this.cache = + totalPages - i !== 0 + ? this.cache.concat(map.brewInfo) + : this.cache; + } + } + + if (filters.sorter?.value?.direction === "asc") { + this.cache = []; + for (let i = 0; i < this.page; i++) { + response = await ( + await fetch( + \\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\`\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\${BREW_API_URL}/?page=\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\${ + i + 1 + }&per_page=\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\${itemsPerPage}\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\` + ) + ).json(); + map = { + brewInfo: response?.map((result: IBrewInfo) => ({ + id: result.id, + name: result.name, + tagline: result.tagline, + first_brewed: result.first_brewed, + description: result.description, + brewers_tips: result.brewers_tips, + })), + total: response?.length, + } as IBrewDatasourceResponse; + this.cache = this.cache.concat(map.brewInfo); + } + } + + this.lastSortValue = filters.sorter?.value; + this.lastVirtualScroll = filters.virtualScroll?.value; + + return { + repeat: { itemsSource: this.sortData(this.cache, filters) }, + paginator: { total: this.totalItems }, + dataFields: this.dataFields, + }; + } + } + + this.busy.next(true); + return new Promise((resolve) => { + setTimeout(() => { + this.getData(start, end, filters).then( + (response: INovaFilteringOutputs) => { + if (!response) { + return; + } + + this.cache = this.cache.concat(response.brewInfo); + + this.dataSubject.next(this.cache); + resolve({ + repeat: { + itemsSource: this.sortData(this.cache, filters), + }, + paginator: { total: this.totalItems }, + dataFields: this.dataFields, + }); + + this.lastSortValue = filters.sorter?.value; + this.lastVirtualScroll = filters.virtualScroll?.value; + this.busy.next(false); + } + ); + }, 500); + }); + } + + public async getData( + start: number = 0, + end: number = 20, + filters: INovaFilters + ): Promise { + const delta = end - start; + const totalPages = Math.ceil(delta ? this.totalItems / delta : 1); + let response: Array | null = null; + // The api.punk.com is able to return only 80 items per page + const itemsPerPage: number = Math.max(delta < 80 ? delta : 80, 1); + + if (filters.sorter?.value?.direction === "asc") { + response = await ( + await fetch( + \\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\`\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\${BREW_API_URL}/?page=\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\${this.page}&per_page=\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\${itemsPerPage}\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\` + ) + ).json(); + } + + if (filters.sorter?.value?.direction === "desc") { + response = await ( + await fetch( + \\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\`\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\${BREW_API_URL}/?page=\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\${ + totalPages - this.page + }&per_page=\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\${itemsPerPage}\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\` + ) + ).json(); + } + + if (!filters.sorter) { + response = await ( + await fetch( + \\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\`\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\${BREW_API_URL}/?page=\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\${this.page}&per_page=\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\${itemsPerPage}\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\` + ) + ).json(); + } + return { + brewInfo: response?.map((result: IBrewInfo, i: number) => ({ + id: result.id, + name: result.name, + tagline: result.tagline, + first_brewed: result.first_brewed, + description: result.description, + brewers_tips: result.brewers_tips, + })), + total: response?.length, + } as IBrewDatasourceResponse; + } + + private sortData(data: IBrewInfo[], filters: INovaFilters) { + return orderBy( + data, + filters.sorter?.value?.sortBy, + filters.sorter?.value?.direction as "desc" | "asc" + ); + } +} +\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\`, + "overview/hero/data/table/constants.ts": \\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\`// © 2022 SolarWinds Worldwide, LLC. All rights reserved. +// +// Permission is hereby granted, free of charge, to any person obtaining a copy +// of this software and associated documentation files (the "Software"), to +// deal in the Software without restriction, including without limitation the +// rights to use, copy, modify, merge, publish, distribute, sublicense, and/or +// sell copies of the Software, and to permit persons to whom the Software is +// furnished to do so, subject to the following conditions: +// +// The above copyright notice and this permission notice shall be included in +// all copies or substantial portions of the Software. +// +// THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR +// IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, +// FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE +// AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER +// LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, +// OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN +// THE SOFTWARE. + +export const corsProxy = "https://cors-anywhere.herokuapp.com"; +export const RANDOMUSER_API_URL = "https://randomuser.me"; +export const BREW_API_URL = "https://api.punkapi.com/v2/beers"; +export const GOOGLE_BOOKS_URL = "https://www.googleapis.com/books/v1/volumes"; +export const apiRoute = "api/1.3"; +export const responseError = \\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\`Error responding from server. Please visit \\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\${RANDOMUSER_API_URL} and \\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\${corsProxy} to see if they're available\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\`; +\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\`, + "overview/hero/data/table/random-user-data-source.ts": \\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\`// © 2022 SolarWinds Worldwide, LLC. All rights reserved. +// +// Permission is hereby granted, free of charge, to any person obtaining a copy +// of this software and associated documentation files (the "Software"), to +// deal in the Software without restriction, including without limitation the +// rights to use, copy, modify, merge, publish, distribute, sublicense, and/or +// sell copies of the Software, and to permit persons to whom the Software is +// furnished to do so, subject to the following conditions: +// +// The above copyright notice and this permission notice shall be included in +// all copies or substantial portions of the Software. +// +// THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR +// IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, +// FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE +// AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER +// LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, +// OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN +// THE SOFTWARE. + +import { ListRange } from "@angular/cdk/collections"; +import { Injectable } from "@angular/core"; +import isEqual from "lodash/isEqual"; +import orderBy from "lodash/orderBy"; +import { BehaviorSubject } from "rxjs"; + +import { + DataSourceService, + IDataField, + INovaFilteringOutputs, + INovaFilters, + ISorterFilter, + LoggerService, +} from "@nova-ui/bits"; + +import { + IRandomUserResponse, + IRandomUserResults, + IRandomUserTableModel, + UsersQueryResponse, +} from "../types"; +import { + apiRoute, + corsProxy, + RANDOMUSER_API_URL, + responseError, +} from "./constants"; + +@Injectable() +export class RandomUserDataSource extends DataSourceService { + public static providerId = "RandomUserDataSource"; + + private readonly seed = "sw"; + + private cache = Array.from({ length: 0 }); + private lastSortValue?: ISorterFilter; + private lastVirtualScroll?: ListRange; + + public page: number = 1; + public busy = new BehaviorSubject(false); + + public dataFields: Array = [ + { id: "no", label: $localize\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\`No\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\`, dataType: "number" }, + { id: "nameTitle", label: $localize\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\`Title\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\`, dataType: "string" }, + { id: "nameFirst", label: $localize\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\`First\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\`, dataType: "string" }, + { id: "nameLast", label: $localize\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\`Last\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\`, dataType: "string" }, + { id: "gender", label: $localize\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\`Gender\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\`, dataType: "string" }, + { id: "country", label: $localize\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\`Country\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\`, dataType: "string" }, + { id: "city", label: $localize\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\`City\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\`, dataType: "string" }, + { id: "postcode", label: $localize\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\`Postcode\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\`, dataType: "number" }, + { id: "email", label: $localize\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\`E-Mail\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\`, dataType: "string" }, + { id: "cell", label: $localize\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\`Cell\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\`, dataType: "string" }, + ]; + + constructor(private logger: LoggerService) { + super(); + } + + public async getFilteredData( + filters: INovaFilters + ): Promise { + // This condition handles sorting. We want to sort columns without fetching another chunk of data. + // Since the data is being fetched when scrolled, we compare virtual scroll indexes here in the condition as well. + if (filters.sorter?.value) { + if ( + !isEqual(this.lastSortValue, filters.sorter.value) && + isEqual(this.lastVirtualScroll, filters.virtualScroll?.value) + ) { + this.lastSortValue = filters.sorter?.value; + this.lastVirtualScroll = filters.virtualScroll?.value; + + return { + repeat: { itemsSource: this.sortData(this.cache, filters) }, + paginator: { total: 200 }, + dataFields: this.dataFields, + }; + } + } + this.busy.next(true); + + const virtualScrollFilter = + filters.virtualScroll && filters.virtualScroll.value; + const start = virtualScrollFilter + ? filters.virtualScroll?.value.start + : 0; + const end = virtualScrollFilter ? filters.virtualScroll?.value.end : 0; + + // We're returning Promise with setTimeout here to make the response from the server longer, as the API being used sends responses + // almost immediately. We need it longer to be able the show the spinner component on data load + return new Promise((resolve) => { + setTimeout(() => { + this.getData(start, end).then( + (response: INovaFilteringOutputs | undefined) => { + if (!response) { + return; + } + + this.cache = this.cache.concat(response.users); + + this.dataSubject.next(this.cache); + resolve({ + repeat: { + itemsSource: this.sortData(this.cache, filters), + }, + // This API can return thousands of results, however doesn't return the max number of results, + // so we set the max number of result manually here. + paginator: { total: 200 }, + dataFields: this.dataFields, + }); + + this.lastSortValue = filters.sorter?.value; + this.lastVirtualScroll = filters.virtualScroll?.value; + this.busy.next(false); + } + ); + }, 300); + }); + } + + public async getData( + start: number = 0, + end: number = 20 + ): Promise { + let response: IRandomUserResponse | null = null; + try { + response = await ( + await fetch( + \\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\`\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\${corsProxy}/\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\${RANDOMUSER_API_URL}/\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\${apiRoute}/?page=\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\${ + this.page + }&results=\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\${end - start}&seed=\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\${this.seed}\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\` + ) + ).json(); + return { + users: response?.results.map( + (result: IRandomUserResults, i: number) => ({ + no: this.cache.length + i + 1, + nameTitle: result.name.title, + nameFirst: result.name.first, + nameLast: result.name.last, + gender: result.gender, + country: result.location.country, + city: result.location.city, + postcode: result.location.postcode, + email: result.email, + cell: result.cell, + }) + ), + total: response?.results.length, + start: start, + } as UsersQueryResponse; + } catch (e) { + this.logger.error(responseError); + } + } + + private sortData(data: IRandomUserTableModel[], filters: INovaFilters) { + return orderBy( + data, + filters.sorter?.value?.sortBy, + filters.sorter?.value?.direction as "desc" | "asc" + ); + } +} +\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\`, + "overview/hero/data/table/types.ts": \\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\`// © 2022 SolarWinds Worldwide, LLC. All rights reserved. +// +// Permission is hereby granted, free of charge, to any person obtaining a copy +// of this software and associated documentation files (the "Software"), to +// deal in the Software without restriction, including without limitation the +// rights to use, copy, modify, merge, publish, distribute, sublicense, and/or +// sell copies of the Software, and to permit persons to whom the Software is +// furnished to do so, subject to the following conditions: +// +// The above copyright notice and this permission notice shall be included in +// all copies or substantial portions of the Software. +// +// THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR +// IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, +// FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE +// AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER +// LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, +// OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN +// THE SOFTWARE. + +import { IDataField, INovaFilteringOutputs } from "@nova-ui/bits"; +export interface BasicTableModel { + position: number; + name: string; + features: any; + status: string; + checks: any; + "cpu-load": number; + firstUrl: string; + firstUrlLabel: string; + secondUrl: string; + secondUrlLabel: string; +} + +export interface ITableDataSourceOutput extends INovaFilteringOutputs { + dataFields: IDataField[]; +} +\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\`, + "overview/hero/data/timeseries-data-sources.ts": \\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\`// © 2022 SolarWinds Worldwide, LLC. All rights reserved. +// +// Permission is hereby granted, free of charge, to any person obtaining a copy +// of this software and associated documentation files (the "Software"), to +// deal in the Software without restriction, including without limitation the +// rights to use, copy, modify, merge, publish, distribute, sublicense, and/or +// sell copies of the Software, and to permit persons to whom the Software is +// furnished to do so, subject to the following conditions: +// +// The above copyright notice and this permission notice shall be included in +// all copies or substantial portions of the Software. +// +// THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR +// IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, +// FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE +// AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER +// LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, +// OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN +// THE SOFTWARE. + +import { Injectable } from "@angular/core"; +import { Moment } from "moment/moment"; +import { BehaviorSubject } from "rxjs"; + +import { DataSourceService, IDataSource, INovaFilters } from "@nova-ui/bits"; +import { + ITimeseriesOutput, + ITimeseriesWidgetData, + ITimeseriesWidgetSeriesData, +} from "@nova-ui/dashboards"; + +import { + BEER_VS_READING_DATA, + LOUNGING_VS_ULTIMATE_FRISBEE_DATA, +} from "./widget-data"; + +@Injectable() +export class BeerVsReadingMockDataSource + extends DataSourceService + implements IDataSource +{ + public static providerId = "BeerVsReadingMockDataSource"; + + public busy = new BehaviorSubject(false); + + constructor() { + super(); + } + + public async getFilteredData( + filters: INovaFilters + ): Promise { + this.busy.next(true); + const result = await delay( + { series: getData(filters, BEER_VS_READING_DATA) }, + 1000 + ); + this.busy.next(false); + return result; + } +} + +@Injectable() +export class LoungingVsFrisbeeGolfMockDataSource + extends DataSourceService + implements IDataSource +{ + public static providerId = "LoungingVsFrisbeeGolfMockDataSource"; + + public busy = new BehaviorSubject(false); + + constructor() { + super(); + } + + public async getFilteredData( + filters: INovaFilters + ): Promise { + this.busy.next(true); + const result = await delay( + { series: getData(filters, LOUNGING_VS_ULTIMATE_FRISBEE_DATA) }, + 1000 + ); + this.busy.next(false); + return result; + } +} + +function getData( + filters: INovaFilters, + data: ITimeseriesWidgetData[] +): ITimeseriesWidgetData[] { + const timeframeFilter = filters.timeframe; + let filteredData = data; + // TIME FRAME PICKER FILTERING + if (timeframeFilter) { + filteredData = filteredData.map((item: ITimeseriesWidgetData) => ({ + id: item.id, + name: item.name, + description: item.description, + data: item.data.filter((seriesData: ITimeseriesWidgetSeriesData) => + filterDates( + seriesData.x, + timeframeFilter.value.startDatetime, + timeframeFilter.value.endDatetime + ) + ), + })); + } + + return filteredData; +} + +function filterDates(dateToCheck: Moment, startDate: Moment, endDate: Moment) { + return ( + dateToCheck.isBetween(startDate, endDate) || + dateToCheck.isSame(startDate) || + dateToCheck.isSame(endDate) + ); +} + +async function delay( + value: ITimeseriesOutput, + ms: number +): Promise { + return new Promise((resolve) => setTimeout(() => resolve(value), ms)); +} +\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\`, + "overview/hero/data/types.ts": \\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\`// © 2022 SolarWinds Worldwide, LLC. All rights reserved. +// +// Permission is hereby granted, free of charge, to any person obtaining a copy +// of this software and associated documentation files (the "Software"), to +// deal in the Software without restriction, including without limitation the +// rights to use, copy, modify, merge, publish, distribute, sublicense, and/or +// sell copies of the Software, and to permit persons to whom the Software is +// furnished to do so, subject to the following conditions: +// +// The above copyright notice and this permission notice shall be included in +// all copies or substantial portions of the Software. +// +// THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR +// IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, +// FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE +// AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER +// LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, +// OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN +// THE SOFTWARE. + +export interface UsersQueryResponse { + users: IRandomUserTableModel[]; + total: number; + start: number; +} + +export interface IRandomUserResponse { + info: Array; + results: Array; +} + +export interface IRandomUserInfo { + page: number; + results: number; + seed: string; + version: string; +} + +export interface IRandomUserResults { + cell: string; + dob: { + age: number; + date: string; + }; + email: string; + gender: string; + id: any; + location: IRandomUserLocation; + login: { + md5: string; + password: string; + salt: string; + sha1: string; + sha256: string; + username: string; + uuid: string; + }; + name: { + title: string; + first: string; + last: string; + }; + nat: string; + phone: string; + picture: { + large: string; + medium: string; + thumbnail: string; + }; + registered: { + date: string; + age: number; + }; +} + +export interface IRandomUserTableModel { + no: number; + nameTitle: string; + nameFirst: string; + nameLast: string; + gender: string; + country: string; + city: string; + postcode: number; + email: string; + cell: string; +} + +export interface IRandomUserLocation { + city: string; + coordinates: { latitude: string; longitude: string }; + country: string; + postcode: number; + state: string; + street: { number: number; name: string }; + timezone: any; +} + +export interface IBrewInfo { + id: number; + name: string; + tagline: string; + first_brewed: string; + description: string; + brewers_tips: string; +} + +export interface IBrewDatasourceResponse { + brewInfo: IBrewInfo[]; + total: number; +} +\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\`, + "overview/hero/data/widget-data.ts": \\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\`// © 2022 SolarWinds Worldwide, LLC. All rights reserved. +// +// Permission is hereby granted, free of charge, to any person obtaining a copy +// of this software and associated documentation files (the "Software"), to +// deal in the Software without restriction, including without limitation the +// rights to use, copy, modify, merge, publish, distribute, sublicense, and/or +// sell copies of the Software, and to permit persons to whom the Software is +// furnished to do so, subject to the following conditions: +// +// The above copyright notice and this permission notice shall be included in +// all copies or substantial portions of the Software. +// +// THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR +// IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, +// FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE +// AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER +// LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, +// OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN +// THE SOFTWARE. + +import moment from "moment/moment"; + +import { ITimeseriesWidgetData } from "@nova-ui/dashboards"; + +import { BasicTableModel } from "./table/types"; + +export interface IProportionalWidgetData { + id: string; + name: string; + data: number[]; + link: string; + value: string; +} + +export function getMockBeerReviewCountsByCity(): IProportionalWidgetData[] { + return [ + { + id: "Brno", + name: "Brno", + data: [Math.round(Math.random() * 100000)], + link: "https://en.wikipedia.org/wiki/Brno", + value: "Brno", + }, + { + id: "kyiv", + name: "Kyiv", + data: [Math.round(Math.random() * 100000)], + link: "https://en.wikipedia.org/wiki/Kyiv", + value: "Kyiv", + }, + { + id: "austin", + name: "Austin", + data: [Math.round(Math.random() * 100000)], + link: "https://en.wikipedia.org/wiki/Austin", + value: "Austin", + }, + { + id: "lisbon", + name: "Lisbon", + data: [Math.round(Math.random() * 100000)], + link: "https://en.wikipedia.org/wiki/Lisbon", + value: "Lisbon", + }, + { + id: "sydney", + name: "Sydney", + data: [Math.round(Math.random() * 100000)], + link: "https://en.wikipedia.org/wiki/Sydney", + value: "Sydney", + }, + { + id: "nur-sultan", + name: "Nur-Sultan", + data: [Math.round(Math.random() * 100000)], + link: "https://en.wikipedia.org/wiki/Nur-Sultan", + value: "Nur-Sultan", + }, + ].sort((a, b) => a.data[0] - b.data[0]); +} + +export function getMockBeerReviewCountsByCity2(): IProportionalWidgetData[] { + return [ + { + id: "london", + name: "London", + data: [Math.round(Math.random() * 100000)], + link: "https://en.wikipedia.org/wiki/London", + value: "London", + }, + { + id: "paris", + name: "Paris", + data: [Math.round(Math.random() * 100000)], + link: "https://en.wikipedia.org/wiki/Paris", + value: "Paris", + }, + { + id: "rio", + name: "Rio", + data: [Math.round(Math.random() * 100000)], + link: "https://en.wikipedia.org/wiki/Rio_de_Janeiro", + value: "Rio", + }, + ].sort((a, b) => a.data[0] - b.data[0]); +} + +export const BEER_VS_READING_DATA: ITimeseriesWidgetData[] = [ + { + id: "series-1", + name: "Beer Tasting", + description: "Havin' some suds", + data: [ + { x: moment().subtract(10, "day"), y: 30 }, + { x: moment().subtract(9, "day"), y: 35 }, + { x: moment().subtract(8, "day"), y: 33 }, + { x: moment().subtract(7, "day"), y: 40 }, + { x: moment().subtract(6, "day"), y: 35 }, + { x: moment().subtract(5, "day"), y: 30 }, + { x: moment().subtract(4, "day"), y: 35 }, + { x: moment().subtract(3, "day"), y: 15 }, + { x: moment().subtract(2, "day"), y: 30 }, + { x: moment().subtract(1, "day"), y: 35 }, + { x: moment().subtract(24, "hour"), y: 34 }, + { x: moment().subtract(15, "hour"), y: 33 }, + { x: moment().subtract(10, "hour"), y: 35 }, + { x: moment().subtract(5, "hour"), y: 36 }, + { x: moment().subtract(1, "hour"), y: 34 }, + { x: moment().subtract(50, "minute"), y: 33 }, + { x: moment().subtract(40, "minute"), y: 30 }, + { x: moment().subtract(30, "minute"), y: 32 }, + { x: moment().subtract(20, "minute"), y: 31 }, + { x: moment().subtract(10, "minute"), y: 34 }, + ], + }, + { + id: "series-2", + name: "Reading", + description: "Hittin' the books", + data: [ + { x: moment().subtract(10, "day"), y: 60 }, + { x: moment().subtract(9, "day"), y: 64 }, + { x: moment().subtract(8, "day"), y: 70 }, + { x: moment().subtract(7, "day"), y: 55 }, + { x: moment().subtract(6, "day"), y: 55 }, + { x: moment().subtract(5, "day"), y: 45 }, + { x: moment().subtract(4, "day"), y: 10 }, + { x: moment().subtract(3, "day"), y: 65 }, + { x: moment().subtract(2, "day"), y: 35 }, + { x: moment().subtract(1, "day"), y: 60 }, + { x: moment().subtract(24, "hour"), y: 61 }, + { x: moment().subtract(15, "hour"), y: 65 }, + { x: moment().subtract(10, "hour"), y: 63 }, + { x: moment().subtract(5, "hour"), y: 58 }, + { x: moment().subtract(1, "hour"), y: 64 }, + { x: moment().subtract(50, "minute"), y: 63 }, + { x: moment().subtract(40, "minute"), y: 60 }, + { x: moment().subtract(30, "minute"), y: 62 }, + { x: moment().subtract(20, "minute"), y: 61 }, + { x: moment().subtract(10, "minute"), y: 62 }, + ], + }, +]; + +export const LOUNGING_VS_ULTIMATE_FRISBEE_DATA: ITimeseriesWidgetData[] = [ + { + id: "series-a", + name: "Lounging", + description: "Shootin' the Breeze", + data: [ + { x: moment().subtract(10, "day"), y: 10 }, + { x: moment().subtract(9, "day"), y: 15 }, + { x: moment().subtract(8, "day"), y: 13 }, + { x: moment().subtract(7, "day"), y: 20 }, + { x: moment().subtract(6, "day"), y: 15 }, + { x: moment().subtract(5, "day"), y: 10 }, + { x: moment().subtract(4, "day"), y: 15 }, + { x: moment().subtract(3, "day"), y: 5 }, + { x: moment().subtract(2, "day"), y: 10 }, + { x: moment().subtract(1, "day"), y: 15 }, + { x: moment().subtract(24, "hour"), y: 14 }, + { x: moment().subtract(15, "hour"), y: 13 }, + { x: moment().subtract(10, "hour"), y: 15 }, + { x: moment().subtract(5, "hour"), y: 16 }, + { x: moment().subtract(1, "hour"), y: 14 }, + { x: moment().subtract(50, "minute"), y: 13 }, + { x: moment().subtract(40, "minute"), y: 10 }, + { x: moment().subtract(30, "minute"), y: 12 }, + { x: moment().subtract(20, "minute"), y: 11 }, + { x: moment().subtract(10, "minute"), y: 14 }, + ], + }, + { + id: "series-b", + name: "Frisbee Golfing", + description: "Golfin' with a disc", + data: [ + { x: moment().subtract(10, "day"), y: 80 }, + { x: moment().subtract(9, "day"), y: 84 }, + { x: moment().subtract(8, "day"), y: 80 }, + { x: moment().subtract(7, "day"), y: 75 }, + { x: moment().subtract(6, "day"), y: 95 }, + { x: moment().subtract(5, "day"), y: 85 }, + { x: moment().subtract(4, "day"), y: 80 }, + { x: moment().subtract(3, "day"), y: 85 }, + { x: moment().subtract(2, "day"), y: 85 }, + { x: moment().subtract(1, "day"), y: 80 }, + { x: moment().subtract(24, "hour"), y: 81 }, + { x: moment().subtract(15, "hour"), y: 85 }, + { x: moment().subtract(10, "hour"), y: 83 }, + { x: moment().subtract(5, "hour"), y: 88 }, + { x: moment().subtract(1, "hour"), y: 84 }, + { x: moment().subtract(50, "minute"), y: 83 }, + { x: moment().subtract(40, "minute"), y: 80 }, + { x: moment().subtract(30, "minute"), y: 82 }, + { x: moment().subtract(20, "minute"), y: 81 }, + { x: moment().subtract(10, "minute"), y: 82 }, + ], + }, +]; + +export const TABLE_DATA: BasicTableModel[] = [ + { + position: 1, + name: "FOCUS-SVR-02258", + features: ["remote-access-vpn-tunnel", "patch-manager01"], + status: "Active", + checks: { + icon: "status_up", + num: 25, + }, + "cpu-load": 86, + firstUrl: "https://en.wikipedia.org/wiki/Brno", + firstUrlLabel: "Brno", + secondUrl: "https://en.wikipedia.org/wiki/VMware_Workstation", + secondUrlLabel: "Workstation", + }, + { + position: 2, + name: "FOCUS-SVR-03312", + features: ["tools", "database", "orion-ape-backup"], + status: "Active", + checks: { + icon: "status_critical", + num: 25, + }, + "cpu-load": 47, + firstUrl: "https://en.wikipedia.org/wiki/Brno", + firstUrlLabel: "Brno", + secondUrl: "https://en.wikipedia.org/wiki/VMware_Workstation", + secondUrlLabel: "Workstation", + }, + { + position: 3, + name: "FOCUS-SVR-02258", + features: [ + "remote-access-vpn-tunnel", + "database", + "orion-ape-backup", + "patch-manager01", + ], + status: "Active", + checks: { + icon: "status_down", + num: 25, + }, + "cpu-load": 53, + firstUrl: "https://en.wikipedia.org/wiki/Kyiv", + firstUrlLabel: "Kyiv", + secondUrl: "https://en.wikipedia.org/wiki/VMware_Workstation", + secondUrlLabel: "Workstation", + }, + { + position: 4, + name: "Man-LT-JYJ4AD5", + features: [ + "remote-access-vpn-tunnel", + "tools", + "database", + "orion-ape-backup", + ], + status: "Active", + checks: { + icon: "status_up", + num: 25, + }, + "cpu-load": 32, + firstUrl: "https://en.wikipedia.org/wiki/Kyiv", + firstUrlLabel: "Kyiv", + secondUrl: "https://en.wikipedia.org/wiki/VirtualBox", + secondUrlLabel: "VirtualBox", + }, + { + position: 5, + name: "Man-LT-JYJ425", + features: [ + "remote-access-vpn-tunnel", + "tools", + "database", + "orion-ape-backup", + ], + status: "Active", + checks: { + icon: "status_up", + num: 25, + }, + "cpu-load": 22, + firstUrl: "https://en.wikipedia.org/wiki/Kyiv", + firstUrlLabel: "Kyiv", + secondUrl: "https://en.wikipedia.org/wiki/VirtualBox", + secondUrlLabel: "VirtualBox", + }, + { + position: 6, + name: "Man-LT-JYJ4333", + features: [ + "remote-access-vpn-tunnel", + "tools", + "database", + "orion-ape-backup", + ], + status: "Active", + checks: { + icon: "status_up", + num: 25, + }, + "cpu-load": 12, + firstUrl: "https://en.wikipedia.org/wiki/Kyiv", + firstUrlLabel: "Kyiv", + secondUrl: "https://en.wikipedia.org/wiki/VirtualBox", + secondUrlLabel: "VirtualBox", + }, + { + position: 7, + name: "FOCUS-SVR-02258", + features: ["remote-access-vpn-tunnel", "patch-manager01"], + status: "Active", + checks: { + icon: "status_up", + num: 25, + }, + "cpu-load": 86, + firstUrl: "https://en.wikipedia.org/wiki/Austin", + firstUrlLabel: "Austin", + secondUrl: "https://en.wikipedia.org/wiki/VMware_Workstation", + secondUrlLabel: "Workstation", + }, + { + position: 8, + name: "Man-LT-JYJ4AD5", + features: [ + "remote-access-vpn-tunnel", + "tools", + "database", + "orion-ape-backup", + "patch-manager01", + ], + status: "Active", + checks: { + icon: "status_inactive", + num: 25, + }, + "cpu-load": 35, + firstUrl: "https://en.wikipedia.org/wiki/Austin", + firstUrlLabel: "Austin", + secondUrl: "https://en.wikipedia.org/wiki/VMware_Workstation", + secondUrlLabel: "Workstation", + }, + { + position: 9, + name: "Man-LT-JYJ4AD5", + features: [ + "remote-access-vpn-tunnel", + "tools", + "database", + "patch-manager01", + ], + status: "Active", + checks: { + icon: "status_up", + num: 25, + }, + "cpu-load": 32, + firstUrl: "https://en.wikipedia.org/wiki/Brno", + firstUrlLabel: "Brno", + secondUrl: "https://en.wikipedia.org/wiki/VMware_Workstation", + secondUrlLabel: "Workstation", + }, + { + position: 10, + name: "Man-LT-JYJ4AD5", + features: [ + "remote-access-vpn-tunnel", + "database", + "orion-ape-backup", + "patch-manager01", + ], + status: "Active", + checks: { + icon: "status_up", + num: 25, + }, + "cpu-load": 64, + firstUrl: "https://en.wikipedia.org/wiki/Kyiv", + firstUrlLabel: "Kyiv", + secondUrl: "https://en.wikipedia.org/wiki/VMware_Workstation", + secondUrlLabel: "Workstation", + }, + { + position: 11, + name: "Man-LT-111", + features: [], + status: "Active", + checks: { + icon: "status_external", + num: 25, + }, + "cpu-load": 55, + firstUrl: "https://en.wikipedia.org/wiki/Brno", + firstUrlLabel: "Brno", + secondUrl: "https://en.wikipedia.org/wiki/VMware_Workstation", + secondUrlLabel: "Workstation", + }, + { + position: 12, + name: "Man-LT-2222", + features: [ + "remote-access-vpn-tunnel", + "tools", + "database", + "orion-ape-backup", + "patch-manager01", + ], + status: "Active", + checks: { + icon: "status_inactive", + num: 25, + }, + "cpu-load": 34, + firstUrl: "https://en.wikipedia.org/wiki/Brno", + firstUrlLabel: "Brno", + secondUrl: "https://en.wikipedia.org/wiki/VMware_Workstation", + secondUrlLabel: "Workstation", + }, + { + position: 13, + name: "Man-LT-333333", + features: [ + "remote-access-vpn-tunnel", + "tools", + "database", + "patch-manager01", + ], + status: "Active", + checks: { + icon: "status_up", + num: 25, + }, + "cpu-load": 56, + firstUrl: "https://en.wikipedia.org/wiki/Kyiv", + firstUrlLabel: "Kyiv", + secondUrl: "https://en.wikipedia.org/wiki/VMware_Workstation", + secondUrlLabel: "Workstation", + }, + { + position: 14, + name: "Man-LT-444444", + features: [ + "remote-access-vpn-tunnel", + "database", + "orion-ape-backup", + "patch-manager01", + ], + status: "Active", + checks: { + icon: "status_up", + num: 25, + }, + "cpu-load": 26, + firstUrl: "https://en.wikipedia.org/wiki/Kyiv", + firstUrlLabel: "Kyiv", + secondUrl: "https://en.wikipedia.org/wiki/VMware_Workstation", + secondUrlLabel: "Workstation", + }, + { + position: 15, + name: "Man-LT-555555", + features: [ + "remote-access-vpn-tunnel", + "database", + "orion-ape-backup", + "patch-manager01", + ], + status: "Active", + checks: { + icon: "status_up", + num: 25, + }, + "cpu-load": 76, + firstUrl: "https://en.wikipedia.org/wiki/Austin", + firstUrlLabel: "Austin", + secondUrl: "https://en.wikipedia.org/wiki/VMware_Workstation", + secondUrlLabel: "Workstation", + }, + { + position: 16, + name: "FOCUS-SVR-02258", + features: ["remote-access-vpn-tunnel", "patch-manager01"], + status: "Active", + checks: { + icon: "status_up", + num: 25, + }, + "cpu-load": 86, + firstUrl: "https://en.wikipedia.org/wiki/Brno", + firstUrlLabel: "Brno", + secondUrl: "https://en.wikipedia.org/wiki/VMware_Workstation", + secondUrlLabel: "Workstation", + }, + { + position: 17, + name: "FOCUS-SVR-03312", + features: ["tools", "database", "orion-ape-backup"], + status: "Active", + checks: { + icon: "status_critical", + num: 25, + }, + "cpu-load": 47, + firstUrl: "https://en.wikipedia.org/wiki/Brno", + firstUrlLabel: "Brno", + secondUrl: "https://en.wikipedia.org/wiki/VMware_Workstation", + secondUrlLabel: "Workstation", + }, + { + position: 18, + name: "FOCUS-SVR-02258", + features: [ + "remote-access-vpn-tunnel", + "database", + "orion-ape-backup", + "patch-manager01", + ], + status: "Active", + checks: { + icon: "status_down", + num: 25, + }, + "cpu-load": 53, + firstUrl: "https://en.wikipedia.org/wiki/Kyiv", + firstUrlLabel: "Kyiv", + secondUrl: "https://en.wikipedia.org/wiki/VMware_Workstation", + secondUrlLabel: "Workstation", + }, + { + position: 19, + name: "Man-LT-JYJ4AD5", + features: [ + "remote-access-vpn-tunnel", + "tools", + "database", + "orion-ape-backup", + ], + status: "Active", + checks: { + icon: "status_up", + num: 25, + }, + "cpu-load": 32, + firstUrl: "https://en.wikipedia.org/wiki/Kyiv", + firstUrlLabel: "Kyiv", + secondUrl: "https://en.wikipedia.org/wiki/VirtualBox", + secondUrlLabel: "VirtualBox", + }, + { + position: 20, + name: "Man-LT-JYJ425", + features: [ + "remote-access-vpn-tunnel", + "tools", + "database", + "orion-ape-backup", + ], + status: "Active", + checks: { + icon: "status_up", + num: 25, + }, + "cpu-load": 22, + firstUrl: "https://en.wikipedia.org/wiki/Kyiv", + firstUrlLabel: "Kyiv", + secondUrl: "https://en.wikipedia.org/wiki/VirtualBox", + secondUrlLabel: "VirtualBox", + }, + { + position: 21, + name: "Man-LT-JYJ4333", + features: [ + "remote-access-vpn-tunnel", + "tools", + "database", + "orion-ape-backup", + ], + status: "Active", + checks: { + icon: "status_up", + num: 25, + }, + "cpu-load": 12, + firstUrl: "https://en.wikipedia.org/wiki/Kyiv", + firstUrlLabel: "Kyiv", + secondUrl: "https://en.wikipedia.org/wiki/VirtualBox", + secondUrlLabel: "VirtualBox", + }, + { + position: 22, + name: "FOCUS-SVR-02258", + features: ["remote-access-vpn-tunnel", "patch-manager01"], + status: "Active", + checks: { + icon: "status_up", + num: 25, + }, + "cpu-load": 86, + firstUrl: "https://en.wikipedia.org/wiki/Austin", + firstUrlLabel: "Austin", + secondUrl: "https://en.wikipedia.org/wiki/VMware_Workstation", + secondUrlLabel: "Workstation", + }, + { + position: 23, + name: "Man-LT-JYJ4AD5", + features: [ + "remote-access-vpn-tunnel", + "tools", + "database", + "orion-ape-backup", + "patch-manager01", + ], + status: "Active", + checks: { + icon: "status_inactive", + num: 25, + }, + "cpu-load": 35, + firstUrl: "https://en.wikipedia.org/wiki/Austin", + firstUrlLabel: "Austin", + secondUrl: "https://en.wikipedia.org/wiki/VMware_Workstation", + secondUrlLabel: "Workstation", + }, + { + position: 24, + name: "Man-LT-JYJ4AD5", + features: [ + "remote-access-vpn-tunnel", + "tools", + "database", + "patch-manager01", + ], + status: "Active", + checks: { + icon: "status_up", + num: 25, + }, + "cpu-load": 32, + firstUrl: "https://en.wikipedia.org/wiki/Brno", + firstUrlLabel: "Brno", + secondUrl: "https://en.wikipedia.org/wiki/VMware_Workstation", + secondUrlLabel: "Workstation", + }, + { + position: 25, + name: "Man-LT-JYJ4AD5", + features: [ + "remote-access-vpn-tunnel", + "database", + "orion-ape-backup", + "patch-manager01", + ], + status: "Active", + checks: { + icon: "status_up", + num: 25, + }, + "cpu-load": 64, + firstUrl: "https://en.wikipedia.org/wiki/Kyiv", + firstUrlLabel: "Kyiv", + secondUrl: "https://en.wikipedia.org/wiki/VMware_Workstation", + secondUrlLabel: "Workstation", + }, + { + position: 26, + name: "Man-LT-111", + features: [], + status: "Active", + checks: { + icon: "status_external", + num: 25, + }, + "cpu-load": 55, + firstUrl: "https://en.wikipedia.org/wiki/Brno", + firstUrlLabel: "Brno", + secondUrl: "https://en.wikipedia.org/wiki/VMware_Workstation", + secondUrlLabel: "Workstation", + }, + { + position: 27, + name: "Man-LT-2222", + features: [ + "remote-access-vpn-tunnel", + "tools", + "database", + "orion-ape-backup", + "patch-manager01", + ], + status: "Active", + checks: { + icon: "status_inactive", + num: 25, + }, + "cpu-load": 34, + firstUrl: "https://en.wikipedia.org/wiki/Brno", + firstUrlLabel: "Brno", + secondUrl: "https://en.wikipedia.org/wiki/VMware_Workstation", + secondUrlLabel: "Workstation", + }, + { + position: 28, + name: "Man-LT-333333", + features: [ + "remote-access-vpn-tunnel", + "tools", + "database", + "patch-manager01", + ], + status: "Active", + checks: { + icon: "status_up", + num: 25, + }, + "cpu-load": 56, + firstUrl: "https://en.wikipedia.org/wiki/Kyiv", + firstUrlLabel: "Kyiv", + secondUrl: "https://en.wikipedia.org/wiki/VMware_Workstation", + secondUrlLabel: "Workstation", + }, + { + position: 29, + name: "Man-LT-444444", + features: [ + "remote-access-vpn-tunnel", + "database", + "orion-ape-backup", + "patch-manager01", + ], + status: "Active", + checks: { + icon: "status_up", + num: 25, + }, + "cpu-load": 26, + firstUrl: "https://en.wikipedia.org/wiki/Kyiv", + firstUrlLabel: "Kyiv", + secondUrl: "https://en.wikipedia.org/wiki/VMware_Workstation", + secondUrlLabel: "Workstation", + }, + { + position: 30, + name: "Man-LT-555555", + features: [ + "remote-access-vpn-tunnel", + "database", + "orion-ape-backup", + "patch-manager01", + ], + status: "Active", + checks: { + icon: "status_up", + num: 25, + }, + "cpu-load": 76, + firstUrl: "https://en.wikipedia.org/wiki/Austin", + firstUrlLabel: "Austin", + secondUrl: "https://en.wikipedia.org/wiki/VMware_Workstation", + secondUrlLabel: "Workstation", + }, + { + position: 31, + name: "FOCUS-SVR-02258", + features: ["remote-access-vpn-tunnel", "patch-manager01"], + status: "Active", + checks: { + icon: "status_up", + num: 25, + }, + "cpu-load": 86, + firstUrl: "https://en.wikipedia.org/wiki/Brno", + firstUrlLabel: "Brno", + secondUrl: "https://en.wikipedia.org/wiki/VMware_Workstation", + secondUrlLabel: "Workstation", + }, + { + position: 32, + name: "FOCUS-SVR-03312", + features: ["tools", "database", "orion-ape-backup"], + status: "Active", + checks: { + icon: "status_critical", + num: 25, + }, + "cpu-load": 47, + firstUrl: "https://en.wikipedia.org/wiki/Brno", + firstUrlLabel: "Brno", + secondUrl: "https://en.wikipedia.org/wiki/VMware_Workstation", + secondUrlLabel: "Workstation", + }, + { + position: 33, + name: "FOCUS-SVR-02258", + features: [ + "remote-access-vpn-tunnel", + "database", + "orion-ape-backup", + "patch-manager01", + ], + status: "Active", + checks: { + icon: "status_down", + num: 25, + }, + "cpu-load": 53, + firstUrl: "https://en.wikipedia.org/wiki/Kyiv", + firstUrlLabel: "Kyiv", + secondUrl: "https://en.wikipedia.org/wiki/VMware_Workstation", + secondUrlLabel: "Workstation", + }, + { + position: 34, + name: "Man-LT-JYJ4AD5", + features: [ + "remote-access-vpn-tunnel", + "tools", + "database", + "orion-ape-backup", + ], + status: "Active", + checks: { + icon: "status_up", + num: 25, + }, + "cpu-load": 32, + firstUrl: "https://en.wikipedia.org/wiki/Kyiv", + firstUrlLabel: "Kyiv", + secondUrl: "https://en.wikipedia.org/wiki/VirtualBox", + secondUrlLabel: "VirtualBox", + }, + { + position: 35, + name: "Man-LT-JYJ425", + features: [ + "remote-access-vpn-tunnel", + "tools", + "database", + "orion-ape-backup", + ], + status: "Active", + checks: { + icon: "status_up", + num: 25, + }, + "cpu-load": 22, + firstUrl: "https://en.wikipedia.org/wiki/Kyiv", + firstUrlLabel: "Kyiv", + secondUrl: "https://en.wikipedia.org/wiki/VirtualBox", + secondUrlLabel: "VirtualBox", + }, + { + position: 36, + name: "Man-LT-JYJ4333", + features: [ + "remote-access-vpn-tunnel", + "tools", + "database", + "orion-ape-backup", + ], + status: "Active", + checks: { + icon: "status_up", + num: 25, + }, + "cpu-load": 12, + firstUrl: "https://en.wikipedia.org/wiki/Kyiv", + firstUrlLabel: "Kyiv", + secondUrl: "https://en.wikipedia.org/wiki/VirtualBox", + secondUrlLabel: "VirtualBox", + }, + { + position: 37, + name: "FOCUS-SVR-02258", + features: ["remote-access-vpn-tunnel", "patch-manager01"], + status: "Active", + checks: { + icon: "status_up", + num: 25, + }, + "cpu-load": 86, + firstUrl: "https://en.wikipedia.org/wiki/Austin", + firstUrlLabel: "Austin", + secondUrl: "https://en.wikipedia.org/wiki/VMware_Workstation", + secondUrlLabel: "Workstation", + }, + { + position: 38, + name: "Man-LT-JYJ4AD5", + features: [ + "remote-access-vpn-tunnel", + "tools", + "database", + "orion-ape-backup", + "patch-manager01", + ], + status: "Active", + checks: { + icon: "status_inactive", + num: 25, + }, + "cpu-load": 35, + firstUrl: "https://en.wikipedia.org/wiki/Austin", + firstUrlLabel: "Austin", + secondUrl: "https://en.wikipedia.org/wiki/VMware_Workstation", + secondUrlLabel: "Workstation", + }, + { + position: 39, + name: "Man-LT-JYJ4AD5", + features: [ + "remote-access-vpn-tunnel", + "tools", + "database", + "patch-manager01", + ], + status: "Active", + checks: { + icon: "status_up", + num: 25, + }, + "cpu-load": 32, + firstUrl: "https://en.wikipedia.org/wiki/Brno", + firstUrlLabel: "Brno", + secondUrl: "https://en.wikipedia.org/wiki/VMware_Workstation", + secondUrlLabel: "Workstation", + }, + { + position: 40, + name: "Man-LT-JYJ4AD5", + features: [ + "remote-access-vpn-tunnel", + "database", + "orion-ape-backup", + "patch-manager01", + ], + status: "Active", + checks: { + icon: "status_up", + num: 25, + }, + "cpu-load": 64, + firstUrl: "https://en.wikipedia.org/wiki/Kyiv", + firstUrlLabel: "Kyiv", + secondUrl: "https://en.wikipedia.org/wiki/VMware_Workstation", + secondUrlLabel: "Workstation", + }, + { + position: 41, + name: "Man-LT-111", + features: [], + status: "Active", + checks: { + icon: "status_external", + num: 25, + }, + "cpu-load": 55, + firstUrl: "https://en.wikipedia.org/wiki/Brno", + firstUrlLabel: "Brno", + secondUrl: "https://en.wikipedia.org/wiki/VMware_Workstation", + secondUrlLabel: "Workstation", + }, + { + position: 42, + name: "Man-LT-2222", + features: [ + "remote-access-vpn-tunnel", + "tools", + "database", + "orion-ape-backup", + "patch-manager01", + ], + status: "Active", + checks: { + icon: "status_inactive", + num: 25, + }, + "cpu-load": 34, + firstUrl: "https://en.wikipedia.org/wiki/Brno", + firstUrlLabel: "Brno", + secondUrl: "https://en.wikipedia.org/wiki/VMware_Workstation", + secondUrlLabel: "Workstation", + }, + { + position: 43, + name: "Man-LT-333333", + features: [ + "remote-access-vpn-tunnel", + "tools", + "database", + "patch-manager01", + ], + status: "Active", + checks: { + icon: "status_up", + num: 25, + }, + "cpu-load": 56, + firstUrl: "https://en.wikipedia.org/wiki/Kyiv", + firstUrlLabel: "Kyiv", + secondUrl: "https://en.wikipedia.org/wiki/VMware_Workstation", + secondUrlLabel: "Workstation", + }, + { + position: 44, + name: "Man-LT-444444", + features: [ + "remote-access-vpn-tunnel", + "database", + "orion-ape-backup", + "patch-manager01", + ], + status: "Active", + checks: { + icon: "status_up", + num: 25, + }, + "cpu-load": 26, + firstUrl: "https://en.wikipedia.org/wiki/Kyiv", + firstUrlLabel: "Kyiv", + secondUrl: "https://en.wikipedia.org/wiki/VMware_Workstation", + secondUrlLabel: "Workstation", + }, + { + position: 45, + name: "Man-LT-555555", + features: [ + "remote-access-vpn-tunnel", + "database", + "orion-ape-backup", + "patch-manager01", + ], + status: "Active", + checks: { + icon: "status_up", + num: 25, + }, + "cpu-load": 76, + firstUrl: "https://en.wikipedia.org/wiki/Austin", + firstUrlLabel: "Austin", + secondUrl: "https://en.wikipedia.org/wiki/VMware_Workstation", + secondUrlLabel: "Workstation", + }, +]; +\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\`, + "overview/hero/widget-configs/kpi.ts": \\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\`// © 2022 SolarWinds Worldwide, LLC. All rights reserved. +// +// Permission is hereby granted, free of charge, to any person obtaining a copy +// of this software and associated documentation files (the "Software"), to +// deal in the Software without restriction, including without limitation the +// rights to use, copy, modify, merge, publish, distribute, sublicense, and/or +// sell copies of the Software, and to permit persons to whom the Software is +// furnished to do so, subject to the following conditions: +// +// The above copyright notice and this permission notice shall be included in +// all copies or substantial portions of the Software. +// +// THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR +// IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, +// FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE +// AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER +// LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, +// OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN +// THE SOFTWARE. + +import { + DEFAULT_PIZZAGNA_ROOT, + IProviderConfiguration, + IRefresherProperties, + IWidget, + KpiComponent, + NOVA_KPI_DATASOURCE_ADAPTER, + PizzagnaLayer, + WellKnownProviders, +} from "@nova-ui/dashboards"; + +import { + HarryPotterAverageRatingDataSource, + HarryPotterRatingsCountDataSource, +} from "../data/kpi-datasources"; + +export const kpiConfig: IWidget = { + id: "kpiWidgetId", + type: "kpi", + pizzagna: { + [PizzagnaLayer.Configuration]: { + [DEFAULT_PIZZAGNA_ROOT]: { + providers: { + [WellKnownProviders.Refresher]: { + properties: { + // Configuring the refresher interval so that our data source is invoked every ten minutes + interval: 60 * 10, + enabled: true, + } as IRefresherProperties, + } as Partial, + }, + }, + header: { + properties: { + title: "Harry Potter and the Sorcerer's Stone", + subtitle: "By J. K. Rowling", + }, + }, + tiles: { + properties: { + nodes: ["kpi1", "kpi2"], + }, + }, + kpi1: { + id: "kpi1", + componentType: KpiComponent.lateLoadKey, + properties: { + widgetData: { + label: "Average Rating", + backgroundColor: "var(--nui-color-chart-three)", + units: "out of 5 Stars", + }, + }, + providers: { + [WellKnownProviders.DataSource]: { + // Setting the data source providerId for the tile with id "kpi1" + providerId: + HarryPotterAverageRatingDataSource.providerId, + } as IProviderConfiguration, + [WellKnownProviders.Adapter]: { + providerId: NOVA_KPI_DATASOURCE_ADAPTER, + properties: { + componentId: "kpi1", + propertyPath: "widgetData", + }, + } as IProviderConfiguration, + }, + }, + kpi2: { + id: "kpi2", + componentType: KpiComponent.lateLoadKey, + properties: { + widgetData: { + label: "Reader Feedback", + units: "Ratings", + }, + }, + providers: { + [WellKnownProviders.DataSource]: { + // Setting the data source providerId for the tile with id "kpi" + providerId: + HarryPotterRatingsCountDataSource.providerId, + } as IProviderConfiguration, + [WellKnownProviders.Adapter]: { + providerId: NOVA_KPI_DATASOURCE_ADAPTER, + properties: { + componentId: "kpi2", + propertyPath: "widgetData", + }, + } as IProviderConfiguration, + }, + }, + }, + }, +}; +\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\`, + "overview/hero/widget-configs/proportional.ts": \\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\`// © 2022 SolarWinds Worldwide, LLC. All rights reserved. +// +// Permission is hereby granted, free of charge, to any person obtaining a copy +// of this software and associated documentation files (the "Software"), to +// deal in the Software without restriction, including without limitation the +// rights to use, copy, modify, merge, publish, distribute, sublicense, and/or +// sell copies of the Software, and to permit persons to whom the Software is +// furnished to do so, subject to the following conditions: +// +// The above copyright notice and this permission notice shall be included in +// all copies or substantial portions of the Software. +// +// THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR +// IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, +// FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE +// AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER +// LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, +// OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN +// THE SOFTWARE. + +import { + DEFAULT_PIZZAGNA_ROOT, + IProportionalWidgetChartOptions, + IProviderConfiguration, + IWidget, + LegendPlacement, + PizzagnaLayer, + ProportionalWidgetChartTypes, + WellKnownProviders, +} from "@nova-ui/dashboards"; + +import { BeerReviewCountsByCityMockDataSource } from "../data/proportional-datasources"; + +export const proportionalConfig: IWidget = { + id: "proportionalWidgetId", + type: "proportional", + pizzagna: { + [PizzagnaLayer.Configuration]: { + [DEFAULT_PIZZAGNA_ROOT]: { + providers: { + [WellKnownProviders.Refresher]: { + properties: { + interval: 0, + }, + }, + }, + }, + header: { + properties: { + title: "Beer Review Tally by City", + subtitle: "These People Love Beer", + }, + }, + chart: { + providers: { + [WellKnownProviders.DataSource]: { + providerId: + BeerReviewCountsByCityMockDataSource.providerId, + } as IProviderConfiguration, + }, + properties: { + configuration: { + chartOptions: { + type: ProportionalWidgetChartTypes.DonutChart, + legendPlacement: LegendPlacement.Right, + } as IProportionalWidgetChartOptions, + }, + }, + }, + }, + }, +}; +\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\`, + "overview/hero/widget-configs/risk-score.ts": \\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\`// © 2023 SolarWinds Worldwide, LLC. All rights reserved. +// +// Permission is hereby granted, free of charge, to any person obtaining a copy +// of this software and associated documentation files (the "Software"), to +// deal in the Software without restriction, including without limitation the +// rights to use, copy, modify, merge, publish, distribute, sublicense, and/or +// sell copies of the Software, and to permit persons to whom the Software is +// furnished to do so, subject to the following conditions: +// +// The above copyright notice and this permission notice shall be included in +// all copies or substantial portions of the Software. +// +// THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR +// IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, +// FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE +// AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER +// LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, +// OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN +// THE SOFTWARE. + +import { + DEFAULT_PIZZAGNA_ROOT, + IProviderConfiguration, + IRefresherProperties, + IWidget, + RiskScoreTileComponent, + NOVA_KPI_DATASOURCE_ADAPTER, + PizzagnaLayer, + WellKnownProviders, +} from "@nova-ui/dashboards"; + +import { HarryPotterAverageRatingDataSource } from "../data/kpi-datasources"; + +export const riskScoreConfig: IWidget = { + id: "riskScoreWidgetId", + type: "risk-score", + pizzagna: { + [PizzagnaLayer.Configuration]: { + [DEFAULT_PIZZAGNA_ROOT]: { + providers: { + [WellKnownProviders.Refresher]: { + properties: { + // Configuring the refresher interval so that our data source is invoked every ten minutes + interval: 60 * 10, + enabled: true, + } as IRefresherProperties, + } as Partial, + }, + }, + header: { + properties: { + title: "Harry Potter and the Sorcerer's Stone", + subtitle: "By J. K. Rowling", + }, + }, + tiles: { + properties: { + nodes: ["riskScore1"], + }, + }, + riskScore1: { + id: "riskScore1", + componentType: RiskScoreTileComponent.lateLoadKey, + properties: { + widgetData: { + minValue: 0, + maxValue: 5, + useStaticLabel: false, + staticLabel: undefined, + label: \\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\`Average Rating\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\`, + description: \\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\`Harry Potter and the Sorcerer's Stone By J. K. Rowling Average Rating Risk Score\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\`, + }, + }, + providers: { + [WellKnownProviders.DataSource]: { + // Setting the data source providerId for the tile with id "riskScore1" + providerId: + HarryPotterAverageRatingDataSource.providerId, + } as IProviderConfiguration, + [WellKnownProviders.Adapter]: { + providerId: NOVA_KPI_DATASOURCE_ADAPTER, + properties: { + componentId: "riskScore1", + propertyPath: "widgetData", + }, + } as IProviderConfiguration, + }, + }, + }, + }, +}; +\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\`, + "overview/hero/widget-configs/table.ts": \\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\`// © 2022 SolarWinds Worldwide, LLC. All rights reserved. +// +// Permission is hereby granted, free of charge, to any person obtaining a copy +// of this software and associated documentation files (the "Software"), to +// deal in the Software without restriction, including without limitation the +// rights to use, copy, modify, merge, publish, distribute, sublicense, and/or +// sell copies of the Software, and to permit persons to whom the Software is +// furnished to do so, subject to the following conditions: +// +// The above copyright notice and this permission notice shall be included in +// all copies or substantial portions of the Software. +// +// THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR +// IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, +// FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE +// AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER +// LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, +// OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN +// THE SOFTWARE. + +import { + ITableWidgetColumnConfig, + ITableWidgetSorterConfig, + IWidget, + PizzagnaLayer, + RawFormatterComponent, + WellKnownProviders, +} from "@nova-ui/dashboards"; + +import { BeerDataSource } from "../data/table/beer-data-source"; + +export const tableConfig: IWidget = { + id: "tableWidgetId", + type: "table", + pizzagna: { + [PizzagnaLayer.Configuration]: { + header: { + properties: { + title: "Stupendous Suds", + subtitle: "Try These Brilliant Brews", + }, + }, + table: { + providers: { + [WellKnownProviders.DataSource]: { + providerId: BeerDataSource.providerId, + }, + }, + properties: { + configuration: { + columns: [ + { + id: "column1", + label: "Beer Name", + isActive: true, + width: 185, + formatter: { + componentType: + RawFormatterComponent.lateLoadKey, + properties: { + dataFieldIds: { + value: "name", + }, + }, + }, + }, + { + id: "column2", + label: "Tagline", + isActive: true, + width: 250, + formatter: { + componentType: + RawFormatterComponent.lateLoadKey, + properties: { + dataFieldIds: { + value: "tagline", + }, + }, + }, + }, + { + id: "column3", + label: "First Brewed", + isActive: true, + formatter: { + componentType: + RawFormatterComponent.lateLoadKey, + properties: { + dataFieldIds: { + value: "first_brewed", + }, + }, + }, + }, + { + id: "column4", + label: "Description", + isActive: true, + width: 275, + formatter: { + componentType: + RawFormatterComponent.lateLoadKey, + properties: { + dataFieldIds: { + value: "description", + }, + }, + }, + }, + ] as ITableWidgetColumnConfig[], + sorterConfiguration: { + descendantSorting: false, + sortBy: "", + } as ITableWidgetSorterConfig, + hasVirtualScroll: true, + }, + }, + }, + }, + }, +}; +\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\`, + "overview/hero/widget-configs/timeseries.ts": \\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\`// © 2022 SolarWinds Worldwide, LLC. All rights reserved. +// +// Permission is hereby granted, free of charge, to any person obtaining a copy +// of this software and associated documentation files (the "Software"), to +// deal in the Software without restriction, including without limitation the +// rights to use, copy, modify, merge, publish, distribute, sublicense, and/or +// sell copies of the Software, and to permit persons to whom the Software is +// furnished to do so, subject to the following conditions: +// +// The above copyright notice and this permission notice shall be included in +// all copies or substantial portions of the Software. +// +// THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR +// IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, +// FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE +// AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER +// LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, +// OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN +// THE SOFTWARE. + +import moment from "moment/moment"; + +import { + DEFAULT_PIZZAGNA_ROOT, + IProviderConfiguration, + ISerializableTimeframe, + ITimeseriesItemConfiguration, + IWidget, + LegendPlacement, + WellKnownProviders, +} from "@nova-ui/dashboards"; + +import { BeerVsReadingMockDataSource } from "../data/timeseries-data-sources"; + +export const timeseriesConfig: IWidget = { + id: "timeseriesWidgetId", + type: "timeseries", + pizzagna: { + configuration: { + [DEFAULT_PIZZAGNA_ROOT]: { + providers: { + [WellKnownProviders.DataSource]: { + providerId: BeerVsReadingMockDataSource.providerId, + } as IProviderConfiguration, + }, + }, + header: { + properties: { + title: "Primary Leisure Activity Over Time", + subtitle: "Survey of 1000 Solarians", + }, + }, + chart: { + providers: { + [WellKnownProviders.Adapter]: { + properties: { + series: [ + { + id: "series-1", + label: "Beer Tasting", + selectedSeriesId: "series-1", + }, + { + id: "series-2", + label: "Reading", + selectedSeriesId: "series-2", + }, + ] as ITimeseriesItemConfiguration[], + }, + } as Partial, + }, + properties: { + configuration: { + legendPlacement: LegendPlacement.Right, + enableZoom: true, + leftAxisLabel: "Solarians (%)", + }, + }, + }, + timeframeSelection: { + properties: { + timeframe: { + selectedPresetId: "last7Days", + } as ISerializableTimeframe, + minDate: moment().subtract(10, "days").format(), + maxDate: moment().format(), + }, + }, + }, + }, +}; +\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\`, + "overview/overview-docs.component.ts": \\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\`// © 2022 SolarWinds Worldwide, LLC. All rights reserved. +// +// Permission is hereby granted, free of charge, to any person obtaining a copy +// of this software and associated documentation files (the "Software"), to +// deal in the Software without restriction, including without limitation the +// rights to use, copy, modify, merge, publish, distribute, sublicense, and/or +// sell copies of the Software, and to permit persons to whom the Software is +// furnished to do so, subject to the following conditions: +// +// The above copyright notice and this permission notice shall be included in +// all copies or substantial portions of the Software. +// +// THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR +// IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, +// FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE +// AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER +// LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, +// OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN +// THE SOFTWARE. + +import { Component } from "@angular/core"; + +@Component({ + selector: "nui-dashboard-overview-docs", + templateUrl: "./overview-docs.component.html", + standalone: false, +}) +export class OverviewDocsComponent {} +\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\`, + "overview/overview.module.ts": \\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\`// © 2022 SolarWinds Worldwide, LLC. All rights reserved. +// +// Permission is hereby granted, free of charge, to any person obtaining a copy +// of this software and associated documentation files (the "Software"), to +// deal in the Software without restriction, including without limitation the +// rights to use, copy, modify, merge, publish, distribute, sublicense, and/or +// sell copies of the Software, and to permit persons to whom the Software is +// furnished to do so, subject to the following conditions: +// +// The above copyright notice and this permission notice shall be included in +// all copies or substantial portions of the Software. +// +// THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR +// IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, +// FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE +// AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER +// LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, +// OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN +// THE SOFTWARE. + +import { CommonModule } from "@angular/common"; +import { NgModule } from "@angular/core"; +import { RouterModule } from "@angular/router"; + +import { + NuiBusyModule, + NuiButtonModule, + NuiDocsModule, + NuiIconModule, + NuiMessageModule, + NuiSwitchModule, +} from "@nova-ui/bits"; +import { + ConfiguratorHeadingService, + IFormatterDefinition, + LinkFormatterComponent, + NuiDashboardsModule, + WellKnownPathKey, + WidgetTypesService, +} from "@nova-ui/dashboards"; + +import { HeroDashboardComponent } from "./hero/dashboard/hero-dashboard.component"; +import { + HarryPotterAverageRatingDataSource, + HarryPotterRatingsCountDataSource, +} from "./hero/data/kpi-datasources"; +import { + BeerReviewCountsByCityMockDataSource, + BeerReviewCountsByCityMockDataSource2, +} from "./hero/data/proportional-datasources"; +import { BeerDataSource } from "./hero/data/table/beer-data-source"; +import { RandomUserDataSource } from "./hero/data/table/random-user-data-source"; +import { + BeerVsReadingMockDataSource, + LoungingVsFrisbeeGolfMockDataSource, +} from "./hero/data/timeseries-data-sources"; +import { OverviewDocsComponent } from "./overview-docs.component"; + +const routes = [ + { + path: "", + component: OverviewDocsComponent, + data: { + srlc: { + hideIndicator: true, + }, + showThemeSwitcher: true, + }, + }, + { + path: "hero", + component: HeroDashboardComponent, + data: { + srlc: { + hideIndicator: true, + }, + }, + }, +]; + +@NgModule({ + imports: [ + CommonModule, + NuiDashboardsModule, + NuiBusyModule, + NuiButtonModule, + NuiDocsModule, + NuiMessageModule, + NuiSwitchModule, + NuiIconModule, + RouterModule.forChild(routes), + ], + declarations: [OverviewDocsComponent, HeroDashboardComponent], + providers: [ConfiguratorHeadingService], +}) +export default class OverviewModule { + constructor(private widgetTypesService: WidgetTypesService) { + this.setupDataSourceProviders(); + this.setupProportionalLegendFormatters(); + } + + private setupDataSourceProviders() { + this.setDataSourceProviders("table", [ + RandomUserDataSource.providerId, + BeerDataSource.providerId, + ]); + this.setDataSourceProviders("kpi", [ + HarryPotterAverageRatingDataSource.providerId, + HarryPotterRatingsCountDataSource.providerId, + ]); + this.setDataSourceProviders("risk-score", [ + HarryPotterAverageRatingDataSource.providerId, + HarryPotterRatingsCountDataSource.providerId, + ]); + this.setDataSourceProviders("proportional", [ + BeerReviewCountsByCityMockDataSource.providerId, + BeerReviewCountsByCityMockDataSource2.providerId, + ]); + this.setDataSourceProviders("timeseries", [ + BeerVsReadingMockDataSource.providerId, + LoungingVsFrisbeeGolfMockDataSource.providerId, + ]); + } + + private setDataSourceProviders(type: string, providers: string[]) { + const widgetTemplate = this.widgetTypesService.getWidgetType(type, 1); + this.widgetTypesService.setNode( + widgetTemplate, + "configurator", + WellKnownPathKey.DataSourceProviders, + providers + ); + } + + private setupProportionalLegendFormatters() { + const formatters: IFormatterDefinition[] = [ + { + componentType: LinkFormatterComponent.lateLoadKey, + label: $localize\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\`Link\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\`, + dataTypes: { + value: "label", + link: "link", + }, + }, + ]; + + const widgetTemplate = this.widgetTypesService.getWidgetType( + "proportional", + 1 + ); + this.widgetTypesService.setNode( + widgetTemplate, + "configurator", + WellKnownPathKey.Formatters, + formatters + ); + } +} +\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\`, + "tutorials/customization/configurator-section/custom-configurator-section/custom-configurator-section.example.component.ts": \\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\`// © 2022 SolarWinds Worldwide, LLC. All rights reserved. +// +// Permission is hereby granted, free of charge, to any person obtaining a copy +// of this software and associated documentation files (the "Software"), to +// deal in the Software without restriction, including without limitation the +// rights to use, copy, modify, merge, publish, distribute, sublicense, and/or +// sell copies of the Software, and to permit persons to whom the Software is +// furnished to do so, subject to the following conditions: +// +// The above copyright notice and this permission notice shall be included in +// all copies or substantial portions of the Software. +// +// THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR +// IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, +// FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE +// AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER +// LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, +// OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN +// THE SOFTWARE. + +import { HttpClient, HttpErrorResponse } from "@angular/common/http"; +import { + ChangeDetectionStrategy, + ChangeDetectorRef, + Component, + EventEmitter, + Injectable, + Input, + OnChanges, + OnDestroy, + OnInit, + Output, + SimpleChanges, +} from "@angular/core"; +import { FormBuilder, FormGroup, Validators } from "@angular/forms"; +import { GridsterConfig, GridsterItem } from "angular-gridster2"; +// eslint-disable-next-line import/no-deprecated +import { BehaviorSubject, combineLatest, Observable } from "rxjs"; +// eslint-disable-next-line import/no-deprecated +import { finalize, map, startWith } from "rxjs/operators"; + +import { DataSourceService, IFilteringOutputs } from "@nova-ui/bits"; +import { + ComponentRegistryService, + DATA_SOURCE, + DEFAULT_PIZZAGNA_ROOT, + IDashboard, + IHasChangeDetector, + IHasForm, + IKpiData, + IProviderConfiguration, + IRefresherProperties, + IWidget, + IWidgets, + KpiComponent, + NOVA_KPI_DATASOURCE_ADAPTER, + PizzagnaLayer, + ProviderRegistryService, + WellKnownPathKey, + WellKnownProviders, + WidgetTypesService, +} from "@nova-ui/dashboards"; + +/** + * A custom version of the KpiDescriptionConfigurationComponent provided by the dashboards framework. + * --- + * For this example, the existing background color selection functionality has been replaced by custom + * template content. + */ +@Component({ + selector: "custom-kpi-description-configuration", + template: \\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\` + + +
+
+ + + +
+ + +
+
+ Custom Content +
+
+ The default version of this configurator section + displays a background color selector here. +
+
+ + +
+ + + +
+
+
+ \\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\`, + styleUrls: ["./custom-configurator-section.example.component.less"], + changeDetection: ChangeDetectionStrategy.OnPush, + standalone: false, +}) +// Remember to declare this class in the parent module +export class CustomKpiDescriptionConfigurationComponent + implements OnInit, OnChanges, IHasChangeDetector, IHasForm +{ + // Ensure that the lateLoadKey value matches class name + public static lateLoadKey = "CustomKpiDescriptionConfigurationComponent"; + + @Input() componentId: string; + @Input() configurableUnits: boolean; + + @Input() label: string = ""; + @Input() units: string = ""; + + @Output() formReady = new EventEmitter(); + + public form: FormGroup; + public subtitle$: Observable; + + constructor( + public changeDetector: ChangeDetectorRef, + private formBuilder: FormBuilder + ) {} + + public ngOnInit(): void { + this.form = this.formBuilder.group({ + label: [this.label, [Validators.required]], + }); + + if (this.configurableUnits) { + this.form.addControl("units", this.formBuilder.control(this.units)); + } + + const label = this.form.get("label"); + // eslint-disable-next-line import/no-deprecated + const labelValue = label?.valueChanges.pipe(startWith(label?.value)); + + // eslint-disable-next-line import/no-deprecated + this.subtitle$ = combineLatest([ + labelValue?.pipe(map((t) => t || $localize\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\`no label\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\`)), + ]).pipe(map((labels) => labels.join(", "))); + + this.formReady.emit(this.form); + } + + public ngOnChanges(changes: SimpleChanges): void { + if (changes.label) { + this.form.patchValue({ label: changes.label.currentValue }); + } + if (changes.units) { + this.form.patchValue({ units: changes.units.currentValue }); + } + } +} + +/** + * A simple KPI data source to retrieve the average rating of Harry Potter and the Sorcerer's Stone (book) via googleapis + */ +@Injectable() +export class AverageRatingKpiDataSource + extends DataSourceService + implements OnDestroy +{ + // This is the ID we'll use to identify the provider + public static providerId = "AverageRatingKpiDataSource"; + + // Use this subject to communicate the data source's busy state + public busy = new BehaviorSubject(false); + + constructor(private http: HttpClient) { + super(); + } + + // In this example, getFilteredData is invoked every 10 minutes (Take a look at the refresher + // provider definition in the widget configuration below to see how the interval is set) + public async getFilteredData(): Promise { + this.busy.next(true); + return new Promise((resolve) => { + // *** Make a resource request to an external API (if needed) + this.http + .get("https://www.googleapis.com/books/v1/volumes/5MQFrgEACAAJ") + .pipe(finalize(() => this.busy.next(false))) + .subscribe({ + next: (data: any) => { + resolve({ + result: { + value: data.volumeInfo.averageRating, + }, + }); + }, + error: (error: HttpErrorResponse) => { + resolve({ + result: null, + error: { + type: error.status, + }, + }); + }, + }); + }); + } + + public ngOnDestroy(): void { + this.outputsSubject.complete(); + } +} + +/** + * A simple KPI data source to retrieve the ratings count of Harry Potter and the Sorcerer's Stone (book) via googleapis + */ +@Injectable() +export class RatingsCountKpiDataSource + extends DataSourceService + implements OnDestroy +{ + public static providerId = "RatingsCountKpiDataSource"; + + // Use this subject to communicate the data source's busy state + public busy = new BehaviorSubject(false); + + constructor(private http: HttpClient) { + super(); + } + + public async getFilteredData(): Promise { + this.busy.next(true); + return new Promise((resolve) => { + this.http + .get("https://www.googleapis.com/books/v1/volumes/5MQFrgEACAAJ") + .pipe(finalize(() => this.busy.next(false))) + .subscribe({ + next: (data: any) => { + resolve({ + result: { + value: data.volumeInfo.ratingsCount, + }, + }); + }, + error: (error: HttpErrorResponse) => { + resolve({ + result: null, + error: { + type: error.status, + }, + }); + }, + }); + }); + } + + public ngOnDestroy(): void { + this.outputsSubject.complete(); + } +} + +/** + * A component that instantiates the dashboard + */ +@Component({ + selector: "custom-configurator-section-example", + templateUrl: "./custom-configurator-section.example.component.html", + styleUrls: ["./custom-configurator-section.example.component.less"], + standalone: false, +}) +export class CustomConfiguratorSectionExampleComponent implements OnInit { + // This variable will hold all the data needed to define the layout and behavior of the widgets. + // Pass this to the dashboard component's dashboard input in the template. + public dashboard: IDashboard | undefined; + + // Angular gridster requires a configuration object even if it's empty. + // Pass this to the dashboard component's gridsterConfig input in the template. + public gridsterConfig: GridsterConfig = {}; + + // Boolean which dashboard takes in as an input if its true it allows you to move widgets around. + public editMode: boolean = false; + + constructor( + // WidgetTypesService provides the widget's necessary structure information + private widgetTypesService: WidgetTypesService, + + // In general, the ProviderRegistryService is used for making entities available for injection into dynamically loaded components. + private providerRegistry: ProviderRegistryService, + + // Inject the ComponentRegistryService to make our custom component available for late loading by the dashboards framework + private componentRegistry: ComponentRegistryService, + + private changeDetectorRef: ChangeDetectorRef + ) {} + + public ngOnInit(): void { + // Grab the widget's default template which will be needed as a parameter for setNode. + const widgetTemplate = this.widgetTypesService.getWidgetType("kpi", 1); + + // Replace the default KPI description configuration component with our custom one. + // Note: This could also be done in the parent module's constructor to give + // multiple dashboards access to the same custom configurator section. + this.widgetTypesService.setNode( + widgetTemplate, + "configurator", + WellKnownPathKey.TileDescriptionConfigComponentType, + CustomKpiDescriptionConfigurationComponent.lateLoadKey + ); + + // Register the custom configurator section with the component registry to make it available + // for late loading by the dashboards framework. + this.componentRegistry.registerByLateLoadKey( + CustomKpiDescriptionConfigurationComponent + ); + + // Register our data sources as dropdown options in the widget editor/configurator + // Note: This could also be done in the parent module's constructor so that + // multiple dashboards could have access to the same widget template modification. + this.widgetTypesService.setNode( + // This is the template we grabbed above with getWidgetType + widgetTemplate, + // We are setting the editor/configurator part of the widget template + "configurator", + // This indicates which node you are changing and we want to change + // the data source providers available for selection in the editor. + WellKnownPathKey.DataSourceProviders, + // We are setting the data sources available for selection in the editor + [ + AverageRatingKpiDataSource.providerId, + RatingsCountKpiDataSource.providerId, + ] + ); + + // Register the data sources available for injection into the KPI tiles. + // Note: Each tile of a KPI widget is assigned its own instance of a data source + this.providerRegistry.setProviders({ + [AverageRatingKpiDataSource.providerId]: { + provide: DATA_SOURCE, + useClass: AverageRatingKpiDataSource, + // Any dependencies that need to be injected into the provider must be listed here + deps: [HttpClient], + }, + [RatingsCountKpiDataSource.providerId]: { + provide: DATA_SOURCE, + useClass: RatingsCountKpiDataSource, + deps: [HttpClient], + }, + }); + + this.initializeDashboard(); + } + + public initializeDashboard(): void { + // We're using a static configuration object for this example (see widgetConfig at the bottom of the file), + // but this is where the widget's configuration could potentially be populated from a database + const kpiWidget = widgetConfig; + const widgetIndex: IWidgets = { + // Complete the KPI widget with information coming from its type definition + [kpiWidget.id]: + this.widgetTypesService.mergeWithWidgetType(kpiWidget), + }; + + // Setting the widget dimensions and position (this is for gridster) + const positions: Record = { + [kpiWidget.id]: { + cols: 4, + rows: 6, + y: 0, + x: 0, + }, + }; + + // Finally, assigning the variables we created above to the dashboard + this.dashboard = { + positions, + widgets: widgetIndex, + }; + } + + /** Used for restoring widgets state */ + public reInitializeDashboard(): void { + // destroys the components and their providers so the dashboard can re init data + this.dashboard = undefined; + this.changeDetectorRef.detectChanges(); + + this.initializeDashboard(); + } +} + +const widgetConfig: IWidget = { + id: "widget1", + type: "kpi", + pizzagna: { + [PizzagnaLayer.Configuration]: { + [DEFAULT_PIZZAGNA_ROOT]: { + providers: { + [WellKnownProviders.Refresher]: { + properties: { + // Configuring the refresher interval so that our data source is invoked every ten minutes + interval: 60 * 10, + enabled: true, + } as IRefresherProperties, + } as Partial, + }, + }, + header: { + properties: { + title: "Harry Potter and the Sorcerer's Stone", + subtitle: "By J. K. Rowling", + }, + }, + tiles: { + properties: { + nodes: ["kpi1"], + }, + }, + kpi1: { + id: "kpi1", + componentType: KpiComponent.lateLoadKey, + properties: { + widgetData: { + units: "out of 5 Stars", + label: "Average Rating", + }, + }, + providers: { + [WellKnownProviders.DataSource]: { + // Setting the data source providerId for the tile with id "kpi1" + providerId: AverageRatingKpiDataSource.providerId, + } as IProviderConfiguration, + [WellKnownProviders.Adapter]: { + providerId: NOVA_KPI_DATASOURCE_ADAPTER, + properties: { + componentId: "kpi1", + propertyPath: "widgetData", + }, + } as IProviderConfiguration, + }, + }, + }, + }, +}; +\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\`, + "tutorials/customization/configurator-section/custom-configurator-section-docs.component.ts": \\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\`// © 2022 SolarWinds Worldwide, LLC. All rights reserved. +// +// Permission is hereby granted, free of charge, to any person obtaining a copy +// of this software and associated documentation files (the "Software"), to +// deal in the Software without restriction, including without limitation the +// rights to use, copy, modify, merge, publish, distribute, sublicense, and/or +// sell copies of the Software, and to permit persons to whom the Software is +// furnished to do so, subject to the following conditions: +// +// The above copyright notice and this permission notice shall be included in +// all copies or substantial portions of the Software. +// +// THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR +// IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, +// FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE +// AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER +// LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, +// OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN +// THE SOFTWARE. + +import { Component } from "@angular/core"; + +@Component({ + selector: "custom-configurator-section-docs", + templateUrl: "./custom-configurator-section-docs.component.html", + standalone: false, +}) +export class CustomConfiguratorSectionDocsComponent {} +\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\`, + "tutorials/customization/configurator-section/custom-configurator-section.module.ts": \\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\`// © 2022 SolarWinds Worldwide, LLC. All rights reserved. +// +// Permission is hereby granted, free of charge, to any person obtaining a copy +// of this software and associated documentation files (the "Software"), to +// deal in the Software without restriction, including without limitation the +// rights to use, copy, modify, merge, publish, distribute, sublicense, and/or +// sell copies of the Software, and to permit persons to whom the Software is +// furnished to do so, subject to the following conditions: +// +// The above copyright notice and this permission notice shall be included in +// all copies or substantial portions of the Software. +// +// THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR +// IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, +// FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE +// AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER +// LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, +// OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN +// THE SOFTWARE. + +import { CommonModule } from "@angular/common"; +import { HttpClientModule } from "@angular/common/http"; +import { NgModule } from "@angular/core"; +import { ReactiveFormsModule } from "@angular/forms"; +import { RouterModule } from "@angular/router"; + +import { + NuiButtonModule, + NuiDocsModule, + NuiFormFieldModule, + NuiIconModule, + NuiMessageModule, + NuiSwitchModule, + NuiTextboxModule, + DEMO_PATH_TOKEN, +} from "@nova-ui/bits"; +import { + NuiDashboardConfiguratorModule, + NuiDashboardsModule, +} from "@nova-ui/dashboards"; + +import { + CustomConfiguratorSectionExampleComponent, + CustomKpiDescriptionConfigurationComponent, +} from "./custom-configurator-section/custom-configurator-section.example.component"; +import { CustomConfiguratorSectionDocsComponent } from "./custom-configurator-section-docs.component"; +import { getDemoFiles } from "../../../../../demo-files-factory"; + +const routes = [ + { + path: "", + component: CustomConfiguratorSectionDocsComponent, + data: { + srlc: { + hideIndicator: true, + }, + showThemeSwitcher: true, + }, + }, + { + path: "example", + component: CustomConfiguratorSectionExampleComponent, + data: { + srlc: { + hideIndicator: true, + }, + }, + }, +]; + +@NgModule({ + imports: [ + CommonModule, + ReactiveFormsModule, + HttpClientModule, + NuiDashboardsModule, + NuiDashboardConfiguratorModule, + NuiDocsModule, + NuiFormFieldModule, + NuiIconModule, + NuiMessageModule, + NuiSwitchModule, + NuiTextboxModule, + NuiButtonModule, + RouterModule.forChild(routes), + ], + declarations: [ + CustomConfiguratorSectionDocsComponent, + CustomKpiDescriptionConfigurationComponent, + CustomConfiguratorSectionExampleComponent, + ], + providers: [ + { + provide: DEMO_PATH_TOKEN, + useValue: getDemoFiles("configurator-section"), + }, + ], +}) +export default class CustomConfiguratorSectionModule {} +\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\`, + "tutorials/customization/customization.module.ts": \\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\`// © 2022 SolarWinds Worldwide, LLC. All rights reserved. +// +// Permission is hereby granted, free of charge, to any person obtaining a copy +// of this software and associated documentation files (the "Software"), to +// deal in the Software without restriction, including without limitation the +// rights to use, copy, modify, merge, publish, distribute, sublicense, and/or +// sell copies of the Software, and to permit persons to whom the Software is +// furnished to do so, subject to the following conditions: +// +// The above copyright notice and this permission notice shall be included in +// all copies or substantial portions of the Software. +// +// THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR +// IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, +// FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE +// AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER +// LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, +// OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN +// THE SOFTWARE. + +import { NgModule, Type } from "@angular/core"; +import { RouterModule, Routes } from "@angular/router"; + +import { ConfiguratorHeadingService } from "@nova-ui/dashboards"; + +enum CustomizationModuleRoute { + ConfiguratorSection = "configurator-section", + Widget = "widget", + Formatter = "formatter", + DataSourceConfigurator = "data-source-configurator", +} + +const routes: Routes = [ + { + path: CustomizationModuleRoute.ConfiguratorSection, + loadChildren: async () => + import( + "./configurator-section/custom-configurator-section.module" + ) as object as Promise>, + }, + { + path: CustomizationModuleRoute.Widget, + loadChildren: async () => + import("./widget/custom-widget.module") as object as Promise< + Type + >, + }, + { + path: CustomizationModuleRoute.Formatter, + loadChildren: async () => + import("./formatter/custom-formatter.module") as object as Promise< + Type + >, + }, + { + path: CustomizationModuleRoute.DataSourceConfigurator, + loadChildren: async () => + import( + "./data-source-configurator/custom-data-source-configurator.module" + ) as object as Promise>, + }, +]; + +@NgModule({ + imports: [RouterModule.forChild(routes)], + providers: [ConfiguratorHeadingService], +}) +export default class CustomizationModule {} +\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\`, + "tutorials/customization/data-source-configurator/custom-data-source-configurator-docs.component.ts": \\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\`// © 2022 SolarWinds Worldwide, LLC. All rights reserved. +// +// Permission is hereby granted, free of charge, to any person obtaining a copy +// of this software and associated documentation files (the "Software"), to +// deal in the Software without restriction, including without limitation the +// rights to use, copy, modify, merge, publish, distribute, sublicense, and/or +// sell copies of the Software, and to permit persons to whom the Software is +// furnished to do so, subject to the following conditions: +// +// The above copyright notice and this permission notice shall be included in +// all copies or substantial portions of the Software. +// +// THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR +// IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, +// FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE +// AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER +// LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, +// OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN +// THE SOFTWARE. + +import { Component } from "@angular/core"; + +@Component({ + selector: "nui-custom-data-source-configurator-docs", + templateUrl: "./custom-data-source-configurator-docs.component.html", + standalone: false, +}) +export class CustomDataSourceConfiguratorDocComponent {} +\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\`, + "tutorials/customization/data-source-configurator/custom-data-source-configurator.module.ts": \\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\`// © 2022 SolarWinds Worldwide, LLC. All rights reserved. +// +// Permission is hereby granted, free of charge, to any person obtaining a copy +// of this software and associated documentation files (the "Software"), to +// deal in the Software without restriction, including without limitation the +// rights to use, copy, modify, merge, publish, distribute, sublicense, and/or +// sell copies of the Software, and to permit persons to whom the Software is +// furnished to do so, subject to the following conditions: +// +// The above copyright notice and this permission notice shall be included in +// all copies or substantial portions of the Software. +// +// THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR +// IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, +// FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE +// AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER +// LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, +// OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN +// THE SOFTWARE. + +import { NgModule } from "@angular/core"; +import { ReactiveFormsModule } from "@angular/forms"; +import { RouterModule, Routes } from "@angular/router"; + +// eslint-disable-next-line max-len +import { + NuiButtonModule, + NuiDocsModule, + NuiFormFieldModule, + NuiIconModule, + NuiMessageModule, + NuiSelectV2Module, + NuiSwitchModule, + NuiTextboxModule, + NuiValidationMessageModule, + DEMO_PATH_TOKEN, +} from "@nova-ui/bits"; +import { + NuiDashboardConfiguratorModule, + NuiDashboardsModule, +} from "@nova-ui/dashboards"; + +import { CustomDataSourceConfiguratorDocComponent } from "./custom-data-source-configurator-docs.component"; +import { + CustomDataSourceConfiguratorExampleComponent, + HarryPotterDataSourceConfiguratorComponent, +} from "./example/custom-data-source-configurator-example.component"; +import { getDemoFiles } from "../../../../../demo-files-factory"; + +const routes: Routes = [ + { + path: "", + component: CustomDataSourceConfiguratorDocComponent, + data: { + srlc: { + hideIndicator: true, + }, + showThemeSwitcher: true, + }, + }, +]; + +@NgModule({ + imports: [ + RouterModule.forChild(routes), + NuiDocsModule, + NuiButtonModule, + NuiMessageModule, + NuiDashboardConfiguratorModule, + NuiDashboardsModule, + NuiFormFieldModule, + NuiTextboxModule, + NuiSwitchModule, + NuiSelectV2Module, + NuiValidationMessageModule, + NuiIconModule, + ReactiveFormsModule, + ], + declarations: [ + CustomDataSourceConfiguratorDocComponent, + CustomDataSourceConfiguratorExampleComponent, + HarryPotterDataSourceConfiguratorComponent, + ], + providers: [ + { + provide: DEMO_PATH_TOKEN, + useValue: getDemoFiles("data-source-configurator"), + }, + ], +}) +export default class CustomDataSourceConfiguratorModuleRoute {} +\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\`, + "tutorials/customization/data-source-configurator/example/custom-data-source-configurator-example.component.ts": \\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\`// © 2022 SolarWinds Worldwide, LLC. All rights reserved. +// +// Permission is hereby granted, free of charge, to any person obtaining a copy +// of this software and associated documentation files (the "Software"), to +// deal in the Software without restriction, including without limitation the +// rights to use, copy, modify, merge, publish, distribute, sublicense, and/or +// sell copies of the Software, and to permit persons to whom the Software is +// furnished to do so, subject to the following conditions: +// +// The above copyright notice and this permission notice shall be included in +// all copies or substantial portions of the Software. +// +// THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR +// IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, +// FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE +// AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER +// LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, +// OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN +// THE SOFTWARE. + +import { HttpClient, HttpErrorResponse } from "@angular/common/http"; +import { + ChangeDetectorRef, + Component, + Inject, + Injectable, + Injector, + OnDestroy, + OnInit, +} from "@angular/core"; +import { FormBuilder, Validators } from "@angular/forms"; +import { GridsterConfig, GridsterItem } from "angular-gridster2"; +import { BehaviorSubject } from "rxjs"; +import { finalize } from "rxjs/operators"; + +import { + DataSourceService, + EventBus, + IEvent, + IFilteringOutputs, + LoggerService, +} from "@nova-ui/bits"; +import { + ComponentRegistryService, + ConfiguratorHeadingService, + DataSourceConfigurationV2Component, + DATA_SOURCE, + DEFAULT_PIZZAGNA_ROOT, + IConfigurable, + IDashboard, + IKpiData, + IProperties, + IProviderConfiguration, + IRefresherProperties, + IWidget, + IWidgets, + KpiComponent, + NOVA_KPI_DATASOURCE_ADAPTER, + PizzagnaLayer, + PIZZAGNA_EVENT_BUS, + ProviderRegistryService, + WellKnownPathKey, + WellKnownProviders, + WidgetTypesService, +} from "@nova-ui/dashboards"; + +/** + * This component will serve as the data source accordion in the configurator. + */ +@Component({ + selector: "harry-potter-data-source-configurator", + styleUrls: ["./custom-data-source-configurator-example.component.less"], + template: \\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\` + +
+ +
+ Data Source +
+ Harry Potter Books +
+
+
+
+ + + + {{ book.title }} + + + +
+
+ + + + {{ metric.label }} + + + +
+
+ \\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\`, + standalone: false, +}) +@Injectable() +export class HarryPotterDataSourceConfiguratorComponent + extends DataSourceConfigurationV2Component + implements OnInit +{ + // This lateLoadKey allows the component to be able to be registered by the componentRegistry + public static lateLoadKey = "HarryPotterDataSourceConfiguratorComponent"; + + // Array of books that will populate the book select + public books = [ + { + id: "5MQFrgEACAAJ", + title: $localize\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\`Harry Potter and the Sorcerer's Stone\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\`, + }, + { + id: "5iTebBW-w7QC", + title: $localize\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\`Harry Potter and the Chamber of Secrets\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\`, + }, + ]; + + // Array of metrics that will populate the metric select + public metrics = [ + { + id: "averageRating", + label: $localize\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\`Average Rating\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\`, + }, + { + id: "ratingsCount", + label: $localize\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\`Ratings Count\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\`, + }, + ]; + + // These need to be injected because DataSourceConfigurationV2Component uses them + constructor( + changeDetector: ChangeDetectorRef, + configuratorHeading: ConfiguratorHeadingService, + formBuilder: FormBuilder, + providerRegistryService: ProviderRegistryService, + @Inject(PIZZAGNA_EVENT_BUS) eventBus: EventBus, + injector: Injector, + logger: LoggerService + ) { + super( + changeDetector, + configuratorHeading, + formBuilder, + providerRegistryService, + eventBus, + injector, + logger + ); + } + + // Overriding 'ngOnInit' to add custom controls to the 'properties' form group + public ngOnInit(): void { + super.ngOnInit(); + + // Overriding the 'properties' control on the form to create a form group that accommodates our custom properties + this.form.setControl( + "properties", + this.formBuilder.group({ + bookId: [this.properties?.bookId ?? "", Validators.required], + metric: [this.properties?.metric ?? "", Validators.required], + }) + ); + // The default data source control has a required validator we're removing that validator here since we aren't using it. + this.form.setControl("dataSource", this.formBuilder.control(null)); + // Here we set the providerId to our only data source so when a new tile gets created it will default to it. + this.form.get("providerId")?.setValue(AcmeKpiDataSource.providerId); + // Here we subscribe to the form and if there are any changes we invoke the data source + this.form.valueChanges.subscribe((value) => { + if (!value.providerId) { + return; + } + this.invokeDataSource(value); + }); + } +} + +/** + * A simple KPI data source to retrieve the average rating of Harry Potter and the Sorcerer's Stone (book) via googleapis + */ +@Injectable() +export class AcmeKpiDataSource + extends DataSourceService + implements OnDestroy, IConfigurable +{ + // This is the ID we'll use to identify the provider + public static providerId = "AcmeKpiDataSource"; + + // Use this subject to communicate the data source's busy state + public busy = new BehaviorSubject(false); + + public properties: IProperties; + + constructor(private http: HttpClient) { + super(); + } + + // This function MUST be implemented in order to receive property updates from our configurator + public updateConfiguration(properties: IProperties): void { + // Saving the properties because we will need it for this data source. + this.properties = properties; + } + + // In this example, getFilteredData is invoked every 10 minutes (Take a look at the refresher + // provider definition in the widget configuration below to see how the interval is set) + public async getFilteredData(): Promise { + // For loading indicator to show + this.busy.next(true); + return new Promise((resolve) => { + // *** Make a resource request to an external API (if needed) + this.http + .get( + \\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\`https://www.googleapis.com/books/v1/volumes/\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\${this.properties?.bookId}\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\` + ) + // For loading indicator to be hidden + .pipe(finalize(() => this.busy.next(false))) + .subscribe({ + next: (data: any) => { + resolve({ + result: { + value: data.volumeInfo[this.properties?.metric], + }, + }); + }, + error: (error: HttpErrorResponse) => { + resolve({ + result: null, + error: { + type: error.status, + }, + }); + }, + }); + }); + } + + public ngOnDestroy(): void { + this.outputsSubject.complete(); + } +} + +/** + * A component that instantiates the dashboard + */ +@Component({ + selector: "custom-data-source-configurator-example", + templateUrl: "./custom-data-source-configurator-example.component.html", + styleUrls: ["./custom-data-source-configurator-example.component.less"], + standalone: false, +}) +export class CustomDataSourceConfiguratorExampleComponent implements OnInit { + // This variable will hold all the data needed to define the layout and behavior of the widgets. + // Pass this to the dashboard component's dashboard input in the template. + public dashboard: IDashboard; + + // Angular gridster requires a configuration object even if it's empty. + // Pass this to the dashboard component's gridsterConfig input in the template. + public gridsterConfig: GridsterConfig = {}; + + // Boolean which dashboard takes in as an input if its true it allows you to move widgets around. + public editMode: boolean = false; + + constructor( + // WidgetTypesService provides the widget's necessary structure information + private widgetTypesService: WidgetTypesService, + + // In general, the ProviderRegistryService is used for making entities available for injection into dynamically loaded components. + private providerRegistry: ProviderRegistryService, + + // Inject the ComponentRegistryService to make our custom component available for late loading by the dashboards framework + private componentRegistry: ComponentRegistryService + ) {} + + public ngOnInit(): void { + // Registering the new data source configurator so it can be used. + this.componentRegistry.registerByLateLoadKey( + HarryPotterDataSourceConfiguratorComponent + ); + // Registering the data source for injection into the KPI tile. + // Note: Each tile of a KPI widget is assigned its own instance of the data source + this.providerRegistry.setProviders({ + [AcmeKpiDataSource.providerId]: { + provide: DATA_SOURCE, + useClass: AcmeKpiDataSource, + // Any dependencies that need to be injected into the provider must be listed here + deps: [HttpClient], + }, + }); + + const kpiWidgetTemplate = this.widgetTypesService.getWidgetType( + "kpi", + 1 + ); + + this.widgetTypesService.setNode( + // This is the template we grabbed above with getWidgetType + kpiWidgetTemplate, + // We are setting the editor/configurator part of the widget template + "configurator", + // This is the path to go to the data source config component type. + WellKnownPathKey.DataSourceConfigComponentType, + // We are changing it to use the component we just created above instead of the default. + HarryPotterDataSourceConfiguratorComponent.lateLoadKey + ); + + // We're using a static configuration object for this example, but this is where + // the widget's configuration could potentially be populated from a database + const kpiWidget = widgetConfig; + const widgetIndex: IWidgets = { + // Complete the KPI widget with information coming from its type definition + [kpiWidget.id]: + this.widgetTypesService.mergeWithWidgetType(kpiWidget), + }; + + // Setting the widget dimensions and position (this is for gridster) + const positions: Record = { + [kpiWidget.id]: { + cols: 4, + rows: 6, + y: 0, + x: 0, + }, + }; + + // Finally, assigning the variables we created above to the dashboard + this.dashboard = { + positions, + widgets: widgetIndex, + }; + } +} + +const widgetConfig: IWidget = { + id: "widget1", + type: "kpi", + pizzagna: { + [PizzagnaLayer.Configuration]: { + [DEFAULT_PIZZAGNA_ROOT]: { + providers: { + [WellKnownProviders.Refresher]: { + properties: { + // Configuring the refresher interval so that our data source is invoked every ten minutes + interval: 60 * 10, + enabled: true, + } as IRefresherProperties, + } as Partial, + }, + }, + header: { + properties: { + title: "Harry Potter and the Sorcerer's Stone", + subtitle: "By J. K. Rowling", + }, + }, + tiles: { + properties: { + nodes: ["kpi1"], + }, + }, + kpi1: { + id: "kpi1", + componentType: KpiComponent.lateLoadKey, + properties: { + widgetData: { + units: "out of 5 Stars", + label: "Average Rating", + }, + }, + providers: { + [WellKnownProviders.DataSource]: { + // Setting the data source providerId for the tile with id "kpi1" + providerId: AcmeKpiDataSource.providerId, + properties: { + bookId: "5MQFrgEACAAJ", + metric: "averageRating", + }, + } as IProviderConfiguration, + [WellKnownProviders.Adapter]: { + providerId: NOVA_KPI_DATASOURCE_ADAPTER, + properties: { + componentId: "kpi1", + propertyPath: "widgetData", + }, + } as IProviderConfiguration, + }, + }, + }, + }, +}; +\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\`, + "tutorials/customization/formatter/custom-formatter.module.ts": \\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\`// © 2022 SolarWinds Worldwide, LLC. All rights reserved. +// +// Permission is hereby granted, free of charge, to any person obtaining a copy +// of this software and associated documentation files (the "Software"), to +// deal in the Software without restriction, including without limitation the +// rights to use, copy, modify, merge, publish, distribute, sublicense, and/or +// sell copies of the Software, and to permit persons to whom the Software is +// furnished to do so, subject to the following conditions: +// +// The above copyright notice and this permission notice shall be included in +// all copies or substantial portions of the Software. +// +// THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR +// IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, +// FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE +// AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER +// LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, +// OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN +// THE SOFTWARE. + +import { NgModule } from "@angular/core"; +import { ReactiveFormsModule } from "@angular/forms"; +import { RouterModule, Routes } from "@angular/router"; + +// eslint-disable-next-line max-len +import { + NuiButtonModule, + NuiDocsModule, + NuiFormFieldModule, + NuiIconModule, + NuiMessageModule, + NuiSelectV2Module, + NuiSwitchModule, + NuiTextboxModule, + NuiValidationMessageModule, + DEMO_PATH_TOKEN, +} from "@nova-ui/bits"; +import { NuiDashboardsModule } from "@nova-ui/dashboards"; + +import { CustomDonutContentFormatterDocComponent } from "./donut-content-formatter-example/custom-donut-content-formatter-docs.component"; +import { + CustomDonutContentFormatterComponent, + CustomDonutContentFormatterConfiguratorComponent, + CustomDonutContentFormatterExampleComponent, +} from "./donut-content-formatter-example/custom-donut-content-formatter-example.component"; +import { CustomFormatterDocComponent } from "./formatter-example/custom-formatter-docs.component"; +import { + CustomFormatterComponent, + CustomFormatterConfiguratorComponent, + CustomFormatterExampleComponent, +} from "./formatter-example/custom-formatter-example.component"; +import { getDemoFiles } from "../../../../../demo-files-factory"; + +const routes: Routes = [ + { + path: "table-formatter", + component: CustomFormatterDocComponent, + data: { + srlc: { + hideIndicator: true, + }, + showThemeSwitcher: true, + }, + }, + { + path: "donut-content-formatter", + component: CustomDonutContentFormatterDocComponent, + data: { + srlc: { + hideIndicator: true, + }, + showThemeSwitcher: true, + }, + }, +]; + +@NgModule({ + imports: [ + RouterModule.forChild(routes), + NuiDocsModule, + NuiButtonModule, + NuiMessageModule, + NuiDashboardsModule, + NuiFormFieldModule, + NuiTextboxModule, + NuiSwitchModule, + NuiSelectV2Module, + NuiValidationMessageModule, + NuiIconModule, + ReactiveFormsModule, + ], + declarations: [ + CustomDonutContentFormatterComponent, + CustomDonutContentFormatterExampleComponent, + CustomDonutContentFormatterConfiguratorComponent, + CustomDonutContentFormatterDocComponent, + CustomFormatterDocComponent, + CustomFormatterExampleComponent, + CustomFormatterConfiguratorComponent, + CustomFormatterComponent, + ], + providers: [ + { + provide: DEMO_PATH_TOKEN, + useValue: getDemoFiles("formatter"), + }, + ], +}) +export default class CustomFormatterModuleRoute {} +\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\`, + "tutorials/customization/formatter/donut-content-formatter-example/custom-donut-content-formatter-docs.component.ts": \\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\`// © 2022 SolarWinds Worldwide, LLC. All rights reserved. +// +// Permission is hereby granted, free of charge, to any person obtaining a copy +// of this software and associated documentation files (the "Software"), to +// deal in the Software without restriction, including without limitation the +// rights to use, copy, modify, merge, publish, distribute, sublicense, and/or +// sell copies of the Software, and to permit persons to whom the Software is +// furnished to do so, subject to the following conditions: +// +// The above copyright notice and this permission notice shall be included in +// all copies or substantial portions of the Software. +// +// THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR +// IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, +// FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE +// AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER +// LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, +// OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN +// THE SOFTWARE. + +import { Component } from "@angular/core"; + +@Component({ + selector: "nui-custom-donut-content-formatter-docs", + templateUrl: "./custom-donut-content-formatter-docs.component.html", + standalone: false, +}) +export class CustomDonutContentFormatterDocComponent {} +\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\`, + "tutorials/customization/formatter/donut-content-formatter-example/custom-donut-content-formatter-example.component.ts": \\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\`// © 2022 SolarWinds Worldwide, LLC. All rights reserved. +// +// Permission is hereby granted, free of charge, to any person obtaining a copy +// of this software and associated documentation files (the "Software"), to +// deal in the Software without restriction, including without limitation the +// rights to use, copy, modify, merge, publish, distribute, sublicense, and/or +// sell copies of the Software, and to permit persons to whom the Software is +// furnished to do so, subject to the following conditions: +// +// The above copyright notice and this permission notice shall be included in +// all copies or substantial portions of the Software. +// +// THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR +// IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, +// FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE +// AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER +// LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, +// OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN +// THE SOFTWARE. + +import { HttpClient } from "@angular/common/http"; +import { + ChangeDetectorRef, + Component, + Injectable, + Input, + OnChanges, + OnDestroy, + OnInit, + SimpleChanges, +} from "@angular/core"; +import { FormBuilder, FormGroup, Validators } from "@angular/forms"; +import { GridsterConfig, GridsterItem } from "angular-gridster2"; +import { Subject } from "rxjs"; +import { takeUntil, tap } from "rxjs/operators"; + +import { + DataSourceService, + IconService, + IDataSource, + IFilteringOutputs, + LoggerService, +} from "@nova-ui/bits"; +import { + ChartAssist, + IAccessors, + IChartAssistEvent, + IChartAssistSeries, +} from "@nova-ui/charts"; +import { + ComponentRegistryService, + ConfiguratorHeadingService, + DATA_SOURCE, + DonutChartFormatterConfiguratorComponent, + DonutContentPercentageConfigurationComponent, + DonutContentPercentageFormatterComponent, + DonutContentSumFormatterComponent, + IDashboard, + IFormatterDefinition, + IHasChangeDetector, + IProperties, + IProportionalWidgetChartOptions, + IProportionalWidgetConfig, + IProviderConfiguration, + IWidget, + IWidgets, + LegendPlacement, + PizzagnaLayer, + ProportionalWidgetChartTypes, + ProviderRegistryService, + WellKnownPathKey, + WellKnownProviders, + WidgetTypesService, +} from "@nova-ui/dashboards"; + +export enum Units { + Days = "Day(s)", + Weeks = "Week(s)", + Hours = "Hour(s)", +} + +@Component({ + selector: "custom-donut-content-formatter", + host: { class: "d-flex flex-column align-items-center" }, + template: \\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\` +
+ + {{ chartMetric || properties?.currentMetric || data[0].id }} +
+
+ {{ chartContent }} +
+
+ {{ units }} +
+
\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\`, + styleUrls: ["./custom-donut-content-formatter-example.component.less"], + standalone: false, +}) +export class CustomDonutContentFormatterComponent + implements IHasChangeDetector, OnInit, OnChanges +{ + public static lateLoadKey = "CustomDonutContentFormatterComponent"; + + // Used to emphasize the chart series when user interacts either with the chart legend, or chart segments. + public emphasizedSeriesData: IChartAssistSeries | undefined; + + // Current raw value of the metric to display + public currentMetricData: number; + + // Metric value rendered inside the template, when user selects a metric, and gets automatically recalculated depending on selected units + public chartContent: number; + + // Metric value rendered inside the template, when user interacts with either chart legend, or chart segments + public chartMetric: number; + + // Units which user can select from the configuration + public units: Units = Units.Days; + + private readonly destroy$ = new Subject(); + + constructor(public changeDetector: ChangeDetectorRef) {} + + // The data we receive from the chart, including metrics names and their values + @Input() data: IChartAssistSeries[]; + + // We use this chart assist instance to subscribe to the events triggered when an interaction with the chart occurs + @Input() chartAssist: ChartAssist; + + // These are the current properties from pizzagna. Used to use data set at the configuration layer + @Input() properties: IProperties; + + public ngOnChanges(changes: SimpleChanges): void { + if (changes.properties || !this.properties) { + // If current metric is not in the list of metrics any more we fall back to the very first one from the list we get from the datasource + this.currentMetricData = + this.data.find( + (item) => item.id === this.properties?.currentMetric + )?.data[0] || this.data[0].data[0]; + + // We either take the selected value, or fall back to the preselected default one + this.units = this.properties?.units || this.units; + } + + this.setContentValue(); + } + + public ngOnInit(): void { + // Here 'chartAssistSubject' is the entity that emits events every time user interacts with either chart legend, or chart segments. + // Subscribing to properly react on these kind of events + this.chartAssist.chartAssistSubject + .pipe( + tap( + (data: IChartAssistEvent) => + (this.emphasizedSeriesData = this.data.find( + (item) => item.id === data.payload.seriesId + )) + ), + tap(() => this.setContentValue()), + tap(() => this.setMetricValue()), + takeUntil(this.destroy$) + ) + .subscribe(); + } + + public getConvertedData(emphData: number): number { + // Recalculating data depending on the units user selected from the configuration view + switch (this.units) { + case Units.Weeks: + return this.emphasizedSeriesData + ? this.convertToWeeks(emphData) + : this.convertToWeeks(this.currentMetricData); + + case Units.Hours: + return this.emphasizedSeriesData + ? this.convertToHours(emphData) + : this.convertToHours(this.currentMetricData); + + default: + return this.emphasizedSeriesData + ? emphData + : this.currentMetricData; + } + } + + public setContentValue(): void { + this.chartContent = this.getConvertedData( + this.emphasizedSeriesData?.data[0] + ); + } + + public setMetricValue(): void { + this.chartMetric = this.emphasizedSeriesData + ? this.data.find( + (item) => + this.getConvertedData(item.data[0]) === + this.getConvertedData(this.emphasizedSeriesData?.data[0]) + )?.id + : // if metric was not initially selected we fall back to the very first one + this.properties?.currentMetric || this.data[0].id; + } + + private convertToWeeks(days: number | undefined): number { + return days ? Number((days / 7).toFixed(2)) : 0; + } + + private convertToHours(days: number | undefined): number { + return days ? Number((days * 24).toFixed(2)) : 0; + } +} + +@Component({ + selector: "custom-donut-content-formatter-configurator", + styleUrls: ["./custom-donut-content-formatter-example.component.less"], + template: \\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\` +
+
+ + + + {{ itemValue?.name }} + + + + This field is required + + +
+
+ + + + {{ itemValue }} + + + + This field is required + + +
+
+ \\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\`, + standalone: false, +}) +export class CustomDonutContentFormatterConfiguratorComponent + extends DonutChartFormatterConfiguratorComponent + implements OnChanges, OnInit, IHasChangeDetector +{ + public static lateLoadKey = "CustomFormatterConfiguratorComponent"; + + constructor( + changeDetector: ChangeDetectorRef, + formBuilder: FormBuilder, + logger: LoggerService, + public iconService: IconService, + public configuratorHeading: ConfiguratorHeadingService + ) { + super(changeDetector, formBuilder, logger); + } + + public availableUnits: Units[] = [Units.Days, Units.Hours, Units.Weeks]; + + protected addCustomFormControls(form: FormGroup): void { + form.addControl( + "units", + this.formBuilder.control(Units.Days, Validators.required) + ); + } +} + +/** + * A component that instantiates the dashboard + */ +@Component({ + selector: "custom-donut-content-formatter-example", + templateUrl: "./custom-donut-content-formatter-example.component.html", + styleUrls: ["./custom-donut-content-formatter-example.component.less"], + standalone: false, +}) +export class CustomDonutContentFormatterExampleComponent implements OnInit { + public editMode: boolean = false; + // This variable will hold all the data needed to define the layout and behavior of the widgets. + // Pass this to the dashboard component's dashboard input in the template. + public dashboard: IDashboard | undefined; + + // Angular gridster requires a configuration object even if it's empty. + // Pass this to the dashboard component's gridsterConfig input in the template. + public gridsterConfig: GridsterConfig = {}; + + constructor( + // WidgetTypesService provides the widget's necessary structure information + private widgetTypesService: WidgetTypesService, + // In general, the ProviderRegistryService is used for making entities available for injection into dynamically loaded components. + private providerRegistry: ProviderRegistryService, + // Inject the ComponentRegistryService to make our custom component available for late loading by the dashboards framework + private componentRegistry: ComponentRegistryService, + private changeDetectorRef: ChangeDetectorRef + ) { + // Register the custom configurator component with the component registry to make it available + // for late loading by the dashboard framework. + this.componentRegistry.registerByLateLoadKey( + CustomDonutContentFormatterConfiguratorComponent + ); + // Register the custom formatter component with the component registry to make it available + // for late loading by the dashboard framework. + this.componentRegistry.registerByLateLoadKey( + CustomDonutContentFormatterComponent + ); + + // Grab the widget's default template which will be needed as a parameter for setNode below. + const proportional = this.widgetTypesService.getWidgetType( + "proportional", + 1 + ); + + const donutFormatters: IFormatterDefinition[] = [ + { + componentType: DonutContentSumFormatterComponent.lateLoadKey, + label: $localize\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\`Sum\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\`, + } as IFormatterDefinition, + { + componentType: + DonutContentPercentageFormatterComponent.lateLoadKey, + label: $localize\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\`Percentage\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\`, + configurationComponent: + DonutContentPercentageConfigurationComponent.lateLoadKey, + } as IFormatterDefinition, + { + componentType: CustomDonutContentFormatterComponent.lateLoadKey, + label: $localize\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\`Custom\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\`, + // This is a custom configurator that will pop up below the formatter once it gets selected + configurationComponent: + CustomDonutContentFormatterConfiguratorComponent.lateLoadKey, + } as IFormatterDefinition, + ]; + + this.widgetTypesService.setNode( + // This is the template we grabbed above with getWidgetType + proportional, + // We are setting the editor/configurator part of the widget template + "configurator", + // This indicates which node you are changing and we want to change the formatters available for selection in the editor. + WellKnownPathKey.Formatters, + // We are setting the available formatters with the array we created above. + donutFormatters + ); + + // This sets the donut chart's datasource to have the StatusesExampleDatasource so the drop down is filled similar to the line above. + this.widgetTypesService.setNode( + proportional, + "configurator", + WellKnownPathKey.DataSourceProviders, + [StatusesExampleDatasource.providerId] + ); + + // Registering the data source for injection into the widget. + this.providerRegistry.setProviders({ + [StatusesExampleDatasource.providerId]: { + provide: DATA_SOURCE, + useClass: StatusesExampleDatasource, + // Any dependencies that need to be injected into the provider must be listed here + deps: [], + }, + }); + } + + public ngOnInit(): void { + this.initializeDashboard(); + } + + /** Used for restoring widgets state */ + public reInitializeDashboard(): void { + // destroys the components and their providers so the dashboard can re init data + this.dashboard = undefined; + this.changeDetectorRef.detectChanges(); + + this.initializeDashboard(); + } + + public initializeDashboard(): void { + // We're using a static configuration object for this example, but this is where + // the widget's configuration could potentially be populated from a database + const proportionalWidget = widgetConfig; + const widgetIndex: IWidgets = { + // Enhance the widget with information coming from it's type definition + [proportionalWidget.id]: + this.widgetTypesService.mergeWithWidgetType(proportionalWidget), + }; + + // Setting the widget dimensions and position (this is for gridster) + const positions: Record = { + [proportionalWidget.id]: { + cols: 12, + rows: 6, + y: 0, + x: 0, + }, + }; + + // Finally, assigning the variables we created above to the dashboard + this.dashboard = { + positions, + widgets: widgetIndex, + }; + } +} + +export interface IStatusesWidgetData { + id: string; + name: string; + data: number[]; +} + +export const randomStatusesWidgetData: IStatusesWidgetData[] = [ + { + id: "Down", + name: "Down", + data: [Math.round(Math.random() * 100)], + }, + { + id: "Critical", + name: "Critical", + data: [Math.round(Math.random() * 100)], + }, + { + id: "Warning", + name: "Warning", + data: [Math.round(Math.random() * 100)], + }, + { + id: "Unknown", + name: "Unknown", + data: [Math.round(Math.random() * 100)], + }, + { + id: "Up", + name: "Up", + data: [Math.round(Math.random() * 100)], + }, + { + id: "Unmanaged", + name: "Unmanaged", + data: [Math.round(Math.random() * 100)], + }, +]; + +@Injectable() +export class StatusesExampleDatasource + extends DataSourceService + implements IDataSource, OnDestroy +{ + public static providerId = "StatusesExampleDatasource"; + + public busy = new Subject(); + + constructor(private http: HttpClient) { + super(); + } + + public async getFilteredData(): Promise { + this.busy.next(true); + + return new Promise((resolve) => { + setTimeout(() => { + resolve({ + result: randomStatusesWidgetData, + }); + this.busy.next(false); + }, 1000); + }); + } + + public ngOnDestroy(): void { + this.outputsSubject.complete(); + } +} + +export const widgetConfig: IWidget = { + id: "proportionalWidgetId", + type: "proportional", + pizzagna: { + [PizzagnaLayer.Configuration]: { + header: { + properties: { + title: "Proportional Widget!", + subtitle: "Proportional widget with legend formatters", + }, + }, + chart: { + providers: { + [WellKnownProviders.DataSource]: { + providerId: StatusesExampleDatasource.providerId, + } as IProviderConfiguration, + }, + properties: { + configuration: { + interactive: true, + chartOptions: { + type: ProportionalWidgetChartTypes.DonutChart, + legendPlacement: LegendPlacement.Right, + contentFormatter: { + componentType: + CustomDonutContentFormatterComponent.lateLoadKey, + properties: { + // here you can set the default value for the metric you receive. If not set the first one from the list will be taken + currentMetric: "Down", + // here you set the default value for your custom controls. If not set the first one from the list will be taken + units: Units.Weeks, + }, + }, + } as IProportionalWidgetChartOptions, + chartColors: [ + "var(--nui-color-chart-eight)", + "var(--nui-color-chart-nine)", + "var(--nui-color-chart-ten)", + ], + } as IProportionalWidgetConfig, + }, + }, + }, + }, +}; +\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\`, + "tutorials/customization/formatter/formatter-example/custom-formatter-docs.component.ts": \\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\`// © 2022 SolarWinds Worldwide, LLC. All rights reserved. +// +// Permission is hereby granted, free of charge, to any person obtaining a copy +// of this software and associated documentation files (the "Software"), to +// deal in the Software without restriction, including without limitation the +// rights to use, copy, modify, merge, publish, distribute, sublicense, and/or +// sell copies of the Software, and to permit persons to whom the Software is +// furnished to do so, subject to the following conditions: +// +// The above copyright notice and this permission notice shall be included in +// all copies or substantial portions of the Software. +// +// THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR +// IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, +// FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE +// AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER +// LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, +// OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN +// THE SOFTWARE. + +import { Component } from "@angular/core"; + +@Component({ + selector: "nui-custom-formatter-docs", + templateUrl: "./custom-formatter-docs.component.html", + standalone: false, +}) +export class CustomFormatterDocComponent {} +\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\`, + "tutorials/customization/formatter/formatter-example/custom-formatter-example.component.ts": \\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\`// © 2022 SolarWinds Worldwide, LLC. All rights reserved. +// +// Permission is hereby granted, free of charge, to any person obtaining a copy +// of this software and associated documentation files (the "Software"), to +// deal in the Software without restriction, including without limitation the +// rights to use, copy, modify, merge, publish, distribute, sublicense, and/or +// sell copies of the Software, and to permit persons to whom the Software is +// furnished to do so, subject to the following conditions: +// +// The above copyright notice and this permission notice shall be included in +// all copies or substantial portions of the Software. +// +// THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR +// IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, +// FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE +// AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER +// LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, +// OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN +// THE SOFTWARE. + +import { ListRange } from "@angular/cdk/collections"; +import { ChangeDetectorRef, Component, Input, OnInit } from "@angular/core"; +import { FormBuilder, FormGroup, Validators } from "@angular/forms"; +import { GridsterConfig, GridsterItem } from "angular-gridster2"; +import isEqual from "lodash/isEqual"; +import orderBy from "lodash/orderBy"; +import { BehaviorSubject } from "rxjs"; + +import { + DataSourceService, + IconService, + IDataField, + INovaFilteringOutputs, + INovaFilters, + ISorterFilter, + LoggerService, +} from "@nova-ui/bits"; +import { + ComponentRegistryService, + ConfiguratorHeadingService, + DATA_SOURCE, + FormatterConfiguratorComponent, + IDashboard, + IDataSourceOutput, + IFormatterDefinition, + IHasChangeDetector, + ITableWidgetColumnConfig, + ITableWidgetSorterConfig, + IWidget, + IWidgets, + PizzagnaLayer, + ProviderRegistryService, + RawFormatterComponent, + TableFormatterRegistryService, + WellKnownPathKey, + WellKnownProviders, + WidgetTypesService, +} from "@nova-ui/dashboards"; + +export const BREW_API_URL = "https://api.punkapi.com/v2/beers"; + +@Component({ + selector: "custom-formatter", + host: { class: "d-flex" }, + template: \\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\` +
+
+ +
+
+ {{ data.value }} +
+
+ \\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\`, + styleUrls: ["./custom-formatter-example.component.less"], + standalone: false, +}) +export class CustomFormatterComponent implements IHasChangeDetector { + public static lateLoadKey = "CustomFormatterComponent"; + + constructor(public changeDetector: ChangeDetectorRef) {} + + @Input() public data: any; + @Input() public icon: string; + @Input() public threshold: string; + + public isAboveThreshold(): boolean { + return parseFloat(this.threshold) <= this.data.value; + } +} + +@Component({ + selector: "custom-formatter-configurator", + styleUrls: ["./custom-formatter-example.component.less"], + template: \\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\` +
+
+ + + + {{ item.label }} + + + + This field is required + + +
+
+ + + + + + + + This field is required + + +
+
+ + + + + This field is required + + +
+
+ +
+
+ +
+ + +
+ + + Select Item + +
+ \\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\`, + standalone: false, +}) +export class CustomFormatterConfiguratorComponent + extends FormatterConfiguratorComponent + implements OnInit, IHasChangeDetector +{ + public static lateLoadKey = "CustomFormatterConfiguratorComponent"; + + constructor( + changeDetector: ChangeDetectorRef, + configuratorHeading: ConfiguratorHeadingService, + formBuilder: FormBuilder, + logger: LoggerService, + public iconService: IconService + ) { + super(changeDetector, configuratorHeading, formBuilder, logger); + } + + public formatterFormGroup: FormGroup; + // This array is where the icon names will be stored + public options: string[] = []; + + public ngOnInit(): void { + for (const icon of this.iconService.icons) { + if (icon.category === "severity") { + this.options.push(icon.name); + } + } + } + + protected addCustomFormControls(form: FormGroup): void { + form.addControl( + "icon", + this.formBuilder.control("", Validators.required) + ); + form.addControl( + "threshold", + this.formBuilder.control(null, Validators.required) + ); + } +} + +/** + * A component that instantiates the dashboard + */ +@Component({ + selector: "custom-formatter-example", + templateUrl: "./custom-formatter-example.component.html", + styleUrls: ["./custom-formatter-example.component.less"], + standalone: false, +}) +export class CustomFormatterExampleComponent implements OnInit { + public editMode: boolean = false; + // This variable will hold all the data needed to define the layout and behavior of the widgets. + // Pass this to the dashboard component's dashboard input in the template. + public dashboard: IDashboard | undefined; + + // Angular gridster requires a configuration object even if it's empty. + // Pass this to the dashboard component's gridsterConfig input in the template. + public gridsterConfig: GridsterConfig = {}; + + constructor( + // WidgetTypesService provides the widget's necessary structure information + private widgetTypesService: WidgetTypesService, + // In general, the ProviderRegistryService is used for making entities available for injection into dynamically loaded components. + private providerRegistry: ProviderRegistryService, + // Inject the ComponentRegistryService to make our custom component available for late loading by the dashboards framework + private componentRegistry: ComponentRegistryService, + private tableFormatterRegistryService: TableFormatterRegistryService, + private changeDetectorRef: ChangeDetectorRef + ) { + // Register the custom configurator component with the component registry to make it available + // for late loading by the dashboard framework. + this.componentRegistry.registerByLateLoadKey( + CustomFormatterConfiguratorComponent + ); + // Register the custom formatter component with the component registry to make it available + // for late loading by the dashboard framework. + this.componentRegistry.registerByLateLoadKey(CustomFormatterComponent); + + // Grab the widget's default template which will be needed as a parameter for setNode below. + const table = this.widgetTypesService.getWidgetType("table", 1); + + const tableFormatters: IFormatterDefinition[] = [ + { + // This will be the component that will format the data + componentType: RawFormatterComponent.lateLoadKey, + // This is the label for what the formatter is selected in the drop down + label: $localize\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\`:table formatter|:No formatter\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\`, + // This says what datatype the formatter supports. If the value node is null, it accepts any data type. + dataTypes: { + // @ts-ignore: Ignoring compiler error to keep the same flow + value: null, + }, + }, + { + componentType: CustomFormatterComponent.lateLoadKey, + label: $localize\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\`:table formatter|:Custom formatter\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\`, + // This is a custom configurator that will pop up below the formatter once it gets selected + configurationComponent: + CustomFormatterConfiguratorComponent.lateLoadKey, + // This says what data types the formatter supports. + // In this case, it supports abv values only. + // If you look below in the table data source you'll see where we define our column's data types. + dataTypes: { + value: ["abv"], + }, + }, + ]; + + // Registering the formatters + this.tableFormatterRegistryService.addItems(tableFormatters); + + // This sets the table's datasource to have the BeerDataSource so the drop down is filled similar to the line above. + this.widgetTypesService.setNode( + table, + "configurator", + WellKnownPathKey.DataSourceProviders, + [BeerDataSource.providerId] + ); + + // Registering the data source for injection into the widget. + this.providerRegistry.setProviders({ + [BeerDataSource.providerId]: { + provide: DATA_SOURCE, + useClass: BeerDataSource, + // Any dependencies that need to be injected into the provider must be listed here + deps: [], + }, + }); + } + + public ngOnInit(): void { + this.initializeDashboard(); + } + + /** Used for restoring widgets state */ + public reInitializeDashboard(): void { + // destroys the components and their providers so the dashboard can re init data + this.dashboard = undefined; + this.changeDetectorRef.detectChanges(); + + this.initializeDashboard(); + } + + public initializeDashboard(): void { + // We're using a static configuration object for this example, but this is where + // the widget's configuration could potentially be populated from a database + const tableWidget = widgetConfig; + const widgetIndex: IWidgets = { + // Enhance the widget with information coming from it's type definition + [tableWidget.id]: + this.widgetTypesService.mergeWithWidgetType(tableWidget), + }; + + // Setting the widget dimensions and position (this is for gridster) + const positions: Record = { + [tableWidget.id]: { + cols: 12, + rows: 6, + y: 0, + x: 0, + }, + }; + + // Finally, assigning the variables we created above to the dashboard + this.dashboard = { + positions, + widgets: widgetIndex, + }; + } +} + +export interface IBrewInfo { + id: number; + name: string; + tagline: string; + first_brewed: string; + description: string; + brewers_tips: string; + abv: number; +} + +export interface IBrewDatasourceResponse { + brewInfo: IBrewInfo[]; + total: number; +} + +export class BeerDataSource extends DataSourceService { + public static providerId = "BeerDataSource"; + + private cache = Array.from({ length: 0 }); + private lastSortValue?: ISorterFilter; + private lastVirtualScroll?: ListRange; + // For simplicity, the totalItems value is hard-coded here, but in a real-world scenario the value would likely be retrieved via an async backend call + private totalItems: number = 325; + + public page: number = 1; + public busy = new BehaviorSubject(false); + + public dataFields: Array = [ + { id: "id", label: "No", dataType: "number" }, + { id: "name", label: "Name", dataType: "string" }, + { id: "tagline", label: "Tagline", dataType: "string" }, + { id: "first_brewed", label: "First Brewed", dataType: "string" }, + { id: "description", label: "Description", dataType: "string" }, + { id: "brewers_tips", label: "Brewer's Tips", dataType: "string" }, + // We are giving this field a custom data type of 'abv' so the dropdown in the custom formatter configurator can use it to filter out other data types + { id: "abv", label: "Alcohol By Volume", dataType: "abv" }, + ]; + + constructor(private logger: LoggerService) { + super(); + } + + public async getFilteredData( + filters: INovaFilters + ): Promise> { + const start = filters.virtualScroll?.value?.start ?? 0; + const end = filters.virtualScroll?.value?.end ?? 0; + const delta = end - start; + + // Note: We should start with a clean cache every time first page is requested + if (start === 0) { + this.cache = []; + } + + // This condition handles sorting. We want to sort columns without fetching another chunk of data. + // Since the data is being fetched when scrolled, we compare virtual scroll indexes here in the condition as well. + if (filters.sorter?.value) { + if ( + !isEqual(this.lastSortValue, filters.sorter.value) && + filters.virtualScroll?.value.start === 0 && + !!this.lastVirtualScroll + ) { + const totalPages = Math.ceil( + delta ? this.totalItems / delta : 1 + ); + const itemsPerPage: number = Math.max( + delta < 80 ? delta : 80, + 1 + ); + let response: Array | null = null; + let map: IBrewDatasourceResponse; + + if (filters.sorter?.value?.direction === "desc") { + this.cache = []; + for (let i = 0; i < this.page; ++i) { + response = await ( + await fetch( + \\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\`\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\${BREW_API_URL}/?page=\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\${ + totalPages - i || 1 + }&per_page=\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\${itemsPerPage}\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\` + ) + ).json(); + + // since the last page contains only 5 items we need to fetch another page to give virtual scroll enough space to work + if (response && response.length < itemsPerPage) { + this.page++; + } + map = { + brewInfo: response?.map((result: IBrewInfo) => ({ + id: result.id, + name: result.name, + tagline: result.tagline, + first_brewed: result.first_brewed, + description: result.description, + brewers_tips: result.brewers_tips, + })), + total: response?.length, + } as IBrewDatasourceResponse; + this.cache = + totalPages - i !== 0 + ? this.cache.concat(map.brewInfo) + : this.cache; + } + } + + if (filters.sorter?.value?.direction === "asc") { + this.cache = []; + for (let i = 0; i < this.page; i++) { + response = await ( + await fetch( + \\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\`\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\${BREW_API_URL}/?page=\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\${ + i + 1 + }&per_page=\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\${itemsPerPage}\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\` + ) + ).json(); + map = { + brewInfo: response?.map((result: IBrewInfo) => ({ + id: result.id, + name: result.name, + tagline: result.tagline, + first_brewed: result.first_brewed, + description: result.description, + brewers_tips: result.brewers_tips, + })), + total: response?.length, + } as IBrewDatasourceResponse; + this.cache = this.cache.concat(map.brewInfo); + } + } + + this.lastSortValue = filters.sorter?.value; + this.lastVirtualScroll = filters.virtualScroll?.value; + + return { + result: { + repeat: { + itemsSource: this.sortData(this.cache, filters), + }, + paginator: { total: this.totalItems }, + dataFields: this.dataFields, + }, + }; + } + } + + this.busy.next(true); + return new Promise((resolve) => { + setTimeout(() => { + this.getData(start, end, filters).then( + (response: INovaFilteringOutputs) => { + if (!response) { + return; + } + + this.cache = this.cache.concat(response.brewInfo); + + this.dataSubject.next(this.cache); + resolve({ + result: { + repeat: { + itemsSource: this.sortData( + this.cache, + filters + ), + }, + paginator: { total: this.totalItems }, + dataFields: this.dataFields, + }, + }); + + this.lastSortValue = filters.sorter?.value; + this.lastVirtualScroll = filters.virtualScroll?.value; + this.busy.next(false); + } + ); + }, 500); + }); + } + + public async getData( + start: number = 0, + end: number = 20, + filters: INovaFilters + ): Promise { + const delta = end - start; + const totalPages = Math.ceil(delta ? this.totalItems / delta : 1); + let response: Array | null = null; + // The api.punk.com is able to return only 80 items per page + const itemsPerPage: number = Math.max(delta < 80 ? delta : 80, 1); + + if (filters.sorter?.value?.direction === "asc") { + response = await ( + await fetch( + \\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\`\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\${BREW_API_URL}/?page=\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\${this.page}&per_page=\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\${itemsPerPage}\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\` + ) + ).json(); + } + + if (filters.sorter?.value?.direction === "desc") { + response = await ( + await fetch( + \\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\`\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\${BREW_API_URL}/?page=\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\${ + totalPages - this.page + }&per_page=\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\${itemsPerPage}\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\` + ) + ).json(); + } + + if (!filters.sorter) { + response = await ( + await fetch( + \\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\`\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\${BREW_API_URL}/?page=\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\${this.page}&per_page=\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\${itemsPerPage}\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\` + ) + ).json(); + } + return { + brewInfo: response?.map((result: IBrewInfo, i: number) => ({ + id: result.id, + abv: result.abv, + name: result.name, + tagline: result.tagline, + first_brewed: result.first_brewed, + description: result.description, + brewers_tips: result.brewers_tips, + })), + total: response?.length, + } as IBrewDatasourceResponse; + } + + private sortData(data: IBrewInfo[], filters: INovaFilters) { + return orderBy( + data, + filters.sorter?.value?.sortBy, + filters.sorter?.value?.direction as "desc" | "asc" + ); + } +} + +export const widgetConfig: IWidget = { + id: "tableWidgetId", + type: "table", + pizzagna: { + [PizzagnaLayer.Configuration]: { + header: { + properties: { + title: "Stupendous Suds", + subtitle: "Try These Brilliant Brews", + }, + }, + table: { + providers: { + [WellKnownProviders.DataSource]: { + providerId: BeerDataSource.providerId, + }, + }, + properties: { + configuration: { + columns: [ + { + id: "column1", + label: "Beer Name", + isActive: true, + width: 185, + formatter: { + componentType: + RawFormatterComponent.lateLoadKey, + properties: { + dataFieldIds: { + value: "name", + }, + }, + }, + }, + { + id: "column2", + label: "Tagline", + isActive: true, + width: 250, + formatter: { + componentType: + RawFormatterComponent.lateLoadKey, + properties: { + dataFieldIds: { + value: "tagline", + }, + }, + }, + }, + { + id: "column3", + label: "Alcohol By Volume", + isActive: true, + width: 150, + formatter: { + componentType: + CustomFormatterComponent.lateLoadKey, + properties: { + dataFieldIds: { + value: "abv", + }, + icon: "severity_error", + threshold: "5", + }, + }, + }, + { + id: "column4", + label: "Description", + isActive: true, + formatter: { + componentType: + RawFormatterComponent.lateLoadKey, + properties: { + dataFieldIds: { + value: "description", + }, + }, + }, + }, + ] as ITableWidgetColumnConfig[], + sorterConfiguration: { + descendantSorting: false, + sortBy: "", + } as ITableWidgetSorterConfig, + hasVirtualScroll: true, + }, + }, + }, + }, + }, +}; +\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\`, + "tutorials/customization/widget/custom-widget-docs.component.ts": \\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\`// © 2022 SolarWinds Worldwide, LLC. All rights reserved. +// +// Permission is hereby granted, free of charge, to any person obtaining a copy +// of this software and associated documentation files (the "Software"), to +// deal in the Software without restriction, including without limitation the +// rights to use, copy, modify, merge, publish, distribute, sublicense, and/or +// sell copies of the Software, and to permit persons to whom the Software is +// furnished to do so, subject to the following conditions: +// +// The above copyright notice and this permission notice shall be included in +// all copies or substantial portions of the Software. +// +// THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR +// IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, +// FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE +// AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER +// LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, +// OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN +// THE SOFTWARE. + +import { Component } from "@angular/core"; + +@Component({ + selector: "custom-widget-docs", + templateUrl: "./custom-widget-docs.component.html", + standalone: false, +}) +export class CustomWidgetDocsComponent {} +\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\`, + "tutorials/customization/widget/custom-widget.component.ts": \\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\`// © 2022 SolarWinds Worldwide, LLC. All rights reserved. +// +// Permission is hereby granted, free of charge, to any person obtaining a copy +// of this software and associated documentation files (the "Software"), to +// deal in the Software without restriction, including without limitation the +// rights to use, copy, modify, merge, publish, distribute, sublicense, and/or +// sell copies of the Software, and to permit persons to whom the Software is +// furnished to do so, subject to the following conditions: +// +// The above copyright notice and this permission notice shall be included in +// all copies or substantial portions of the Software. +// +// THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR +// IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, +// FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE +// AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER +// LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, +// OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN +// THE SOFTWARE. + +import { + ChangeDetectionStrategy, + ChangeDetectorRef, + Component, + EventEmitter, + HostBinding, + Input, + OnChanges, + OnInit, + Output, + SimpleChanges, +} from "@angular/core"; +import { FormBuilder, FormGroup, Validators } from "@angular/forms"; +import { GridsterConfig, GridsterItem } from "angular-gridster2"; + +import { IMenuItem } from "@nova-ui/bits"; +import { + ComponentRegistryService, + ConfiguratorHeadingService, + DEFAULT_PIZZAGNA_ROOT, + EVENT_PROXY, + FormStackComponent, + IConverterFormPartsProperties, + IDashboard, + IHasChangeDetector, + IHasForm, + IProviderConfiguration, + IWidget, + IWidgets, + IWidgetTypeDefinition, + NOVA_GENERIC_CONVERTER, + NOVA_TITLE_AND_DESCRIPTION_CONVERTER, + PizzagnaLayer, + refresher, + StackComponent, + TitleAndDescriptionConfigurationComponent, + WellKnownPathKey, + WellKnownProviders, + widgetBodyContentNodes, + WidgetConfiguratorSectionComponent, + WidgetTypesService, + WIDGET_BODY, + WIDGET_HEADER, + WIDGET_LOADING, +} from "@nova-ui/dashboards"; + +// The custom widget type name we'll use +const CUSTOM_WIDGET_TYPENAME = "example-custom-widget"; +// The path key we'll use for image selection in the configurator definition +const IMAGE_SELECTION_CONFIGURATOR_PATH_KEY = "imageSelection"; + +@Component({ + selector: "custom-widget-body", + // A simple template for our custom widget + template: \\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\`\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\`, + styleUrls: ["./custom-widget.component.less"], + changeDetection: ChangeDetectionStrategy.OnPush, + standalone: false, +}) +// Remember to declare this class in the parent module +export class CustomWidgetBodyContentComponent implements IHasChangeDetector { + // Ensure that the lateLoadKey value matches class name + public static lateLoadKey = "CustomWidgetBodyContentComponent"; + + // Optionally, providing an input for styling of the host element + @Input() @HostBinding("class") public elementClass = ""; + + // We'll map this input with the configurator form using the NOVA_GENERIC_CONVERTER. + // See the customWidget definition at the bottom of the file. + @Input() public imageSource: string; + + // Injecting the ChangeDetectorRef to implement IHasChangeDetector. + // This allows the dashboard framework to reliably propagate component property changes to the DOM. + constructor(public changeDetector: ChangeDetectorRef) {} +} + +/** + * A custom configurator section component for selecting the image source for the custom widget + */ +@Component({ + selector: "custom-configurator-section", + template: \\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\` + + + + +
+ + +
+ Image Selection +
+ {{ imageDisplayValue }} +
+
+
+
+ + + + + {{ item.title }} + + + +
+
+ \\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\`, + styleUrls: ["./custom-widget.component.less"], + changeDetection: ChangeDetectionStrategy.OnPush, + standalone: false, +}) +// Remember to declare this class in the parent module +export class CustomConfiguratorSectionComponent + implements OnInit, OnChanges, IHasChangeDetector, IHasForm +{ + // Ensure that the lateLoadKey value matches the class name + public static lateLoadKey = "CustomConfiguratorSectionComponent"; + + /** + * This input serves as the itemsSource a user can select an image from. + */ + @Input() imageItems: IMenuItem[] = []; + /** + * This property holds the currently selected image source string. + */ + @Input() imageSource: string; + + /** + * An output for emitting formReady to allow the immediate parent formGroup component to register us as a form control + * in the larger form. In this case, the immediate parent would be the WidgetConfiguratorSectionComponent as specified + * in the customWidget configurator definition at the bottom of this file. + */ + @Output() formReady = new EventEmitter(); + + public form: FormGroup; + public imageDisplayValue: string; + + constructor( + public changeDetector: ChangeDetectorRef, + private formBuilder: FormBuilder, + public configuratorHeading: ConfiguratorHeadingService + ) {} + + public ngOnInit(): void { + // Initializing the form + this.form = this.formBuilder.group({ + // Note: When using the NOVA_GENERIC_CONVERTER, the form control name, in this case 'imageSource', must match the input name on + // this component as well as that of the corresponding property on the custom widget body component. + imageSource: [{}, [Validators.required]], + }); + + // Emitting the formReady as described above. + this.formReady.emit(this.form); + } + + public ngOnChanges(changes: SimpleChanges): void { + if (changes.imageSource && !changes.imageSource.isFirstChange()) { + const previousValue: string = changes.imageSource.previousValue; + if (previousValue !== this.imageSource) { + // Setting the display value according to the current imageSource value + this.imageDisplayValue = this.imageItems.find( + (item: IMenuItem) => item.url === this.imageSource + )?.title; + + // Updating the form when the imageSource input gets updated + this.form.get("imageSource")?.setValue(this.imageSource); + } + } + } + + public onChanged(newValue: string): void { + // Keeping the display value updated as the user changes the dropdown selection + this.imageDisplayValue = this.imageItems.find( + (item: IMenuItem) => item.url === newValue + )?.title; + } +} + +/** + * The component that instantiates the dashboard + */ +@Component({ + selector: "custom-widget", + templateUrl: "./custom-widget.component.html", + styleUrls: ["./custom-widget.component.less"], + standalone: false, +}) +export class CustomWidgetComponent implements OnInit { + // This variable will hold all the data needed to define the layout and behavior of the widgets. + // Pass this to the dashboard component's dashboard input in the template. + public dashboard: IDashboard | undefined; + + // Angular gridster requires a configuration object even if it's empty. + // Pass this to the dashboard component's gridsterConfig input in the template. + public gridsterConfig: GridsterConfig = {}; + + // Boolean which dashboard takes in as an input if its true it allows you to move widgets around. + public editMode: boolean = false; + + constructor( + // WidgetTypesService provides the widget's necessary structure information + private widgetTypesService: WidgetTypesService, + + // Inject the ComponentRegistryService to make our custom component available for late loading by the dashboards framework + private componentRegistry: ComponentRegistryService, + private changeDetectorRef: ChangeDetectorRef + ) {} + + public ngOnInit(): void { + // Register the custom widget type and custom components + // Note: This could also be done in the parent module's constructor so that + // multiple dashboards could have access to the same registrations. + this.prepareNovaDashboards(); + + // Register some image items as dropdown options in the widget editor/configurator + // Note: This could also be done in the parent module's constructor so that + // multiple dashboards could have access to the same dropdown options. + this.registerImageOptions(); + + // Initialize our current instance of a dashboard with an instance of our custom widget + this.initializeDashboard(); + } + + /** Used for restoring widgets state */ + public reInitializeDashboard(): void { + // destroys the components and their providers so the dashboard can re init data + this.dashboard = undefined; + this.changeDetectorRef.detectChanges(); + + this.initializeDashboard(); + } + + public initializeDashboard(): void { + // We're using a static configuration object for this example (see widgetConfig at the bottom of the file), + // but this is where the widget's configuration could potentially be populated from a database + const widget = widgetConfig; + + // Create an index of widgets complete with structure and configuration to assign to the dashboard + const widgets: IWidgets = { + // Complete the custom widget with structure information coming from its type definition + [widget.id]: this.widgetTypesService.mergeWithWidgetType(widget), + }; + + // Setting the widget dimensions and position (this is for gridster) + const positions: Record = { + [widget.id]: { + cols: 4, + rows: 11, + y: 0, + x: 0, + }, + }; + + // Finally, assigning the variables we created above to the dashboard + this.dashboard = { positions, widgets }; + } + + private prepareNovaDashboards() { + // Register the custom widget type + this.widgetTypesService.registerWidgetType( + CUSTOM_WIDGET_TYPENAME, + 1, + customWidget + ); + + // Register the custom widget body component with the component registry to make it available + // for late loading by the dashboard framework. + this.componentRegistry.registerByLateLoadKey( + CustomWidgetBodyContentComponent + ); + + // Register the custom configurator section with the component registry to make it available + // for late loading by the dashboard framework. + this.componentRegistry.registerByLateLoadKey( + CustomConfiguratorSectionComponent + ); + } + + private registerImageOptions() { + // Grab the widget's default template which will be needed as a parameter for setNode below. + const widgetTemplate = this.widgetTypesService.getWidgetType( + CUSTOM_WIDGET_TYPENAME, + 1 + ); + + // Register some image items as dropdown options in the widget editor/configurator + this.widgetTypesService.setNode( + // This is the template we grabbed above with getWidgetType + widgetTemplate, + // We are setting the editor/configurator part of the widget template + "configurator", + // This indicates which node you are changing and we want to change the image items available for selection in the editor. + // For reference, see the 'paths' property of the custom widget's IWidgetTypeDefinition at the bottom of this file. + IMAGE_SELECTION_CONFIGURATOR_PATH_KEY, + // We are setting the image items available for selection in the editor. 'imageItems' is defined + // at the bottom of this file. + imageItems + ); + } +} + +/*************************************************************************************************** + * This is the type definition of our custom widget + ***************************************************************************************************/ +const customWidget: IWidgetTypeDefinition = { + /*************************************************************************************************** + * Paths to important settings in this type definition + ***************************************************************************************************/ + paths: { + widget: { + [WellKnownPathKey.Root]: DEFAULT_PIZZAGNA_ROOT, + }, + configurator: { + [WellKnownPathKey.Root]: DEFAULT_PIZZAGNA_ROOT, + // for the custom configuration component, this is the path for the list of image items available for selection + [IMAGE_SELECTION_CONFIGURATOR_PATH_KEY]: + "imageSelection.properties.imageItems", + }, + }, + /*************************************************************************************************** + * Widget section describes the structural part of the custom widget + ***************************************************************************************************/ + widget: { + [PizzagnaLayer.Structure]: { + [DEFAULT_PIZZAGNA_ROOT]: { + id: DEFAULT_PIZZAGNA_ROOT, + // base layout of the widget - all components referenced herein will be stacked in a column + componentType: StackComponent.lateLoadKey, + providers: { + // When enabled, this provider emits the REFRESH event on the pizzagna event bus every X seconds + [WellKnownProviders.Refresher]: refresher(), + // event proxy manages the transmission of events between widget and dashboard such as the WIDGET_EDIT and WIDGET_REMOVE events + [WellKnownProviders.EventProxy]: EVENT_PROXY, + }, + properties: { + // these values reference child components in the widget structure defined below + nodes: ["header", "loading", "body"], + }, + }, + // standard widget header + header: WIDGET_HEADER, + // this is the loading bar below the header + loading: WIDGET_LOADING, + // the body node + body: WIDGET_BODY, + + // retrieving the definitions for the body content nodes. the argument corresponds to the main content node key + ...widgetBodyContentNodes("mainContent"), + + // the component that supplies the content of our custom widget + mainContent: { + id: "mainContent", + componentType: CustomWidgetBodyContentComponent.lateLoadKey, + properties: { + elementClass: "d-flex w-100 justify-content-center", + }, + }, + }, + [PizzagnaLayer.Configuration]: { + [DEFAULT_PIZZAGNA_ROOT]: { + id: DEFAULT_PIZZAGNA_ROOT, + providers: { + // default refresher configuration + [WellKnownProviders.Refresher]: refresher(false, 60), + }, + }, + // default header configuration + header: { + properties: { + title: $localize\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\`Empty Custom Widget\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\`, + }, + }, + }, + }, + /*************************************************************************************************** + * Configurator section describes the form that's used to configure the widget + ***************************************************************************************************/ + configurator: { + [PizzagnaLayer.Structure]: { + [DEFAULT_PIZZAGNA_ROOT]: { + id: DEFAULT_PIZZAGNA_ROOT, + // base layout of the configurator - all form components referenced herein will be stacked in a column + componentType: FormStackComponent.lateLoadKey, + properties: { + elementClass: + "flex-grow-1 overflow-auto nui-scroll-shadows", + // these values reference child components laid out in this form (defined below) + nodes: ["presentation", "customConfig"], + }, + }, + // /presentation + presentation: { + id: "presentation", + componentType: WidgetConfiguratorSectionComponent.lateLoadKey, + properties: { + headerText: $localize\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\`Presentation\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\`, + nodes: ["titleAndDescription"], + }, + }, + // /presentation/titleAndDescription + titleAndDescription: { + id: "titleAndDescription", + componentType: + TitleAndDescriptionConfigurationComponent.lateLoadKey, + providers: { + converter: { + providerId: NOVA_TITLE_AND_DESCRIPTION_CONVERTER, + } as IProviderConfiguration, + }, + }, + // /customConfig + customConfig: { + id: "customConfig", + componentType: WidgetConfiguratorSectionComponent.lateLoadKey, + properties: { + headerText: $localize\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\`Custom Widget Configuration\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\`, + nodes: ["imageSelection"], + }, + }, + // /customConfig/imageSelection + imageSelection: { + id: "imageSelection", + // Here's where we set the configurator to use our custom configurator section + componentType: CustomConfiguratorSectionComponent.lateLoadKey, + properties: { + // This corresponds to the 'imageItems' input on the custom configurator section component + // which defines the list of image items to pick from. The empty value shown here is overridden + // in the 'registerImageOptions' method above. + imageItems: [] as IMenuItem[], + }, + providers: { + // Using the generic converter to map the selected image source between the widget and the form + [WellKnownProviders.Converter]: { + providerId: NOVA_GENERIC_CONVERTER, + properties: { + formParts: [ + { + // Setting up the generic converter to update the 'imageSource' property of the custom widget 'mainContent' component + previewPath: "mainContent.properties", + // Note: To use the NOVA_GENERIC_CONVERTER, the linked properties must have the same name between the configurator + // section component and the widget 'mainContent' component. Additionally, the property name must match the formControl + // name used in the configurator section component. In this case, the common name among all three is 'imageSource'. + keys: ["imageSource"], + }, + ] as IConverterFormPartsProperties[], + }, + } as IProviderConfiguration, + }, + }, + }, + }, +}; + +// For this example, we're using static items for the image selection dropdown. In a more realistic scenario, +// the items available for selection might come from a backend database. +const imageItems = [ + { + title: "Harry Potter Book Cover", + url: "https://imgc.allpostersimages.com/img/print/u-g-F8PQ9I0.jpg?w=550&h=550&p=0", + }, + { + title: "Harry Potter Movie Poster", + url: "https://images-na.ssl-images-amazon.com/images/I/81gpmMdKOHL._AC_SY741_.jpg", + }, +] as IMenuItem[]; + +// We're using a static configuration object for this example. In a more realistic scenario, +// a widget's configuration would likely be stored in a database. +const widgetConfig: IWidget = { + id: "widget1", + // This custom type is registered in the 'prepareNovaDashboards' method above. + type: CUSTOM_WIDGET_TYPENAME, + pizzagna: { + [PizzagnaLayer.Configuration]: { + header: { + // Setting the initial property values for the WidgetHeaderComponent + properties: { + title: "Harry Potter and the Sorcerer's Stone", + subtitle: "By J. K. Rowling", + }, + }, + mainContent: { + properties: { + // Setting the initial value for the 'imageSource' property on our custom widget body + imageSource: imageItems[0].url, + }, + }, + }, + }, +}; +\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\`, + "tutorials/customization/widget/custom-widget.module.ts": \\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\`// © 2022 SolarWinds Worldwide, LLC. All rights reserved. +// +// Permission is hereby granted, free of charge, to any person obtaining a copy +// of this software and associated documentation files (the "Software"), to +// deal in the Software without restriction, including without limitation the +// rights to use, copy, modify, merge, publish, distribute, sublicense, and/or +// sell copies of the Software, and to permit persons to whom the Software is +// furnished to do so, subject to the following conditions: +// +// The above copyright notice and this permission notice shall be included in +// all copies or substantial portions of the Software. +// +// THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR +// IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, +// FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE +// AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER +// LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, +// OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN +// THE SOFTWARE. + +import { CommonModule } from "@angular/common"; +import { HttpClientModule } from "@angular/common/http"; +import { NgModule } from "@angular/core"; +import { ReactiveFormsModule } from "@angular/forms"; +import { RouterModule } from "@angular/router"; + +import { + NuiButtonModule, + NuiDocsModule, + NuiFormFieldModule, + NuiIconModule, + NuiImageModule, + NuiMessageModule, + NuiSelectV2Module, + NuiSwitchModule, + DEMO_PATH_TOKEN, +} from "@nova-ui/bits"; +import { + NuiDashboardConfiguratorModule, + NuiDashboardsModule, +} from "@nova-ui/dashboards"; + +import { CustomWidgetDocsComponent } from "./custom-widget-docs.component"; +import { + CustomConfiguratorSectionComponent, + CustomWidgetBodyContentComponent, + CustomWidgetComponent, +} from "./custom-widget.component"; +import { getDemoFiles } from "../../../../../demo-files-factory"; + +const routes = [ + { + path: "", + component: CustomWidgetDocsComponent, + data: { + srlc: { + hideIndicator: true, + }, + showThemeSwitcher: true, + }, + }, + { + path: "example", + component: CustomWidgetComponent, + data: { + srlc: { + hideIndicator: true, + }, + }, + }, +]; + +@NgModule({ + imports: [ + CommonModule, + ReactiveFormsModule, + HttpClientModule, + NuiDashboardsModule, + NuiDashboardConfiguratorModule, + NuiDocsModule, + NuiFormFieldModule, + NuiIconModule, + NuiImageModule, + NuiMessageModule, + NuiSelectV2Module, + NuiSwitchModule, + NuiButtonModule, + RouterModule.forChild(routes), + ], + declarations: [ + CustomWidgetDocsComponent, + CustomConfiguratorSectionComponent, + CustomWidgetBodyContentComponent, + CustomWidgetComponent, + ], + providers: [ + { + provide: DEMO_PATH_TOKEN, + useValue: getDemoFiles("widget"), + }, + ], +}) +export default class CustomWidgetModule {} +\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\`, + "tutorials/data-source-setup/data-source-setup-docs.component.ts": \\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\`// © 2022 SolarWinds Worldwide, LLC. All rights reserved. +// +// Permission is hereby granted, free of charge, to any person obtaining a copy +// of this software and associated documentation files (the "Software"), to +// deal in the Software without restriction, including without limitation the +// rights to use, copy, modify, merge, publish, distribute, sublicense, and/or +// sell copies of the Software, and to permit persons to whom the Software is +// furnished to do so, subject to the following conditions: +// +// The above copyright notice and this permission notice shall be included in +// all copies or substantial portions of the Software. +// +// THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR +// IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, +// FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE +// AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER +// LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, +// OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN +// THE SOFTWARE. + +import { Component } from "@angular/core"; + +@Component({ + selector: "nui-dashboard-data-source-docs", + templateUrl: "./data-source-setup-docs.component.html", + standalone: false, +}) +export class DataSourceDocsComponent {} +\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\`, + "tutorials/data-source-setup/data-source-setup.component.ts": \\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\`// © 2022 SolarWinds Worldwide, LLC. All rights reserved. +// +// Permission is hereby granted, free of charge, to any person obtaining a copy +// of this software and associated documentation files (the "Software"), to +// deal in the Software without restriction, including without limitation the +// rights to use, copy, modify, merge, publish, distribute, sublicense, and/or +// sell copies of the Software, and to permit persons to whom the Software is +// furnished to do so, subject to the following conditions: +// +// The above copyright notice and this permission notice shall be included in +// all copies or substantial portions of the Software. +// +// THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR +// IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, +// FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE +// AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER +// LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, +// OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN +// THE SOFTWARE. + +import { HttpClient, HttpErrorResponse } from "@angular/common/http"; +import { Component, Injectable, OnDestroy, OnInit } from "@angular/core"; +import { GridsterConfig, GridsterItem } from "angular-gridster2"; +import { BehaviorSubject } from "rxjs"; +import { finalize } from "rxjs/operators"; + +import { DataSourceService, IFilteringOutputs } from "@nova-ui/bits"; +import { + DATA_SOURCE, + DEFAULT_PIZZAGNA_ROOT, + IDashboard, + IKpiData, + IProviderConfiguration, + IRefresherProperties, + IWidget, + IWidgets, + KpiComponent, + NOVA_KPI_DATASOURCE_ADAPTER, + PizzagnaLayer, + ProviderRegistryService, + WellKnownProviders, + WidgetTypesService, +} from "@nova-ui/dashboards"; + +/** + * A simple KPI data source to retrieve the average rating of Harry Potter and the Sorcerer's Stone (book) via googleapis + */ +@Injectable() +export class AverageRatingKpiDataSource + extends DataSourceService + implements OnDestroy +{ + // This is the ID we'll use to identify the provider + public static providerId = "AverageRatingKpiDataSource"; + + // Use this subject to communicate the data source's busy state + public busy = new BehaviorSubject(false); + + constructor(private http: HttpClient) { + super(); + } + + // In this example, getFilteredData is invoked every 10 minutes (Take a look at the refresher + // provider definition in the widget configuration below to see how the interval is set) + public async getFilteredData(): Promise { + this.busy.next(true); + return new Promise((resolve) => { + // *** Make a resource request to an external API (if needed) + this.http + .get("https://www.googleapis.com/books/v1/volumes/5MQFrgEACAAJ") + .pipe(finalize(() => this.busy.next(false))) + .subscribe({ + next: (data: any) => { + resolve({ + result: { + value: data.volumeInfo.averageRating, + }, + }); + }, + error: (error: HttpErrorResponse) => { + resolve({ + result: null, + error: { + type: error.status, + }, + }); + }, + }); + }); + } + + public ngOnDestroy(): void { + this.outputsSubject.complete(); + } +} + +/** + * A component that instantiates the dashboard + */ +@Component({ + selector: "data-source-setup", + templateUrl: "./data-source-setup.component.html", + styleUrls: ["./data-source-setup.component.less"], + standalone: false, +}) +export class DataSourceSetupComponent implements OnInit { + // This variable will hold all the data needed to define the layout and behavior of the widgets. + // Pass this to the dashboard component's dashboard input in the template. + public dashboard: IDashboard; + + // Angular gridster requires a configuration object even if it's empty. + // Pass this to the dashboard component's gridsterConfig input in the template. + public gridsterConfig: GridsterConfig = {}; + + constructor( + // WidgetTypesService provides the widget's necessary structure information + private widgetTypesService: WidgetTypesService, + + // In general, the ProviderRegistryService is used for making entities available for injection into dynamically loaded components. + private providerRegistry: ProviderRegistryService + ) {} + + public ngOnInit(): void { + // Registering the data source for injection into the KPI tile. + // Note: Each tile of a KPI widget is assigned its own instance of the data source + this.providerRegistry.setProviders({ + [AverageRatingKpiDataSource.providerId]: { + provide: DATA_SOURCE, + useClass: AverageRatingKpiDataSource, + // Any dependencies that need to be injected into the provider must be listed here + deps: [HttpClient], + }, + }); + // We're using a static configuration object for this example, but this is where + // the widget's configuration could potentially be populated from a database + const kpiWidget = widgetConfig; + const widgetIndex: IWidgets = { + // Complete the KPI widget with information coming from its type definition + [kpiWidget.id]: + this.widgetTypesService.mergeWithWidgetType(kpiWidget), + }; + + // Setting the widget dimensions and position (this is for gridster) + const positions: Record = { + [kpiWidget.id]: { + cols: 4, + rows: 6, + y: 0, + x: 0, + }, + }; + + // Finally, assigning the variables we created above to the dashboard + this.dashboard = { + positions, + widgets: widgetIndex, + }; + } +} + +const widgetConfig: IWidget = { + id: "widget1", + type: "kpi", + pizzagna: { + [PizzagnaLayer.Configuration]: { + [DEFAULT_PIZZAGNA_ROOT]: { + providers: { + [WellKnownProviders.Refresher]: { + properties: { + // Configuring the refresher interval so that our data source is invoked every ten minutes + interval: 60 * 10, + enabled: true, + } as IRefresherProperties, + } as Partial, + }, + }, + header: { + properties: { + title: "Harry Potter and the Sorcerer's Stone", + subtitle: "By J. K. Rowling", + }, + }, + tiles: { + properties: { + nodes: ["kpi1"], + }, + }, + kpi1: { + id: "kpi1", + componentType: KpiComponent.lateLoadKey, + properties: { + widgetData: { + units: "out of 5 Stars", + label: "Average Rating", + }, + }, + providers: { + [WellKnownProviders.DataSource]: { + // Setting the data source providerId for the tile with id "kpi1" + providerId: AverageRatingKpiDataSource.providerId, + } as IProviderConfiguration, + [WellKnownProviders.Adapter]: { + providerId: NOVA_KPI_DATASOURCE_ADAPTER, + properties: { + componentId: "kpi1", + propertyPath: "widgetData", + }, + } as IProviderConfiguration, + }, + }, + }, + }, +}; +\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\`, + "tutorials/data-source-setup/data-source-setup.module.ts": \\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\`// © 2022 SolarWinds Worldwide, LLC. All rights reserved. +// +// Permission is hereby granted, free of charge, to any person obtaining a copy +// of this software and associated documentation files (the "Software"), to +// deal in the Software without restriction, including without limitation the +// rights to use, copy, modify, merge, publish, distribute, sublicense, and/or +// sell copies of the Software, and to permit persons to whom the Software is +// furnished to do so, subject to the following conditions: +// +// The above copyright notice and this permission notice shall be included in +// all copies or substantial portions of the Software. +// +// THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR +// IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, +// FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE +// AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER +// LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, +// OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN +// THE SOFTWARE. + +import { CommonModule } from "@angular/common"; +import { HttpClientModule } from "@angular/common/http"; +import { NgModule } from "@angular/core"; +import { RouterModule } from "@angular/router"; + +import { + NuiDocsModule, + NuiMessageModule, + DEMO_PATH_TOKEN, +} from "@nova-ui/bits"; +import { NuiDashboardsModule } from "@nova-ui/dashboards"; + +import { DataSourceDocsComponent } from "./data-source-setup-docs.component"; +import { DataSourceSetupComponent } from "./data-source-setup.component"; +import { getDemoFiles } from "../../../../demo-files-factory"; + +const routes = [ + { + path: "", + component: DataSourceDocsComponent, + data: { + srlc: { + hideIndicator: true, + }, + showThemeSwitcher: true, + }, + }, + { + path: "example", + component: DataSourceSetupComponent, + data: { + srlc: { + hideIndicator: true, + }, + }, + }, +]; + +@NgModule({ + imports: [ + CommonModule, + HttpClientModule, + NuiDashboardsModule, + NuiDocsModule, + NuiMessageModule, + RouterModule.forChild(routes), + ], + declarations: [DataSourceDocsComponent, DataSourceSetupComponent], + providers: [ + { + provide: DEMO_PATH_TOKEN, + useValue: getDemoFiles("data-source-setup"), + }, + ], +}) +export default class DataSourceSetupModule {} +\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\`, + "tutorials/dynamic-header-links/dynamic-header-links-docs.component.ts": \\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\`// © 2022 SolarWinds Worldwide, LLC. All rights reserved. +// +// Permission is hereby granted, free of charge, to any person obtaining a copy +// of this software and associated documentation files (the "Software"), to +// deal in the Software without restriction, including without limitation the +// rights to use, copy, modify, merge, publish, distribute, sublicense, and/or +// sell copies of the Software, and to permit persons to whom the Software is +// furnished to do so, subject to the following conditions: +// +// The above copyright notice and this permission notice shall be included in +// all copies or substantial portions of the Software. +// +// THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR +// IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, +// FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE +// AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER +// LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, +// OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN +// THE SOFTWARE. + +import { Component } from "@angular/core"; + +@Component({ + selector: "nui-dynamic-header-links-docs", + templateUrl: "./dynamic-header-links-docs.component.html", + standalone: false, +}) +export class DynamicHeaderLinksDocsComponent {} +\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\`, + "tutorials/dynamic-header-links/dynamic-header-links-docs.module.ts": \\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\`// © 2022 SolarWinds Worldwide, LLC. All rights reserved. +// +// Permission is hereby granted, free of charge, to any person obtaining a copy +// of this software and associated documentation files (the "Software"), to +// deal in the Software without restriction, including without limitation the +// rights to use, copy, modify, merge, publish, distribute, sublicense, and/or +// sell copies of the Software, and to permit persons to whom the Software is +// furnished to do so, subject to the following conditions: +// +// The above copyright notice and this permission notice shall be included in +// all copies or substantial portions of the Software. +// +// THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR +// IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, +// FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE +// AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER +// LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, +// OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN +// THE SOFTWARE. + +import { NgModule } from "@angular/core"; +import { RouterModule } from "@angular/router"; + +import { DynamicHeaderLinksDocsComponent } from "./dynamic-header-links-docs.component"; + +const routes = [ + { + path: "", + component: DynamicHeaderLinksDocsComponent, + data: { + srlc: { + hideIndicator: true, + }, + showThemeSwitcher: true, + }, + }, +]; + +@NgModule({ + imports: [RouterModule.forChild(routes)], + declarations: [DynamicHeaderLinksDocsComponent], +}) +export default class DynamicHeaderLinksDocsModule {} +\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\`, + "tutorials/hello-dashboards/hello-dashboards-docs.component.ts": \\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\`// © 2022 SolarWinds Worldwide, LLC. All rights reserved. +// +// Permission is hereby granted, free of charge, to any person obtaining a copy +// of this software and associated documentation files (the "Software"), to +// deal in the Software without restriction, including without limitation the +// rights to use, copy, modify, merge, publish, distribute, sublicense, and/or +// sell copies of the Software, and to permit persons to whom the Software is +// furnished to do so, subject to the following conditions: +// +// The above copyright notice and this permission notice shall be included in +// all copies or substantial portions of the Software. +// +// THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR +// IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, +// FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE +// AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER +// LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, +// OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN +// THE SOFTWARE. + +import { Component } from "@angular/core"; + +@Component({ + selector: "nui-dashboard-hello-dashboards-docs", + templateUrl: "./hello-dashboards-docs.component.html", + standalone: false, +}) +export class HelloDashboardsDocsComponent {} +\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\`, + "tutorials/hello-dashboards/hello-dashboards-example/hello-dashboards-example.component.ts": \\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\`// © 2022 SolarWinds Worldwide, LLC. All rights reserved. +// +// Permission is hereby granted, free of charge, to any person obtaining a copy +// of this software and associated documentation files (the "Software"), to +// deal in the Software without restriction, including without limitation the +// rights to use, copy, modify, merge, publish, distribute, sublicense, and/or +// sell copies of the Software, and to permit persons to whom the Software is +// furnished to do so, subject to the following conditions: +// +// The above copyright notice and this permission notice shall be included in +// all copies or substantial portions of the Software. +// +// THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR +// IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, +// FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE +// AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER +// LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, +// OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN +// THE SOFTWARE. + +import { Component, OnInit } from "@angular/core"; +import { GridsterConfig, GridsterItem } from "angular-gridster2"; + +import { + IDashboard, + IWidget, + IWidgets, + KpiComponent, + PizzagnaLayer, + WidgetTypesService, +} from "@nova-ui/dashboards"; + +/** + * A component that instantiates the dashboard + */ +@Component({ + selector: "hello-dashboards-example", + templateUrl: "./hello-dashboards-example.component.html", + styleUrls: ["./hello-dashboards-example.component.less"], + standalone: false, +}) +export class HelloDashboardsExampleComponent implements OnInit { + // This variable will have all the data needed to render the widgets widgets. + // Pass this to the dashboard component's dashboard input. + public dashboard: IDashboard; + // Angular gridster requires a configuration object even if its empty. + // Pass this to the dashboard component's gridsterConfig input. + public gridsterConfig: GridsterConfig = {}; + + // WidgetTypesService provides the widget's necessary structure information + constructor(private widgetTypesService: WidgetTypesService) {} + + public ngOnInit(): void { + // Here we are hard-coding the widget config for this example, but this is where you + // could potentially populate the widget's configuration from a database + const kpiWidget = widgetConfig; + const widgetIndex: IWidgets = { + // Complete the KPI widget with information coming from its type definition + [kpiWidget.id]: + this.widgetTypesService.mergeWithWidgetType(kpiWidget), + }; + // Setting widget position and dimensions (this is for gridster) + const positions: Record = { + [kpiWidget.id]: { + cols: 4, + rows: 6, + y: 0, + x: 0, + }, + }; + // Finally, assigning the variables we created above to the dashboard + this.dashboard = { + positions, + widgets: widgetIndex, + }; + } +} + +// In a real-world scenario, this configuration would typically be fetched from a database or at least live in another file +const widgetConfig: IWidget = { + id: "widget1", + type: "kpi", + pizzagna: { + [PizzagnaLayer.Configuration]: { + header: { + properties: { + title: "Hello, KPI Widget!", + subtitle: "A Venue for Meaningful Values", + }, + }, + tiles: { + properties: { + nodes: ["kpi1"], + }, + }, + kpi1: { + id: "kpi1", + componentType: KpiComponent.lateLoadKey, + properties: { + widgetData: { + id: "totalStorage", + value: 1, + label: "Total storage", + units: "TB", + }, + }, + }, + }, + }, +}; +\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\`, + "tutorials/hello-dashboards/hello-dashboards.module.ts": \\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\`// © 2022 SolarWinds Worldwide, LLC. All rights reserved. +// +// Permission is hereby granted, free of charge, to any person obtaining a copy +// of this software and associated documentation files (the "Software"), to +// deal in the Software without restriction, including without limitation the +// rights to use, copy, modify, merge, publish, distribute, sublicense, and/or +// sell copies of the Software, and to permit persons to whom the Software is +// furnished to do so, subject to the following conditions: +// +// The above copyright notice and this permission notice shall be included in +// all copies or substantial portions of the Software. +// +// THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR +// IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, +// FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE +// AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER +// LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, +// OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN +// THE SOFTWARE. + +import { CommonModule } from "@angular/common"; +import { NgModule } from "@angular/core"; +import { RouterModule } from "@angular/router"; + +import { + NuiDocsModule, + NuiMessageModule, + DEMO_PATH_TOKEN, +} from "@nova-ui/bits"; +import { NuiDashboardsModule } from "@nova-ui/dashboards"; + +import { HelloDashboardsDocsComponent } from "./hello-dashboards-docs.component"; +import { HelloDashboardsExampleComponent } from "./hello-dashboards-example/hello-dashboards-example.component"; +import { getDemoFiles } from "../../../../demo-files-factory"; + +const routes = [ + { + path: "", + component: HelloDashboardsDocsComponent, + data: { + srlc: { + hideIndicator: true, + }, + showThemeSwitcher: true, + }, + }, + { + path: "example", + component: HelloDashboardsExampleComponent, + data: { + srlc: { + hideIndicator: true, + }, + }, + }, +]; + +@NgModule({ + imports: [ + CommonModule, + NuiDashboardsModule, + NuiDocsModule, + NuiMessageModule, + RouterModule.forChild(routes), + ], + declarations: [ + HelloDashboardsDocsComponent, + HelloDashboardsExampleComponent, + ], + providers: [ + { + provide: DEMO_PATH_TOKEN, + useValue: getDemoFiles("hello-dashboards"), + }, + ], +}) +export default class HelloDashboardsModule {} +\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\`, + "tutorials/persistence-handler-setup/persistence-handler-setup-docs.component.ts": \\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\`// © 2022 SolarWinds Worldwide, LLC. All rights reserved. +// +// Permission is hereby granted, free of charge, to any person obtaining a copy +// of this software and associated documentation files (the "Software"), to +// deal in the Software without restriction, including without limitation the +// rights to use, copy, modify, merge, publish, distribute, sublicense, and/or +// sell copies of the Software, and to permit persons to whom the Software is +// furnished to do so, subject to the following conditions: +// +// The above copyright notice and this permission notice shall be included in +// all copies or substantial portions of the Software. +// +// THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR +// IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, +// FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE +// AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER +// LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, +// OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN +// THE SOFTWARE. + +import { Component } from "@angular/core"; + +@Component({ + selector: "nui-dashboard-persistence-handler-setup-docs", + templateUrl: "./persistence-handler-setup-docs.component.html", + standalone: false, +}) +export class PersistenceHandlerSetupDocsComponent {} +\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\`, + "tutorials/persistence-handler-setup/persistence-handler-setup.component.ts": \\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\`// © 2022 SolarWinds Worldwide, LLC. All rights reserved. +// +// Permission is hereby granted, free of charge, to any person obtaining a copy +// of this software and associated documentation files (the "Software"), to +// deal in the Software without restriction, including without limitation the +// rights to use, copy, modify, merge, publish, distribute, sublicense, and/or +// sell copies of the Software, and to permit persons to whom the Software is +// furnished to do so, subject to the following conditions: +// +// The above copyright notice and this permission notice shall be included in +// all copies or substantial portions of the Software. +// +// THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR +// IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, +// FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE +// AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER +// LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, +// OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN +// THE SOFTWARE. + +import { HttpClient, HttpErrorResponse } from "@angular/common/http"; +import { + ChangeDetectorRef, + Component, + Injectable, + OnDestroy, + OnInit, +} from "@angular/core"; +import { GridsterConfig, GridsterItem } from "angular-gridster2"; +import { BehaviorSubject, Observable, Subject } from "rxjs"; +import { finalize } from "rxjs/operators"; + +import { + DataSourceService, + IFilteringOutputs, + ToastService, + uuid, +} from "@nova-ui/bits"; +import { + DATA_SOURCE, + DEFAULT_PIZZAGNA_ROOT, + IDashboard, + IDashboardPersistenceHandler, + IKpiData, + IProviderConfiguration, + IRefresherProperties, + IWidget, + IWidgets, + KpiComponent, + NOVA_KPI_DATASOURCE_ADAPTER, + PizzagnaLayer, + ProviderRegistryService, + WellKnownPathKey, + WellKnownProviders, + WidgetTypesService, +} from "@nova-ui/dashboards"; + +/** + * A simple persistence handler that is tied to the widget editor directive + */ +@Injectable() +// The realizer of IDashboardPersistenceHandler may implement a trySubmit and/or a tryRemove method. +export class PersistenceHandler implements IDashboardPersistenceHandler { + // This variable is just to show how to handle error handling. + private persistenceSucceeded: boolean = true; + + // The example uses the toast service to demonstrate the + // invocation of each of the persistence handler callbacks + constructor(private toastService: ToastService) { + // toastService options to let it sit on the page for 2 seconds. + this.toastService.setConfig({ + timeOut: 2000, + }); + } + + // This method will be invoked anytime the widget editor form gets submitted. + public trySubmit = (widget: IWidget): Observable => { + // Since we are working asynchronously, we'll return a subject. So, after the submit attempt + // succeeds or fails, we can let the subscriber know the result. + const subject = new Subject(); + + if (!widget.id) { + // Creates an id if the widget has no id. + // (This step will make more sense in the context of the widget cloning tutorial + // in which we handle the persistence of a newly created widget.) + widget.id = uuid(); + } + + // For this example, we're using a setTimeout to mock an asynchronous persistence request to a backend + setTimeout(() => { + if (this.persistenceSucceeded) { + // Passes along the new widget after one second. + subject.next(widget); + // Toast on the page on success. + this.toastService.success({ + title: $localize\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\`Submit succeeded.\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\`, + }); + } else { + const errorText = $localize\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\`Submit failed.\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\`; + // Toast on the page on failure. + this.toastService.error({ title: errorText }); + // Makes the subject say there is an error. + subject.error(errorText); + } + // Completes the subject so whoever subscribes to it knows its finished. + subject.complete(); + }, 1000); + + // Returns the subject as an observable. + return subject.asObservable(); + }; + + // This method will be invoked anytime there's a widget removal attempt. + public tryRemove = (widgetId: string): Observable => { + const subject = new Subject(); + + setTimeout(() => { + if (this.persistenceSucceeded) { + // Pass through the id of the widget that was removed. + subject.next(widgetId); + this.toastService.success({ + title: $localize\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\`Removal succeeded.\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\`, + }); + } else { + const errorText = $localize\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\`Removal failed.\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\`; + this.toastService.error({ title: errorText }); + subject.error(errorText); + } + subject.complete(); + }, 1000); + + return subject.asObservable(); + }; +} + +/** + * A component that instantiates the dashboard + */ +@Component({ + selector: "persistence-handler-setup", + templateUrl: "./persistence-handler-setup.component.html", + styleUrls: ["./persistence-handler-setup.component.less"], + // Here we provide our persistence handler at the component level; this can also be done in the module. + providers: [PersistenceHandler], + standalone: false, +}) +export class PersistenceHandlerSetupComponent implements OnInit { + // This variable will hold all the data needed to define the layout and behavior of the widgets. + // Pass this to the dashboard component's dashboard input in the template. + public dashboard: IDashboard | undefined; + + // Angular gridster requires a configuration object even if it's empty. + // Pass this to the dashboard component's gridsterConfig input in the template. + public gridsterConfig: GridsterConfig = {}; + + // Boolean which dashboard takes in as an input if its true it allows you to move widgets around. + public editMode: boolean = false; + + constructor( + // WidgetTypesService provides the widget's necessary structure information + private widgetTypesService: WidgetTypesService, + + // In general, the ProviderRegistryService is used for making entities available for injection into dynamically loaded components. + private providerRegistry: ProviderRegistryService, + + // We are injecting the PersistenceHandler we created and assigning it to a property we use in the template. + public persistenceHandler: PersistenceHandler, + private changeDetectorRef: ChangeDetectorRef + ) {} + + public ngOnInit(): void { + // Grabbing the widget's default template which will be needed as a parameter for setNode + const widgetTemplate = this.widgetTypesService.getWidgetType("kpi", 1); + // Registering our data sources as dropdown options in the widget editor/configurator + // Note: This could also be done in the parent module's constructor so that + // multiple dashboards could have access to the same widget template modification. + this.widgetTypesService.setNode( + // This is the template we grabbed above with getWidgetType + widgetTemplate, + // We are setting the editor/configurator part of the widget template + "configurator", + // This indicates which node you are changing and we want to change + // the data source providers available for selection in the editor. + WellKnownPathKey.DataSourceProviders, + // We are setting the data sources available for selection in the editor + [ + AverageRatingKpiDataSource.providerId, + RatingsCountKpiDataSource.providerId, + ] + ); + + // Registering the data sources available for injection into the KPI tiles. + // Note: Each tile of a KPI widget is assigned its own instance of a data source + this.providerRegistry.setProviders({ + [AverageRatingKpiDataSource.providerId]: { + provide: DATA_SOURCE, + useClass: AverageRatingKpiDataSource, + // Any dependencies that need to be injected into the provider must be listed here + deps: [HttpClient], + }, + [RatingsCountKpiDataSource.providerId]: { + provide: DATA_SOURCE, + useClass: RatingsCountKpiDataSource, + deps: [HttpClient], + }, + }); + + this.initializeDashboard(); + } + + /** Used for restoring widgets state */ + public reInitializeDashboard(): void { + // destroys the components and their providers so the dashboard can re init data + this.dashboard = undefined; + this.changeDetectorRef.detectChanges(); + + this.initializeDashboard(); + } + + public initializeDashboard(): void { + // We're using a static configuration object for this example (see widgetConfig at the bottom of the file), + // but this is where the widget's configuration could potentially be populated from a database + const kpiWidget = widgetConfig; + const widgetIndex: IWidgets = { + // Complete the KPI widget with information coming from its type definition + [kpiWidget.id]: + this.widgetTypesService.mergeWithWidgetType(kpiWidget), + }; + + // Setting the widget dimensions and position (this is for gridster) + const positions: Record = { + [kpiWidget.id]: { + cols: 4, + rows: 6, + y: 0, + x: 0, + }, + }; + + // Finally, assigning the variables we created above to the dashboard + this.dashboard = { + positions, + widgets: widgetIndex, + }; + } +} + +/** + * A simple KPI data source to retrieve the average rating of Harry Potter and the Sorcerer's Stone (book) via googleapis + */ +@Injectable() +export class AverageRatingKpiDataSource + extends DataSourceService + implements OnDestroy +{ + // This is the ID we'll use to identify the provider + public static providerId = "AverageRatingKpiDataSource"; + + // Use this subject to communicate the data source's busy state + public busy = new BehaviorSubject(false); + + constructor(private http: HttpClient) { + super(); + } + + // In this example, getFilteredData is invoked every 10 minutes (Take a look at the refresher + // provider definition in the widget configuration below to see how the interval is set) + public async getFilteredData(): Promise { + this.busy.next(true); + return new Promise((resolve) => { + // *** Make a resource request to an external API (if needed) + this.http + .get("https://www.googleapis.com/books/v1/volumes/5MQFrgEACAAJ") + .pipe(finalize(() => this.busy.next(false))) + .subscribe({ + next: (data: any) => { + resolve({ + result: { + value: data.volumeInfo.averageRating, + }, + }); + }, + error: (error: HttpErrorResponse) => { + resolve({ + result: null, + error: { + type: error.status, + }, + }); + }, + }); + }); + } + + public ngOnDestroy(): void { + this.outputsSubject.complete(); + } +} + +/** + * A simple KPI data source to retrieve the ratings count of Harry Potter and the Sorcerer's Stone (book) via googleapis + */ +@Injectable() +export class RatingsCountKpiDataSource + extends DataSourceService + implements OnDestroy +{ + public static providerId = "RatingsCountKpiDataSource"; + + // Use this subject to communicate the data source's busy state + public busy = new BehaviorSubject(false); + + constructor(private http: HttpClient) { + super(); + } + + public async getFilteredData(): Promise { + this.busy.next(true); + return new Promise((resolve) => { + this.http + .get("https://www.googleapis.com/books/v1/volumes/5MQFrgEACAAJ") + .pipe(finalize(() => this.busy.next(false))) + .subscribe({ + next: (data: any) => { + resolve({ + result: { + value: data.volumeInfo.ratingsCount, + }, + }); + }, + error: (error: HttpErrorResponse) => { + resolve({ + result: null, + error: { + type: error.status, + }, + }); + }, + }); + }); + } + + public ngOnDestroy(): void { + this.outputsSubject.complete(); + } +} + +const widgetConfig: IWidget = { + id: "widget1", + type: "kpi", + pizzagna: { + [PizzagnaLayer.Configuration]: { + [DEFAULT_PIZZAGNA_ROOT]: { + providers: { + [WellKnownProviders.Refresher]: { + properties: { + // Configuring the refresher interval so that our data source is invoked every ten minutes + interval: 60 * 10, + enabled: true, + } as IRefresherProperties, + } as Partial, + }, + }, + header: { + properties: { + title: "Harry Potter and the Sorcerer's Stone", + subtitle: "By J. K. Rowling", + }, + }, + tiles: { + properties: { + nodes: ["kpi1"], + }, + }, + kpi1: { + id: "kpi1", + componentType: KpiComponent.lateLoadKey, + properties: { + widgetData: { + units: "out of 5 Stars", + label: "Average Rating", + }, + }, + providers: { + [WellKnownProviders.DataSource]: { + // Setting the data source providerId for the tile with id "kpi1" + providerId: AverageRatingKpiDataSource.providerId, + } as IProviderConfiguration, + [WellKnownProviders.Adapter]: { + providerId: NOVA_KPI_DATASOURCE_ADAPTER, + properties: { + componentId: "kpi1", + propertyPath: "widgetData", + }, + } as IProviderConfiguration, + }, + }, + }, + }, +}; +\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\`, + "tutorials/persistence-handler-setup/persistence-handler-setup.module.ts": \\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\`// © 2022 SolarWinds Worldwide, LLC. All rights reserved. +// +// Permission is hereby granted, free of charge, to any person obtaining a copy +// of this software and associated documentation files (the "Software"), to +// deal in the Software without restriction, including without limitation the +// rights to use, copy, modify, merge, publish, distribute, sublicense, and/or +// sell copies of the Software, and to permit persons to whom the Software is +// furnished to do so, subject to the following conditions: +// +// The above copyright notice and this permission notice shall be included in +// all copies or substantial portions of the Software. +// +// THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR +// IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, +// FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE +// AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER +// LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, +// OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN +// THE SOFTWARE. + +import { CommonModule } from "@angular/common"; +import { HttpClientModule } from "@angular/common/http"; +import { NgModule } from "@angular/core"; +import { RouterModule } from "@angular/router"; + +import { + NuiButtonModule, + NuiDocsModule, + NuiMessageModule, + NuiSwitchModule, + NuiToastModule, + DEMO_PATH_TOKEN, +} from "@nova-ui/bits"; +import { NuiDashboardsModule } from "@nova-ui/dashboards"; + +import { PersistenceHandlerSetupDocsComponent } from "./persistence-handler-setup-docs.component"; +import { PersistenceHandlerSetupComponent } from "./persistence-handler-setup.component"; +import { getDemoFiles } from "../../../../demo-files-factory"; + +const routes = [ + { + path: "", + component: PersistenceHandlerSetupDocsComponent, + data: { + srlc: { + hideIndicator: true, + }, + showThemeSwitcher: true, + }, + }, + { + path: "example", + component: PersistenceHandlerSetupComponent, + data: { + srlc: { + hideIndicator: true, + }, + }, + }, +]; + +@NgModule({ + imports: [ + CommonModule, + HttpClientModule, + NuiDashboardsModule, + NuiDocsModule, + NuiMessageModule, + NuiSwitchModule, + NuiToastModule, + NuiButtonModule, + RouterModule.forChild(routes), + ], + declarations: [ + PersistenceHandlerSetupDocsComponent, + PersistenceHandlerSetupComponent, + ], + providers: [ + { + provide: DEMO_PATH_TOKEN, + useValue: getDemoFiles("persistence-handler-setup"), + }, + ], +}) +export default class PersistenceHandlerSetupModule {} +\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\`, + "tutorials/tutorials.module.ts": \\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\`// © 2022 SolarWinds Worldwide, LLC. All rights reserved. +// +// Permission is hereby granted, free of charge, to any person obtaining a copy +// of this software and associated documentation files (the "Software"), to +// deal in the Software without restriction, including without limitation the +// rights to use, copy, modify, merge, publish, distribute, sublicense, and/or +// sell copies of the Software, and to permit persons to whom the Software is +// furnished to do so, subject to the following conditions: +// +// The above copyright notice and this permission notice shall be included in +// all copies or substantial portions of the Software. +// +// THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR +// IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, +// FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE +// AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER +// LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, +// OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN +// THE SOFTWARE. + +import { NgModule, Type } from "@angular/core"; +import { RouterModule, Routes } from "@angular/router"; + +import { ConfiguratorHeadingService } from "@nova-ui/dashboards"; + +export enum TutorialsModuleRoute { + HelloDashboards = "hello-dashboards", + DataSource = "data-source-setup", + WidgetEditor = "widget-editor-setup", + SubmitHandler = "persistence-handler-setup", + WidgetCreation = "widget-creation", + Customization = "customization", + WidgetErrorHandling = "widget-error-handling", + DynamicHeaderLinks = "dynamic-header-links", +} + +const routes: Routes = [ + { + path: TutorialsModuleRoute.HelloDashboards, + loadChildren: async () => + import( + "./hello-dashboards/hello-dashboards.module" + ) as object as Promise>, + }, + { + path: TutorialsModuleRoute.DataSource, + loadChildren: async () => + import( + "./data-source-setup/data-source-setup.module" + ) as object as Promise>, + }, + { + path: TutorialsModuleRoute.WidgetEditor, + loadChildren: async () => + import( + "./widget-editor-setup/widget-editor-setup.module" + ) as object as Promise>, + }, + { + path: TutorialsModuleRoute.SubmitHandler, + loadChildren: async () => + import( + "./persistence-handler-setup/persistence-handler-setup.module" + ) as object as Promise>, + }, + { + path: TutorialsModuleRoute.WidgetCreation, + loadChildren: async () => + import( + "./widget-creation/widget-creation.module" + ) as object as Promise>, + }, + { + path: TutorialsModuleRoute.Customization, + loadChildren: async () => + import("./customization/customization.module") as object as Promise< + Type + >, + }, + { + path: TutorialsModuleRoute.WidgetErrorHandling, + loadChildren: async () => + import( + "./widget-error-handling/widget-error-handling.module" + ) as object as Promise>, + }, + { + path: TutorialsModuleRoute.DynamicHeaderLinks, + loadChildren: async () => + import( + "./dynamic-header-links/dynamic-header-links-docs.module" + ) as object as Promise>, + }, +]; + +@NgModule({ + imports: [RouterModule.forChild(routes)], + providers: [ConfiguratorHeadingService], +}) +export default class TutorialsModule {} +\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\`, + "tutorials/widget-creation/widget-creation-docs.component.ts": \\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\`// © 2022 SolarWinds Worldwide, LLC. All rights reserved. +// +// Permission is hereby granted, free of charge, to any person obtaining a copy +// of this software and associated documentation files (the "Software"), to +// deal in the Software without restriction, including without limitation the +// rights to use, copy, modify, merge, publish, distribute, sublicense, and/or +// sell copies of the Software, and to permit persons to whom the Software is +// furnished to do so, subject to the following conditions: +// +// The above copyright notice and this permission notice shall be included in +// all copies or substantial portions of the Software. +// +// THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR +// IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, +// FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE +// AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER +// LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, +// OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN +// THE SOFTWARE. + +import { Component } from "@angular/core"; + +@Component({ + selector: "nui-dashboard-widget-creation-docs", + templateUrl: "./widget-creation-docs.component.html", + standalone: false, +}) +export class WidgetCreationDocsComponent {} +\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\`, + "tutorials/widget-creation/widget-creation.component.ts": \\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\`// © 2022 SolarWinds Worldwide, LLC. All rights reserved. +// +// Permission is hereby granted, free of charge, to any person obtaining a copy +// of this software and associated documentation files (the "Software"), to +// deal in the Software without restriction, including without limitation the +// rights to use, copy, modify, merge, publish, distribute, sublicense, and/or +// sell copies of the Software, and to permit persons to whom the Software is +// furnished to do so, subject to the following conditions: +// +// The above copyright notice and this permission notice shall be included in +// all copies or substantial portions of the Software. +// +// THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR +// IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, +// FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE +// AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER +// LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, +// OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN +// THE SOFTWARE. + +import { HttpClient, HttpErrorResponse } from "@angular/common/http"; +import { + Component, + EventEmitter, + Injectable, + OnDestroy, + OnInit, + Output, + ViewChild, +} from "@angular/core"; +import { GridsterConfig, GridsterItem } from "angular-gridster2"; +import { BehaviorSubject, Observable, Subject } from "rxjs"; +import { finalize, take, takeUntil } from "rxjs/operators"; + +import { + DataSourceService, + IFilteringOutputs, + ToastService, + uuid, +} from "@nova-ui/bits"; +import { + DashboardComponent, + DATA_SOURCE, + DEFAULT_PIZZAGNA_ROOT, + IDashboard, + IDashboardPersistenceHandler, + IDataSourceOutput, + IKpiData, + IProviderConfiguration, + IRefresherProperties, + IWidget, + IWidgets, + IWidgetSelector, + IWidgetTemplateSelector, + KpiComponent, + NOVA_KPI_DATASOURCE_ADAPTER, + PizzagnaLayer, + ProviderRegistryService, + WellKnownPathKey, + WellKnownProviders, + WidgetClonerService, + WidgetTypesService, +} from "@nova-ui/dashboards"; + +// Interface of a widget item +interface IWidgetItem { + name: string; + widget: IWidget; +} + +// This component acts as the first step, or page, in the wizard where the user selects a wizard type to create. +// It's recommended to have this component in a different file. For this tutorial, it's included in the same +// file for simplicity. +@Component({ + selector: "widget-template-selection", + styleUrls: ["./widget-creation.component.less"], + template: \\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\` +
+ + +
+ + +
+
{{ item.name }}
+
+
+ \\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\`, + standalone: false, +}) +export class WidgetTemplateSelectionComponent + implements IWidgetTemplateSelector, OnInit +{ + // This output will notify the wizard that a widget has been selected. + @Output() public widgetSelected = new EventEmitter(); + + public widgetItems: IWidgetItem[] = []; + public widgetSelection: IWidgetItem[]; + + constructor(private widgetTypesService: WidgetTypesService) {} + + public ngOnInit(): void { + // Here we combine the widget structure from the WidgetTypesService with the corresponding widget + // configuration to create an array of widget objects for the itemSource on the repeat component. + this.widgetItems = [ + { + name: "Fully Configured KPI Widget", + widget: this.widgetTypesService.mergeWithWidgetType( + fullKpiWidgetConfig + ), + }, + { + name: "Unconfigured Proportional Widget", + // Note that 'partialPropWidgetConfig' sets 'metadata.needsConfiguration' to true. + // When this widget is selected in the wizard, the 'Create Widget' button will be hidden + // to guide the user to the second step where they can complete the configuration. + widget: this.widgetTypesService.mergeWithWidgetType( + partialPropWidgetConfig + ), + }, + ]; + + // You can optionally auto-select a widget by doing the following + // this.onSelect([this.widgetItems[0]]); + } + + public onSelect(selectedItems: any[]): void { + // We emit the selected widget to communicate the selection to the configurator + this.widgetSelected.emit(selectedItems[0].widget); + this.widgetSelection = selectedItems; + } +} + +/** + * A simple persistence handler that is tied to the widget editor directive + */ +@Injectable() +// The realizer of IDashboardPersistenceHandler may implement a trySubmit and/or a tryRemove method. +export class PersistenceHandler implements IDashboardPersistenceHandler { + // This variable is just to show how to handle error handling. + private persistenceSucceeded: boolean = true; + + // The example uses the toast service to demonstrate the + // invocation of each of the persistence handler callbacks + constructor(private toastService: ToastService) { + // toastService options to let it sit on the page for 2 seconds. + this.toastService.setConfig({ + timeOut: 2000, + }); + } + + // This method will be invoked anytime the widget editor form gets submitted. + public trySubmit = (widget: IWidget): Observable => { + // Since we are working asynchronously, we'll return a subject. So, after the submit attempt + // succeeds or fails, we can let the subscriber know the result. + const subject = new Subject(); + + if (!widget.id) { + // Creates an id if the widget has no id. + // (This step will make more sense in the context of the widget cloning tutorial + // in which we handle the persistence of a newly created widget.) + widget.id = uuid(); + } + + // For this example, we're using a setTimeout to mock an asynchronous persistence request to a backend + setTimeout(() => { + if (this.persistenceSucceeded) { + // Passes along the new widget after one second. + subject.next(widget); + // Toast on the page on success. + this.toastService.success({ + title: $localize\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\`Submit succeeded.\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\`, + }); + } else { + const errorText = $localize\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\`Submit failed.\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\`; + // Toast on the page on failure. + this.toastService.error({ title: errorText }); + // Makes the subject say there is an error. + subject.error(errorText); + } + // Completes the subject so whoever subscribes to it knows its finished. + subject.complete(); + }, 1000); + + // Returns the subject as an observable. + return subject.asObservable(); + }; + + // This method will be invoked anytime there's a widget removal attempt. + public tryRemove = (widgetId: string): Observable => { + const subject = new Subject(); + + setTimeout(() => { + if (this.persistenceSucceeded) { + // Pass through the id of the widget that was removed. + subject.next(widgetId); + this.toastService.success({ + title: $localize\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\`Removal success\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\`, + }); + } else { + const errorText = $localize\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\`Removal failed.\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\`; + this.toastService.error({ title: errorText }); + subject.error(errorText); + } + subject.complete(); + }, 1000); + + return subject.asObservable(); + }; +} + +/** + * A component that instantiates the dashboard + */ +@Component({ + selector: "widget-creation", + templateUrl: "./widget-creation.component.html", + styleUrls: ["./widget-creation.component.less"], + // Here we provide our persistence handler at the component level; this can also be done in the module. + providers: [PersistenceHandler], + standalone: false, +}) +export class WidgetCreationComponent implements OnInit { + // The WidgetClonerService will need this for updating the dashboard + @ViewChild(DashboardComponent, { static: true }) + dashboardComponent: DashboardComponent; + // This variable will hold all the data needed to define the layout and behavior of the widgets. + // Pass this to the dashboard component's dashboard input in the template. + public dashboard: IDashboard; + + // Angular gridster requires a configuration object even if it's empty. + // Pass this to the dashboard component's gridsterConfig input in the template. + public gridsterConfig: GridsterConfig = { + // These values will be used to set the initial widget dimensions on creation. + // If not set, they each default to 6. + defaultItemCols: 3, + defaultItemRows: 5, + }; + + // Boolean the dashboard takes in as an input; if it's set to true + // the dashboard allows you to resize widgets and move them around. + public editMode: boolean = false; + + // Subject used for auto-unsubscribing from subscriptions on component destruction + private readonly destroy$ = new Subject(); + + constructor( + // WidgetTypesService provides the widget's necessary structure information + private widgetTypesService: WidgetTypesService, + + // In general, the ProviderRegistryService is used for making entities available for injection into dynamically loaded components. + private providerRegistry: ProviderRegistryService, + + // Injecting the PersistenceHandler we created and assigning it to a property we use in the template. + public persistenceHandler: PersistenceHandler, + + // Injecting the cloner service which is needed for opening up the cloner wizard. + private widgetClonerService: WidgetClonerService + ) {} + + public ngOnInit(): void { + // Grabbing the widget's default template which will be needed as a parameter for setNode + const kpiTemplate = this.widgetTypesService.getWidgetType("kpi", 1); + const proportionalTemplate = this.widgetTypesService.getWidgetType( + "proportional", + 1 + ); + + // Registering our data sources as dropdown options in the widget editor/configurator + // Note: This could also be done in the parent module's constructor so that + // multiple dashboards could have access to the same widget template modification. + this.widgetTypesService.setNode( + // This is the template we grabbed above with getWidgetType + proportionalTemplate, + // Setting the editor/configurator part of the widget template + "configurator", + // This indicates which node you are changing and we want to change + // the data source providers available for selection in the editor. + WellKnownPathKey.DataSourceProviders, + // Setting the data sources available for selection in the editor + [RandomCitiesProportionalDataSource.providerId] + ); + + // Same as above, but for the KPI data sources + this.widgetTypesService.setNode( + kpiTemplate, + "configurator", + WellKnownPathKey.DataSourceProviders, + [ + AverageRatingKpiDataSource.providerId, + RatingsCountKpiDataSource.providerId, + ] + ); + + // Registering the data sources available for injection into the KPI tiles and proportional widget. + // Note: Each tile of a KPI widget is assigned its own instance of a data source. + this.providerRegistry.setProviders({ + [AverageRatingKpiDataSource.providerId]: { + provide: DATA_SOURCE, + useClass: AverageRatingKpiDataSource, + // Any dependencies that need to be injected into the provider must be listed here + deps: [HttpClient], + }, + [RatingsCountKpiDataSource.providerId]: { + provide: DATA_SOURCE, + useClass: RatingsCountKpiDataSource, + deps: [HttpClient], + }, + [RandomCitiesProportionalDataSource.providerId]: { + provide: DATA_SOURCE, + useClass: RandomCitiesProportionalDataSource, + deps: [], + }, + }); + + this.initializeDashboard(); + } + + public onCreateWidget(): void { + const widgetSelector: IWidgetSelector = { + // Template ref of the dashboard component. + dashboardComponent: this.dashboardComponent, + // A trySubmit function; in this case, we use the trySubmit from the PersistenceHandler created in the previous tutorial. + trySubmit: this.persistenceHandler.trySubmit, + // WidgetTemplateSelectionComponent will act as step one of the wizard to allow the user to select which widget will be cloned. + widgetSelectionComponentType: WidgetTemplateSelectionComponent, + }; + this.widgetClonerService + .open(widgetSelector) + .pipe( + // Auto-unsubscribe after one emission or on component destruction + take(1), + takeUntil(this.destroy$) + ) + .subscribe(); + } + + public initializeDashboard(): void { + // We're using a static configuration object for this example (see widgetConfig at the bottom of the file), + // but this is where the widget's configuration could potentially be populated from a database + const kpiWidget = fullKpiWidgetConfig; + const widgetIndex: IWidgets = { + // Complete the KPI widget with information coming from its type definition + [kpiWidget.id]: + this.widgetTypesService.mergeWithWidgetType(kpiWidget), + }; + + // Setting the widget dimensions and position (this is for gridster) + // Note: If no position is given for a widget the 'defaultItemCols' and 'defaultItemRows' properties + // from the gridsterConfig will be used for the dimensions + const positions: Record = { + [kpiWidget.id]: { + cols: 3, + rows: 5, + y: 0, + x: 0, + }, + }; + + // Finally, assigning the variables we created above to the dashboard + this.dashboard = { + positions, + widgets: widgetIndex, + }; + } +} + +// Interface for each data point in a proportional widget. +interface IProportionalWidgetData { + id: string; + name: string; + data: number[]; + icon: string; + link: string; + value: string; +} + +@Injectable() +export class RandomCitiesProportionalDataSource implements OnDestroy { + public static providerId = "RandomCitiesProportionalDataSource"; + + public outputsSubject = new Subject< + IDataSourceOutput + >(); + + // Every time applyFilters gets ran we are changing the data source. + public applyFilters(): void { + setTimeout(() => { + this.outputsSubject.next({ + result: this.getRandomProportionalWidgetData(), + }); + }, 1000); + } + + public ngOnDestroy(): void { + this.outputsSubject.complete(); + } + + private getRandomProportionalWidgetData(): IProportionalWidgetData[] { + return [ + { + id: "Down", + name: "Down", + data: [Math.round(Math.random() * 100)], + icon: "status_down", + link: "https://en.wikipedia.org/wiki/Brno", + value: "Brno", + }, + { + id: "Critical", + name: "Critical", + data: [Math.round(Math.random() * 100)], + icon: "status_critical", + link: "https://en.wikipedia.org/wiki/Kyiv", + value: "Kyiv", + }, + { + id: "Warning", + name: "Warning", + data: [Math.round(Math.random() * 100)], + icon: "status_warning", + link: "https://en.wikipedia.org/wiki/Austin", + value: "Austin", + }, + { + id: "Unknown", + name: "Unknown", + data: [Math.round(Math.random() * 100)], + icon: "status_unknown", + link: "https://en.wikipedia.org/wiki/Lisbon", + value: "Lisbon", + }, + { + id: "Up", + name: "Up", + data: [Math.round(Math.random() * 100)], + icon: "status_up", + link: "https://en.wikipedia.org/wiki/Sydney", + value: "Sydney", + }, + { + id: "Unmanaged", + name: "Unmanaged", + data: [Math.round(Math.random() * 100)], + icon: "status_unmanaged", + link: "https://en.wikipedia.org/wiki/Nur-Sultan", + value: "Nur-Sultan", + }, + ]; + } +} + +/** + * A simple KPI data source to retrieve the average rating of Harry Potter and the Sorcerer's Stone (book) via googleapis + */ +@Injectable() +export class AverageRatingKpiDataSource + extends DataSourceService + implements OnDestroy +{ + // This is the ID we'll use to identify the provider + public static providerId = "AverageRatingKpiDataSource"; + + // Use this subject to communicate the data source's busy state + public busy = new BehaviorSubject(false); + + constructor(private http: HttpClient) { + super(); + } + + // In this example, getFilteredData is invoked every 10 minutes (Take a look at the refresher + // provider definition in the widget configuration below to see how the interval is set) + public async getFilteredData(): Promise { + this.busy.next(true); + return new Promise((resolve) => { + // *** Make a resource request to an external API (if needed) + this.http + .get("https://www.googleapis.com/books/v1/volumes/5MQFrgEACAAJ") + .pipe(finalize(() => this.busy.next(false))) + .subscribe({ + next: (data: any) => { + resolve({ + result: { + value: data.volumeInfo.averageRating, + }, + }); + }, + error: (error: HttpErrorResponse) => { + resolve({ + result: null, + error: { + type: error.status, + }, + }); + }, + }); + }); + } + + public ngOnDestroy(): void { + this.outputsSubject.complete(); + } +} + +/** + * A simple KPI data source to retrieve the ratings count of Harry Potter and the Sorcerer's Stone (book) via googleapis + */ +@Injectable() +export class RatingsCountKpiDataSource + extends DataSourceService + implements OnDestroy +{ + public static providerId = "RatingsCountKpiDataSource"; + + // Use this subject to communicate the data source's busy state + public busy = new BehaviorSubject(false); + + constructor(private http: HttpClient) { + super(); + } + + public async getFilteredData(): Promise { + this.busy.next(true); + return new Promise((resolve) => { + this.http + .get("https://www.googleapis.com/books/v1/volumes/5MQFrgEACAAJ") + .pipe(finalize(() => this.busy.next(false))) + .subscribe({ + next: (data: any) => { + resolve({ + result: { + value: data.volumeInfo.ratingsCount, + }, + }); + }, + error: (error: HttpErrorResponse) => { + resolve({ + result: null, + error: { + type: error.status, + }, + }); + }, + }); + }); + } + + public ngOnDestroy(): void { + this.outputsSubject.complete(); + } +} + +const fullKpiWidgetConfig: IWidget = { + id: "widget1", + type: "kpi", + pizzagna: { + [PizzagnaLayer.Configuration]: { + [DEFAULT_PIZZAGNA_ROOT]: { + providers: { + [WellKnownProviders.Refresher]: { + properties: { + // Configuring the refresher interval so that our data source is invoked every ten minutes + interval: 60 * 10, + enabled: true, + } as IRefresherProperties, + } as Partial, + }, + }, + header: { + properties: { + title: "Harry Potter and the Sorcerer's Stone", + subtitle: "By J. K. Rowling", + }, + }, + tiles: { + properties: { + nodes: ["kpi1"], + }, + }, + kpi1: { + id: "kpi1", + componentType: KpiComponent.lateLoadKey, + properties: { + widgetData: { + units: \\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\`out of 5 Stars\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\`, + label: \\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\`Average Rating\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\`, + }, + }, + providers: { + [WellKnownProviders.DataSource]: { + // Setting the data source providerId for the tile with id "kpi1" + providerId: AverageRatingKpiDataSource.providerId, + } as IProviderConfiguration, + [WellKnownProviders.Adapter]: { + providerId: NOVA_KPI_DATASOURCE_ADAPTER, + properties: { + componentId: "kpi1", + propertyPath: "widgetData", + }, + } as IProviderConfiguration, + }, + }, + }, + }, +}; + +const partialPropWidgetConfig: IWidget = { + id: "widget2", + type: "proportional", + metadata: { + // Set 'needsConfiguration' to true if the widget needs further configuration before it can be + // placed on the dashboard. The "Create Widget" button will be hidden in the wizard when this + // widget is selected. + needsConfiguration: true, + }, + pizzagna: { + [PizzagnaLayer.Configuration]: { + header: { + properties: { + title: "*New Proportional Widget*", + }, + }, + }, + }, +}; +\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\`, + "tutorials/widget-creation/widget-creation.module.ts": \\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\`// © 2022 SolarWinds Worldwide, LLC. All rights reserved. +// +// Permission is hereby granted, free of charge, to any person obtaining a copy +// of this software and associated documentation files (the "Software"), to +// deal in the Software without restriction, including without limitation the +// rights to use, copy, modify, merge, publish, distribute, sublicense, and/or +// sell copies of the Software, and to permit persons to whom the Software is +// furnished to do so, subject to the following conditions: +// +// The above copyright notice and this permission notice shall be included in +// all copies or substantial portions of the Software. +// +// THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR +// IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, +// FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE +// AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER +// LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, +// OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN +// THE SOFTWARE. + +import { CommonModule } from "@angular/common"; +import { HttpClientModule } from "@angular/common/http"; +import { NgModule } from "@angular/core"; +import { RouterModule } from "@angular/router"; + +import { + NuiButtonModule, + NuiDocsModule, + NuiImageModule, + NuiMessageModule, + NuiRepeatModule, + NuiSwitchModule, + NuiToastModule, + DEMO_PATH_TOKEN, +} from "@nova-ui/bits"; +import { NuiDashboardsModule } from "@nova-ui/dashboards"; + +import { WidgetCreationDocsComponent } from "./widget-creation-docs.component"; +import { + WidgetCreationComponent, + WidgetTemplateSelectionComponent, +} from "./widget-creation.component"; +import { getDemoFiles } from "../../../../demo-files-factory"; + +const routes = [ + { + path: "", + component: WidgetCreationDocsComponent, + data: { + srlc: { + hideIndicator: true, + }, + showThemeSwitcher: true, + }, + }, + { + path: "example", + component: WidgetCreationComponent, + data: { + srlc: { + hideIndicator: true, + }, + }, + }, +]; + +@NgModule({ + imports: [ + CommonModule, + HttpClientModule, + NuiDashboardsModule, + NuiDocsModule, + NuiMessageModule, + NuiSwitchModule, + NuiToastModule, + NuiButtonModule, + NuiRepeatModule, + NuiImageModule, + RouterModule.forChild(routes), + ], + declarations: [ + WidgetCreationDocsComponent, + WidgetCreationComponent, + WidgetTemplateSelectionComponent, + ], + providers: [ + { + provide: DEMO_PATH_TOKEN, + useValue: getDemoFiles("widget-creation"), + }, + ], +}) +export default class WidgetCreationModule {} +\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\`, + "tutorials/widget-editor-setup/widget-editor-setup-docs.component.ts": \\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\`// © 2022 SolarWinds Worldwide, LLC. All rights reserved. +// +// Permission is hereby granted, free of charge, to any person obtaining a copy +// of this software and associated documentation files (the "Software"), to +// deal in the Software without restriction, including without limitation the +// rights to use, copy, modify, merge, publish, distribute, sublicense, and/or +// sell copies of the Software, and to permit persons to whom the Software is +// furnished to do so, subject to the following conditions: +// +// The above copyright notice and this permission notice shall be included in +// all copies or substantial portions of the Software. +// +// THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR +// IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, +// FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE +// AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER +// LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, +// OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN +// THE SOFTWARE. + +import { Component } from "@angular/core"; + +@Component({ + selector: "nui-dashboard-widget-editor-docs", + templateUrl: "./widget-editor-setup-docs.component.html", + standalone: false, +}) +export class WidgetEditorDocsComponent {} +\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\`, + "tutorials/widget-editor-setup/widget-editor-setup.component.ts": \\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\`// © 2022 SolarWinds Worldwide, LLC. All rights reserved. +// +// Permission is hereby granted, free of charge, to any person obtaining a copy +// of this software and associated documentation files (the "Software"), to +// deal in the Software without restriction, including without limitation the +// rights to use, copy, modify, merge, publish, distribute, sublicense, and/or +// sell copies of the Software, and to permit persons to whom the Software is +// furnished to do so, subject to the following conditions: +// +// The above copyright notice and this permission notice shall be included in +// all copies or substantial portions of the Software. +// +// THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR +// IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, +// FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE +// AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER +// LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, +// OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN +// THE SOFTWARE. + +import { HttpClient, HttpErrorResponse } from "@angular/common/http"; +import { + ChangeDetectorRef, + Component, + Injectable, + OnDestroy, + OnInit, +} from "@angular/core"; +import { GridsterConfig, GridsterItem } from "angular-gridster2"; +import { BehaviorSubject } from "rxjs"; +import { finalize } from "rxjs/operators"; + +import { DataSourceService, IFilteringOutputs } from "@nova-ui/bits"; +import { + DATA_SOURCE, + DEFAULT_PIZZAGNA_ROOT, + IDashboard, + IKpiData, + IProviderConfiguration, + IRefresherProperties, + IWidget, + IWidgets, + KpiComponent, + NOVA_KPI_DATASOURCE_ADAPTER, + PizzagnaLayer, + ProviderRegistryService, + WellKnownPathKey, + WellKnownProviders, + WidgetTypesService, +} from "@nova-ui/dashboards"; + +/** + * A simple KPI data source to retrieve the average rating of Harry Potter and the Sorcerer's Stone (book) via googleapis + */ +@Injectable() +export class AverageRatingKpiDataSource + extends DataSourceService + implements OnDestroy +{ + // This is the ID we'll use to identify the provider + public static providerId = "AverageRatingKpiDataSource"; + + // Use this subject to communicate the data source's busy state + public busy = new BehaviorSubject(false); + + constructor(private http: HttpClient) { + super(); + } + + // In this example, getFilteredData is invoked every 10 minutes (Take a look at the refresher + // provider definition in the widget configuration below to see how the interval is set) + public async getFilteredData(): Promise { + this.busy.next(true); + return new Promise((resolve) => { + // *** Make a resource request to an external API (if needed) + this.http + .get("https://www.googleapis.com/books/v1/volumes/5MQFrgEACAAJ") + .pipe(finalize(() => this.busy.next(false))) + .subscribe({ + next: (data: any) => { + resolve({ + result: { + value: data.volumeInfo.averageRating, + }, + }); + }, + error: (error: HttpErrorResponse) => { + resolve({ + result: null, + error: { + type: error.status, + }, + }); + }, + }); + }); + } + + public ngOnDestroy(): void { + this.outputsSubject.complete(); + } +} + +/** + * A simple KPI data source to retrieve the ratings count of Harry Potter and the Sorcerer's Stone (book) via googleapis + */ +@Injectable() +export class RatingsCountKpiDataSource + extends DataSourceService + implements OnDestroy +{ + public static providerId = "RatingsCountKpiDataSource"; + + // Use this subject to communicate the data source's busy state + public busy = new BehaviorSubject(false); + + constructor(private http: HttpClient) { + super(); + } + + public async getFilteredData(): Promise { + this.busy.next(true); + return new Promise((resolve) => { + this.http + .get("https://www.googleapis.com/books/v1/volumes/5MQFrgEACAAJ") + .pipe(finalize(() => this.busy.next(false))) + .subscribe({ + next: (data: any) => { + resolve({ + result: { + value: data.volumeInfo.ratingsCount, + }, + }); + }, + error: (error: HttpErrorResponse) => { + resolve({ + result: null, + error: { + type: error.status, + }, + }); + }, + }); + }); + } + + public ngOnDestroy(): void { + this.outputsSubject.complete(); + } +} + +/** + * A component that instantiates the dashboard + */ +@Component({ + selector: "widget-editor-setup", + templateUrl: "./widget-editor-setup.component.html", + styleUrls: ["./widget-editor-setup.component.less"], + standalone: false, +}) +export class WidgetEditorSetupComponent implements OnInit { + // This variable will hold all the data needed to define the layout and behavior of the widgets. + // Pass this to the dashboard component's dashboard input in the template. + public dashboard: IDashboard | undefined; + + // Angular gridster requires a configuration object even if it's empty. + // Pass this to the dashboard component's gridsterConfig input in the template. + public gridsterConfig: GridsterConfig = {}; + + // Boolean which dashboard takes in as an input if its true it allows you to move widgets around. + public editMode: boolean = false; + + constructor( + // WidgetTypesService provides the widget's necessary structure information + private widgetTypesService: WidgetTypesService, + + // In general, the ProviderRegistryService is used for making entities available for injection into dynamically loaded components. + private providerRegistry: ProviderRegistryService, + private changeDetectorRef: ChangeDetectorRef + ) {} + + public ngOnInit(): void { + // Grabbing the widget's default template which will be needed as a parameter for setNode + const widgetTemplate = this.widgetTypesService.getWidgetType("kpi", 1); + // Registering our data sources as dropdown options in the widget editor/configurator + // Note: This could also be done in the parent module's constructor so that + // multiple dashboards could have access to the same widget template modification. + this.widgetTypesService.setNode( + // This is the template we grabbed above with getWidgetType + widgetTemplate, + // We are setting the editor/configurator part of the widget template + "configurator", + // This indicates which node you are changing and we want to change + // the data source providers available for selection in the editor. + WellKnownPathKey.DataSourceProviders, + // We are setting the data sources available for selection in the editor + [ + AverageRatingKpiDataSource.providerId, + RatingsCountKpiDataSource.providerId, + ] + ); + + // Registering the data sources available for injection into the KPI tiles. + // Note: Each tile of a KPI widget is assigned its own instance of a data source + this.providerRegistry.setProviders({ + [AverageRatingKpiDataSource.providerId]: { + provide: DATA_SOURCE, + useClass: AverageRatingKpiDataSource, + // Any dependencies that need to be injected into the provider must be listed here + deps: [HttpClient], + }, + [RatingsCountKpiDataSource.providerId]: { + provide: DATA_SOURCE, + useClass: RatingsCountKpiDataSource, + deps: [HttpClient], + }, + }); + + this.initializeDashboard(); + } + + /** Used for restoring widgets state */ + public reInitializeDashboard(): void { + // destroys the components and their providers so the dashboard can re init data + this.dashboard = undefined; + this.changeDetectorRef.detectChanges(); + + this.initializeDashboard(); + } + + public initializeDashboard(): void { + // We're using a static configuration object for this example (see widgetConfig at the bottom of the file), + // but this is where the widget's configuration could potentially be populated from a database + const kpiWidget = widgetConfig; + const widgetIndex: IWidgets = { + // Complete the KPI widget with information coming from its type definition + [kpiWidget.id]: + this.widgetTypesService.mergeWithWidgetType(kpiWidget), + }; + + // Setting the widget dimensions and position (this is for gridster) + const positions: Record = { + [kpiWidget.id]: { + cols: 4, + rows: 6, + y: 0, + x: 0, + }, + }; + + // Finally, assigning the variables we created above to the dashboard + this.dashboard = { + positions, + widgets: widgetIndex, + }; + } +} + +const widgetConfig: IWidget = { + id: "widget1", + type: "kpi", + pizzagna: { + [PizzagnaLayer.Configuration]: { + [DEFAULT_PIZZAGNA_ROOT]: { + providers: { + [WellKnownProviders.Refresher]: { + properties: { + // Configuring the refresher interval so that our data source is invoked every ten minutes + interval: 60 * 10, + enabled: true, + } as IRefresherProperties, + } as Partial, + }, + }, + header: { + properties: { + title: "Harry Potter and the Sorcerer's Stone", + subtitle: "By J. K. Rowling", + }, + }, + tiles: { + properties: { + nodes: ["kpi1"], + }, + }, + kpi1: { + id: "kpi1", + componentType: KpiComponent.lateLoadKey, + properties: { + widgetData: { + units: "out of 5 Stars", + label: "Average Rating", + }, + }, + providers: { + [WellKnownProviders.DataSource]: { + // Setting the data source providerId for the tile with id "kpi1" + providerId: AverageRatingKpiDataSource.providerId, + } as IProviderConfiguration, + [WellKnownProviders.Adapter]: { + providerId: NOVA_KPI_DATASOURCE_ADAPTER, + properties: { + componentId: "kpi1", + propertyPath: "widgetData", + }, + } as IProviderConfiguration, + }, + }, + }, + }, +}; +\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\`, + "tutorials/widget-editor-setup/widget-editor-setup.module.ts": \\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\`// © 2022 SolarWinds Worldwide, LLC. All rights reserved. +// +// Permission is hereby granted, free of charge, to any person obtaining a copy +// of this software and associated documentation files (the "Software"), to +// deal in the Software without restriction, including without limitation the +// rights to use, copy, modify, merge, publish, distribute, sublicense, and/or +// sell copies of the Software, and to permit persons to whom the Software is +// furnished to do so, subject to the following conditions: +// +// The above copyright notice and this permission notice shall be included in +// all copies or substantial portions of the Software. +// +// THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR +// IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, +// FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE +// AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER +// LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, +// OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN +// THE SOFTWARE. + +import { CommonModule } from "@angular/common"; +import { HttpClientModule } from "@angular/common/http"; +import { NgModule } from "@angular/core"; +import { RouterModule } from "@angular/router"; + +import { + NuiButtonModule, + NuiDocsModule, + NuiMessageModule, + NuiSwitchModule, + DEMO_PATH_TOKEN, +} from "@nova-ui/bits"; +import { NuiDashboardsModule } from "@nova-ui/dashboards"; + +import { WidgetEditorDocsComponent } from "./widget-editor-setup-docs.component"; +import { WidgetEditorSetupComponent } from "./widget-editor-setup.component"; +import { getDemoFiles } from "../../../../demo-files-factory"; + +const routes = [ + { + path: "", + component: WidgetEditorDocsComponent, + data: { + srlc: { + hideIndicator: true, + }, + showThemeSwitcher: true, + }, + }, + { + path: "example", + component: WidgetEditorSetupComponent, + data: { + srlc: { + hideIndicator: true, + }, + }, + }, +]; + +@NgModule({ + imports: [ + CommonModule, + HttpClientModule, + NuiDashboardsModule, + NuiDocsModule, + NuiMessageModule, + NuiSwitchModule, + NuiButtonModule, + RouterModule.forChild(routes), + ], + declarations: [WidgetEditorDocsComponent, WidgetEditorSetupComponent], + providers: [ + { + provide: DEMO_PATH_TOKEN, + useValue: getDemoFiles("widget-editor-setup"), + }, + ], +}) +export default class WidgetEditorSetupModule {} +\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\`, + "tutorials/widget-error-handling/widget-error-handling-docs.component.ts": \\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\`// © 2022 SolarWinds Worldwide, LLC. All rights reserved. +// +// Permission is hereby granted, free of charge, to any person obtaining a copy +// of this software and associated documentation files (the "Software"), to +// deal in the Software without restriction, including without limitation the +// rights to use, copy, modify, merge, publish, distribute, sublicense, and/or +// sell copies of the Software, and to permit persons to whom the Software is +// furnished to do so, subject to the following conditions: +// +// The above copyright notice and this permission notice shall be included in +// all copies or substantial portions of the Software. +// +// THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR +// IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, +// FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE +// AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER +// LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, +// OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN +// THE SOFTWARE. + +import { Component } from "@angular/core"; + +@Component({ + selector: "nui-widget-error-handling-docs", + templateUrl: "./widget-error-handling-docs.component.html", + standalone: false, +}) +export class WidgetErrorHandlingDocsComponent { + public fallbackAdapter = \\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\` +@Injectable() +export class StatusContentFallbackAdapter implements OnDestroy, IHasComponent { + + protected readonly destroy$ = new Subject(); + protected componentId: string; + + constructor(@Inject(PIZZAGNA_EVENT_BUS) protected eventBus: EventBus, + protected pizzagnaService: PizzagnaService) { + this.eventBus.getStream(DATA_SOURCE_OUTPUT) + .pipe(takeUntil(this.destroy$)).subscribe((event: IEvent>) => { + this.handleDataSourceOutput(event); + }); + } + + public ngOnDestroy(): void { + this.destroy$.next(); + this.destroy$.complete(); + } + + public setComponent(component: any, componentId: string) { + this.componentId = componentId; + } + + protected handleDataSourceOutput(event: IEvent>) { + this.pizzagnaService.setProperty({ + componentId: this.componentId, + propertyPath: ["fallbackKey"], + pizzagnaKey: PizzagnaLayer.Data, + }, typeof event.payload?.error?.type !== "undefined" ? event.payload?.error?.type.toString() : undefined); + } +}\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\`; + public errorsMap = \\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\` +export const ERROR_FALLBACK_MAP: Record = { + [HttpStatusCode.Unknown]: ErrorNodeKey.ErrorUnknown, + [HttpStatusCode.Forbidden]: ErrorNodeKey.ErrorForbidden, + [HttpStatusCode.NotFound]: ErrorNodeKey.ErrorNotFound, +}; +\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\`; + public errorNodes = \\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\` +export const ERROR_NODES: Record = { + [ErrorNodeKey.ErrorUnknown]: { + id: ErrorNodeKey.ErrorUnknown, + componentType: WidgetErrorComponent.lateLoadKey, + properties: { + image: "no-data-to-show", + title: $localize\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\`Whoops, something went wrong\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\`, + description: $localize\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\`There was an unexpected error.\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\`, + } as IWidgetErrorDisplayProperties, + }, + [ErrorNodeKey.ErrorForbidden]: { + id: ErrorNodeKey.ErrorForbidden, + componentType: WidgetErrorComponent.lateLoadKey, + properties: { + image: "no-data-to-show", + title: $localize\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\`403 - Forbidden\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\`, + description: $localize\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\`The requested action was forbidden.\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\`, + } as IWidgetErrorDisplayProperties, + }, + [ErrorNodeKey.ErrorNotFound]: { + id: ErrorNodeKey.ErrorNotFound, + componentType: WidgetErrorComponent.lateLoadKey, + properties: { + image: "no-data-to-show", + title: $localize\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\`404 - Not Found\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\`, + description: $localize\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\`The requested resource could not be found.\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\`, + } as IWidgetErrorDisplayProperties, + }, +};\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\`; + public widgetBodyContentNodesSignature = \\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\` +/** + * Retrieves an index of the basic widget body content nodes including fallback nodes + * + * @param mainContentNodeKey The key corresponding to the main body content node + * @param fallbackAdapterId The id for the adapter responsible for activating fallback content in case of an error + * @param fallbackMap A map of node keys to fallback content definitions + * @param fallbackNodes An index of fallback content definitions + * + * @returns An index of component configurations + */ +export function widgetBodyContentNodes( + mainContentNodeKey: string, + fallbackAdapterId = NOVA_STATUS_CONTENT_FALLBACK_ADAPTER, + fallbackMap: Record = ERROR_FALLBACK_MAP, + fallbackNodes: Record = ERROR_NODES +): Record { ... } +\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\`; +} +\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\`, + "tutorials/widget-error-handling/widget-error-handling.component.ts": \\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\`// © 2022 SolarWinds Worldwide, LLC. All rights reserved. +// +// Permission is hereby granted, free of charge, to any person obtaining a copy +// of this software and associated documentation files (the "Software"), to +// deal in the Software without restriction, including without limitation the +// rights to use, copy, modify, merge, publish, distribute, sublicense, and/or +// sell copies of the Software, and to permit persons to whom the Software is +// furnished to do so, subject to the following conditions: +// +// The above copyright notice and this permission notice shall be included in +// all copies or substantial portions of the Software. +// +// THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR +// IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, +// FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE +// AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER +// LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, +// OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN +// THE SOFTWARE. + +import { HttpClient, HttpErrorResponse } from "@angular/common/http"; +import { + ChangeDetectorRef, + Component, + Injectable, + OnDestroy, + OnInit, +} from "@angular/core"; +import { GridsterConfig, GridsterItem } from "angular-gridster2"; +import { BehaviorSubject } from "rxjs"; +import { finalize } from "rxjs/operators"; + +import { DataSourceService, IFilteringOutputs } from "@nova-ui/bits"; +import { + DATA_SOURCE, + DEFAULT_PIZZAGNA_ROOT, + HttpStatusCode, + IDashboard, + IKpiData, + IProviderConfiguration, + IRefresherProperties, + IWidget, + IWidgets, + KpiComponent, + NOVA_KPI_DATASOURCE_ADAPTER, + PizzagnaLayer, + ProviderRegistryService, + WellKnownPathKey, + WellKnownProviders, + WidgetTypesService, +} from "@nova-ui/dashboards"; + +/** + * A simple KPI data source to retrieve the average rating of Harry Potter and the Sorcerer's Stone (book) via googleapis + */ +@Injectable() +export class AverageRatingKpiDataSource + extends DataSourceService + implements OnDestroy +{ + // This is the ID we'll use to identify the provider + public static providerId = "AverageRatingKpiDataSource"; + + // Use this subject to communicate the data source's busy state + public busy = new BehaviorSubject(false); + + constructor(private http: HttpClient) { + super(); + } + + // In this example, getFilteredData is invoked every 10 minutes (Take a look at the refresher + // provider definition in the widget configuration below to see how the interval is set) + public async getFilteredData(): Promise { + this.busy.next(true); + return new Promise((resolve) => { + // *** Make a resource request to an external API (if needed) + this.http + .get("https://www.googleapis.com/books/v1/volumes/5MQFrgEACAAJ") + .pipe(finalize(() => this.busy.next(false))) + .subscribe({ + next: (data: any) => { + resolve({ + result: { + value: data.volumeInfo.averageRating, + }, + }); + }, + error: (error: HttpErrorResponse) => { + resolve({ + result: null, + error: { + type: error.status, + }, + }); + }, + }); + }); + } + + public ngOnDestroy(): void { + this.outputsSubject.complete(); + } +} + +/** + * A simple KPI data source to retrieve the average rating of Harry Potter and the Sorcerer's Stone (book) via googleapis + */ +@Injectable() +export class ErrorUnknownDataSource + extends DataSourceService + implements OnDestroy +{ + // This is the ID we'll use to identify the provider + public static providerId = "ErrorUnknownDataSource"; + + // Use this subject to communicate the data source's busy state + public busy = new BehaviorSubject(false); + + // In this example, getFilteredData is invoked every 10 minutes (Take a look at the refresher + // provider definition in the widget configuration below to see how the interval is set) + public async getFilteredData(): Promise { + this.busy.next(true); + const mockError = { + result: null, + error: { type: HttpStatusCode.Unknown }, + }; + this.busy.next(false); + return mockError; + } + + public ngOnDestroy(): void { + this.outputsSubject.complete(); + } +} + +/** + * A simple KPI data source to retrieve the ratings count of Harry Potter and the Sorcerer's Stone (book) via googleapis + */ +@Injectable() +export class ErrorForbiddenDataSource + extends DataSourceService + implements OnDestroy +{ + public static providerId = "ErrorForbiddenDataSource"; + + // Use this subject to communicate the data source's busy state + public busy = new BehaviorSubject(false); + + constructor(private http: HttpClient) { + super(); + } + + public async getFilteredData(): Promise { + this.busy.next(true); + // generate a 403 + return new Promise((resolve) => { + this.http + .get( + "http://www.mocky.io/v2/5ecc724a3200000f0023614a?mocky-delay=4000ms" + ) + .pipe(finalize(() => this.busy.next(false))) + .subscribe({ + error: (error: HttpErrorResponse) => { + resolve({ + result: null, + error: { + type: error.status, + }, + }); + }, + }); + }); + } + + public ngOnDestroy(): void { + this.outputsSubject.complete(); + } +} + +/** + * A simple KPI data source to retrieve the ratings count of Harry Potter and the Sorcerer's Stone (book) via googleapis + */ +@Injectable() +export class ErrorNotFoundDataSource + extends DataSourceService + implements OnDestroy +{ + public static providerId = "ErrorNotFoundDataSource"; + + // Use this subject to communicate the data source's busy state + public busy = new BehaviorSubject(false); + + constructor(private http: HttpClient) { + super(); + } + + public async getFilteredData(): Promise { + this.busy.next(true); + // generate a 404 + return new Promise((resolve) => { + this.http + .get( + "http://www.mocky.io/v2/5ec6bfd93200007800d75100?mocky-delay=1000ms" + ) + .pipe(finalize(() => this.busy.next(false))) + .subscribe({ + error: (error: HttpErrorResponse) => { + resolve({ + result: null, + error: { + type: error.status, + }, + }); + }, + }); + }); + } + + public ngOnDestroy(): void { + this.outputsSubject.complete(); + } +} + +/** + * A component that instantiates the dashboard + */ +@Component({ + selector: "widget-error-handling", + templateUrl: "./widget-error-handling.component.html", + styleUrls: ["./widget-error-handling.component.less"], + standalone: false, +}) +export class WidgetErrorHandlingComponent implements OnInit { + // This variable will hold all the data needed to define the layout and behavior of the widgets. + // Pass this to the dashboard component's dashboard input in the template. + public dashboard: IDashboard | undefined; + + // Angular gridster requires a configuration object even if it's empty. + // Pass this to the dashboard component's gridsterConfig input in the template. + public gridsterConfig: GridsterConfig = {}; + + // Boolean which dashboard takes in as an input if its true it allows you to move widgets around. + public editMode: boolean = false; + + constructor( + // WidgetTypesService provides the widget's necessary structure information + private widgetTypesService: WidgetTypesService, + + // In general, the ProviderRegistryService is used for making entities available for injection into dynamically loaded components. + private providerRegistry: ProviderRegistryService, + private changeDetectorRef: ChangeDetectorRef + ) {} + + public ngOnInit(): void { + // Grab the widget's default template which will be needed as a parameter for setNode. + const widgetTemplate = this.widgetTypesService.getWidgetType("kpi", 1); + // Register our data sources as dropdown options in the widget editor/configurator + // Note: This could also be done in the parent module's constructor so that + // multiple dashboards could have access to the same widget template modification. + this.widgetTypesService.setNode( + // This is the template we grabbed above with getWidgetType + widgetTemplate, + // We are setting the editor/configurator part of the widget template + "configurator", + // This indicates which node you are changing and we want to change + // the data source providers available for selection in the editor. + WellKnownPathKey.DataSourceProviders, + // We are setting the data sources available for selection in the editor + [ + ErrorUnknownDataSource.providerId, + ErrorForbiddenDataSource.providerId, + ErrorNotFoundDataSource.providerId, + AverageRatingKpiDataSource.providerId, + ] + ); + + // Register the data sources available for injection into the KPI tiles. + // Note: Each tile of a KPI widget is assigned its own instance of a data source + this.providerRegistry.setProviders({ + [ErrorUnknownDataSource.providerId]: { + provide: DATA_SOURCE, + useClass: ErrorUnknownDataSource, + // Any dependencies that need to be injected into the provider must be listed here + deps: [HttpClient], + }, + [ErrorForbiddenDataSource.providerId]: { + provide: DATA_SOURCE, + useClass: ErrorForbiddenDataSource, + deps: [HttpClient], + }, + [ErrorNotFoundDataSource.providerId]: { + provide: DATA_SOURCE, + useClass: ErrorNotFoundDataSource, + deps: [HttpClient], + }, + [AverageRatingKpiDataSource.providerId]: { + provide: DATA_SOURCE, + useClass: AverageRatingKpiDataSource, + // Any dependencies that need to be injected into the provider must be listed here + deps: [HttpClient], + }, + }); + + this.initializeDashboard(); + } + + /** Used for restoring widgets state */ + public reInitializeDashboard(): void { + // destroys the components and their providers so the dashboard can re init data + this.dashboard = undefined; + this.changeDetectorRef.detectChanges(); + + this.initializeDashboard(); + } + + public initializeDashboard(): void { + // We're using a static configuration object for this example (see widgetConfig at the bottom of the file), + // but this is where the widget's configuration could potentially be populated from a database + const kpiWidget = widgetConfig; + const widgetIndex: IWidgets = { + // Complete the KPI widget with information coming from its type definition + [kpiWidget.id]: + this.widgetTypesService.mergeWithWidgetType(kpiWidget), + }; + + // Setting the widget dimensions and position (this is for gridster) + const positions: Record = { + [kpiWidget.id]: { + cols: 4, + rows: 6, + y: 0, + x: 0, + }, + }; + + // Finally, assigning the variables we created above to the dashboard + this.dashboard = { + positions, + widgets: widgetIndex, + }; + } +} + +const widgetConfig: IWidget = { + id: "widget1", + type: "kpi", + pizzagna: { + [PizzagnaLayer.Configuration]: { + [DEFAULT_PIZZAGNA_ROOT]: { + providers: { + [WellKnownProviders.Refresher]: { + properties: { + // Configuring the refresher interval so that our data source is invoked every ten minutes + interval: 60 * 10, + enabled: true, + } as IRefresherProperties, + } as Partial, + }, + }, + header: { + properties: { + title: "Harry Potter and the Sorcerer's Stone", + subtitle: "By J. K. Rowling", + }, + }, + tiles: { + properties: { + nodes: ["kpi1"], + }, + }, + kpi1: { + id: "kpi1", + componentType: KpiComponent.lateLoadKey, + properties: { + widgetData: { + units: "out of 5 Stars", + label: "Average Rating", + }, + }, + providers: { + [WellKnownProviders.DataSource]: { + // Setting the data source providerId for the tile with id "kpi1" + providerId: ErrorUnknownDataSource.providerId, + } as IProviderConfiguration, + [WellKnownProviders.Adapter]: { + providerId: NOVA_KPI_DATASOURCE_ADAPTER, + properties: { + componentId: "kpi1", + propertyPath: "widgetData", + }, + } as IProviderConfiguration, + }, + }, + }, + }, +}; +\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\`, + "tutorials/widget-error-handling/widget-error-handling.module.ts": \\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\`// © 2022 SolarWinds Worldwide, LLC. All rights reserved. +// +// Permission is hereby granted, free of charge, to any person obtaining a copy +// of this software and associated documentation files (the "Software"), to +// deal in the Software without restriction, including without limitation the +// rights to use, copy, modify, merge, publish, distribute, sublicense, and/or +// sell copies of the Software, and to permit persons to whom the Software is +// furnished to do so, subject to the following conditions: +// +// The above copyright notice and this permission notice shall be included in +// all copies or substantial portions of the Software. +// +// THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR +// IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, +// FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE +// AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER +// LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, +// OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN +// THE SOFTWARE. + +import { CommonModule } from "@angular/common"; +import { HttpClientModule } from "@angular/common/http"; +import { NgModule } from "@angular/core"; +import { ReactiveFormsModule } from "@angular/forms"; +import { RouterModule } from "@angular/router"; + +import { + NuiButtonModule, + NuiDocsModule, + NuiFormFieldModule, + NuiIconModule, + NuiMessageModule, + NuiSwitchModule, + NuiTextboxModule, + DEMO_PATH_TOKEN, +} from "@nova-ui/bits"; +import { + NuiDashboardConfiguratorModule, + NuiDashboardsModule, +} from "@nova-ui/dashboards"; + +import { WidgetErrorHandlingDocsComponent } from "./widget-error-handling-docs.component"; +import { WidgetErrorHandlingComponent } from "./widget-error-handling.component"; +import { getDemoFiles } from "../../../../demo-files-factory"; + +const routes = [ + { + path: "", + component: WidgetErrorHandlingDocsComponent, + data: { + srlc: { + hideIndicator: true, + }, + showThemeSwitcher: true, + }, + }, + { + path: "example", + component: WidgetErrorHandlingComponent, + data: { + srlc: { + hideIndicator: true, + }, + }, + }, +]; + +@NgModule({ + imports: [ + CommonModule, + ReactiveFormsModule, + HttpClientModule, + NuiButtonModule, + NuiDashboardsModule, + NuiDashboardConfiguratorModule, + NuiDocsModule, + NuiFormFieldModule, + NuiIconModule, + NuiMessageModule, + NuiIconModule, + NuiTextboxModule, + NuiIconModule, + NuiSwitchModule, + RouterModule.forChild(routes), + ], + declarations: [ + WidgetErrorHandlingDocsComponent, + WidgetErrorHandlingComponent, + ], + providers: [ + { + provide: DEMO_PATH_TOKEN, + useValue: getDemoFiles("widget-error-handling"), + }, + ], +}) +export default class WidgetErrorHandlingModule {} +\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\`, + "types.ts": \\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\`// © 2022 SolarWinds Worldwide, LLC. All rights reserved. +// +// Permission is hereby granted, free of charge, to any person obtaining a copy +// of this software and associated documentation files (the "Software"), to +// deal in the Software without restriction, including without limitation the +// rights to use, copy, modify, merge, publish, distribute, sublicense, and/or +// sell copies of the Software, and to permit persons to whom the Software is +// furnished to do so, subject to the following conditions: +// +// The above copyright notice and this permission notice shall be included in +// all copies or substantial portions of the Software. +// +// THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR +// IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, +// FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE +// AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER +// LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, +// OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN +// THE SOFTWARE. + +export enum APOLLO_API_NAMESPACE { + COUNTRIES = "countries", +} +\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\`, + "widget-types/drilldown/drilldown-multi-request-widget/drilldown-multi-request-widget-example.component.ts": \\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\`// © 2022 SolarWinds Worldwide, LLC. All rights reserved. +// +// Permission is hereby granted, free of charge, to any person obtaining a copy +// of this software and associated documentation files (the "Software"), to +// deal in the Software without restriction, including without limitation the +// rights to use, copy, modify, merge, publish, distribute, sublicense, and/or +// sell copies of the Software, and to permit persons to whom the Software is +// furnished to do so, subject to the following conditions: +// +// The above copyright notice and this permission notice shall be included in +// all copies or substantial portions of the Software. +// +// THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR +// IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, +// FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE +// AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER +// LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, +// OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN +// THE SOFTWARE. + +import { HttpClient } from "@angular/common/http"; +import { + ChangeDetectorRef, + Component, + Injectable, + OnDestroy, + OnInit, +} from "@angular/core"; +import { GridsterConfig, GridsterItem } from "angular-gridster2"; +import { Apollo, gql } from "apollo-angular"; +import { BehaviorSubject, Observable, of, Subject } from "rxjs"; +// eslint-disable-next-line import/no-deprecated +import { finalize, map, switchMap, tap } from "rxjs/operators"; + +import { + DataSourceService, + IconStatus, + IDataField, + IFilters, + INovaFilters, +} from "@nova-ui/bits"; +import { + DATA_SOURCE, + DEFAULT_PIZZAGNA_ROOT, + IDashboard, + IDrilldownComponentsConfiguration, + IListWidgetConfiguration, + IProviderConfiguration, + IWidget, + IWidgets, + ListGroupItemComponent, + ListLeafItemComponent, + NOVA_DRILLDOWN_DATASOURCE_ADAPTER, + PizzagnaLayer, + ProviderRegistryService, + WellKnownPathKey, + WellKnownProviders, + WidgetTypesService, +} from "@nova-ui/dashboards"; + +import { APOLLO_API_NAMESPACE } from "../../../types"; + +/** + * A simple KPI data source to retrieve the average rating of Harry Potter and the Sorcerer's Stone (book) via googleapis + */ +@Injectable() +export class DrilldownDataSource + extends DataSourceService + implements OnDestroy +{ + // This is the ID we'll use to identify the provider + public static providerId = "DrilldownDataSource"; + + // Use this subject to communicate the data source's busy state + public busy = new BehaviorSubject(false); + + public dataFields: Partial[] = [ + { id: "Region", label: "Region name" }, + { id: "Subregion", label: "Subregion name" }, + ]; + + private drillState: string[] = []; + private groupBy: string[]; + private cache: any; + private lastDrillState: string[] = []; + private leafGroup: string = "country"; + private applyFilters$ = new Subject(); + + constructor(private http: HttpClient, private apollo: Apollo) { + super(); + + // TODO: remove Partial in vNext after marking dataType field as optional - NUI-5838 + ( + this.dataFieldsConfig.dataFields$ as BehaviorSubject< + Partial[] + > + ).next(this.dataFields); + + this.applyFilters$ + // eslint-disable-next-line import/no-deprecated + .pipe(switchMap((filters) => this.getData(filters))) + .subscribe(async (res) => { + this.outputsSubject.next(await this.getFilteredData(res)); + }); + } + + private groupedDataHistory: any[] = []; + + // In this example, getFilteredData is invoked every 10 minutes (Take a look at the refresher + // provider definition in the widget configuration below to see how the interval is set) + public async getFilteredData(data: any): Promise { + return of(data) + .pipe( + map((entries) => { + if (this.isDrillDown()) { + const activeDrillLvl = this.drillState.length; + const group = this.groupBy[activeDrillLvl]; + const lastGroupedValue = + this.getTransformedDataForGroup( + entries, + group, + getLast(this.drillState) + ); + + this.groupedDataHistory.push(lastGroupedValue); + + return lastGroupedValue; + } + + const mapIconsToEntries = entries.map((item: any) => ({ + ...item, + icon: "virtual-host", + icon_status: IconStatus.Up, + })); + this.groupedDataHistory.push(mapIconsToEntries); + const widgetInput = this.getOutput(entries); + + return widgetInput; + }) + ) + .toPromise(); + } + + public ngOnDestroy(): void { + this.outputsSubject.complete(); + } + + // redefine parent method + public async applyFilters(): Promise { + this.applyFilters$.next(this.getFilters()); + } + + private getQuery(key: string, value: string) { + const groupToRequestMap: Record = { + Region: \\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\`{ Region { name } }\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\`, + Subregion: \\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\`{ Subregion(filter: { region: { name: "\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\${value}" } } ) { name } }\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\`, + Country: \\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\`{ Country(filter: { subregion: { name: "\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\${value}" } } ) { name capital } }\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\`, + }; + + return gql\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\` + \\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\${groupToRequestMap[key]} + \\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\`; + } + + private getData(filters: INovaFilters): Observable { + this.drillState = filters.drillstate?.value; + this.groupBy = filters.group?.value; + const group = this.groupBy[this.drillState.length]; + const isDrillUp = this.drillState.length < this.lastDrillState.length; + + this.lastDrillState = [...this.drillState]; + + if (!this.drillState.length) { + this.groupedDataHistory.length = 0; + } + + this.busy.next(true); + + if (this.cache && (isDrillUp || this.isHome())) { + return of(this.cache).pipe( + map((data) => data.data[group]), + finalize(() => this.busy.next(false)) + ); + } else { + return this.apollo + .use(APOLLO_API_NAMESPACE.COUNTRIES) + .query({ + query: this.getQuery( + group || this.leafGroup, + getLast(this.drillState) + ), + }) + .pipe( + tap( + (data) => + (this.cache = { + data: { ...this.cache?.data, ...data?.data }, + }) + ), + map((data) => data.data[group || this.leafGroup]), + finalize(() => this.busy.next(false)) + ); + } + } + + private getTransformedDataForGroup( + data: any, + group: string, + drillStateValue: string + ) { + const fallback: string = \\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\`No \\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\${group} for \\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\${drillStateValue}\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\`; + const dataArr = Object.values(data).map((val: any) => ({ + id: val.name || fallback, + label: val.name || fallback, + statuses: [ + { key: "state_ok", value: val.name?.length }, + { + key: "status_unreachable", + value: generateNumberUpTo(100000), + }, + { key: "status_warning", value: generateNumberUpTo(10000) }, + { key: "status_unknown", value: generateNumberUpTo(1000) }, + ], + })); + + return dataArr; + } + + private isHome(): boolean { + return this.drillState.length === 0; + } + + private isDrillDown(): boolean { + return this.drillState.length !== this.groupBy.length; + } + + private getOutput(data: any) { + if (this.isHome()) { + this.groupedDataHistory.length = 0; + } + + const lastHistoryValue = getLast(this.groupedDataHistory); + + if (!lastHistoryValue) { + return data; + } + + return lastHistoryValue[getLast(this.drillState)] || lastHistoryValue; + } +} + +@Component({ + selector: "drilldown-multi-request-widget-example", + templateUrl: "./drilldown-multi-request-widget-example.component.html", + styleUrls: ["./drilldown-multi-request-widget-example.component.less"], + standalone: false, +}) +export class DrilldownMultiRequestWidgetExampleComponent implements OnInit { + // This variable will hold all the data needed to define the layout and behavior of the widgets. + // Pass this to the dashboard component's dashboard input in the template. + public dashboard: IDashboard | undefined; + + // Angular gridster requires a configuration object even if it's empty. + // Pass this to the dashboard component's gridsterConfig input in the template. + public gridsterConfig: GridsterConfig = {}; + + // Boolean passed as an input to the dashboard. When true, widgets can be moved, resized, removed, or edited + public editMode: boolean = false; + + constructor( + // WidgetTypesService provides the widget's necessary structure information + private widgetTypesService: WidgetTypesService, + private providerRegistry: ProviderRegistryService, + private changeDetectorRef: ChangeDetectorRef + ) {} + + public ngOnInit(): void { + // this.prepareNovaDashboards(); + this.initializeDashboard(); + const widgetTemplate = this.widgetTypesService.getWidgetType( + "drilldown", + 1 + ); + this.widgetTypesService.setNode( + widgetTemplate, + "configurator", + WellKnownPathKey.DataSourceProviders, + [DrilldownDataSource.providerId] + ); + + // Registering the data source for injection into the KPI tile. + // Note: Each tile of a KPI widget is assigned its own instance of the data source + this.providerRegistry.setProviders({ + [DrilldownDataSource.providerId]: { + provide: DATA_SOURCE, + useClass: DrilldownDataSource, + // Any dependencies that need to be injected into the provider must be listed here + deps: [HttpClient, Apollo], + }, + }); + } + + /** Used for restoring widgets state */ + public reInitializeDashboard(): void { + // destroys the components and their providers so the dashboard can re init data + this.dashboard = undefined; + this.changeDetectorRef.detectChanges(); + + this.initializeDashboard(); + } + + public initializeDashboard(): void { + // We're using a static configuration object for this example, but this is where + // the widget's configuration could potentially be populated from a database + const drilldownWidget = widgetConfig; + const widgets: IWidgets = { + // Complete the widget with information coming from its type definition + [drilldownWidget.id]: + this.widgetTypesService.mergeWithWidgetType(drilldownWidget), + }; + + // Setting the widget dimensions and position (this is for gridster) + const positions: Record = { + [drilldownWidget.id]: { + cols: 10, + rows: 10, + y: 0, + x: 0, + }, + }; + + // Finally, assigning the variables we created above to the dashboard + this.dashboard = { positions, widgets }; + } +} + +const widgetConfig: IWidget = { + id: "drilldown", + type: "drilldown", + pizzagna: { + [PizzagnaLayer.Configuration]: { + header: { + properties: { + title: "Drilldown Widget", + subtitle: "Countries BY continent THEN currency", + }, + }, + [DEFAULT_PIZZAGNA_ROOT]: { + providers: { + [WellKnownProviders.DataSource]: { + // Setting the data source providerId for the tile with id "kpi1" + providerId: DrilldownDataSource.providerId, + properties: {}, + } as IProviderConfiguration, + }, + }, + listWidget: { + providers: { + [WellKnownProviders.Adapter]: { + providerId: NOVA_DRILLDOWN_DATASOURCE_ADAPTER, + properties: { + // widget + navigationBarId: "navigationBar", + componentId: "listWidget", + dataPath: "data", + + // adapter props + drillstate: [], + groups: ["Region", "Subregion"], + groupBy: ["Region", "Subregion"], + + // components + componentsConfig: { + group: { + componentType: + ListGroupItemComponent.lateLoadKey, + properties: { + dataFieldIds: { + id: "id", + label: "label", + statuses: "statuses", + }, + }, + itemProperties: { + canNavigate: true, + }, + }, + leaf: { + componentType: + ListLeafItemComponent.lateLoadKey, + properties: { + dataFieldIds: { + icon: "icon", + status: "icon_status", + detailedUrl: "capital", + label: "name", + }, + }, + itemProperties: { + canNavigate: false, + }, + }, + } as IDrilldownComponentsConfiguration, + }, + }, + }, + properties: { + configuration: { + // FORMAT: + // componentType: ListLeafItemComponent.lateLoadKey, + // properties: { + // dataFieldIds: { + // icon: "", + // status: "code", + // detailedUrl: "capital", + // label: "name", + // }, + // }, + // + } as IListWidgetConfiguration, + }, + }, + }, + }, +}; + +const getLast = (arr: any[]) => arr[arr.length - 1]; + +const generateNumberUpTo = (upperLimit: number): number => + Math.floor(Math.random() * upperLimit + 1); +\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\`, + "widget-types/drilldown/drilldown-widget/data-mock.ts": \\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\`// © 2022 SolarWinds Worldwide, LLC. All rights reserved. +// +// Permission is hereby granted, free of charge, to any person obtaining a copy +// of this software and associated documentation files (the "Software"), to +// deal in the Software without restriction, including without limitation the +// rights to use, copy, modify, merge, publish, distribute, sublicense, and/or +// sell copies of the Software, and to permit persons to whom the Software is +// furnished to do so, subject to the following conditions: +// +// The above copyright notice and this permission notice shall be included in +// all copies or substantial portions of the Software. +// +// THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR +// IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, +// FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE +// AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER +// LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, +// OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN +// THE SOFTWARE. + +import { IconStatus } from "@nova-ui/bits"; + +export const GRAPH_DATA_MOCK = { + data: { + countries: [ + { + name: "Andorra", + code: "AD", + icon: "virtual-host", + icon_status: IconStatus.Up, + capital: "Andorra la Vella", + continent: { + name: "Europe", + }, + currency: "EUR", + languages: [ + { + name: "Catalan", + }, + ], + url: "https://en.wikipedia.org/wiki/Andorra", + }, + { + name: "United Arab Emirates", + code: "AE", + icon: "virtual-host", + icon_status: IconStatus.Up, + capital: "Abu Dhabi", + continent: { + name: "Asia", + }, + currency: "AED", + languages: [ + { + name: "Arabic", + }, + ], + url: "https://en.wikipedia.org/wiki/United_Arab_Emirates", + }, + { + name: "Afghanistan", + code: "AF", + icon: "virtual-host", + icon_status: IconStatus.Up, + capital: "Kabul", + continent: { + name: "Asia", + }, + currency: "AFN", + languages: [ + { + name: "Pashto", + }, + { + name: "Uzbek", + }, + { + name: "Turkmen", + }, + ], + url: "https://en.wikipedia.org/wiki/Afghanistan", + }, + { + name: "Antigua and Barbuda", + code: "AG", + icon: "virtual-host", + icon_status: IconStatus.Up, + capital: "Saint John's", + continent: { + name: "North America", + }, + currency: "XCD", + languages: [ + { + name: "English", + }, + ], + url: "https://en.wikipedia.org/wiki/Antigua_and_Barbuda", + }, + { + name: "Anguilla", + code: "AI", + icon: "virtual-host", + icon_status: IconStatus.Up, + capital: "The Valley", + continent: { + name: "North America", + }, + currency: "XCD", + languages: [ + { + name: "English", + }, + ], + url: "https://en.wikipedia.org/wiki/Anguilla", + }, + { + name: "Albania", + code: "AL", + icon: "virtual-host", + icon_status: IconStatus.Up, + capital: "Tirana", + continent: { + name: "Europe", + }, + currency: "ALL", + languages: [ + { + name: "Albanian", + }, + ], + url: "https://en.wikipedia.org/wiki/Albania", + }, + { + name: "Armenia", + code: "AM", + icon: "virtual-host", + icon_status: IconStatus.Up, + capital: "Yerevan", + continent: { + name: "Asia", + }, + currency: "AMD", + languages: [ + { + name: "Armenian", + }, + { + name: "Russian", + }, + ], + url: "https://en.wikipedia.org/wiki/Armenia", + }, + { + name: "Angola", + code: "AO", + icon: "virtual-host", + icon_status: IconStatus.Up, + capital: "Luanda", + continent: { + name: "Africa", + }, + currency: "AOA", + languages: [ + { + name: "Portuguese", + }, + ], + }, + { + name: "Antarctica", + code: "AQ", + icon: "virtual-host", + icon_status: IconStatus.Up, + capital: null, + continent: { + name: "Antarctica", + }, + currency: null, + languages: [], + }, + { + name: "Argentina", + code: "AR", + icon: "virtual-host", + icon_status: IconStatus.Up, + capital: "Buenos Aires", + continent: { + name: "South America", + }, + currency: "ARS", + languages: [ + { + name: "Spanish", + }, + { + name: "Guarani", + }, + ], + }, + { + name: "American Samoa", + code: "AS", + icon: "virtual-host", + icon_status: IconStatus.Up, + capital: "Pago Pago", + continent: { + name: "Oceania", + }, + currency: "USD", + languages: [ + { + name: "English", + }, + { + name: "Samoan", + }, + ], + }, + { + name: "Austria", + code: "AT", + icon: "virtual-host", + icon_status: IconStatus.Up, + capital: "Vienna", + continent: { + name: "Europe", + }, + currency: "EUR", + languages: [ + { + name: "German", + }, + ], + }, + { + name: "Australia", + code: "AU", + icon: "virtual-host", + icon_status: IconStatus.Up, + capital: "Canberra", + continent: { + name: "Oceania", + }, + currency: "AUD", + languages: [ + { + name: "English", + }, + ], + }, + { + name: "Aruba", + code: "AW", + icon: "virtual-host", + icon_status: IconStatus.Up, + capital: "Oranjestad", + continent: { + name: "North America", + }, + currency: "AWG", + languages: [ + { + name: "Dutch", + }, + { + name: "Panjabi / Punjabi", + }, + ], + }, + { + name: "Åland", + code: "AX", + icon: "virtual-host", + icon_status: IconStatus.Up, + capital: "Mariehamn", + continent: { + name: "Europe", + }, + currency: "EUR", + languages: [ + { + name: "Swedish", + }, + ], + }, + { + name: "Azerbaijan", + code: "AZ", + icon: "virtual-host", + icon_status: IconStatus.Up, + capital: "Baku", + continent: { + name: "Asia", + }, + currency: "AZN", + languages: [ + { + name: "Azerbaijani", + }, + ], + }, + { + name: "Bosnia and Herzegovina", + code: "BA", + icon: "virtual-host", + icon_status: IconStatus.Up, + capital: "Sarajevo", + continent: { + name: "Europe", + }, + currency: "BAM", + languages: [ + { + name: "Bosnian", + }, + { + name: "Croatian", + }, + { + name: "Serbian", + }, + ], + }, + { + name: "Barbados", + code: "BB", + icon: "virtual-host", + icon_status: IconStatus.Up, + capital: "Bridgetown", + continent: { + name: "North America", + }, + currency: "BBD", + languages: [ + { + name: "English", + }, + ], + }, + { + name: "Bangladesh", + code: "BD", + icon: "virtual-host", + icon_status: IconStatus.Up, + capital: "Dhaka", + continent: { + name: "Asia", + }, + currency: "BDT", + languages: [ + { + name: "Bengali", + }, + ], + }, + { + name: "Belgium", + code: "BE", + icon: "virtual-host", + icon_status: IconStatus.Up, + capital: "Brussels", + continent: { + name: "Europe", + }, + currency: "EUR", + languages: [ + { + name: "Dutch", + }, + { + name: "French", + }, + { + name: "German", + }, + ], + }, + { + name: "Burkina Faso", + code: "BF", + icon: "virtual-host", + icon_status: IconStatus.Up, + capital: "Ouagadougou", + continent: { + name: "Africa", + }, + currency: "XOF", + languages: [ + { + name: "French", + }, + { + name: "Peul", + }, + ], + }, + { + name: "Bulgaria", + code: "BG", + icon: "virtual-host", + icon_status: IconStatus.Up, + capital: "Sofia", + continent: { + name: "Europe", + }, + currency: "BGN", + languages: [ + { + name: "Bulgarian", + }, + ], + }, + { + name: "Bahrain", + code: "BH", + icon: "virtual-host", + icon_status: IconStatus.Up, + capital: "Manama", + continent: { + name: "Asia", + }, + currency: "BHD", + languages: [ + { + name: "Arabic", + }, + ], + }, + { + name: "Burundi", + code: "BI", + icon: "virtual-host", + icon_status: IconStatus.Up, + capital: "Bujumbura", + continent: { + name: "Africa", + }, + currency: "BIF", + languages: [ + { + name: "French", + }, + { + name: "Kirundi", + }, + ], + }, + { + name: "Benin", + code: "BJ", + icon: "virtual-host", + icon_status: IconStatus.Up, + capital: "Porto-Novo", + continent: { + name: "Africa", + }, + currency: "XOF", + languages: [ + { + name: "French", + }, + ], + }, + { + name: "Saint Barthélemy", + code: "BL", + icon: "virtual-host", + icon_status: IconStatus.Up, + capital: "Gustavia", + continent: { + name: "North America", + }, + currency: "EUR", + languages: [ + { + name: "French", + }, + ], + }, + { + name: "Bermuda", + code: "BM", + icon: "virtual-host", + icon_status: IconStatus.Up, + capital: "Hamilton", + continent: { + name: "North America", + }, + currency: "BMD", + languages: [ + { + name: "English", + }, + ], + }, + { + name: "Brunei", + code: "BN", + icon: "virtual-host", + icon_status: IconStatus.Up, + capital: "Bandar Seri Begawan", + continent: { + name: "Asia", + }, + currency: "BND", + languages: [ + { + name: "Malay", + }, + ], + }, + { + name: "Bolivia", + code: "BO", + icon: "virtual-host", + icon_status: IconStatus.Up, + capital: "Sucre", + continent: { + name: "South America", + }, + currency: "BOB,BOV", + languages: [ + { + name: "Spanish", + }, + { + name: "Aymara", + }, + { + name: "Quechua", + }, + ], + }, + { + name: "Bonaire", + code: "BQ", + icon: "virtual-host", + icon_status: IconStatus.Up, + capital: "Kralendijk", + continent: { + name: "North America", + }, + currency: "USD", + languages: [ + { + name: "Dutch", + }, + ], + }, + { + name: "Brazil", + code: "BR", + icon: "virtual-host", + icon_status: IconStatus.Up, + capital: "Brasília", + continent: { + name: "South America", + }, + currency: "BRL", + languages: [ + { + name: "Portuguese", + }, + ], + }, + { + name: "Bahamas", + code: "BS", + icon: "virtual-host", + icon_status: IconStatus.Up, + capital: "Nassau", + continent: { + name: "North America", + }, + currency: "BSD", + languages: [ + { + name: "English", + }, + ], + }, + { + name: "Bhutan", + code: "BT", + icon: "virtual-host", + icon_status: IconStatus.Up, + capital: "Thimphu", + continent: { + name: "Asia", + }, + currency: "BTN,INR", + languages: [ + { + name: "Dzongkha", + }, + ], + }, + { + name: "Bouvet Island", + code: "BV", + icon: "virtual-host", + icon_status: IconStatus.Up, + capital: null, + continent: { + name: "Antarctica", + }, + currency: "NOK", + languages: [ + { + name: "Norwegian", + }, + { + name: "Norwegian Bokmål", + }, + { + name: "Norwegian Nynorsk", + }, + ], + }, + { + name: "Botswana", + code: "BW", + icon: "virtual-host", + icon_status: IconStatus.Up, + capital: "Gaborone", + continent: { + name: "Africa", + }, + currency: "BWP", + languages: [ + { + name: "English", + }, + { + name: "Tswana", + }, + ], + }, + { + name: "Belarus", + code: "BY", + icon: "virtual-host", + icon_status: IconStatus.Up, + capital: "Minsk", + continent: { + name: "Europe", + }, + currency: "BYN", + languages: [ + { + name: "Belarusian", + }, + { + name: "Russian", + }, + ], + }, + { + name: "Belize", + code: "BZ", + icon: "virtual-host", + icon_status: IconStatus.Up, + capital: "Belmopan", + continent: { + name: "North America", + }, + currency: "BZD", + languages: [ + { + name: "English", + }, + { + name: "Spanish", + }, + ], + }, + { + name: "Canada", + code: "CA", + icon: "virtual-host", + icon_status: IconStatus.Up, + capital: "Ottawa", + continent: { + name: "North America", + }, + currency: "CAD", + languages: [ + { + name: "English", + }, + { + name: "French", + }, + ], + }, + { + name: "Cocos [Keeling] Islands", + code: "CC", + icon: "virtual-host", + icon_status: IconStatus.Up, + capital: "West Island", + continent: { + name: "Asia", + }, + currency: "AUD", + languages: [ + { + name: "English", + }, + ], + }, + { + name: "Democratic Republic of the Congo", + code: "CD", + icon: "virtual-host", + icon_status: IconStatus.Up, + capital: "Kinshasa", + continent: { + name: "Africa", + }, + currency: "CDF", + languages: [ + { + name: "French", + }, + { + name: "Lingala", + }, + { + name: "Kongo", + }, + { + name: "Swahili", + }, + { + name: "Luba-Katanga", + }, + ], + }, + { + name: "Central African Republic", + code: "CF", + icon: "virtual-host", + icon_status: IconStatus.Up, + capital: "Bangui", + continent: { + name: "Africa", + }, + currency: "XAF", + languages: [ + { + name: "French", + }, + { + name: "Sango", + }, + ], + }, + { + name: "Republic of the Congo", + code: "CG", + icon: "virtual-host", + icon_status: IconStatus.Up, + capital: "Brazzaville", + continent: { + name: "Africa", + }, + currency: "XAF", + languages: [ + { + name: "French", + }, + { + name: "Lingala", + }, + ], + }, + { + name: "Switzerland", + code: "CH", + icon: "virtual-host", + icon_status: IconStatus.Up, + capital: "Bern", + continent: { + name: "Europe", + }, + currency: "CHE,CHF,CHW", + languages: [ + { + name: "German", + }, + { + name: "French", + }, + { + name: "Italian", + }, + ], + }, + { + name: "Ivory Coast", + code: "CI", + icon: "virtual-host", + icon_status: IconStatus.Up, + capital: "Yamoussoukro", + continent: { + name: "Africa", + }, + currency: "XOF", + languages: [ + { + name: "French", + }, + ], + }, + { + name: "Cook Islands", + code: "CK", + icon: "virtual-host", + icon_status: IconStatus.Up, + capital: "Avarua", + continent: { + name: "Oceania", + }, + currency: "NZD", + languages: [ + { + name: "English", + }, + ], + }, + { + name: "Chile", + code: "CL", + icon: "virtual-host", + icon_status: IconStatus.Up, + capital: "Santiago", + continent: { + name: "South America", + }, + currency: "CLF,CLP", + languages: [ + { + name: "Spanish", + }, + ], + }, + { + name: "Cameroon", + code: "CM", + icon: "virtual-host", + icon_status: IconStatus.Up, + capital: "Yaoundé", + continent: { + name: "Africa", + }, + currency: "XAF", + languages: [ + { + name: "English", + }, + { + name: "French", + }, + ], + }, + { + name: "China", + code: "CN", + icon: "virtual-host", + icon_status: IconStatus.Up, + capital: "Beijing", + continent: { + name: "Asia", + }, + currency: "CNY", + languages: [ + { + name: "Chinese", + }, + ], + }, + { + name: "Colombia", + code: "CO", + icon: "virtual-host", + icon_status: IconStatus.Up, + capital: "Bogotá", + continent: { + name: "South America", + }, + currency: "COP", + languages: [ + { + name: "Spanish", + }, + ], + }, + { + name: "Costa Rica", + code: "CR", + icon: "virtual-host", + icon_status: IconStatus.Up, + capital: "San José", + continent: { + name: "North America", + }, + currency: "CRC", + languages: [ + { + name: "Spanish", + }, + ], + }, + { + name: "Cuba", + code: "CU", + icon: "virtual-host", + icon_status: IconStatus.Up, + capital: "Havana", + continent: { + name: "North America", + }, + currency: "CUC,CUP", + languages: [ + { + name: "Spanish", + }, + ], + }, + { + name: "Cape Verde", + code: "CV", + icon: "virtual-host", + icon_status: IconStatus.Up, + capital: "Praia", + continent: { + name: "Africa", + }, + currency: "CVE", + languages: [ + { + name: "Portuguese", + }, + ], + }, + { + name: "Curacao", + code: "CW", + icon: "virtual-host", + icon_status: IconStatus.Up, + capital: "Willemstad", + continent: { + name: "North America", + }, + currency: "ANG", + languages: [ + { + name: "Dutch", + }, + { + name: "Panjabi / Punjabi", + }, + { + name: "English", + }, + ], + }, + { + name: "Christmas Island", + code: "CX", + icon: "virtual-host", + icon_status: IconStatus.Up, + capital: "Flying Fish Cove", + continent: { + name: "Asia", + }, + currency: "AUD", + languages: [ + { + name: "English", + }, + ], + }, + { + name: "Cyprus", + code: "CY", + icon: "virtual-host", + icon_status: IconStatus.Up, + capital: "Nicosia", + continent: { + name: "Europe", + }, + currency: "EUR", + languages: [ + { + name: "Greek", + }, + { + name: "Turkish", + }, + { + name: "Armenian", + }, + ], + }, + { + name: "Czech Republic", + code: "CZ", + icon: "virtual-host", + icon_status: IconStatus.Up, + capital: "Prague", + continent: { + name: "Europe", + }, + currency: "CZK", + languages: [ + { + name: "Czech", + }, + { + name: "Slovak", + }, + ], + }, + { + name: "Germany", + code: "DE", + icon: "virtual-host", + icon_status: IconStatus.Up, + capital: "Berlin", + continent: { + name: "Europe", + }, + currency: "EUR", + languages: [ + { + name: "German", + }, + ], + }, + { + name: "Djibouti", + code: "DJ", + icon: "virtual-host", + icon_status: IconStatus.Up, + capital: "Djibouti", + continent: { + name: "Africa", + }, + currency: "DJF", + languages: [ + { + name: "French", + }, + { + name: "Arabic", + }, + ], + }, + { + name: "Denmark", + code: "DK", + icon: "virtual-host", + icon_status: IconStatus.Up, + capital: "Copenhagen", + continent: { + name: "Europe", + }, + currency: "DKK", + languages: [ + { + name: "Danish", + }, + ], + }, + { + name: "Dominica", + code: "DM", + icon: "virtual-host", + icon_status: IconStatus.Up, + capital: "Roseau", + continent: { + name: "North America", + }, + currency: "XCD", + languages: [ + { + name: "English", + }, + ], + }, + { + name: "Dominican Republic", + code: "DO", + icon: "virtual-host", + icon_status: IconStatus.Up, + capital: "Santo Domingo", + continent: { + name: "North America", + }, + currency: "DOP", + languages: [ + { + name: "Spanish", + }, + ], + }, + { + name: "Algeria", + code: "DZ", + icon: "virtual-host", + icon_status: IconStatus.Up, + capital: "Algiers", + continent: { + name: "Africa", + }, + currency: "DZD", + languages: [ + { + name: "Arabic", + }, + ], + }, + { + name: "Ecuador", + code: "EC", + icon: "virtual-host", + icon_status: IconStatus.Up, + capital: "Quito", + continent: { + name: "South America", + }, + currency: "USD", + languages: [ + { + name: "Spanish", + }, + ], + }, + { + name: "Estonia", + code: "EE", + icon: "virtual-host", + icon_status: IconStatus.Up, + capital: "Tallinn", + continent: { + name: "Europe", + }, + currency: "EUR", + languages: [ + { + name: "Estonian", + }, + ], + }, + { + name: "Egypt", + code: "EG", + icon: "virtual-host", + icon_status: IconStatus.Up, + capital: "Cairo", + continent: { + name: "Africa", + }, + currency: "EGP", + languages: [ + { + name: "Arabic", + }, + ], + }, + { + name: "Western Sahara", + code: "EH", + icon: "virtual-host", + icon_status: IconStatus.Up, + capital: "El Aaiún", + continent: { + name: "Africa", + }, + currency: "MAD,DZD,MRU", + languages: [ + { + name: "Spanish", + }, + ], + }, + { + name: "Eritrea", + code: "ER", + icon: "virtual-host", + icon_status: IconStatus.Up, + capital: "Asmara", + continent: { + name: "Africa", + }, + currency: "ERN", + languages: [ + { + name: "Tigrinya", + }, + { + name: "Arabic", + }, + { + name: "English", + }, + ], + }, + { + name: "Spain", + code: "ES", + icon: "virtual-host", + icon_status: IconStatus.Up, + capital: "Madrid", + continent: { + name: "Europe", + }, + currency: "EUR", + languages: [ + { + name: "Spanish", + }, + { + name: "Basque", + }, + { + name: "Catalan", + }, + { + name: "Galician", + }, + { + name: "Occitan", + }, + ], + }, + { + name: "Ethiopia", + code: "ET", + icon: "virtual-host", + icon_status: IconStatus.Up, + capital: "Addis Ababa", + continent: { + name: "Africa", + }, + currency: "ETB", + languages: [ + { + name: "Amharic", + }, + ], + }, + { + name: "Finland", + code: "FI", + icon: "virtual-host", + icon_status: IconStatus.Up, + capital: "Helsinki", + continent: { + name: "Europe", + }, + currency: "EUR", + languages: [ + { + name: "Finnish", + }, + { + name: "Swedish", + }, + ], + }, + { + name: "Fiji", + code: "FJ", + icon: "virtual-host", + icon_status: IconStatus.Up, + capital: "Suva", + continent: { + name: "Oceania", + }, + currency: "FJD", + languages: [ + { + name: "English", + }, + { + name: "Fijian", + }, + { + name: "Hindi", + }, + { + name: "Urdu", + }, + ], + }, + { + name: "Falkland Islands", + code: "FK", + icon: "virtual-host", + icon_status: IconStatus.Up, + capital: "Stanley", + continent: { + name: "South America", + }, + currency: "FKP", + languages: [ + { + name: "English", + }, + ], + }, + { + name: "Micronesia", + code: "FM", + icon: "virtual-host", + icon_status: IconStatus.Up, + capital: "Palikir", + continent: { + name: "Oceania", + }, + currency: "USD", + languages: [ + { + name: "English", + }, + ], + }, + { + name: "Faroe Islands", + code: "FO", + icon: "virtual-host", + icon_status: IconStatus.Up, + capital: "Tórshavn", + continent: { + name: "Europe", + }, + currency: "DKK", + languages: [ + { + name: "Faroese", + }, + ], + }, + { + name: "France", + code: "FR", + icon: "virtual-host", + icon_status: IconStatus.Up, + capital: "Paris", + continent: { + name: "Europe", + }, + currency: "EUR", + languages: [ + { + name: "French", + }, + ], + }, + { + name: "Gabon", + code: "GA", + icon: "virtual-host", + icon_status: IconStatus.Up, + capital: "Libreville", + continent: { + name: "Africa", + }, + currency: "XAF", + languages: [ + { + name: "French", + }, + ], + }, + { + name: "United Kingdom", + code: "GB", + icon: "virtual-host", + icon_status: IconStatus.Up, + capital: "London", + continent: { + name: "Europe", + }, + currency: "GBP", + languages: [ + { + name: "English", + }, + ], + }, + { + name: "Grenada", + code: "GD", + icon: "virtual-host", + icon_status: IconStatus.Up, + capital: "St. George's", + continent: { + name: "North America", + }, + currency: "XCD", + languages: [ + { + name: "English", + }, + ], + }, + { + name: "Georgia", + code: "GE", + icon: "virtual-host", + icon_status: IconStatus.Up, + capital: "Tbilisi", + continent: { + name: "Asia", + }, + currency: "GEL", + languages: [ + { + name: "Georgian", + }, + ], + }, + { + name: "French Guiana", + code: "GF", + icon: "virtual-host", + icon_status: IconStatus.Up, + capital: "Cayenne", + continent: { + name: "South America", + }, + currency: "EUR", + languages: [ + { + name: "French", + }, + ], + }, + { + name: "Guernsey", + code: "GG", + icon: "virtual-host", + icon_status: IconStatus.Up, + capital: "St. Peter Port", + continent: { + name: "Europe", + }, + currency: "GBP", + languages: [ + { + name: "English", + }, + { + name: "French", + }, + ], + }, + { + name: "Ghana", + code: "GH", + icon: "virtual-host", + icon_status: IconStatus.Up, + capital: "Accra", + continent: { + name: "Africa", + }, + currency: "GHS", + languages: [ + { + name: "English", + }, + ], + }, + { + name: "Gibraltar", + code: "GI", + icon: "virtual-host", + icon_status: IconStatus.Up, + capital: "Gibraltar", + continent: { + name: "Europe", + }, + currency: "GIP", + languages: [ + { + name: "English", + }, + ], + }, + { + name: "Greenland", + code: "GL", + icon: "virtual-host", + icon_status: IconStatus.Up, + capital: "Nuuk", + continent: { + name: "North America", + }, + currency: "DKK", + languages: [ + { + name: "Greenlandic", + }, + ], + }, + { + name: "Gambia", + code: "GM", + icon: "virtual-host", + icon_status: IconStatus.Up, + capital: "Banjul", + continent: { + name: "Africa", + }, + currency: "GMD", + languages: [ + { + name: "English", + }, + ], + }, + { + name: "Guinea", + code: "GN", + icon: "virtual-host", + icon_status: IconStatus.Up, + capital: "Conakry", + continent: { + name: "Africa", + }, + currency: "GNF", + languages: [ + { + name: "French", + }, + { + name: "Peul", + }, + ], + }, + { + name: "Guadeloupe", + code: "GP", + icon: "virtual-host", + icon_status: IconStatus.Up, + capital: "Basse-Terre", + continent: { + name: "North America", + }, + currency: "EUR", + languages: [ + { + name: "French", + }, + ], + }, + { + name: "Equatorial Guinea", + code: "GQ", + icon: "virtual-host", + icon_status: IconStatus.Up, + capital: "Malabo", + continent: { + name: "Africa", + }, + currency: "XAF", + languages: [ + { + name: "Spanish", + }, + { + name: "French", + }, + ], + }, + { + name: "Greece", + code: "GR", + icon: "virtual-host", + icon_status: IconStatus.Up, + capital: "Athens", + continent: { + name: "Europe", + }, + currency: "EUR", + languages: [ + { + name: "Greek", + }, + ], + }, + { + name: "South Georgia and the South Sandwich Islands", + code: "GS", + icon: "virtual-host", + icon_status: IconStatus.Up, + capital: "King Edward Point", + continent: { + name: "Antarctica", + }, + currency: "GBP", + languages: [ + { + name: "English", + }, + ], + }, + { + name: "Guatemala", + code: "GT", + icon: "virtual-host", + icon_status: IconStatus.Up, + capital: "Guatemala City", + continent: { + name: "North America", + }, + currency: "GTQ", + languages: [ + { + name: "Spanish", + }, + ], + }, + { + name: "Guam", + code: "GU", + icon: "virtual-host", + icon_status: IconStatus.Up, + capital: "Hagåtña", + continent: { + name: "Oceania", + }, + currency: "USD", + languages: [ + { + name: "English", + }, + { + name: "Chamorro", + }, + { + name: "Spanish", + }, + ], + }, + { + name: "Guinea-Bissau", + code: "GW", + icon: "virtual-host", + icon_status: IconStatus.Up, + capital: "Bissau", + continent: { + name: "Africa", + }, + currency: "XOF", + languages: [ + { + name: "Portuguese", + }, + ], + }, + { + name: "Guyana", + code: "GY", + icon: "virtual-host", + icon_status: IconStatus.Up, + capital: "Georgetown", + continent: { + name: "South America", + }, + currency: "GYD", + languages: [ + { + name: "English", + }, + ], + }, + { + name: "Hong Kong", + code: "HK", + icon: "virtual-host", + icon_status: IconStatus.Up, + capital: "City of Victoria", + continent: { + name: "Asia", + }, + currency: "HKD", + languages: [ + { + name: "Chinese", + }, + { + name: "English", + }, + ], + }, + { + name: "Heard Island and McDonald Islands", + code: "HM", + icon: "virtual-host", + icon_status: IconStatus.Up, + capital: null, + continent: { + name: "Antarctica", + }, + currency: "AUD", + languages: [ + { + name: "English", + }, + ], + }, + { + name: "Honduras", + code: "HN", + icon: "virtual-host", + icon_status: IconStatus.Up, + capital: "Tegucigalpa", + continent: { + name: "North America", + }, + currency: "HNL", + languages: [ + { + name: "Spanish", + }, + ], + }, + { + name: "Croatia", + code: "HR", + icon: "virtual-host", + icon_status: IconStatus.Up, + capital: "Zagreb", + continent: { + name: "Europe", + }, + currency: "HRK", + languages: [ + { + name: "Croatian", + }, + ], + }, + { + name: "Haiti", + code: "HT", + icon: "virtual-host", + icon_status: IconStatus.Up, + capital: "Port-au-Prince", + continent: { + name: "North America", + }, + currency: "HTG,USD", + languages: [ + { + name: "French", + }, + { + name: "Haitian", + }, + ], + }, + { + name: "Hungary", + code: "HU", + icon: "virtual-host", + icon_status: IconStatus.Up, + capital: "Budapest", + continent: { + name: "Europe", + }, + currency: "HUF", + languages: [ + { + name: "Hungarian", + }, + ], + }, + { + name: "Indonesia", + code: "ID", + icon: "virtual-host", + icon_status: IconStatus.Up, + capital: "Jakarta", + continent: { + name: "Asia", + }, + currency: "IDR", + languages: [ + { + name: "Indonesian", + }, + ], + }, + { + name: "Ireland", + code: "IE", + icon: "virtual-host", + icon_status: IconStatus.Up, + capital: "Dublin", + continent: { + name: "Europe", + }, + currency: "EUR", + languages: [ + { + name: "Irish", + }, + { + name: "English", + }, + ], + }, + { + name: "Israel", + code: "IL", + icon: "virtual-host", + icon_status: IconStatus.Up, + capital: "Jerusalem", + continent: { + name: "Asia", + }, + currency: "ILS", + languages: [ + { + name: "Hebrew", + }, + { + name: "Arabic", + }, + ], + }, + { + name: "Isle of Man", + code: "IM", + icon: "virtual-host", + icon_status: IconStatus.Up, + capital: "Douglas", + continent: { + name: "Europe", + }, + currency: "GBP", + languages: [ + { + name: "English", + }, + { + name: "Manx", + }, + ], + }, + { + name: "India", + code: "IN", + icon: "virtual-host", + icon_status: IconStatus.Up, + capital: "New Delhi", + continent: { + name: "Asia", + }, + currency: "INR", + languages: [ + { + name: "Hindi", + }, + { + name: "English", + }, + ], + }, + { + name: "British Indian Ocean Territory", + code: "IO", + icon: "virtual-host", + icon_status: IconStatus.Up, + capital: "Diego Garcia", + continent: { + name: "Asia", + }, + currency: "USD", + languages: [ + { + name: "English", + }, + ], + }, + { + name: "Iraq", + code: "IQ", + icon: "virtual-host", + icon_status: IconStatus.Up, + capital: "Baghdad", + continent: { + name: "Asia", + }, + currency: "IQD", + languages: [ + { + name: "Arabic", + }, + { + name: "Kurdish", + }, + ], + }, + { + name: "Iran", + code: "IR", + icon: "virtual-host", + icon_status: IconStatus.Up, + capital: "Tehran", + continent: { + name: "Asia", + }, + currency: "IRR", + languages: [ + { + name: "Persian", + }, + ], + }, + { + name: "Iceland", + code: "IS", + icon: "virtual-host", + icon_status: IconStatus.Up, + capital: "Reykjavik", + continent: { + name: "Europe", + }, + currency: "ISK", + languages: [ + { + name: "Icelandic", + }, + ], + }, + { + name: "Italy", + code: "IT", + icon: "virtual-host", + icon_status: IconStatus.Up, + capital: "Rome", + continent: { + name: "Europe", + }, + currency: "EUR", + languages: [ + { + name: "Italian", + }, + ], + }, + { + name: "Jersey", + code: "JE", + icon: "virtual-host", + icon_status: IconStatus.Up, + capital: "Saint Helier", + continent: { + name: "Europe", + }, + currency: "GBP", + languages: [ + { + name: "English", + }, + { + name: "French", + }, + ], + }, + { + name: "Jamaica", + code: "JM", + icon: "virtual-host", + icon_status: IconStatus.Up, + capital: "Kingston", + continent: { + name: "North America", + }, + currency: "JMD", + languages: [ + { + name: "English", + }, + ], + }, + { + name: "Jordan", + code: "JO", + icon: "virtual-host", + icon_status: IconStatus.Up, + capital: "Amman", + continent: { + name: "Asia", + }, + currency: "JOD", + languages: [ + { + name: "Arabic", + }, + ], + }, + { + name: "Japan", + code: "JP", + icon: "virtual-host", + icon_status: IconStatus.Up, + capital: "Tokyo", + continent: { + name: "Asia", + }, + currency: "JPY", + languages: [ + { + name: "Japanese", + }, + ], + }, + { + name: "Kenya", + code: "KE", + icon: "virtual-host", + icon_status: IconStatus.Up, + capital: "Nairobi", + continent: { + name: "Africa", + }, + currency: "KES", + languages: [ + { + name: "English", + }, + { + name: "Swahili", + }, + ], + }, + { + name: "Kyrgyzstan", + code: "KG", + icon: "virtual-host", + icon_status: IconStatus.Up, + capital: "Bishkek", + continent: { + name: "Asia", + }, + currency: "KGS", + languages: [ + { + name: "Kirghiz", + }, + { + name: "Russian", + }, + ], + }, + { + name: "Cambodia", + code: "KH", + icon: "virtual-host", + icon_status: IconStatus.Up, + capital: "Phnom Penh", + continent: { + name: "Asia", + }, + currency: "KHR", + languages: [ + { + name: "Cambodian", + }, + ], + }, + { + name: "Kiribati", + code: "KI", + icon: "virtual-host", + icon_status: IconStatus.Up, + capital: "South Tarawa", + continent: { + name: "Oceania", + }, + currency: "AUD", + languages: [ + { + name: "English", + }, + ], + }, + { + name: "Comoros", + code: "KM", + icon: "virtual-host", + icon_status: IconStatus.Up, + capital: "Moroni", + continent: { + name: "Africa", + }, + currency: "KMF", + languages: [ + { + name: "Arabic", + }, + { + name: "French", + }, + ], + }, + { + name: "Saint Kitts and Nevis", + code: "KN", + icon: "virtual-host", + icon_status: IconStatus.Up, + capital: "Basseterre", + continent: { + name: "North America", + }, + currency: "XCD", + languages: [ + { + name: "English", + }, + ], + }, + { + name: "North Korea", + code: "KP", + icon: "virtual-host", + icon_status: IconStatus.Up, + capital: "Pyongyang", + continent: { + name: "Asia", + }, + currency: "KPW", + languages: [ + { + name: "Korean", + }, + ], + }, + { + name: "South Korea", + code: "KR", + icon: "virtual-host", + icon_status: IconStatus.Up, + capital: "Seoul", + continent: { + name: "Asia", + }, + currency: "KRW", + languages: [ + { + name: "Korean", + }, + ], + }, + { + name: "Kuwait", + code: "KW", + icon: "virtual-host", + icon_status: IconStatus.Up, + capital: "Kuwait City", + continent: { + name: "Asia", + }, + currency: "KWD", + languages: [ + { + name: "Arabic", + }, + ], + }, + { + name: "Cayman Islands", + code: "KY", + icon: "virtual-host", + icon_status: IconStatus.Up, + capital: "George Town", + continent: { + name: "North America", + }, + currency: "KYD", + languages: [ + { + name: "English", + }, + ], + }, + { + name: "Kazakhstan", + code: "KZ", + icon: "virtual-host", + icon_status: IconStatus.Up, + capital: "Astana", + continent: { + name: "Asia", + }, + currency: "KZT", + languages: [ + { + name: "Kazakh", + }, + { + name: "Russian", + }, + ], + }, + { + name: "Laos", + code: "LA", + icon: "virtual-host", + icon_status: IconStatus.Up, + capital: "Vientiane", + continent: { + name: "Asia", + }, + currency: "LAK", + languages: [ + { + name: "Laotian", + }, + ], + }, + { + name: "Lebanon", + code: "LB", + icon: "virtual-host", + icon_status: IconStatus.Up, + capital: "Beirut", + continent: { + name: "Asia", + }, + currency: "LBP", + languages: [ + { + name: "Arabic", + }, + { + name: "French", + }, + ], + }, + { + name: "Saint Lucia", + code: "LC", + icon: "virtual-host", + icon_status: IconStatus.Up, + capital: "Castries", + continent: { + name: "North America", + }, + currency: "XCD", + languages: [ + { + name: "English", + }, + ], + }, + { + name: "Liechtenstein", + code: "LI", + icon: "virtual-host", + icon_status: IconStatus.Up, + capital: "Vaduz", + continent: { + name: "Europe", + }, + currency: "CHF", + languages: [ + { + name: "German", + }, + ], + }, + { + name: "Sri Lanka", + code: "LK", + icon: "virtual-host", + icon_status: IconStatus.Up, + capital: "Colombo", + continent: { + name: "Asia", + }, + currency: "LKR", + languages: [ + { + name: "Sinhalese", + }, + { + name: "Tamil", + }, + ], + }, + { + name: "Liberia", + code: "LR", + icon: "virtual-host", + icon_status: IconStatus.Up, + capital: "Monrovia", + continent: { + name: "Africa", + }, + currency: "LRD", + languages: [ + { + name: "English", + }, + ], + }, + { + name: "Lesotho", + code: "LS", + icon: "virtual-host", + icon_status: IconStatus.Up, + capital: "Maseru", + continent: { + name: "Africa", + }, + currency: "LSL,ZAR", + languages: [ + { + name: "English", + }, + { + name: "Southern Sotho", + }, + ], + }, + { + name: "Lithuania", + code: "LT", + icon: "virtual-host", + icon_status: IconStatus.Up, + capital: "Vilnius", + continent: { + name: "Europe", + }, + currency: "EUR", + languages: [ + { + name: "Lithuanian", + }, + ], + }, + { + name: "Luxembourg", + code: "LU", + icon: "virtual-host", + icon_status: IconStatus.Up, + capital: "Luxembourg", + continent: { + name: "Europe", + }, + currency: "EUR", + languages: [ + { + name: "French", + }, + { + name: "German", + }, + { + name: "Luxembourgish", + }, + ], + }, + { + name: "Latvia", + code: "LV", + icon: "virtual-host", + icon_status: IconStatus.Up, + capital: "Riga", + continent: { + name: "Europe", + }, + currency: "EUR", + languages: [ + { + name: "Latvian", + }, + ], + }, + { + name: "Libya", + code: "LY", + icon: "virtual-host", + icon_status: IconStatus.Up, + capital: "Tripoli", + continent: { + name: "Africa", + }, + currency: "LYD", + languages: [ + { + name: "Arabic", + }, + ], + }, + { + name: "Morocco", + code: "MA", + icon: "virtual-host", + icon_status: IconStatus.Up, + capital: "Rabat", + continent: { + name: "Africa", + }, + currency: "MAD", + languages: [ + { + name: "Arabic", + }, + ], + }, + { + name: "Monaco", + code: "MC", + icon: "virtual-host", + icon_status: IconStatus.Up, + capital: "Monaco", + continent: { + name: "Europe", + }, + currency: "EUR", + languages: [ + { + name: "French", + }, + ], + }, + { + name: "Moldova", + code: "MD", + icon: "virtual-host", + icon_status: IconStatus.Up, + capital: "Chișinău", + continent: { + name: "Europe", + }, + currency: "MDL", + languages: [ + { + name: "Romanian", + }, + ], + }, + { + name: "Montenegro", + code: "ME", + icon: "virtual-host", + icon_status: IconStatus.Up, + capital: "Podgorica", + continent: { + name: "Europe", + }, + currency: "EUR", + languages: [ + { + name: "Serbian", + }, + { + name: "Bosnian", + }, + { + name: "Albanian", + }, + { + name: "Croatian", + }, + ], + }, + { + name: "Saint Martin", + code: "MF", + icon: "virtual-host", + icon_status: IconStatus.Up, + capital: "Marigot", + continent: { + name: "North America", + }, + currency: "EUR", + languages: [ + { + name: "English", + }, + { + name: "French", + }, + { + name: "Dutch", + }, + ], + }, + { + name: "Madagascar", + code: "MG", + icon: "virtual-host", + icon_status: IconStatus.Up, + capital: "Antananarivo", + continent: { + name: "Africa", + }, + currency: "MGA", + languages: [ + { + name: "French", + }, + { + name: "Malagasy", + }, + ], + }, + { + name: "Marshall Islands", + code: "MH", + icon: "virtual-host", + icon_status: IconStatus.Up, + capital: "Majuro", + continent: { + name: "Oceania", + }, + currency: "USD", + languages: [ + { + name: "English", + }, + { + name: "Marshallese", + }, + ], + }, + { + name: "North Macedonia", + code: "MK", + icon: "virtual-host", + icon_status: IconStatus.Up, + capital: "Skopje", + continent: { + name: "Europe", + }, + currency: "MKD", + languages: [ + { + name: "Macedonian", + }, + ], + }, + { + name: "Mali", + code: "ML", + icon: "virtual-host", + icon_status: IconStatus.Up, + capital: "Bamako", + continent: { + name: "Africa", + }, + currency: "XOF", + languages: [ + { + name: "French", + }, + ], + }, + { + name: "Myanmar [Burma]", + code: "MM", + icon: "virtual-host", + icon_status: IconStatus.Up, + capital: "Naypyidaw", + continent: { + name: "Asia", + }, + currency: "MMK", + languages: [ + { + name: "Burmese", + }, + ], + }, + { + name: "Mongolia", + code: "MN", + icon: "virtual-host", + icon_status: IconStatus.Up, + capital: "Ulan Bator", + continent: { + name: "Asia", + }, + currency: "MNT", + languages: [ + { + name: "Mongolian", + }, + ], + }, + { + name: "Macao", + code: "MO", + icon: "virtual-host", + icon_status: IconStatus.Up, + capital: null, + continent: { + name: "Asia", + }, + currency: "MOP", + languages: [ + { + name: "Chinese", + }, + { + name: "Portuguese", + }, + ], + }, + { + name: "Northern Mariana Islands", + code: "MP", + icon: "virtual-host", + icon_status: IconStatus.Up, + capital: "Saipan", + continent: { + name: "Oceania", + }, + currency: "USD", + languages: [ + { + name: "English", + }, + { + name: "Chamorro", + }, + ], + }, + { + name: "Martinique", + code: "MQ", + icon: "virtual-host", + icon_status: IconStatus.Up, + capital: "Fort-de-France", + continent: { + name: "North America", + }, + currency: "EUR", + languages: [ + { + name: "French", + }, + ], + }, + { + name: "Mauritania", + code: "MR", + icon: "virtual-host", + icon_status: IconStatus.Up, + capital: "Nouakchott", + continent: { + name: "Africa", + }, + currency: "MRU", + languages: [ + { + name: "Arabic", + }, + ], + }, + { + name: "Montserrat", + code: "MS", + icon: "virtual-host", + icon_status: IconStatus.Up, + capital: "Plymouth", + continent: { + name: "North America", + }, + currency: "XCD", + languages: [ + { + name: "English", + }, + ], + }, + { + name: "Malta", + code: "MT", + icon: "virtual-host", + icon_status: IconStatus.Up, + capital: "Valletta", + continent: { + name: "Europe", + }, + currency: "EUR", + languages: [ + { + name: "Maltese", + }, + { + name: "English", + }, + ], + }, + { + name: "Mauritius", + code: "MU", + icon: "virtual-host", + icon_status: IconStatus.Up, + capital: "Port Louis", + continent: { + name: "Africa", + }, + currency: "MUR", + languages: [ + { + name: "English", + }, + ], + }, + { + name: "Maldives", + code: "MV", + icon: "virtual-host", + icon_status: IconStatus.Up, + capital: "Malé", + continent: { + name: "Asia", + }, + currency: "MVR", + languages: [ + { + name: "Divehi", + }, + ], + }, + { + name: "Malawi", + code: "MW", + icon: "virtual-host", + icon_status: IconStatus.Up, + capital: "Lilongwe", + continent: { + name: "Africa", + }, + currency: "MWK", + languages: [ + { + name: "English", + }, + { + name: "Chichewa", + }, + ], + }, + { + name: "Mexico", + code: "MX", + icon: "virtual-host", + icon_status: IconStatus.Up, + capital: "Mexico City", + continent: { + name: "North America", + }, + currency: "MXN", + languages: [ + { + name: "Spanish", + }, + ], + }, + { + name: "Malaysia", + code: "MY", + icon: "virtual-host", + icon_status: IconStatus.Up, + capital: "Kuala Lumpur", + continent: { + name: "Asia", + }, + currency: "MYR", + languages: [ + { + name: "Malay", + }, + ], + }, + { + name: "Mozambique", + code: "MZ", + icon: "virtual-host", + icon_status: IconStatus.Up, + capital: "Maputo", + continent: { + name: "Africa", + }, + currency: "MZN", + languages: [ + { + name: "Portuguese", + }, + ], + }, + { + name: "Namibia", + code: "NA", + icon: "virtual-host", + icon_status: IconStatus.Up, + capital: "Windhoek", + continent: { + name: "Africa", + }, + currency: "NAD,ZAR", + languages: [ + { + name: "English", + }, + { + name: "Afrikaans", + }, + ], + }, + { + name: "New Caledonia", + code: "NC", + icon: "virtual-host", + icon_status: IconStatus.Up, + capital: "Nouméa", + continent: { + name: "Oceania", + }, + currency: "XPF", + languages: [ + { + name: "French", + }, + ], + }, + { + name: "Niger", + code: "NE", + icon: "virtual-host", + icon_status: IconStatus.Up, + capital: "Niamey", + continent: { + name: "Africa", + }, + currency: "XOF", + languages: [ + { + name: "French", + }, + ], + }, + { + name: "Norfolk Island", + code: "NF", + icon: "virtual-host", + icon_status: IconStatus.Up, + capital: "Kingston", + continent: { + name: "Oceania", + }, + currency: "AUD", + languages: [ + { + name: "English", + }, + ], + }, + { + name: "Nigeria", + code: "NG", + icon: "virtual-host", + icon_status: IconStatus.Up, + capital: "Abuja", + continent: { + name: "Africa", + }, + currency: "NGN", + languages: [ + { + name: "English", + }, + ], + }, + { + name: "Nicaragua", + code: "NI", + icon: "virtual-host", + icon_status: IconStatus.Up, + capital: "Managua", + continent: { + name: "North America", + }, + currency: "NIO", + languages: [ + { + name: "Spanish", + }, + ], + }, + { + name: "Netherlands", + code: "NL", + icon: "virtual-host", + icon_status: IconStatus.Up, + capital: "Amsterdam", + continent: { + name: "Europe", + }, + currency: "EUR", + languages: [ + { + name: "Dutch", + }, + ], + }, + { + name: "Norway", + code: "NO", + icon: "virtual-host", + icon_status: IconStatus.Up, + capital: "Oslo", + continent: { + name: "Europe", + }, + currency: "NOK", + languages: [ + { + name: "Norwegian", + }, + { + name: "Norwegian Bokmål", + }, + { + name: "Norwegian Nynorsk", + }, + ], + }, + { + name: "Nepal", + code: "NP", + icon: "virtual-host", + icon_status: IconStatus.Up, + capital: "Kathmandu", + continent: { + name: "Asia", + }, + currency: "NPR", + languages: [ + { + name: "Nepali", + }, + ], + }, + { + name: "Nauru", + code: "NR", + icon: "virtual-host", + icon_status: IconStatus.Up, + capital: "Yaren", + continent: { + name: "Oceania", + }, + currency: "AUD", + languages: [ + { + name: "English", + }, + { + name: "Nauruan", + }, + ], + }, + { + name: "Niue", + code: "NU", + icon: "virtual-host", + icon_status: IconStatus.Up, + capital: "Alofi", + continent: { + name: "Oceania", + }, + currency: "NZD", + languages: [ + { + name: "English", + }, + ], + }, + { + name: "New Zealand", + code: "NZ", + icon: "virtual-host", + icon_status: IconStatus.Up, + capital: "Wellington", + continent: { + name: "Oceania", + }, + currency: "NZD", + languages: [ + { + name: "English", + }, + { + name: "Maori", + }, + ], + }, + { + name: "Oman", + code: "OM", + icon: "virtual-host", + icon_status: IconStatus.Up, + capital: "Muscat", + continent: { + name: "Asia", + }, + currency: "OMR", + languages: [ + { + name: "Arabic", + }, + ], + }, + { + name: "Panama", + code: "PA", + icon: "virtual-host", + icon_status: IconStatus.Up, + capital: "Panama City", + continent: { + name: "North America", + }, + currency: "PAB,USD", + languages: [ + { + name: "Spanish", + }, + ], + }, + { + name: "Peru", + code: "PE", + icon: "virtual-host", + icon_status: IconStatus.Up, + capital: "Lima", + continent: { + name: "South America", + }, + currency: "PEN", + languages: [ + { + name: "Spanish", + }, + ], + }, + { + name: "French Polynesia", + code: "PF", + icon: "virtual-host", + icon_status: IconStatus.Up, + capital: "Papeetē", + continent: { + name: "Oceania", + }, + currency: "XPF", + languages: [ + { + name: "French", + }, + ], + }, + { + name: "Papua New Guinea", + code: "PG", + icon: "virtual-host", + icon_status: IconStatus.Up, + capital: "Port Moresby", + continent: { + name: "Oceania", + }, + currency: "PGK", + languages: [ + { + name: "English", + }, + ], + }, + { + name: "Philippines", + code: "PH", + icon: "virtual-host", + icon_status: IconStatus.Up, + capital: "Manila", + continent: { + name: "Asia", + }, + currency: "PHP", + languages: [ + { + name: "English", + }, + ], + }, + { + name: "Pakistan", + code: "PK", + icon: "virtual-host", + icon_status: IconStatus.Up, + capital: "Islamabad", + continent: { + name: "Asia", + }, + currency: "PKR", + languages: [ + { + name: "English", + }, + { + name: "Urdu", + }, + ], + }, + { + name: "Poland", + code: "PL", + icon: "virtual-host", + icon_status: IconStatus.Up, + capital: "Warsaw", + continent: { + name: "Europe", + }, + currency: "PLN", + languages: [ + { + name: "Polish", + }, + ], + }, + { + name: "Saint Pierre and Miquelon", + code: "PM", + icon: "virtual-host", + icon_status: IconStatus.Up, + capital: "Saint-Pierre", + continent: { + name: "North America", + }, + currency: "EUR", + languages: [ + { + name: "French", + }, + ], + }, + { + name: "Pitcairn Islands", + code: "PN", + icon: "virtual-host", + icon_status: IconStatus.Up, + capital: "Adamstown", + continent: { + name: "Oceania", + }, + currency: "NZD", + languages: [ + { + name: "English", + }, + ], + }, + { + name: "Puerto Rico", + code: "PR", + icon: "virtual-host", + icon_status: IconStatus.Up, + capital: "San Juan", + continent: { + name: "North America", + }, + currency: "USD", + languages: [ + { + name: "Spanish", + }, + { + name: "English", + }, + ], + }, + { + name: "Palestine", + code: "PS", + icon: "virtual-host", + icon_status: IconStatus.Up, + capital: "Ramallah", + continent: { + name: "Asia", + }, + currency: "ILS", + languages: [ + { + name: "Arabic", + }, + ], + }, + { + name: "Portugal", + code: "PT", + icon: "virtual-host", + icon_status: IconStatus.Up, + capital: "Lisbon", + continent: { + name: "Europe", + }, + currency: "EUR", + languages: [ + { + name: "Portuguese", + }, + ], + }, + { + name: "Palau", + code: "PW", + icon: "virtual-host", + icon_status: IconStatus.Up, + capital: "Ngerulmud", + continent: { + name: "Oceania", + }, + currency: "USD", + languages: [ + { + name: "English", + }, + ], + }, + { + name: "Paraguay", + code: "PY", + icon: "virtual-host", + icon_status: IconStatus.Up, + capital: "Asunción", + continent: { + name: "South America", + }, + currency: "PYG", + languages: [ + { + name: "Spanish", + }, + { + name: "Guarani", + }, + ], + }, + { + name: "Qatar", + code: "QA", + icon: "virtual-host", + icon_status: IconStatus.Up, + capital: "Doha", + continent: { + name: "Asia", + }, + currency: "QAR", + languages: [ + { + name: "Arabic", + }, + ], + }, + { + name: "Réunion", + code: "RE", + icon: "virtual-host", + icon_status: IconStatus.Up, + capital: "Saint-Denis", + continent: { + name: "Africa", + }, + currency: "EUR", + languages: [ + { + name: "French", + }, + ], + }, + { + name: "Romania", + code: "RO", + icon: "virtual-host", + icon_status: IconStatus.Up, + capital: "Bucharest", + continent: { + name: "Europe", + }, + currency: "RON", + languages: [ + { + name: "Romanian", + }, + ], + }, + { + name: "Serbia", + code: "RS", + icon: "virtual-host", + icon_status: IconStatus.Up, + capital: "Belgrade", + continent: { + name: "Europe", + }, + currency: "RSD", + languages: [ + { + name: "Serbian", + }, + ], + }, + { + name: "Russia", + code: "RU", + icon: "virtual-host", + icon_status: IconStatus.Up, + capital: "Moscow", + continent: { + name: "Europe", + }, + currency: "RUB", + languages: [ + { + name: "Russian", + }, + ], + }, + { + name: "Rwanda", + code: "RW", + icon: "virtual-host", + icon_status: IconStatus.Up, + capital: "Kigali", + continent: { + name: "Africa", + }, + currency: "RWF", + languages: [ + { + name: "Rwandi", + }, + { + name: "English", + }, + { + name: "French", + }, + ], + }, + { + name: "Saudi Arabia", + code: "SA", + icon: "virtual-host", + icon_status: IconStatus.Up, + capital: "Riyadh", + continent: { + name: "Asia", + }, + currency: "SAR", + languages: [ + { + name: "Arabic", + }, + ], + }, + { + name: "Solomon Islands", + code: "SB", + icon: "virtual-host", + icon_status: IconStatus.Up, + capital: "Honiara", + continent: { + name: "Oceania", + }, + currency: "SBD", + languages: [ + { + name: "English", + }, + ], + }, + { + name: "Seychelles", + code: "SC", + icon: "virtual-host", + icon_status: IconStatus.Up, + capital: "Victoria", + continent: { + name: "Africa", + }, + currency: "SCR", + languages: [ + { + name: "French", + }, + { + name: "English", + }, + ], + }, + { + name: "Sudan", + code: "SD", + icon: "virtual-host", + icon_status: IconStatus.Up, + capital: "Khartoum", + continent: { + name: "Africa", + }, + currency: "SDG", + languages: [ + { + name: "Arabic", + }, + { + name: "English", + }, + ], + }, + { + name: "Sweden", + code: "SE", + icon: "virtual-host", + icon_status: IconStatus.Up, + capital: "Stockholm", + continent: { + name: "Europe", + }, + currency: "SEK", + languages: [ + { + name: "Swedish", + }, + ], + }, + { + name: "Singapore", + code: "SG", + icon: "virtual-host", + icon_status: IconStatus.Up, + capital: "Singapore", + continent: { + name: "Asia", + }, + currency: "SGD", + languages: [ + { + name: "English", + }, + { + name: "Malay", + }, + { + name: "Tamil", + }, + { + name: "Chinese", + }, + ], + }, + { + name: "Saint Helena", + code: "SH", + icon: "virtual-host", + icon_status: IconStatus.Up, + capital: "Jamestown", + continent: { + name: "Africa", + }, + currency: "SHP", + languages: [ + { + name: "English", + }, + ], + }, + { + name: "Slovenia", + code: "SI", + icon: "virtual-host", + icon_status: IconStatus.Up, + capital: "Ljubljana", + continent: { + name: "Europe", + }, + currency: "EUR", + languages: [ + { + name: "Slovenian", + }, + ], + }, + { + name: "Svalbard and Jan Mayen", + code: "SJ", + icon: "virtual-host", + icon_status: IconStatus.Up, + capital: "Longyearbyen", + continent: { + name: "Europe", + }, + currency: "NOK", + languages: [ + { + name: "Norwegian", + }, + ], + }, + { + name: "Slovakia", + code: "SK", + icon: "virtual-host", + icon_status: IconStatus.Up, + capital: "Bratislava", + continent: { + name: "Europe", + }, + currency: "EUR", + languages: [ + { + name: "Slovak", + }, + ], + }, + { + name: "Sierra Leone", + code: "SL", + icon: "virtual-host", + icon_status: IconStatus.Up, + capital: "Freetown", + continent: { + name: "Africa", + }, + currency: "SLL", + languages: [ + { + name: "English", + }, + ], + }, + { + name: "San Marino", + code: "SM", + icon: "virtual-host", + icon_status: IconStatus.Up, + capital: "City of San Marino", + continent: { + name: "Europe", + }, + currency: "EUR", + languages: [ + { + name: "Italian", + }, + ], + }, + { + name: "Senegal", + code: "SN", + icon: "virtual-host", + icon_status: IconStatus.Up, + capital: "Dakar", + continent: { + name: "Africa", + }, + currency: "XOF", + languages: [ + { + name: "French", + }, + ], + }, + { + name: "Somalia", + code: "SO", + icon: "virtual-host", + icon_status: IconStatus.Up, + capital: "Mogadishu", + continent: { + name: "Africa", + }, + currency: "SOS", + languages: [ + { + name: "Somalia", + }, + { + name: "Arabic", + }, + ], + }, + { + name: "Suriname", + code: "SR", + icon: "virtual-host", + icon_status: IconStatus.Up, + capital: "Paramaribo", + continent: { + name: "South America", + }, + currency: "SRD", + languages: [ + { + name: "Dutch", + }, + ], + }, + { + name: "South Sudan", + code: "SS", + icon: "virtual-host", + icon_status: IconStatus.Up, + capital: "Juba", + continent: { + name: "Africa", + }, + currency: "SSP", + languages: [ + { + name: "English", + }, + ], + }, + { + name: "São Tomé and Príncipe", + code: "ST", + icon: "virtual-host", + icon_status: IconStatus.Up, + capital: "São Tomé", + continent: { + name: "Africa", + }, + currency: "STN", + languages: [ + { + name: "Portuguese", + }, + ], + }, + { + name: "El Salvador", + code: "SV", + icon: "virtual-host", + icon_status: IconStatus.Up, + capital: "San Salvador", + continent: { + name: "North America", + }, + currency: "SVC,USD", + languages: [ + { + name: "Spanish", + }, + ], + }, + { + name: "Sint Maarten", + code: "SX", + icon: "virtual-host", + icon_status: IconStatus.Up, + capital: "Philipsburg", + continent: { + name: "North America", + }, + currency: "ANG", + languages: [ + { + name: "Dutch", + }, + { + name: "English", + }, + ], + }, + { + name: "Syria", + code: "SY", + icon: "virtual-host", + icon_status: IconStatus.Up, + capital: "Damascus", + continent: { + name: "Asia", + }, + currency: "SYP", + languages: [ + { + name: "Arabic", + }, + ], + }, + { + name: "Swaziland", + code: "SZ", + icon: "virtual-host", + icon_status: IconStatus.Up, + capital: "Lobamba", + continent: { + name: "Africa", + }, + currency: "SZL", + languages: [ + { + name: "English", + }, + { + name: "Swati", + }, + ], + }, + { + name: "Turks and Caicos Islands", + code: "TC", + icon: "virtual-host", + icon_status: IconStatus.Up, + capital: "Cockburn Town", + continent: { + name: "North America", + }, + currency: "USD", + languages: [ + { + name: "English", + }, + ], + }, + { + name: "Chad", + code: "TD", + icon: "virtual-host", + icon_status: IconStatus.Up, + capital: "N'Djamena", + continent: { + name: "Africa", + }, + currency: "XAF", + languages: [ + { + name: "French", + }, + { + name: "Arabic", + }, + ], + }, + { + name: "French Southern Territories", + code: "TF", + icon: "virtual-host", + icon_status: IconStatus.Up, + capital: "Port-aux-Français", + continent: { + name: "Antarctica", + }, + currency: "EUR", + languages: [ + { + name: "French", + }, + ], + }, + { + name: "Togo", + code: "TG", + icon: "virtual-host", + icon_status: IconStatus.Up, + capital: "Lomé", + continent: { + name: "Africa", + }, + currency: "XOF", + languages: [ + { + name: "French", + }, + ], + }, + { + name: "Thailand", + code: "TH", + icon: "virtual-host", + icon_status: IconStatus.Up, + capital: "Bangkok", + continent: { + name: "Asia", + }, + currency: "THB", + languages: [ + { + name: "Thai", + }, + ], + }, + { + name: "Tajikistan", + code: "TJ", + icon: "virtual-host", + icon_status: IconStatus.Up, + capital: "Dushanbe", + continent: { + name: "Asia", + }, + currency: "TJS", + languages: [ + { + name: "Tajik", + }, + { + name: "Russian", + }, + ], + }, + { + name: "Tokelau", + code: "TK", + icon: "virtual-host", + icon_status: IconStatus.Up, + capital: "Fakaofo", + continent: { + name: "Oceania", + }, + currency: "NZD", + languages: [ + { + name: "English", + }, + ], + }, + { + name: "East Timor", + code: "TL", + icon: "virtual-host", + icon_status: IconStatus.Up, + capital: "Dili", + continent: { + name: "Oceania", + }, + currency: "USD", + languages: [ + { + name: "Portuguese", + }, + ], + }, + { + name: "Turkmenistan", + code: "TM", + icon: "virtual-host", + icon_status: IconStatus.Up, + capital: "Ashgabat", + continent: { + name: "Asia", + }, + currency: "TMT", + languages: [ + { + name: "Turkmen", + }, + { + name: "Russian", + }, + ], + }, + { + name: "Tunisia", + code: "TN", + icon: "virtual-host", + icon_status: IconStatus.Up, + capital: "Tunis", + continent: { + name: "Africa", + }, + currency: "TND", + languages: [ + { + name: "Arabic", + }, + ], + }, + { + name: "Tonga", + code: "TO", + icon: "virtual-host", + icon_status: IconStatus.Up, + capital: "Nuku'alofa", + continent: { + name: "Oceania", + }, + currency: "TOP", + languages: [ + { + name: "English", + }, + { + name: "Tonga", + }, + ], + }, + { + name: "Turkey", + code: "TR", + icon: "virtual-host", + icon_status: IconStatus.Up, + capital: "Ankara", + continent: { + name: "Asia", + }, + currency: "TRY", + languages: [ + { + name: "Turkish", + }, + ], + }, + { + name: "Trinidad and Tobago", + code: "TT", + icon: "virtual-host", + icon_status: IconStatus.Up, + capital: "Port of Spain", + continent: { + name: "North America", + }, + currency: "TTD", + languages: [ + { + name: "English", + }, + ], + }, + { + name: "Tuvalu", + code: "TV", + icon: "virtual-host", + icon_status: IconStatus.Up, + capital: "Funafuti", + continent: { + name: "Oceania", + }, + currency: "AUD", + languages: [ + { + name: "English", + }, + ], + }, + { + name: "Taiwan", + code: "TW", + icon: "virtual-host", + icon_status: IconStatus.Up, + capital: "Taipei", + continent: { + name: "Asia", + }, + currency: "TWD", + languages: [ + { + name: "Chinese", + }, + ], + }, + { + name: "Tanzania", + code: "TZ", + icon: "virtual-host", + icon_status: IconStatus.Up, + capital: "Dodoma", + continent: { + name: "Africa", + }, + currency: "TZS", + languages: [ + { + name: "Swahili", + }, + { + name: "English", + }, + ], + }, + { + name: "Ukraine", + code: "UA", + icon: "virtual-host", + icon_status: IconStatus.Up, + capital: "Kyiv", + continent: { + name: "Europe", + }, + currency: "UAH", + languages: [ + { + name: "Ukrainian", + }, + ], + }, + { + name: "Uganda", + code: "UG", + icon: "virtual-host", + icon_status: IconStatus.Up, + capital: "Kampala", + continent: { + name: "Africa", + }, + currency: "UGX", + languages: [ + { + name: "English", + }, + { + name: "Swahili", + }, + ], + }, + { + name: "U.S. Minor Outlying Islands", + code: "UM", + icon: "virtual-host", + icon_status: IconStatus.Up, + capital: null, + continent: { + name: "Oceania", + }, + currency: "USD", + languages: [ + { + name: "English", + }, + ], + }, + { + name: "United States", + code: "US", + icon: "virtual-host", + icon_status: IconStatus.Up, + capital: "Washington D.C.", + continent: { + name: "North America", + }, + currency: "USD,USN,USS", + languages: [ + { + name: "English", + }, + ], + }, + { + name: "Uruguay", + code: "UY", + icon: "virtual-host", + icon_status: IconStatus.Up, + capital: "Montevideo", + continent: { + name: "South America", + }, + currency: "UYI,UYU", + languages: [ + { + name: "Spanish", + }, + ], + }, + { + name: "Uzbekistan", + code: "UZ", + icon: "virtual-host", + icon_status: IconStatus.Up, + capital: "Tashkent", + continent: { + name: "Asia", + }, + currency: "UZS", + languages: [ + { + name: "Uzbek", + }, + { + name: "Russian", + }, + ], + }, + { + name: "Vatican City", + code: "VA", + icon: "virtual-host", + icon_status: IconStatus.Up, + capital: "Vatican City", + continent: { + name: "Europe", + }, + currency: "EUR", + languages: [ + { + name: "Italian", + }, + { + name: "Latin", + }, + ], + }, + { + name: "Saint Vincent and the Grenadines", + code: "VC", + icon: "virtual-host", + icon_status: IconStatus.Up, + capital: "Kingstown", + continent: { + name: "North America", + }, + currency: "XCD", + languages: [ + { + name: "English", + }, + ], + }, + { + name: "Venezuela", + code: "VE", + icon: "virtual-host", + icon_status: IconStatus.Up, + capital: "Caracas", + continent: { + name: "South America", + }, + currency: "VES", + languages: [ + { + name: "Spanish", + }, + ], + }, + { + name: "British Virgin Islands", + code: "VG", + icon: "virtual-host", + icon_status: IconStatus.Up, + capital: "Road Town", + continent: { + name: "North America", + }, + currency: "USD", + languages: [ + { + name: "English", + }, + ], + }, + { + name: "U.S. Virgin Islands", + code: "VI", + icon: "virtual-host", + icon_status: IconStatus.Up, + capital: "Charlotte Amalie", + continent: { + name: "North America", + }, + currency: "USD", + languages: [ + { + name: "English", + }, + ], + }, + { + name: "Vietnam", + code: "VN", + icon: "virtual-host", + icon_status: IconStatus.Up, + capital: "Hanoi", + continent: { + name: "Asia", + }, + currency: "VND", + languages: [ + { + name: "Vietnamese", + }, + ], + }, + { + name: "Vanuatu", + code: "VU", + icon: "virtual-host", + icon_status: IconStatus.Up, + capital: "Port Vila", + continent: { + name: "Oceania", + }, + currency: "VUV", + languages: [ + { + name: "Bislama", + }, + { + name: "English", + }, + { + name: "French", + }, + ], + }, + { + name: "Wallis and Futuna", + code: "WF", + icon: "virtual-host", + icon_status: IconStatus.Up, + capital: "Mata-Utu", + continent: { + name: "Oceania", + }, + currency: "XPF", + languages: [ + { + name: "French", + }, + ], + }, + { + name: "Samoa", + code: "WS", + icon: "virtual-host", + icon_status: IconStatus.Up, + capital: "Apia", + continent: { + name: "Oceania", + }, + currency: "WST", + languages: [ + { + name: "Samoan", + }, + { + name: "English", + }, + ], + }, + { + name: "Kosovo", + code: "XK", + icon: "virtual-host", + icon_status: IconStatus.Up, + capital: "Pristina", + continent: { + name: "Europe", + }, + currency: "EUR", + languages: [ + { + name: "Albanian", + }, + { + name: "Serbian", + }, + ], + }, + { + name: "Yemen", + code: "YE", + icon: "virtual-host", + icon_status: IconStatus.Up, + capital: "Sana'a", + continent: { + name: "Asia", + }, + currency: "YER", + languages: [ + { + name: "Arabic", + }, + ], + }, + { + name: "Mayotte", + code: "YT", + icon: "virtual-host", + icon_status: IconStatus.Up, + capital: "Mamoudzou", + continent: { + name: "Africa", + }, + currency: "EUR", + languages: [ + { + name: "French", + }, + ], + }, + { + name: "South Africa", + code: "ZA", + icon: "virtual-host", + icon_status: IconStatus.Up, + capital: "Pretoria", + continent: { + name: "Africa", + }, + currency: "ZAR", + languages: [ + { + name: "Afrikaans", + }, + { + name: "English", + }, + { + name: "South Ndebele", + }, + { + name: "Southern Sotho", + }, + { + name: "Swati", + }, + { + name: "Tswana", + }, + { + name: "Tsonga", + }, + { + name: "Venda", + }, + { + name: "Xhosa", + }, + { + name: "Zulu", + }, + ], + }, + { + name: "Zambia", + code: "ZM", + icon: "virtual-host", + icon_status: IconStatus.Up, + capital: "Lusaka", + continent: { + name: "Africa", + }, + currency: "ZMW", + languages: [ + { + name: "English", + }, + ], + }, + { + name: "Zimbabwe", + code: "ZW", + icon: "virtual-host", + icon_status: IconStatus.Up, + capital: "Harare", + continent: { + name: "Africa", + }, + currency: "USD,ZAR,BWP,GBP,AUD,CNY,INR,JPY", + languages: [ + { + name: "English", + }, + { + name: "Shona", + }, + { + name: "North Ndebele", + }, + ], + }, + ], + }, +}; +\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\`, + "widget-types/drilldown/drilldown-widget/drilldown-widget-example.component.ts": \\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\`// © 2022 SolarWinds Worldwide, LLC. All rights reserved. +// +// Permission is hereby granted, free of charge, to any person obtaining a copy +// of this software and associated documentation files (the "Software"), to +// deal in the Software without restriction, including without limitation the +// rights to use, copy, modify, merge, publish, distribute, sublicense, and/or +// sell copies of the Software, and to permit persons to whom the Software is +// furnished to do so, subject to the following conditions: +// +// The above copyright notice and this permission notice shall be included in +// all copies or substantial portions of the Software. +// +// THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR +// IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, +// FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE +// AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER +// LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, +// OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN +// THE SOFTWARE. + +import { HttpClient } from "@angular/common/http"; +import { + ChangeDetectorRef, + Component, + Injectable, + OnDestroy, + OnInit, +} from "@angular/core"; +import { GridsterConfig, GridsterItem } from "angular-gridster2"; +import { Apollo, gql } from "apollo-angular"; +import groupBy from "lodash/groupBy"; +import { BehaviorSubject, Observable, of } from "rxjs"; +import { catchError, delay, filter, map } from "rxjs/operators"; + +import { + DataSourceFeatures, + IconStatus, + IDataField, + IDataSource, + IDataSourceFeatures, + IDataSourceFeaturesConfiguration, + INovaFilters, + LoggerService, + ServerSideDataSource, + IFilters, +} from "@nova-ui/bits"; +import { + DATA_SOURCE, + DEFAULT_PIZZAGNA_ROOT, + IDashboard, + IDrilldownComponentsConfiguration, + IListWidgetConfiguration, + IProviderConfiguration, + IWidget, + IWidgets, + ListGroupItemComponent, + ListLeafItemComponent, + NOVA_DRILLDOWN_DATASOURCE_ADAPTER, + PizzagnaLayer, + ProviderRegistryService, + WellKnownPathKey, + WellKnownProviders, + WidgetTypesService, +} from "@nova-ui/dashboards"; + +import { DrilldownDataSource } from "./mock-data-source"; + +/** + * A simple KPI data source to retrieve the average rating of Harry Potter and the Sorcerer's Stone (book) via googleapis + */ +@Injectable() +export class DrilldownDataSourceRealApi + extends ServerSideDataSource + implements OnDestroy, IDataSource +{ + // This is the ID we'll use to identify the provider + public static providerId = "DrilldownDataSourceRealApi"; + + // Use this subject to communicate the data source's busy state + public busy = new BehaviorSubject(false); + public dataFields: Partial[] = [ + { id: "regionName", label: "Region name" }, + { id: "subregionName", label: "Subregion name" }, + ]; + + public features: IDataSourceFeaturesConfiguration; + private supportedFeatures: IDataSourceFeatures = { + search: { enabled: true }, + }; + + private drillState: string[] = []; + private groupBy: string[]; + + constructor( + private logger: LoggerService, + private http: HttpClient, + private apollo: Apollo + ) { + super(); + this.features = new DataSourceFeatures(this.supportedFeatures); + // TODO: remove Partial in vNext after marking dataType field as optional - NUI-5838 + ( + this.dataFieldsConfig.dataFields$ as BehaviorSubject< + Partial[] + > + ).next(this.dataFields); + } + + private groupedDataHistory: Array> = []; + + // In this example, getFilteredData is invoked every 10 minutes (Take a look at the refresher + // provider definition in the widget configuration below to see how the interval is set) + public async getFilteredData(data: IFilters): Promise { + return of(data) + .pipe( + filter(() => !!this.drillState), + map((countries) => { + const lastHistory = () => getLast(this.groupedDataHistory); + + if (!this.drillState.length && !this.groupBy.length) { + return countries; + } + + // adding "ROOT" as a root level for drilling + const fullDrillState = ["ROOT", ...this.drillState]; + const activeDrillLvl = fullDrillState.length; + const historyLvl = this.groupedDataHistory.length; + + // checking how many lvls we have to group for drilling, in case some are missed + const drillLvlDiff = activeDrillLvl - historyLvl; + + if (!drillLvlDiff) { + return lastHistory() || countries; + } + + const drillToGroup = fullDrillState.slice( + fullDrillState.length - drillLvlDiff + ); + + for (const drill of drillToGroup) { + const drillIdx = fullDrillState.findIndex( + (v) => v === drill + ); + const group = this.groupBy[drillIdx]; + + if (group) { + const dataToGroup = lastHistory() + ? lastHistory()[drill] + : countries; + const lastGroupedValue = groupBy( + dataToGroup, + group + ); + + this.groupedDataHistory.push(lastGroupedValue); + } + } + + // take last if we have all data grouped + if (this.groupBy.length === this.drillState.length) { + return lastHistory()[getLast(this.drillState)]; + } + + // get groping and transform to raw data format + return this.getGroupsWidgetData(lastHistory()); + }) + ) + .toPromise(); + } + + public ngOnDestroy(): void { + this.outputsSubject.complete(); + } + + // This method is expected to return all data needed for repeat/paginator/filterGroups in order to work. + // In case of custom filtering participants feel free to extend INovaFilteringOutputs. + protected getBackendData(filters: INovaFilters): Observable { + const mainRequest = this.apollo.watchQuery<{ countries: any }>({ + query: this.generateQuery(filters), + }); + + return mainRequest.valueChanges.pipe( + // mock delay + delay(300), + // data mapping, !DS specific! + map((res) => res.data.countries), + // adds mock icons to be displayed on leaf nodes !DS specific! + map((res: any[]) => + res.map((v) => ({ + ...v, + icon: "virtual-host", + icon_status: IconStatus.Up, + subregionName: + v.subregion?.name || "No Subregion Specified", + regionName: + v.subregion?.region?.name || "No Region Specified", + })) + ), + catchError((e) => { + this.logger.error(e); + return of({} as any); + }) + ); + } + + private generateQuery(filters: INovaFilters) { + const { search } = filters; + const searchValue = search?.value ? \\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\`^[\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\${search.value}]*\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\` : ""; + + const queryString = \\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\` + query { + countries(filter: {name: {regex: "\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\${searchValue}"} }) { + name + native + capital + languages { + name + } + currencies + subdivisions { + name + } + } + } + \\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\`; + + return gql\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\` + \\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\${queryString} + \\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\`; + } + + // Overrides default ServerSideDataSource.beforeApplyFilters implementation + // to save some filters that are used internally + // -- !DS specific + protected beforeApplyFilters(filters: INovaFilters): void { + this.busy.next(true); + + this.drillState = filters.drillstate?.value; + this.groupBy = filters.group?.value; + + if (this.isHome()) { + this.groupedDataHistory.length = 0; + } + + if (this.isBack()) { + this.groupedDataHistory.length = this.groupedDataHistory.length - 1; + } + + if (this.getFilters()["search"] && this.filterChanged("search")) { + this.groupedDataHistory.length = 0; + } + } + + private getGroupsWidgetData(groupByObj: Record) { + return Object.keys(groupByObj).map((property) => ({ + id: property, + label: property, + // statuses that will be displayed on group item + statuses: [ + { key: "virtual-host", value: groupByObj[property].length }, + { + key: "acknowledge", + value: this.getPopulation(groupByObj[property]), + }, + ], + })); + } + + private isHome(): boolean { + return this.drillState?.length === 0; + } + + private isBack(): boolean { + return ( + this.groupedDataHistory?.length > this.drillState?.length && + !this.isHome() + ); + } + + /** + * Gets population for the country(ies) + */ + private getPopulation(countries: any[]) { + const totalPopulation = countries.reduce( + (acc, next) => (acc += next.population), + 0 + ); + return \\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\`\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\${totalPopulation * Math.pow(10, -3)} k\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\`; + } +} + +@Component({ + selector: "drilldown-widget-example", + templateUrl: "./drilldown-widget-example.component.html", + styleUrls: ["./drilldown-widget-example.component.less"], + standalone: false, +}) +export class DrilldownWidgetExampleComponent implements OnInit { + // This variable will hold all the data needed to define the layout and behavior of the widgets. + // Pass this to the dashboard component's dashboard input in the template. + public dashboard: IDashboard | undefined; + + // Angular gridster requires a configuration object even if it's empty. + // Pass this to the dashboard component's gridsterConfig input in the template. + public gridsterConfig: GridsterConfig = {}; + + // Boolean passed as an input to the dashboard. When true, widgets can be moved, resized, removed, or edited + public editMode: boolean = false; + + constructor( + // WidgetTypesService provides the widget's necessary structure information + private widgetTypesService: WidgetTypesService, + private providerRegistry: ProviderRegistryService, + private changeDetectorRef: ChangeDetectorRef + ) {} + + public ngOnInit(): void { + // Registering the data source for injection into the KPI tile. + // Note: Each tile of a KPI widget is assigned its own instance of the data source + this.providerRegistry.setProviders({ + [DrilldownDataSource.providerId]: { + provide: DATA_SOURCE, + useClass: DrilldownDataSource, + // Any dependencies that need to be injected into the provider must be listed here + deps: [HttpClient], + }, + [DrilldownDataSourceRealApi.providerId]: { + provide: DATA_SOURCE, + useClass: DrilldownDataSourceRealApi, + // Any dependencies that need to be injected into the provider must be listed here + deps: [LoggerService, HttpClient, Apollo], + }, + }); + + this.initializeDashboard(); + const widgetTemplate = this.widgetTypesService.getWidgetType( + "drilldown", + 1 + ); + this.widgetTypesService.setNode( + widgetTemplate, + "configurator", + WellKnownPathKey.DataSourceProviders, + [ + DrilldownDataSourceRealApi.providerId, + DrilldownDataSource.providerId, + ] + ); + } + + public initializeDashboard(): void { + // We're using a static configuration object for this example, but this is where + // the widget's configuration could potentially be populated from a database + const drilldownWidget = widgetConfig; + const widgets: IWidgets = { + // Complete the widget with information coming from its type definition + [drilldownWidget.id]: + this.widgetTypesService.mergeWithWidgetType(drilldownWidget), + }; + + // Setting the widget dimensions and position (this is for gridster) + const positions: Record = { + [drilldownWidget.id]: { + cols: 10, + rows: 10, + y: 0, + x: 0, + }, + }; + + // Finally, assigning the variables we created above to the dashboard + this.dashboard = { positions, widgets }; + } + + public reInitializeDashboard(): void { + // destroys the components and their providers so the dashboard can re init data + this.dashboard = undefined; + this.changeDetectorRef.detectChanges(); + + const adapterProperties = + widgetConfig.pizzagna[PizzagnaLayer.Configuration].listWidget + .providers?.adapter?.properties; + + if (adapterProperties) { + adapterProperties.drillstate = []; + } + + this.initializeDashboard(); + } +} + +const widgetConfig: IWidget = { + id: "drilldown", + type: "drilldown", + pizzagna: { + [PizzagnaLayer.Configuration]: { + [DEFAULT_PIZZAGNA_ROOT]: { + providers: { + [WellKnownProviders.DataSource]: { + // Setting the data source providerId for the tile with id "kpi1" + providerId: DrilldownDataSourceRealApi.providerId, + properties: {}, + } as IProviderConfiguration, + }, + }, + header: { + properties: { + title: "Drilldown Widget", + subtitle: "Search is case sensitive!", + }, + }, + listWidget: { + providers: { + [WellKnownProviders.Adapter]: { + providerId: NOVA_DRILLDOWN_DATASOURCE_ADAPTER, + properties: { + // widget + navigationBarId: "navigationBar", + componentId: "listWidget", + dataPath: "data", + + // adapter props + drillstate: [], + groupBy: ["regionName", "subregionName"], + groups: ["regionName", "subregionName"], + + // components + componentsConfig: { + group: { + componentType: + ListGroupItemComponent.lateLoadKey, + properties: { + dataFieldIds: { + id: "id", + label: "label", + statuses: "statuses", + }, + }, + itemProperties: { + canNavigate: true, + }, + }, + leaf: { + componentType: + ListLeafItemComponent.lateLoadKey, + properties: { + dataFieldIds: { + icon: "icon", + status: "icon_status", + detailedUrl: "capital", + label: "name", + }, + }, + itemProperties: { + canNavigate: false, + }, + }, + } as IDrilldownComponentsConfiguration, + }, + }, + }, + properties: { + configuration: { + // FORMAT: + // componentType: ListLeafItemComponent.lateLoadKey, + // properties: { + // dataFieldIds: { + // icon: "", + // status: "code", + // detailedUrl: "capital", + // label: "name", + // }, + // }, + // + } as IListWidgetConfiguration, + }, + }, + }, + }, +}; + +const getLast = (arr: any[]) => arr[arr.length - 1]; +\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\`, + "widget-types/drilldown/drilldown-widget/mock-data-source.ts": \\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\`// © 2022 SolarWinds Worldwide, LLC. All rights reserved. +// +// Permission is hereby granted, free of charge, to any person obtaining a copy +// of this software and associated documentation files (the "Software"), to +// deal in the Software without restriction, including without limitation the +// rights to use, copy, modify, merge, publish, distribute, sublicense, and/or +// sell copies of the Software, and to permit persons to whom the Software is +// furnished to do so, subject to the following conditions: +// +// The above copyright notice and this permission notice shall be included in +// all copies or substantial portions of the Software. +// +// THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR +// IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, +// FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE +// AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER +// LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, +// OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN +// THE SOFTWARE. + +import { Injectable, OnDestroy } from "@angular/core"; +import groupBy from "lodash/groupBy"; +import { BehaviorSubject, Observable, of, Subject } from "rxjs"; +import { + catchError, + delay, + finalize, + map, + // eslint-disable-next-line import/no-deprecated + switchMap, + tap, +} from "rxjs/operators"; + +import { + DataSourceService, + IDataField, + IDataSource, + IFilters, + INovaFilters, +} from "@nova-ui/bits"; + +import { GRAPH_DATA_MOCK } from "./data-mock"; + +/** + * A simple KPI data source to retrieve the average rating of Harry Potter and the Sorcerer's Stone (book) via googleapis + */ +@Injectable() +export class DrilldownDataSource + extends DataSourceService + implements IDataSource, OnDestroy +{ + // This is the ID we'll use to identify the provider + public static providerId = "DrilldownDataSource"; + + // Use this subject to communicate the data source's busy state + public busy = new BehaviorSubject(false); + public dataFields: Partial[] = [ + { id: "continent.name", label: "Continent name" }, + { id: "currency", label: "Currency" }, + ]; + + private drillState: string[] = []; + private groupBy: string[]; + private cache: any; + private applyFilters$ = new Subject(); + + constructor() { + super(); + + // TODO: remove Partial in vNext after marking dataType field as optional - NUI-5838 + ( + this.dataFieldsConfig.dataFields$ as BehaviorSubject< + Partial[] + > + ).next(this.dataFields); + + this.applyFilters$ + // eslint-disable-next-line import/no-deprecated + .pipe(switchMap((filters) => this.getData(filters))) + .subscribe(async (res) => { + this.outputsSubject.next(await this.getFilteredData(res)); + }); + } + + private groupedDataHistory: any[] = []; + + // In this example, getFilteredData is invoked every 10 minutes (Take a look at the refresher + // provider definition in the widget configuration below to see how the interval is set) + public async getFilteredData(data: any): Promise { + return of(data) + .pipe( + map((countries) => { + const widgetInput = this.getOutput(countries); + + if (this.isDrillDown()) { + const activeDrillLvl = this.drillState.length; + const group = this.groupBy[activeDrillLvl]; + const [lastGroupedValue, groupedData] = + this.getTransformedDataForGroup(widgetInput, group); + + this.groupedDataHistory.push(lastGroupedValue); + + return groupedData; + } + + return widgetInput; + }) + ) + .toPromise(); + } + + public ngOnDestroy(): void { + this.outputsSubject.complete(); + } + + // redefine parent method + public async applyFilters(): Promise { + this.applyFilters$.next(this.getFilters()); + } + + private getData(filters: INovaFilters): Observable { + this.drillState = filters.drillstate?.value; + this.groupBy = filters.group?.value; + + this.busy.next(true); + + return of(this.cache || GRAPH_DATA_MOCK).pipe( + delay(1000), + tap((data) => (this.cache = data)), + map((data) => data.data.countries), + catchError((e) => of([])), + finalize(() => this.busy.next(false)) + ); + } + + private getTransformedDataForGroup(data: any, groupName: string) { + const groupedDict = groupBy(data, groupName); + const dataArr = Object.keys(groupedDict).map((property) => ({ + id: property, + label: property, + // TODO: apply groups mapping here + statuses: [ + { key: "state_ok", value: groupedDict[property].length }, + { + key: "status_unreachable", + value: generateNumberUpTo(100000), + }, + { key: "status_warning", value: generateNumberUpTo(10000) }, + { key: "status_unknown", value: generateNumberUpTo(1000) }, + ], + })); + + return [groupedDict, dataArr]; + } + + private isHome(): boolean { + return !this.drillState || this.drillState.length === 0; + } + + private isBack(): boolean { + return ( + this.groupedDataHistory.length > this.drillState?.length && + !this.isHome() + ); + } + + private isDrillDown(): boolean { + return this.drillState?.length !== this.groupBy?.length; + } + + private getOutput(data: any) { + if (this.isHome()) { + this.groupedDataHistory.length = 0; + } + + if (this.isBack()) { + this.groupedDataHistory.length = this.groupedDataHistory.length - 1; + } + + const lastHistoryValue = getLast(this.groupedDataHistory); + + if (!lastHistoryValue) { + return data; + } + + return lastHistoryValue[getLast(this.drillState)] || lastHistoryValue; + } +} + +const getLast = (arr: any[]) => arr[arr.length - 1]; +const generateNumberUpTo = (upperLimit: number): number => + Math.floor(Math.random() * upperLimit + 1); +\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\`, + "widget-types/drilldown/drilldown-widget-docs.component.ts": \\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\`// © 2022 SolarWinds Worldwide, LLC. All rights reserved. +// +// Permission is hereby granted, free of charge, to any person obtaining a copy +// of this software and associated documentation files (the "Software"), to +// deal in the Software without restriction, including without limitation the +// rights to use, copy, modify, merge, publish, distribute, sublicense, and/or +// sell copies of the Software, and to permit persons to whom the Software is +// furnished to do so, subject to the following conditions: +// +// The above copyright notice and this permission notice shall be included in +// all copies or substantial portions of the Software. +// +// THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR +// IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, +// FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE +// AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER +// LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, +// OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN +// THE SOFTWARE. + +import { Component, OnInit } from "@angular/core"; + +import { mapContentFile } from "../../../../demo-files-factory"; + +@Component({ + selector: "nui-drilldown-docs", + templateUrl: "./drilldown-widget-docs.component.html", + standalone: false, +}) +export class DrilldownDocsComponent implements OnInit { + public widgetFileText = ""; + public configuratorFileText = ""; + + public predefinedGroping = \\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\` +listWidget: { + providers: { + [WellKnownProviders.Adapter]: { + providerId: NOVA_DRILLDOWN_DATASOURCE_ADAPTER, + properties: { + ... + // adapter props + drillstate: [], + groupBy: ["regionName", "subregionName"], + groups: ["regionName", "subregionName"], + ... + }, + }, + }, +}, +\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\`; + public featuredDeclaredText = \\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\` + private supportedFeatures: IDataSourceFeatures = { + search: { enabled: true }, + };\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\`; + public featuresUsedText = \\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\` + this.features = new DataSourceFeatures(this.supportedFeatures); + \\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\`; + + public async ngOnInit(): Promise { + this.widgetFileText = await import( + "./../../../../../../src/lib/widget-types/drilldown/drilldown-widget" + ).then(mapContentFile); + this.configuratorFileText = await import( + "./../../../../../../src/lib/widget-types/drilldown/drilldown-configurator" + ).then(mapContentFile); + } +} +\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\`, + "widget-types/drilldown/drilldown-widget-docs.module.ts": \\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\`// © 2022 SolarWinds Worldwide, LLC. All rights reserved. +// +// Permission is hereby granted, free of charge, to any person obtaining a copy +// of this software and associated documentation files (the "Software"), to +// deal in the Software without restriction, including without limitation the +// rights to use, copy, modify, merge, publish, distribute, sublicense, and/or +// sell copies of the Software, and to permit persons to whom the Software is +// furnished to do so, subject to the following conditions: +// +// The above copyright notice and this permission notice shall be included in +// all copies or substantial portions of the Software. +// +// THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR +// IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, +// FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE +// AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER +// LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, +// OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN +// THE SOFTWARE. + +import { NgModule } from "@angular/core"; +import { RouterModule, Routes } from "@angular/router"; + +// eslint-disable-next-line max-len +import { + NuiButtonModule, + NuiDocsModule, + NuiMessageModule, + NuiSwitchModule, + DEMO_PATH_TOKEN, +} from "@nova-ui/bits"; +import { NuiDashboardsModule } from "@nova-ui/dashboards"; + +import { DrilldownMultiRequestWidgetExampleComponent } from "./drilldown-multi-request-widget/drilldown-multi-request-widget-example.component"; +import { DrilldownWidgetExampleComponent } from "./drilldown-widget/drilldown-widget-example.component"; +import { DrilldownDocsComponent } from "./drilldown-widget-docs.component"; +import { getDemoFiles } from "../../../../demo-files-factory"; + +const routes: Routes = [ + { + path: "", + component: DrilldownDocsComponent, + data: { + srlc: { + hideIndicator: true, + }, + }, + }, + { + path: "example", + component: DrilldownWidgetExampleComponent, + data: { + srlc: { + hideIndicator: true, + }, + }, + }, + { + path: "multiple-requests", + component: DrilldownMultiRequestWidgetExampleComponent, + data: { + srlc: { + hideIndicator: true, + }, + }, + }, +]; + +@NgModule({ + imports: [ + RouterModule.forChild(routes), + NuiButtonModule, + NuiDocsModule, + NuiMessageModule, + NuiDashboardsModule, + NuiSwitchModule, + ], + declarations: [ + DrilldownDocsComponent, + DrilldownWidgetExampleComponent, + DrilldownMultiRequestWidgetExampleComponent, + ], + providers: [ + { + provide: DEMO_PATH_TOKEN, + useValue: getDemoFiles("drilldown"), + }, + ], +}) +export default class DrilldownDocsModule {} +\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\`, + "widget-types/embedded-content/embedded-content-docs.component.ts": \\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\`// © 2022 SolarWinds Worldwide, LLC. All rights reserved. +// +// Permission is hereby granted, free of charge, to any person obtaining a copy +// of this software and associated documentation files (the "Software"), to +// deal in the Software without restriction, including without limitation the +// rights to use, copy, modify, merge, publish, distribute, sublicense, and/or +// sell copies of the Software, and to permit persons to whom the Software is +// furnished to do so, subject to the following conditions: +// +// The above copyright notice and this permission notice shall be included in +// all copies or substantial portions of the Software. +// +// THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR +// IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, +// FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE +// AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER +// LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, +// OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN +// THE SOFTWARE. + +import { Component, OnInit } from "@angular/core"; + +import { mapContentFile } from "../../../../demo-files-factory"; + +@Component({ + selector: "nui-embedded-content-docs", + templateUrl: "./embedded-content-docs.component.html", + standalone: false, +}) +export class EmbeddedContentDocsComponent implements OnInit { + public embeddedContentWidgetFileText = ""; + public embeddedContentConfiguratorFileText = ""; + + public async ngOnInit(): Promise { + this.embeddedContentWidgetFileText = await import( + "./../../../../../../src/lib/widget-types/embedded-content/embedded-content-widget" + ).then(mapContentFile); + this.embeddedContentWidgetFileText = await import( + "./../../../../../../src/lib/widget-types/embedded-content/embedded-content-configurator" + ).then(mapContentFile); + } +} +\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\`, + "widget-types/embedded-content/embedded-content-docs.module.ts": \\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\`// © 2022 SolarWinds Worldwide, LLC. All rights reserved. +// +// Permission is hereby granted, free of charge, to any person obtaining a copy +// of this software and associated documentation files (the "Software"), to +// deal in the Software without restriction, including without limitation the +// rights to use, copy, modify, merge, publish, distribute, sublicense, and/or +// sell copies of the Software, and to permit persons to whom the Software is +// furnished to do so, subject to the following conditions: +// +// The above copyright notice and this permission notice shall be included in +// all copies or substantial portions of the Software. +// +// THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR +// IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, +// FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE +// AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER +// LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, +// OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN +// THE SOFTWARE. + +import { NgModule } from "@angular/core"; +import { RouterModule, Routes } from "@angular/router"; + +// eslint-disable-next-line max-len +import { + NuiButtonModule, + NuiDocsModule, + NuiMessageModule, + NuiSwitchModule, + DEMO_PATH_TOKEN, +} from "@nova-ui/bits"; +import { NuiDashboardsModule } from "@nova-ui/dashboards"; + +import { EmbeddedContentDocsComponent } from "./embedded-content-docs.component"; +import { EmbeddedContentWidgetExampleComponent } from "./embedded-content-widget-example/embedded-content-widget-example.component"; +import { getDemoFiles } from "../../../../demo-files-factory"; + +const routes: Routes = [ + { + path: "", + component: EmbeddedContentDocsComponent, + data: { + srlc: { + hideIndicator: true, + }, + }, + }, + { + path: "example", + component: EmbeddedContentWidgetExampleComponent, + data: { + srlc: { + hideIndicator: true, + }, + }, + }, +]; + +@NgModule({ + imports: [ + RouterModule.forChild(routes), + NuiButtonModule, + NuiDocsModule, + NuiMessageModule, + NuiDashboardsModule, + NuiSwitchModule, + ], + declarations: [ + EmbeddedContentDocsComponent, + EmbeddedContentWidgetExampleComponent, + ], + providers: [ + { + provide: DEMO_PATH_TOKEN, + useValue: getDemoFiles("embedded-content"), + }, + ], +}) +export default class EmbeddedContentDocsModule {} +\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\`, + "widget-types/embedded-content/embedded-content-widget-example/embedded-content-widget-example.component.ts": \\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\`// © 2022 SolarWinds Worldwide, LLC. All rights reserved. +// +// Permission is hereby granted, free of charge, to any person obtaining a copy +// of this software and associated documentation files (the "Software"), to +// deal in the Software without restriction, including without limitation the +// rights to use, copy, modify, merge, publish, distribute, sublicense, and/or +// sell copies of the Software, and to permit persons to whom the Software is +// furnished to do so, subject to the following conditions: +// +// The above copyright notice and this permission notice shall be included in +// all copies or substantial portions of the Software. +// +// THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR +// IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, +// FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE +// AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER +// LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, +// OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN +// THE SOFTWARE. + +import { ChangeDetectorRef, Component, OnInit } from "@angular/core"; +import { GridsterConfig, GridsterItem } from "angular-gridster2"; + +import { + ComponentRegistryService, + EmbeddedContentComponent, + EmbeddedContentConfigurationComponent, + EmbeddedContentMode, + IDashboard, + IWidget, + IWidgets, + PizzagnaLayer, + WidgetTypesService, +} from "@nova-ui/dashboards"; + +@Component({ + selector: "embedded-content-widget-example", + templateUrl: "./embedded-content-widget-example.component.html", + styleUrls: ["./embedded-content-widget-example.component.less"], + standalone: false, +}) +export class EmbeddedContentWidgetExampleComponent implements OnInit { + // This variable will hold all the data needed to define the layout and behavior of the widgets. + // Pass this to the dashboard component's dashboard input in the template. + public dashboard: IDashboard | undefined; + + // Angular gridster requires a configuration object even if it's empty. + // Pass this to the dashboard component's gridsterConfig input in the template. + public gridsterConfig: GridsterConfig = {}; + + // Boolean passed as an input to the dashboard. When true, widgets can be moved, resized, removed, or edited + public editMode: boolean = false; + + constructor( + // WidgetTypesService provides the widget's necessary structure information + private widgetTypesService: WidgetTypesService, + + private componentRegistry: ComponentRegistryService, + private changeDetectorRef: ChangeDetectorRef + ) {} + + public ngOnInit(): void { + this.prepareNovaDashboards(); + this.initializeDashboard(); + } + + /** Used for restoring widgets state */ + public reInitializeDashboard(): void { + // destroys the components and their providers so the dashboard can re init data + this.dashboard = undefined; + this.changeDetectorRef.detectChanges(); + + this.initializeDashboard(); + } + + public initializeDashboard(): void { + // We're using a static configuration object for this example, but this is where + // the widget's configuration could potentially be populated from a database + const embeddedContentWidget = widgetConfig; + const widgets: IWidgets = { + // Complete the widget with information coming from its type definition + [embeddedContentWidget.id]: + this.widgetTypesService.mergeWithWidgetType( + embeddedContentWidget + ), + }; + + // Setting the widget dimensions and position (this is for gridster) + const positions: Record = { + [embeddedContentWidget.id]: { + cols: 10, + rows: 10, + y: 0, + x: 0, + }, + }; + + // Finally, assigning the variables we created above to the dashboard + this.dashboard = { positions, widgets }; + } + + private prepareNovaDashboards() { + this.componentRegistry.registerByLateLoadKey(EmbeddedContentComponent); + this.componentRegistry.registerByLateLoadKey( + EmbeddedContentConfigurationComponent + ); + } +} + +const widgetConfig: IWidget = { + id: "embeddedContentWidgetId", + type: "embedded-content", + pizzagna: { + [PizzagnaLayer.Configuration]: { + header: { + properties: { + title: "Embedded Content Widget", + subtitle: "", + }, + }, + mainContent: { + properties: { + sanitized: true, + mode: EmbeddedContentMode.URL, + customEmbeddedContent: "https://www.ventusky.com/", + }, + }, + }, + }, +}; +\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\`, + "widget-types/kpi/kpi-docs.component.ts": \\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\`// © 2022 SolarWinds Worldwide, LLC. All rights reserved. +// +// Permission is hereby granted, free of charge, to any person obtaining a copy +// of this software and associated documentation files (the "Software"), to +// deal in the Software without restriction, including without limitation the +// rights to use, copy, modify, merge, publish, distribute, sublicense, and/or +// sell copies of the Software, and to permit persons to whom the Software is +// furnished to do so, subject to the following conditions: +// +// The above copyright notice and this permission notice shall be included in +// all copies or substantial portions of the Software. +// +// THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR +// IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, +// FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE +// AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER +// LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, +// OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN +// THE SOFTWARE. + +import { Component, OnInit } from "@angular/core"; + +import { mapContentFile } from "../../../../demo-files-factory"; + +@Component({ + selector: "nui-kpi-docs", + templateUrl: "./kpi-docs.component.html", + standalone: false, +}) +export class KpiDocsComponent implements OnInit { + public kpiWidgetFileText = ""; + public kpiConfiguratorFileText = ""; + + public async ngOnInit(): Promise { + this.kpiWidgetFileText = await import( + "./../../../../../../src/lib/widget-types/kpi/kpi-widget" + ).then(mapContentFile); + this.kpiConfiguratorFileText = await import( + "./../../../../../../src/lib/widget-types/kpi/kpi-configurator" + ).then(mapContentFile); + } +} +\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\`, + "widget-types/kpi/kpi-docs.module.ts": \\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\`// © 2022 SolarWinds Worldwide, LLC. All rights reserved. +// +// Permission is hereby granted, free of charge, to any person obtaining a copy +// of this software and associated documentation files (the "Software"), to +// deal in the Software without restriction, including without limitation the +// rights to use, copy, modify, merge, publish, distribute, sublicense, and/or +// sell copies of the Software, and to permit persons to whom the Software is +// furnished to do so, subject to the following conditions: +// +// The above copyright notice and this permission notice shall be included in +// all copies or substantial portions of the Software. +// +// THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR +// IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, +// FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE +// AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER +// LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, +// OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN +// THE SOFTWARE. + +import { NgModule } from "@angular/core"; +import { RouterModule, Routes } from "@angular/router"; + +import { + NuiButtonModule, + NuiDocsModule, + NuiMessageModule, + NuiSwitchModule, + DEMO_PATH_TOKEN, +} from "@nova-ui/bits"; +import { + KpiColorComparatorsRegistryService, + NuiDashboardsModule, +} from "@nova-ui/dashboards"; + +import { KpiDocsComponent } from "./kpi-docs.component"; +import { KpiSyncBrokerExampleComponent } from "./kpi-sync-broker/kpi-sync-broker-example.component"; +import { KpiSyncBrokerDocsComponent } from "./kpi-sync-broker-docs.component"; +import { KpiSyncBrokerForAllTilesExampleComponent } from "./kpi-sync-broker-for-all-tiles/kpi-sync-broker-for-all-tiles-example.component"; +import { KpiWidgetExampleComponent } from "./kpi-widget/kpi-widget-example.component"; +import { KpiWidgetBackgroundColorExampleComponent } from "./kpi-widget-background-color/kpi-widget-background-color-example.component"; +import { KpiWidgetBackgroundColorDocsComponent } from "./kpi-widget-background-color-docs.component"; +import { KpiWidgetInteractiveExampleComponent } from "./kpi-widget-interactive/kpi-widget-interactive-example.component"; +import { getDemoFiles } from "../../../../demo-files-factory"; + +const routes: Routes = [ + { + path: "", + component: KpiDocsComponent, + data: { + srlc: { + hideIndicator: true, + }, + showThemeSwitcher: true, + }, + }, + { + path: "example", + component: KpiWidgetExampleComponent, + data: { + srlc: { + hideIndicator: true, + }, + }, + }, + { + path: "background-color", + component: KpiWidgetBackgroundColorDocsComponent, + data: { + srlc: { + hideIndicator: true, + }, + }, + }, + { + path: "sync-broker", + component: KpiSyncBrokerDocsComponent, + data: { + srlc: { + hideIndicator: true, + }, + }, + }, +]; + +@NgModule({ + imports: [ + RouterModule.forChild(routes), + NuiButtonModule, + NuiDocsModule, + NuiMessageModule, + NuiDashboardsModule, + NuiSwitchModule, + ], + declarations: [ + KpiDocsComponent, + KpiWidgetExampleComponent, + KpiWidgetInteractiveExampleComponent, + KpiWidgetBackgroundColorDocsComponent, + KpiWidgetBackgroundColorExampleComponent, + KpiSyncBrokerDocsComponent, + KpiSyncBrokerExampleComponent, + KpiSyncBrokerForAllTilesExampleComponent, + ], + providers: [ + KpiColorComparatorsRegistryService, + { + provide: DEMO_PATH_TOKEN, + useValue: getDemoFiles("kpi"), + }, + ], +}) +export default class KpiDocsModule { + constructor( + private comparatorsRegistry: KpiColorComparatorsRegistryService + ) { + this.backgroundColorDocsSetup(); + } + + private backgroundColorDocsSetup() { + this.comparatorsRegistry.registerComparators({ + "!=": { + comparatorFn: (actual: any, reference: any) => + // eslint-disable-next-line eqeqeq + actual != reference, + label: "Not equal", + }, + }); + } +} +\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\`, + "widget-types/kpi/kpi-sync-broker/kpi-sync-broker-example.component.ts": \\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\`// © 2022 SolarWinds Worldwide, LLC. All rights reserved. +// +// Permission is hereby granted, free of charge, to any person obtaining a copy +// of this software and associated documentation files (the "Software"), to +// deal in the Software without restriction, including without limitation the +// rights to use, copy, modify, merge, publish, distribute, sublicense, and/or +// sell copies of the Software, and to permit persons to whom the Software is +// furnished to do so, subject to the following conditions: +// +// The above copyright notice and this permission notice shall be included in +// all copies or substantial portions of the Software. +// +// THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR +// IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, +// FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE +// AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER +// LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, +// OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN +// THE SOFTWARE. + +import { HttpClient, HttpErrorResponse } from "@angular/common/http"; +import { + ChangeDetectorRef, + Component, + Injectable, + OnDestroy, + OnInit, +} from "@angular/core"; +import { GridsterConfig, GridsterItem } from "angular-gridster2"; +import keyBy from "lodash/keyBy"; +import { BehaviorSubject, of } from "rxjs"; +import { delay, finalize, take } from "rxjs/operators"; + +import { DataSourceService, IFilteringOutputs } from "@nova-ui/bits"; +import { + DATA_SOURCE, + IDashboard, + IKpiData, + IProviderConfiguration, + IWidget, + KpiComponent, + NOVA_KPI_DATASOURCE_ADAPTER, + NOVA_KPI_SCALE_SYNC_BROKER, + PizzagnaLayer, + ProviderRegistryService, + WellKnownPathKey, + WellKnownProviders, + WidgetTypesService, +} from "@nova-ui/dashboards"; + +/** + * A simple KPI data source to retrieve the average rating of Harry Potter and the Sorcerer's Stone (book) via googleapis + */ +@Injectable() +export class AverageRatingKpiDataSource + extends DataSourceService + implements OnDestroy +{ + public static providerId = "AverageRatingKpiDataSource"; + public busy = new BehaviorSubject(false); + + constructor(private http: HttpClient) { + super(); + } + + public async getFilteredData(): Promise { + this.busy.next(true); + return new Promise((resolve) => { + // *** Make a resource request to an external API (if needed) + this.http + .get("https://www.googleapis.com/books/v1/volumes/5MQFrgEACAAJ") + .pipe(finalize(() => this.busy.next(false))) + .subscribe({ + next: (data: any) => { + resolve({ + result: { + value: data.volumeInfo.averageRating, + }, + }); + }, + error: (error: HttpErrorResponse) => { + resolve({ + result: null, + error: { + type: error.status, + }, + }); + }, + }); + }); + } + + public ngOnDestroy(): void { + this.outputsSubject.complete(); + } +} + +/** + * A simple KPI data source to retrieve the ratings count of Harry Potter and the Sorcerer's Stone (book) via googleapis + */ +@Injectable() +export class RatingsCountKpiDataSource + extends DataSourceService + implements OnDestroy +{ + public static providerId = "RatingsCountKpiDataSource"; + + // Use this subject to communicate the data source's busy state + public busy = new BehaviorSubject(false); + + constructor(private http: HttpClient) { + super(); + } + + public async getFilteredData(): Promise { + this.busy.next(true); + return new Promise((resolve) => { + this.http + .get("https://www.googleapis.com/books/v1/volumes/5MQFrgEACAAJ") + .pipe( + delay(2000), + finalize(() => this.busy.next(false)) + ) + .subscribe({ + next: (data: any) => { + resolve({ + result: { + value: data.volumeInfo.ratingsCount, + }, + }); + }, + error: (error: HttpErrorResponse) => { + resolve({ + result: null, + error: { + type: error.status, + }, + }); + }, + }); + }); + } + + public ngOnDestroy(): void { + this.outputsSubject.complete(); + } +} +/** + * A simple KPI data source to retrieve the ratings count of Harry Potter and the Sorcerer's Stone (book) via googleapis + */ +@Injectable() +export class MockKpiDataSource + extends DataSourceService + implements OnDestroy +{ + public static providerId = "MockKpiDataSource"; + + // Use this subject to communicate the data source's busy state + public busy = new BehaviorSubject(false); + + constructor() { + super(); + } + + public async getFilteredData(): Promise { + this.busy.next(true); + return new Promise((resolve) => { + of(3381342) + .pipe( + delay(5000), + take(1), + finalize(() => this.busy.next(false)) + ) + .subscribe({ + next: (data: any) => { + resolve({ + result: { + value: data, + }, + }); + }, + }); + }); + } + + public ngOnDestroy(): void { + this.outputsSubject.complete(); + } +} + +/** + * A component that instantiates the dashboard + */ +@Component({ + selector: "kpi-sync-broker-example", + templateUrl: "./kpi-sync-broker-example.component.html", + styleUrls: ["./kpi-sync-broker-example.component.less"], + standalone: false, +}) +export class KpiSyncBrokerExampleComponent implements OnInit { + public dashboard: IDashboard | undefined; + public gridsterConfig: GridsterConfig = {}; + public editMode: boolean = false; + + constructor( + private widgetTypesService: WidgetTypesService, + private providerRegistry: ProviderRegistryService, + private changeDetectorRef: ChangeDetectorRef + ) {} + + public ngOnInit(): void { + this.setupDashboard(); + + this.initializeDashboard(); + } + + private setupDashboard() { + const widgetTemplate = this.widgetTypesService.getWidgetType("kpi", 1); + + this.widgetTypesService.setNode( + widgetTemplate, + "configurator", + WellKnownPathKey.DataSourceProviders, + [ + AverageRatingKpiDataSource.providerId, + RatingsCountKpiDataSource.providerId, + MockKpiDataSource.providerId, + ] + ); + + this.providerRegistry.setProviders({ + [AverageRatingKpiDataSource.providerId]: { + provide: DATA_SOURCE, + useClass: AverageRatingKpiDataSource, + deps: [HttpClient], + }, + [RatingsCountKpiDataSource.providerId]: { + provide: DATA_SOURCE, + useClass: RatingsCountKpiDataSource, + deps: [HttpClient], + }, + [MockKpiDataSource.providerId]: { + provide: DATA_SOURCE, + useClass: MockKpiDataSource, + deps: [], + }, + }); + } + + /** Used for restoring widgets state */ + public reInitializeDashboard(): void { + // destroys the components and their providers so the dashboard can re init data + this.dashboard = undefined; + this.changeDetectorRef.detectChanges(); + + this.initializeDashboard(); + } + + private initializeDashboard(): void { + const widgetsWithStructure = widgetsConfig.map((w) => + this.widgetTypesService.mergeWithWidgetType(w) + ); + const widgetsIndex = keyBy(widgetsWithStructure, (w: IWidget) => w.id); + + const positions: Record = { + kpiWidgetId: { + cols: 3, + rows: 6, + y: 0, + x: 0, + }, + kpiWidgetId2: { + cols: 3, + rows: 6, + y: 0, + x: 0, + }, + }; + + this.dashboard = { + positions, + widgets: widgetsIndex, + }; + } +} + +const widgetsConfig: IWidget[] = [ + { + id: "kpiWidgetId", + type: "kpi", + pizzagna: { + [PizzagnaLayer.Configuration]: { + header: { + properties: { + title: "NO Sync Broker", + subtitle: "Values sizes are being not synced", + }, + }, + tiles: { + properties: { + nodes: ["kpi1", "kpi2", "kpi3"], + }, + }, + kpi1: { + id: "kpi1", + componentType: KpiComponent.lateLoadKey, + properties: { + widgetData: { + units: \\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\`out of 5 Stars\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\`, + label: \\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\`Average Rating\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\`, + backgroundColor: "lightpink", + }, + }, + providers: { + [WellKnownProviders.DataSource]: { + providerId: AverageRatingKpiDataSource.providerId, + } as IProviderConfiguration, + [WellKnownProviders.Adapter]: { + providerId: NOVA_KPI_DATASOURCE_ADAPTER, + properties: { + componentId: "kpi1", + propertyPath: "widgetData", + }, + } as IProviderConfiguration, + }, + }, + kpi2: { + id: "kpi2", + componentType: KpiComponent.lateLoadKey, + properties: { + widgetData: { + label: \\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\`Another label which might be a pretty long one\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\`, + units: \\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\`Which comes from somewhere\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\`, + backgroundColor: "skyblue", + }, + }, + providers: { + [WellKnownProviders.DataSource]: { + providerId: RatingsCountKpiDataSource.providerId, + } as IProviderConfiguration, + [WellKnownProviders.Adapter]: { + providerId: NOVA_KPI_DATASOURCE_ADAPTER, + properties: { + componentId: "kpi2", + propertyPath: "widgetData", + }, + } as IProviderConfiguration, + }, + }, + kpi3: { + id: "kpi3", + componentType: KpiComponent.lateLoadKey, + properties: { + widgetData: { + label: \\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\`Random\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\`, + units: \\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\`Data\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\`, + }, + }, + providers: { + [WellKnownProviders.DataSource]: { + providerId: MockKpiDataSource.providerId, + } as IProviderConfiguration, + [WellKnownProviders.Adapter]: { + providerId: NOVA_KPI_DATASOURCE_ADAPTER, + properties: { + componentId: "kpi3", + propertyPath: "widgetData", + }, + } as IProviderConfiguration, + }, + }, + }, + }, + }, + { + id: "kpiWidgetId2", + type: "kpi", + pizzagna: { + [PizzagnaLayer.Configuration]: { + header: { + properties: { + title: "WITH Sync Broker", + subtitle: + "Now the values of label, units, and value are being synced", + }, + }, + tiles: { + properties: { + nodes: ["kpi4", "kpi5", "kpi6"], + }, + providers: { + // This is where and how you set the sync broker provider + kpiScaleSyncBroker: { + providerId: NOVA_KPI_SCALE_SYNC_BROKER, + properties: { + scaleSyncConfig: [ + // You can decide which values to keep in sync. For instance, you can leave only 'label' id in the array below + { id: "value" }, + { id: "label" }, + { id: "units" }, + ], + }, + }, + }, + }, + kpi4: { + id: "kpi4", + componentType: KpiComponent.lateLoadKey, + properties: { + widgetData: { + units: \\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\`out of 5 Stars\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\`, + label: \\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\`Average Rating\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\`, + backgroundColor: "lightpink", + }, + }, + providers: { + [WellKnownProviders.DataSource]: { + providerId: AverageRatingKpiDataSource.providerId, + } as IProviderConfiguration, + [WellKnownProviders.Adapter]: { + providerId: NOVA_KPI_DATASOURCE_ADAPTER, + properties: { + componentId: "kpi4", + propertyPath: "widgetData", + }, + } as IProviderConfiguration, + }, + }, + kpi5: { + id: "kpi5", + componentType: KpiComponent.lateLoadKey, + properties: { + widgetData: { + label: \\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\`Another label which might be a pretty long one\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\`, + units: \\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\`Which comes from somewhere\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\`, + backgroundColor: "skyblue", + }, + }, + providers: { + [WellKnownProviders.DataSource]: { + providerId: RatingsCountKpiDataSource.providerId, + } as IProviderConfiguration, + [WellKnownProviders.Adapter]: { + providerId: NOVA_KPI_DATASOURCE_ADAPTER, + properties: { + componentId: "kpi5", + propertyPath: "widgetData", + }, + } as IProviderConfiguration, + }, + }, + kpi6: { + id: "kpi6", + componentType: KpiComponent.lateLoadKey, + properties: { + widgetData: { + label: \\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\`Random\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\`, + units: \\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\`Data\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\`, + }, + }, + providers: { + [WellKnownProviders.DataSource]: { + providerId: MockKpiDataSource.providerId, + } as IProviderConfiguration, + [WellKnownProviders.Adapter]: { + providerId: NOVA_KPI_DATASOURCE_ADAPTER, + properties: { + componentId: "kpi6", + propertyPath: "widgetData", + }, + } as IProviderConfiguration, + }, + }, + }, + }, + }, +]; +\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\`, + "widget-types/kpi/kpi-sync-broker-docs.component.ts": \\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\`// © 2022 SolarWinds Worldwide, LLC. All rights reserved. +// +// Permission is hereby granted, free of charge, to any person obtaining a copy +// of this software and associated documentation files (the "Software"), to +// deal in the Software without restriction, including without limitation the +// rights to use, copy, modify, merge, publish, distribute, sublicense, and/or +// sell copies of the Software, and to permit persons to whom the Software is +// furnished to do so, subject to the following conditions: +// +// The above copyright notice and this permission notice shall be included in +// all copies or substantial portions of the Software. +// +// THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR +// IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, +// FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE +// AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER +// LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, +// OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN +// THE SOFTWARE. + +import { Component } from "@angular/core"; + +@Component({ + selector: "kpi-sync-broker-docs", + templateUrl: "./kpi-sync-broker-docs.component.html", + standalone: false, +}) +export class KpiSyncBrokerDocsComponent { + public kpiScaleSyncBroker = \\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\` +"tiles": { + "providers": { + kpiScaleSyncBroker: { + providerId: NOVA_KPI_SCALE_SYNC_BROKER, + properties: { + scaleSyncConfig: [ + { id: "value" }, + { id: "label" }, + { id: "units" }, + ], + }, + }, + }, +}, +\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\`; + + public defineScaleBrokerOnDashboardSetup = \\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\` +// To add the sync broker globally to all the kpi tiles you may start with setting up the broker config +// Here you define which values to keep in sync +const brokerConfig = { + providerId: NOVA_KPI_SCALE_SYNC_BROKER, + properties: { + scaleSyncConfig: [ + { id: "value" }, + { id: "label" }, + { id: "units" }, + ], + }, + }; + +// And here is how you set the sync broker for every KPI widget in the dashboard. +// Later, you will be able to override this setting for each separate KPI widget in the configuration (just like it is shown in the third +// width of the example with the 'kpiWidgetId3') +this.widgetTypesService.setNode( + widgetTemplate, + "widget", + "tiles.providers.kpiScaleSyncBroker", + brokerConfig +); +\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\`; +} +\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\`, + "widget-types/kpi/kpi-sync-broker-for-all-tiles/kpi-sync-broker-for-all-tiles-example.component.ts": \\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\`// © 2022 SolarWinds Worldwide, LLC. All rights reserved. +// +// Permission is hereby granted, free of charge, to any person obtaining a copy +// of this software and associated documentation files (the "Software"), to +// deal in the Software without restriction, including without limitation the +// rights to use, copy, modify, merge, publish, distribute, sublicense, and/or +// sell copies of the Software, and to permit persons to whom the Software is +// furnished to do so, subject to the following conditions: +// +// The above copyright notice and this permission notice shall be included in +// all copies or substantial portions of the Software. +// +// THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR +// IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, +// FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE +// AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER +// LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, +// OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN +// THE SOFTWARE. + +import { HttpClient, HttpErrorResponse } from "@angular/common/http"; +import { + ChangeDetectorRef, + Component, + Injectable, + OnDestroy, + OnInit, +} from "@angular/core"; +import { GridsterConfig, GridsterItem } from "angular-gridster2"; +import keyBy from "lodash/keyBy"; +import { BehaviorSubject, of } from "rxjs"; +import { delay, finalize, take } from "rxjs/operators"; + +import { DataSourceService, IFilteringOutputs } from "@nova-ui/bits"; +import { + DATA_SOURCE, + IDashboard, + IKpiData, + IProviderConfiguration, + IWidget, + KpiComponent, + NOVA_KPI_DATASOURCE_ADAPTER, + NOVA_KPI_SCALE_SYNC_BROKER, + PizzagnaLayer, + ProviderRegistryService, + WellKnownPathKey, + WellKnownProviders, + WidgetTypesService, +} from "@nova-ui/dashboards"; + +/** + * A simple KPI data source to retrieve the average rating of Harry Potter and the Sorcerer's Stone (book) via googleapis + */ +@Injectable() +export class AverageRatingKpiDataSource + extends DataSourceService + implements OnDestroy +{ + public static providerId = "AverageRatingKpiDataSource"; + public busy = new BehaviorSubject(false); + + constructor(private http: HttpClient) { + super(); + } + + public async getFilteredData(): Promise { + this.busy.next(true); + return new Promise((resolve) => { + // *** Make a resource request to an external API (if needed) + this.http + .get("https://www.googleapis.com/books/v1/volumes/5MQFrgEACAAJ") + .pipe(finalize(() => this.busy.next(false))) + .subscribe({ + next: (data: any) => { + resolve({ + result: { + value: data.volumeInfo.averageRating, + }, + }); + }, + error: (error: HttpErrorResponse) => { + resolve({ + result: null, + error: { + type: error.status, + }, + }); + }, + }); + }); + } + + public ngOnDestroy(): void { + this.outputsSubject.complete(); + } +} + +/** + * A simple KPI data source to retrieve the ratings count of Harry Potter and the Sorcerer's Stone (book) via googleapis + */ +@Injectable() +export class RatingsCountKpiDataSource + extends DataSourceService + implements OnDestroy +{ + public static providerId = "RatingsCountKpiDataSource"; + + // Use this subject to communicate the data source's busy state + public busy = new BehaviorSubject(false); + + constructor(private http: HttpClient) { + super(); + } + + public async getFilteredData(): Promise { + this.busy.next(true); + return new Promise((resolve) => { + this.http + .get("https://www.googleapis.com/books/v1/volumes/5MQFrgEACAAJ") + .pipe( + delay(2000), + finalize(() => this.busy.next(false)) + ) + .subscribe({ + next: (data: any) => { + resolve({ + result: { + value: data.volumeInfo.ratingsCount, + }, + }); + }, + error: (error: HttpErrorResponse) => { + resolve({ + result: null, + error: { + type: error.status, + }, + }); + }, + }); + }); + } + + public ngOnDestroy(): void { + this.outputsSubject.complete(); + } +} +/** + * A simple KPI data source to retrieve the ratings count of Harry Potter and the Sorcerer's Stone (book) via googleapis + */ +@Injectable() +export class MockKpiDataSource + extends DataSourceService + implements OnDestroy +{ + public static providerId = "MockKpiDataSource"; + + // Use this subject to communicate the data source's busy state + public busy = new BehaviorSubject(false); + public value: number = 3381342; + + constructor() { + super(); + } + + public async getFilteredData(): Promise { + this.busy.next(true); + return new Promise((resolve) => { + of(this.value) + .pipe( + delay(5000), + take(1), + finalize(() => this.busy.next(false)) + ) + .subscribe({ + next: (data: any) => { + resolve({ + result: { + value: data, + }, + }); + }, + }); + }); + } + + public ngOnDestroy(): void { + this.outputsSubject.complete(); + } +} + +/** + * A component that instantiates the dashboard + */ +@Component({ + selector: "kpi-sync-broker-for-all-tiles-example", + templateUrl: "./kpi-sync-broker-for-all-tiles-example.component.html", + styleUrls: ["./kpi-sync-broker-for-all-tiles-example.component.less"], + standalone: false, +}) +export class KpiSyncBrokerForAllTilesExampleComponent implements OnInit { + public dashboard: IDashboard | undefined; + public gridsterConfig: GridsterConfig = {}; + public editMode: boolean = false; + + constructor( + private widgetTypesService: WidgetTypesService, + private providerRegistry: ProviderRegistryService, + private changeDetectorRef: ChangeDetectorRef + ) {} + + public ngOnInit(): void { + this.setupDashboard(); + + this.initializeDashboard(); + } + + /** Used for restoring widgets state */ + public reInitializeDashboard(): void { + // destroys the components and their providers so the dashboard can re init data + this.dashboard = undefined; + this.changeDetectorRef.detectChanges(); + + this.initializeDashboard(); + } + + private setupDashboard() { + // To add the sync broker globally to all the kpi tiles you may start with setting up the broker config + // Here you define which values to keep in sync + const brokerConfig = { + providerId: NOVA_KPI_SCALE_SYNC_BROKER, + properties: { + scaleSyncConfig: [ + { id: "value" }, + { id: "label" }, + { id: "units" }, + ], + }, + }; + const widgetTemplate = this.widgetTypesService.getWidgetType("kpi", 1); + + this.widgetTypesService.setNode( + widgetTemplate, + "configurator", + WellKnownPathKey.DataSourceProviders, + [ + AverageRatingKpiDataSource.providerId, + RatingsCountKpiDataSource.providerId, + MockKpiDataSource.providerId, + ] + ); + + // And here is how you set the sync broker for every KPI widget in the dashboard. + // Later, you will be able to override this setting for each separate KPI widget in the configuration (just like it is shown in the third + // width of the example with the 'kpiWidgetId3') + this.widgetTypesService.setNode( + widgetTemplate, + "widget", + "tiles.providers.kpiScaleSyncBroker", + brokerConfig + ); + + this.providerRegistry.setProviders({ + [AverageRatingKpiDataSource.providerId]: { + provide: DATA_SOURCE, + useClass: AverageRatingKpiDataSource, + deps: [HttpClient], + }, + [RatingsCountKpiDataSource.providerId]: { + provide: DATA_SOURCE, + useClass: RatingsCountKpiDataSource, + deps: [HttpClient], + }, + [MockKpiDataSource.providerId]: { + provide: DATA_SOURCE, + useClass: MockKpiDataSource, + deps: [], + }, + }); + } + + private initializeDashboard(): void { + const widgetsWithStructure = widgetsConfig.map((w) => + this.widgetTypesService.mergeWithWidgetType(w) + ); + const widgetsIndex = keyBy(widgetsWithStructure, (w: IWidget) => w.id); + + const positions: Record = { + kpiWidgetId: { + cols: 3, + rows: 6, + y: 0, + x: 0, + }, + kpiWidgetId2: { + cols: 3, + rows: 6, + y: 0, + x: 3, + }, + kpiWidgetId3: { + cols: 3, + rows: 6, + y: 0, + x: 6, + }, + }; + + this.dashboard = { + positions, + widgets: widgetsIndex, + }; + } +} + +const widgetsConfig: IWidget[] = [ + { + id: "kpiWidgetId", + type: "kpi", + pizzagna: { + [PizzagnaLayer.Configuration]: { + header: { + properties: { + title: "Sync Broker Applied for ALL Widgets", + subtitle: "Values are being synced", + }, + }, + tiles: { + properties: { + nodes: ["kpi1", "kpi2", "kpi3"], + }, + }, + kpi1: { + id: "kpi1", + componentType: KpiComponent.lateLoadKey, + properties: { + widgetData: { + units: \\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\`out of 5 Stars\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\`, + label: \\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\`Average Rating\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\`, + backgroundColor: "lightpink", + }, + }, + providers: { + [WellKnownProviders.DataSource]: { + providerId: AverageRatingKpiDataSource.providerId, + } as IProviderConfiguration, + [WellKnownProviders.Adapter]: { + providerId: NOVA_KPI_DATASOURCE_ADAPTER, + properties: { + componentId: "kpi1", + propertyPath: "widgetData", + }, + } as IProviderConfiguration, + }, + }, + kpi2: { + id: "kpi2", + componentType: KpiComponent.lateLoadKey, + properties: { + widgetData: { + label: \\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\`Another label which might be a pretty long one\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\`, + units: \\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\`Which comes from somewhere\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\`, + backgroundColor: "skyblue", + }, + }, + providers: { + [WellKnownProviders.DataSource]: { + providerId: RatingsCountKpiDataSource.providerId, + } as IProviderConfiguration, + [WellKnownProviders.Adapter]: { + providerId: NOVA_KPI_DATASOURCE_ADAPTER, + properties: { + componentId: "kpi2", + propertyPath: "widgetData", + }, + } as IProviderConfiguration, + }, + }, + kpi3: { + id: "kpi3", + componentType: KpiComponent.lateLoadKey, + properties: { + widgetData: { + label: \\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\`Random\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\`, + units: \\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\`Data\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\`, + }, + }, + providers: { + [WellKnownProviders.DataSource]: { + providerId: MockKpiDataSource.providerId, + } as IProviderConfiguration, + [WellKnownProviders.Adapter]: { + providerId: NOVA_KPI_DATASOURCE_ADAPTER, + properties: { + componentId: "kpi3", + propertyPath: "widgetData", + }, + } as IProviderConfiguration, + }, + }, + }, + }, + }, + { + id: "kpiWidgetId2", + type: "kpi", + pizzagna: { + [PizzagnaLayer.Configuration]: { + header: { + properties: { + title: "Sync Broker Applied for ALL Widgets", + subtitle: + "Now the values of label, units, and value are being synced", + }, + }, + tiles: { + properties: { + nodes: ["kpi1", "kpi2", "kpi3"], + }, + }, + kpi1: { + id: "kpi1", + componentType: KpiComponent.lateLoadKey, + properties: { + widgetData: { + units: \\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\`out of 5 Stars\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\`, + label: \\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\`Average Rating\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\`, + backgroundColor: "lightpink", + }, + }, + providers: { + [WellKnownProviders.DataSource]: { + providerId: AverageRatingKpiDataSource.providerId, + } as IProviderConfiguration, + [WellKnownProviders.Adapter]: { + providerId: NOVA_KPI_DATASOURCE_ADAPTER, + properties: { + componentId: "kpi1", + propertyPath: "widgetData", + }, + } as IProviderConfiguration, + }, + }, + kpi2: { + id: "kpi2", + componentType: KpiComponent.lateLoadKey, + properties: { + widgetData: { + label: \\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\`Another label which might be a pretty long one\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\`, + units: \\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\`Which comes from somewhere\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\`, + backgroundColor: "skyblue", + }, + }, + providers: { + [WellKnownProviders.DataSource]: { + providerId: RatingsCountKpiDataSource.providerId, + } as IProviderConfiguration, + [WellKnownProviders.Adapter]: { + providerId: NOVA_KPI_DATASOURCE_ADAPTER, + properties: { + componentId: "kpi2", + propertyPath: "widgetData", + }, + } as IProviderConfiguration, + }, + }, + kpi3: { + id: "kpi3", + componentType: KpiComponent.lateLoadKey, + properties: { + widgetData: { + label: \\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\`Random\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\`, + units: \\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\`Data\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\`, + }, + }, + providers: { + [WellKnownProviders.DataSource]: { + providerId: MockKpiDataSource.providerId, + } as IProviderConfiguration, + [WellKnownProviders.Adapter]: { + providerId: NOVA_KPI_DATASOURCE_ADAPTER, + properties: { + componentId: "kpi3", + propertyPath: "widgetData", + }, + } as IProviderConfiguration, + }, + }, + }, + }, + }, + { + id: "kpiWidgetId3", + type: "kpi", + pizzagna: { + [PizzagnaLayer.Configuration]: { + header: { + properties: { + title: "Here We Sync Only Labels and Units", + subtitle: + "Now only the label, and units are being synced", + }, + }, + tiles: { + properties: { + nodes: ["kpi1", "kpi2", "kpi3"], + }, + providers: { + // This is where and how you can override the globally set broker config + kpiScaleSyncBroker: { + providerId: NOVA_KPI_SCALE_SYNC_BROKER, + properties: { + scaleSyncConfig: [ + { id: "label" }, + { id: "units" }, + ], + }, + }, + }, + }, + kpi1: { + id: "kpi1", + componentType: KpiComponent.lateLoadKey, + properties: { + widgetData: { + units: \\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\`out of 5 Stars\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\`, + label: \\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\`Average Rating\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\`, + backgroundColor: "lightpink", + }, + }, + providers: { + [WellKnownProviders.DataSource]: { + providerId: AverageRatingKpiDataSource.providerId, + } as IProviderConfiguration, + [WellKnownProviders.Adapter]: { + providerId: NOVA_KPI_DATASOURCE_ADAPTER, + properties: { + componentId: "kpi1", + propertyPath: "widgetData", + }, + } as IProviderConfiguration, + }, + }, + kpi2: { + id: "kpi2", + componentType: KpiComponent.lateLoadKey, + properties: { + widgetData: { + label: \\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\`Another label which might be a pretty long one\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\`, + units: \\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\`Which comes from somewhere\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\`, + backgroundColor: "skyblue", + }, + }, + providers: { + [WellKnownProviders.DataSource]: { + providerId: RatingsCountKpiDataSource.providerId, + } as IProviderConfiguration, + [WellKnownProviders.Adapter]: { + providerId: NOVA_KPI_DATASOURCE_ADAPTER, + properties: { + componentId: "kpi2", + propertyPath: "widgetData", + }, + } as IProviderConfiguration, + }, + }, + kpi3: { + id: "kpi3", + componentType: KpiComponent.lateLoadKey, + properties: { + widgetData: { + label: \\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\`Random\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\`, + units: \\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\`Data\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\`, + }, + }, + providers: { + [WellKnownProviders.DataSource]: { + providerId: MockKpiDataSource.providerId, + } as IProviderConfiguration, + [WellKnownProviders.Adapter]: { + providerId: NOVA_KPI_DATASOURCE_ADAPTER, + properties: { + componentId: "kpi3", + propertyPath: "widgetData", + }, + } as IProviderConfiguration, + }, + }, + }, + }, + }, +]; +\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\`, + "widget-types/kpi/kpi-widget/kpi-widget-example.component.ts": \\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\`// © 2022 SolarWinds Worldwide, LLC. All rights reserved. +// +// Permission is hereby granted, free of charge, to any person obtaining a copy +// of this software and associated documentation files (the "Software"), to +// deal in the Software without restriction, including without limitation the +// rights to use, copy, modify, merge, publish, distribute, sublicense, and/or +// sell copies of the Software, and to permit persons to whom the Software is +// furnished to do so, subject to the following conditions: +// +// The above copyright notice and this permission notice shall be included in +// all copies or substantial portions of the Software. +// +// THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR +// IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, +// FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE +// AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER +// LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, +// OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN +// THE SOFTWARE. + +import { HttpClient, HttpErrorResponse } from "@angular/common/http"; +import { Component, Injectable, OnDestroy, OnInit } from "@angular/core"; +import { GridsterConfig, GridsterItem } from "angular-gridster2"; +import { BehaviorSubject } from "rxjs"; +import { finalize } from "rxjs/operators"; + +import { DataSourceService, IFilteringOutputs } from "@nova-ui/bits"; +import { + DATA_SOURCE, + DEFAULT_PIZZAGNA_ROOT, + IDashboard, + IKpiData, + IProviderConfiguration, + IRefresherProperties, + IWidget, + IWidgets, + KpiComponent, + NOVA_KPI_DATASOURCE_ADAPTER, + PizzagnaLayer, + ProviderRegistryService, + WellKnownPathKey, + WellKnownProviders, + WidgetTypesService, +} from "@nova-ui/dashboards"; + +/** + * A simple KPI data source to retrieve the average rating of Harry Potter and the Sorcerer's Stone (book) via googleapis + */ +@Injectable() +export class AverageRatingKpiDataSource + extends DataSourceService + implements OnDestroy +{ + // This is the ID we'll use to identify the provider + public static providerId = "AverageRatingKpiDataSource"; + + // Use this subject to communicate the data source's busy state + public busy = new BehaviorSubject(false); + + constructor(private http: HttpClient) { + super(); + } + + // In this example, getFilteredData is invoked every 10 minutes (Take a look at the refresher + // provider definition in the widget configuration below to see how the interval is set) + public async getFilteredData(): Promise { + this.busy.next(true); + return new Promise((resolve) => { + // *** Make a resource request to an external API (if needed) + this.http + .get("https://www.googleapis.com/books/v1/volumes/5MQFrgEACAAJ") + .pipe(finalize(() => this.busy.next(false))) + .subscribe({ + next: (data: any) => { + resolve({ + result: { + value: data.volumeInfo.averageRating, + }, + }); + }, + error: (error: HttpErrorResponse) => { + resolve({ + result: null, + error: { + type: error.status, + }, + }); + }, + }); + }); + } + + public ngOnDestroy(): void { + this.outputsSubject.complete(); + } +} + +/** + * A component that instantiates the dashboard + */ +@Component({ + selector: "kpi-widget-example", + templateUrl: "./kpi-widget-example.component.html", + styleUrls: ["./kpi-widget-example.component.less"], + standalone: false, +}) +export class KpiWidgetExampleComponent implements OnInit { + // This variable will hold all the data needed to define the layout and behavior of the widgets. + // Pass this to the dashboard component's dashboard input in the template. + public dashboard: IDashboard; + + // Angular gridster requires a configuration object even if it's empty. + // Pass this to the dashboard component's gridsterConfig input in the template. + public gridsterConfig: GridsterConfig = {}; + + // Boolean passed as an input to the dashboard. When true, widgets can be moved, resized, removed, or edited + public editMode: boolean = false; + + constructor( + // WidgetTypesService provides the widget's necessary structure information + private widgetTypesService: WidgetTypesService, + + // In general, the ProviderRegistryService is used for making entities available for injection into dynamically loaded components. + private providerRegistry: ProviderRegistryService + ) {} + + public ngOnInit(): void { + // Grabbing the widget's default template which will be needed as a parameter for setNode + const widgetTemplate = this.widgetTypesService.getWidgetType("kpi", 1); + // Registering our data sources as dropdown options in the widget editor/configurator + // Note: This could also be done in the parent module's constructor so that + // multiple dashboards could have access to the same widget template modification. + this.widgetTypesService.setNode( + // This is the template we grabbed above with getWidgetType + widgetTemplate, + // We are setting the editor/configurator part of the widget template + "configurator", + // This indicates which node you are changing and we want to change + // the data source providers available for selection in the editor. + WellKnownPathKey.DataSourceProviders, + // We are setting the data sources available for selection in the editor + [AverageRatingKpiDataSource.providerId] + ); + + // Registering the data source for injection into the KPI tile. + // Note: Each tile of a KPI widget is assigned its own instance of the data source + this.providerRegistry.setProviders({ + [AverageRatingKpiDataSource.providerId]: { + provide: DATA_SOURCE, + useClass: AverageRatingKpiDataSource, + // Any dependencies that need to be injected into the provider must be listed here + deps: [HttpClient], + }, + }); + + this.initializeDashboard(); + } + + public initializeDashboard(): void { + // We're using a static configuration object for this example, but this is where + // the widget's configuration could potentially be populated from a database + const kpiWidget = widgetConfig; + const widgetIndex: IWidgets = { + // Complete the KPI widget with information coming from its type definition + [kpiWidget.id]: + this.widgetTypesService.mergeWithWidgetType(kpiWidget), + }; + + // Setting the widget dimensions and position (this is for gridster) + const positions: Record = { + [kpiWidget.id]: { + cols: 4, + rows: 6, + y: 0, + x: 0, + }, + }; + + // Finally, assigning the variables we created above to the dashboard + this.dashboard = { + positions, + widgets: widgetIndex, + }; + } +} + +const widgetConfig: IWidget = { + id: "kpiWidgetId", + type: "kpi", + pizzagna: { + [PizzagnaLayer.Configuration]: { + [DEFAULT_PIZZAGNA_ROOT]: { + providers: { + [WellKnownProviders.Refresher]: { + properties: { + // Configuring the refresher interval so that our data source is invoked every ten minutes + interval: 60 * 10, + enabled: true, + } as IRefresherProperties, + } as Partial, + }, + }, + header: { + properties: { + title: "Harry Potter and the Sorcerer's Stone", + subtitle: "By J. K. Rowling", + }, + }, + tiles: { + properties: { + nodes: ["kpi1"], + }, + }, + kpi1: { + id: "kpi1", + componentType: KpiComponent.lateLoadKey, + properties: { + widgetData: { + units: \\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\`out of 5 Stars\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\`, + label: \\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\`Average Rating\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\`, + }, + }, + providers: { + [WellKnownProviders.DataSource]: { + // Setting the data source providerId for the tile with id "kpi1" + providerId: AverageRatingKpiDataSource.providerId, + } as IProviderConfiguration, + [WellKnownProviders.Adapter]: { + providerId: NOVA_KPI_DATASOURCE_ADAPTER, + properties: { + componentId: "kpi1", + propertyPath: "widgetData", + }, + } as IProviderConfiguration, + }, + }, + }, + }, +}; +\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\`, + "widget-types/kpi/kpi-widget-background-color/kpi-widget-background-color-example.component.ts": \\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\`// © 2022 SolarWinds Worldwide, LLC. All rights reserved. +// +// Permission is hereby granted, free of charge, to any person obtaining a copy +// of this software and associated documentation files (the "Software"), to +// deal in the Software without restriction, including without limitation the +// rights to use, copy, modify, merge, publish, distribute, sublicense, and/or +// sell copies of the Software, and to permit persons to whom the Software is +// furnished to do so, subject to the following conditions: +// +// The above copyright notice and this permission notice shall be included in +// all copies or substantial portions of the Software. +// +// THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR +// IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, +// FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE +// AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER +// LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, +// OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN +// THE SOFTWARE. + +import { HttpClient, HttpErrorResponse } from "@angular/common/http"; +import { + ChangeDetectorRef, + Component, + Injectable, + OnDestroy, + OnInit, +} from "@angular/core"; +import { GridsterConfig, GridsterItem } from "angular-gridster2"; +import { BehaviorSubject } from "rxjs"; +import { finalize } from "rxjs/operators"; + +import { DataSourceService, IFilteringOutputs } from "@nova-ui/bits"; +import { + DATA_SOURCE, + DEFAULT_KPI_BACKGROUND_COLORS, + IDashboard, + IKpiColorRules, + IKpiData, + IProviderConfiguration, + IWidget, + IWidgets, + KpiComponent, + NOVA_KPI_COLOR_PRIORITIZER, + NOVA_KPI_DATASOURCE_ADAPTER, + PizzagnaLayer, + ProviderRegistryService, + WellKnownPathKey, + WellKnownProviders, + WidgetTypesService, +} from "@nova-ui/dashboards"; + +/** + * A simple KPI data source to retrieve the average rating of Harry Potter and the Sorcerer's Stone (book) via googleapis + */ +@Injectable() +export class AverageRatingKpiDataSource + extends DataSourceService + implements OnDestroy +{ + public static providerId = "AverageRatingKpiDataSource"; + public busy = new BehaviorSubject(false); + + constructor(private http: HttpClient) { + super(); + } + + public async getFilteredData(): Promise { + this.busy.next(true); + return new Promise((resolve) => { + // *** Make a resource request to an external API (if needed) + this.http + .get("https://www.googleapis.com/books/v1/volumes/5MQFrgEACAAJ") + .pipe(finalize(() => this.busy.next(false))) + .subscribe({ + next: (data: any) => { + resolve({ + result: { + value: data.volumeInfo.averageRating, + // setting the color on the dataSource "Sea Green", + // uncomment to get the background color update from the "Data" layer + // backgroundColor: "var(--nui-color-chart-three)", + }, + }); + }, + error: (error: HttpErrorResponse) => { + resolve({ + result: null, + error: { + type: error.status, + }, + }); + }, + }); + }); + } + + public ngOnDestroy(): void { + this.outputsSubject.complete(); + } +} + +/** + * A component that instantiates the dashboard + */ +@Component({ + selector: "kpi-widget-background-color-example", + templateUrl: "./kpi-widget-background-color-example.component.html", + styleUrls: ["./kpi-widget-background-color-example.component.less"], + standalone: false, +}) +export class KpiWidgetBackgroundColorExampleComponent implements OnInit { + public dashboard: IDashboard | undefined; + public gridsterConfig: GridsterConfig = {}; + public editMode: boolean = false; + + constructor( + private widgetTypesService: WidgetTypesService, + private providerRegistry: ProviderRegistryService, + private changeDetectorRef: ChangeDetectorRef + ) {} + + public ngOnInit(): void { + this.setupDashboard(); + + // KPI tile default color setup + this.setupDefaultColorStructure(); + + // Sets the custom pallette to the 'Description' section + this.setupCustomPalletteDescription(); + + // Sets the custom pallette to the 'Background color rules' section + this.setupCustomPalletteRules(); + + this.initializeDashboard(); + } + + /** Used for restoring widgets state */ + public reInitializeDashboard(): void { + // destroys the components and their providers so the dashboard can re init data + this.dashboard = undefined; + this.changeDetectorRef.detectChanges(); + + this.initializeDashboard(); + } + + private setupCustomPalletteDescription() { + const kpiWidgetTemplate = this.widgetTypesService.getWidgetType( + "kpi", + 1 + ); + this.widgetTypesService.setNode( + kpiWidgetTemplate, + "configurator", + WellKnownPathKey.TileDescriptionBackgroundColors, + [ + { color: "var(--nui-color-chart-one)", label: "Blue" }, + { + color: "var(--nui-color-chart-one-light)", + label: "Blue Light", + }, + { + color: "var(--nui-color-chart-one-dark)", + label: "Blue Dark", + }, + ] + ); + } + + private setupCustomPalletteRules() { + const kpiWidgetTemplate = this.widgetTypesService.getWidgetType( + "kpi", + 1 + ); + this.widgetTypesService.setNode( + kpiWidgetTemplate, + "configurator", + WellKnownPathKey.TileBackgroundColorRulesBackgroundColors, + [ + { color: "red", label: "Native Red" }, + ...DEFAULT_KPI_BACKGROUND_COLORS, + ] + ); + } + + private setupDefaultColorStructure() { + const widgetTemplate = this.widgetTypesService.getWidgetType("kpi", 1); + this.widgetTypesService.setNode( + widgetTemplate, + "widget", + "tiles.properties.template.properties.widgetData.backgroundColor", + "red" + ); + } + + private setupDashboard() { + const widgetTemplate = this.widgetTypesService.getWidgetType("kpi", 1); + this.widgetTypesService.setNode( + widgetTemplate, + "configurator", + WellKnownPathKey.DataSourceProviders, + [AverageRatingKpiDataSource.providerId] + ); + + this.providerRegistry.setProviders({ + [AverageRatingKpiDataSource.providerId]: { + provide: DATA_SOURCE, + useClass: AverageRatingKpiDataSource, + deps: [HttpClient], + }, + }); + } + + private initializeDashboard(): void { + const kpiWidget = widgetConfig; + const widgetIndex: IWidgets = { + [kpiWidget.id]: + this.widgetTypesService.mergeWithWidgetType(kpiWidget), + }; + + const positions: Record = { + [kpiWidget.id]: { + cols: 4, + rows: 6, + y: 0, + x: 0, + }, + }; + + this.dashboard = { + positions, + widgets: widgetIndex, + }; + } +} + +const widgetConfig: IWidget = { + id: "kpiWidgetId", + type: "kpi", + pizzagna: { + [PizzagnaLayer.Configuration]: { + header: { + properties: { + title: "Harry Potter and the Sorcerer's Stone", + subtitle: "By J. K. Rowling", + }, + }, + tiles: { + properties: { + nodes: ["kpi1"], + }, + }, + kpi1: { + id: "kpi1", + componentType: KpiComponent.lateLoadKey, + properties: { + widgetData: { + units: \\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\`out of 5 Stars\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\`, + label: \\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\`Average Rating\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\`, + // Configuration color "Blue" + backgroundColor: "var(--nui-color-chart-one)", + }, + }, + providers: { + [WellKnownProviders.DataSource]: { + providerId: AverageRatingKpiDataSource.providerId, + } as IProviderConfiguration, + [WellKnownProviders.Adapter]: { + providerId: NOVA_KPI_DATASOURCE_ADAPTER, + properties: { + componentId: "kpi1", + propertyPath: "widgetData", + }, + } as IProviderConfiguration, + [WellKnownProviders.KpiColorPrioritizer]: { + providerId: NOVA_KPI_COLOR_PRIORITIZER, + properties: { + // Color Prioritizer Rules + // settings rules - if the value is more than "2" display "Violet" color + rules: [ + { + comparisonType: ">", + value: 2, + color: "var(--nui-color-chart-four)", + }, + ] as IKpiColorRules[], + }, + } as IProviderConfiguration, + }, + }, + }, + }, +}; +\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\`, + "widget-types/kpi/kpi-widget-background-color-docs.component.ts": \\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\`// © 2022 SolarWinds Worldwide, LLC. All rights reserved. +// +// Permission is hereby granted, free of charge, to any person obtaining a copy +// of this software and associated documentation files (the "Software"), to +// deal in the Software without restriction, including without limitation the +// rights to use, copy, modify, merge, publish, distribute, sublicense, and/or +// sell copies of the Software, and to permit persons to whom the Software is +// furnished to do so, subject to the following conditions: +// +// The above copyright notice and this permission notice shall be included in +// all copies or substantial portions of the Software. +// +// THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR +// IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, +// FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE +// AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER +// LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, +// OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN +// THE SOFTWARE. + +import { Component } from "@angular/core"; + +@Component({ + selector: "nui-kpi-background-color-docs", + templateUrl: "./kpi-widget-background-color-docs.component.html", + standalone: false, +}) +export class KpiWidgetBackgroundColorDocsComponent { + public comparatorsRegistryCode = \\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\` + this.comparatorsRegistry.registerComparators({ + "!=": { + comparatorFn: (actual: any, reference: any) => actual != reference, + label: "Not equal", + }, + }); + \\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\`; +} +\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\`, + "widget-types/kpi/kpi-widget-interactive/kpi-widget-interactive-example.component.ts": \\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\`// © 2022 SolarWinds Worldwide, LLC. All rights reserved. +// +// Permission is hereby granted, free of charge, to any person obtaining a copy +// of this software and associated documentation files (the "Software"), to +// deal in the Software without restriction, including without limitation the +// rights to use, copy, modify, merge, publish, distribute, sublicense, and/or +// sell copies of the Software, and to permit persons to whom the Software is +// furnished to do so, subject to the following conditions: +// +// The above copyright notice and this permission notice shall be included in +// all copies or substantial portions of the Software. +// +// THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR +// IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, +// FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE +// AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER +// LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, +// OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN +// THE SOFTWARE. + +import { HttpClient, HttpErrorResponse } from "@angular/common/http"; +import { Component, Injectable, OnDestroy, OnInit } from "@angular/core"; +import { GridsterConfig, GridsterItem } from "angular-gridster2"; +import { BehaviorSubject } from "rxjs"; +import { finalize } from "rxjs/operators"; + +import { DataSourceService, IFilteringOutputs } from "@nova-ui/bits"; +import { + DATA_SOURCE, + IDashboard, + IKpiData, + IProviderConfiguration, + IWidget, + IWidgets, + KpiComponent, + NOVA_KPI_DATASOURCE_ADAPTER, + NOVA_URL_INTERACTION_HANDLER, + PizzagnaLayer, + ProviderRegistryService, + WellKnownPathKey, + WellKnownProviders, + WidgetTypesService, +} from "@nova-ui/dashboards"; + +/** + * A simple KPI data source to retrieve the average rating of Harry Potter and the Sorcerer's Stone (book) via googleapis + */ +@Injectable() +export class BookRatingDataSource + extends DataSourceService + implements OnDestroy +{ + // This is the ID we'll use to identify the provider + public static providerId = "BookRatingDataSource"; + + // Use this subject to communicate the data source's busy state + public busy = new BehaviorSubject(false); + + constructor(private http: HttpClient) { + super(); + } + + // In this example, getFilteredData is invoked every 10 minutes (Take a look at the refresher + // provider definition in the widget configuration below to see how the interval is set) + public async getFilteredData(): Promise { + this.busy.next(true); + return new Promise((resolve) => { + // *** Make a resource request to an external API (if needed) + this.http + .get("https://www.googleapis.com/books/v1/volumes/zpvysRGsBlwC") + .pipe(finalize(() => this.busy.next(false))) + .subscribe({ + next: (data: any) => { + resolve({ + result: { + value: data.volumeInfo.averageRating, + link: data.volumeInfo.infoLink, + }, + }); + }, + error: (error: HttpErrorResponse) => { + resolve({ + result: null, + error: { + type: error.status, + }, + }); + }, + }); + }); + } + + public ngOnDestroy(): void { + this.outputsSubject.complete(); + } +} + +/** + * A component that instantiates the dashboard + */ +@Component({ + selector: "kpi-widget-interactive-example", + templateUrl: "./kpi-widget-interactive-example.component.html", + styleUrls: ["./kpi-widget-interactive-example.component.less"], + standalone: false, +}) +export class KpiWidgetInteractiveExampleComponent implements OnInit { + // This variable will hold all the data needed to define the layout and behavior of the widgets. + // Pass this to the dashboard component's dashboard input in the template. + public dashboard: IDashboard; + + // Angular gridster requires a configuration object even if it's empty. + // Pass this to the dashboard component's gridsterConfig input in the template. + public gridsterConfig: GridsterConfig = {}; + + // Boolean passed as an input to the dashboard. When true, widgets can be moved, resized, removed, or edited + public editMode: boolean = false; + + constructor( + // WidgetTypesService provides the widget's necessary structure information + private widgetTypesService: WidgetTypesService, + + // In general, the ProviderRegistryService is used for making entities available for injection into dynamically loaded components. + private providerRegistry: ProviderRegistryService + ) {} + + public ngOnInit(): void { + // Grabbing the widget's default template which will be needed as a parameter for setNode + const widgetTemplate = this.widgetTypesService.getWidgetType("kpi", 1); + // Registering our data sources as dropdown options in the widget editor/configurator + // Note: This could also be done in the parent module's constructor so that + // multiple dashboards could have access to the same widget template modification. + this.widgetTypesService.setNode( + // This is the template we grabbed above with getWidgetType + widgetTemplate, + // We are setting the editor/configurator part of the widget template + "configurator", + // This indicates which node you are changing and we want to change + // the data source providers available for selection in the editor. + WellKnownPathKey.DataSourceProviders, + // We are setting the data sources available for selection in the editor + [BookRatingDataSource.providerId] + ); + + // Registering the data source for injection into the KPI tile. + // Note: Each tile of a KPI widget is assigned its own instance of the data source + this.providerRegistry.setProviders({ + [BookRatingDataSource.providerId]: { + provide: DATA_SOURCE, + useClass: BookRatingDataSource, + // Any dependencies that need to be injected into the provider must be listed here + deps: [HttpClient], + }, + }); + + this.initializeDashboard(); + } + + public initializeDashboard(): void { + // We're using a static configuration object for this example, but this is where + // the widget's configuration could potentially be populated from a database + const kpiWidget = widgetConfig; + const widgetIndex: IWidgets = { + // Complete the KPI widget with information coming from its type definition + [kpiWidget.id]: + this.widgetTypesService.mergeWithWidgetType(kpiWidget), + }; + + // Setting the widget dimensions and position (this is for gridster) + const positions: Record = { + [kpiWidget.id]: { + cols: 4, + rows: 6, + y: 0, + x: 0, + }, + }; + + // Finally, assigning the variables we created above to the dashboard + this.dashboard = { + positions, + widgets: widgetIndex, + }; + } +} + +const widgetConfig: IWidget = { + id: "kpiWidgetId", + type: "kpi", + pizzagna: { + [PizzagnaLayer.Configuration]: { + header: { + properties: { + title: "Harry Potter and the Order of the Phoenix", + subtitle: "By: J. K. Rowling", + }, + }, + tiles: { + providers: { + interaction: { + // Configuring the UrlInteractionHandler for interactions on the tiles + providerId: NOVA_URL_INTERACTION_HANDLER, + properties: { + // the 'url' property tells the handler what link to use when interaction occurs on the series + url: "\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\${data.link}", + }, + }, + }, + properties: { + nodes: ["kpi1"], + }, + }, + kpi1: { + id: "kpi1", + componentType: KpiComponent.lateLoadKey, + properties: { + widgetData: { + units: \\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\`out of 5 stars\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\`, + label: \\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\`Average Rating\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\`, + value: 0, + // the link property that is passed to the UrlInteractionHandler when the title is clicked + // this will be updated in BookRatingDataSource's 'getFilteredData' call. + link: "http://www.google.com", + }, + }, + providers: { + [WellKnownProviders.DataSource]: { + // Setting the data source providerId for the tile with id "kpi1" + providerId: BookRatingDataSource.providerId, + } as IProviderConfiguration, + [WellKnownProviders.Adapter]: { + providerId: NOVA_KPI_DATASOURCE_ADAPTER, + properties: { + componentId: "kpi1", + propertyPath: "widgetData", + }, + } as IProviderConfiguration, + }, + }, + }, + }, +}; +\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\`, + "widget-types/proportional/models.ts": \\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\`export interface IMockBeerReview { + id: string; + name: string; + data: number[]; + icon: string; + link?: string; + value: string; + color?: string; +} +\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\`, + "widget-types/proportional/proportional-docs.component.ts": \\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\`// © 2022 SolarWinds Worldwide, LLC. All rights reserved. +// +// Permission is hereby granted, free of charge, to any person obtaining a copy +// of this software and associated documentation files (the "Software"), to +// deal in the Software without restriction, including without limitation the +// rights to use, copy, modify, merge, publish, distribute, sublicense, and/or +// sell copies of the Software, and to permit persons to whom the Software is +// furnished to do so, subject to the following conditions: +// +// The above copyright notice and this permission notice shall be included in +// all copies or substantial portions of the Software. +// +// THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR +// IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, +// FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE +// AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER +// LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, +// OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN +// THE SOFTWARE. + +import { Component, OnInit } from "@angular/core"; + +import { mapContentFile } from "../../../../demo-files-factory"; + +@Component({ + selector: "nui-proportional-docs", + templateUrl: "./proportional-docs.component.html", + standalone: false, +}) +export class ProportionalDocsComponent implements OnInit { + public proportionalWidgetFileText = ""; + public proportionalConfiguratorFileText = ""; + + public async ngOnInit(): Promise { + this.proportionalWidgetFileText = await import( + "./../../../../../../src/lib/widget-types/proportional/proportional-widget" + ).then(mapContentFile); + this.proportionalConfiguratorFileText = await import( + "./../../../../../../src/lib/widget-types/proportional/proportional-configurator" + ).then(mapContentFile); + } +} +\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\`, + "widget-types/proportional/proportional-docs.module.ts": \\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\`// © 2022 SolarWinds Worldwide, LLC. All rights reserved. +// +// Permission is hereby granted, free of charge, to any person obtaining a copy +// of this software and associated documentation files (the "Software"), to +// deal in the Software without restriction, including without limitation the +// rights to use, copy, modify, merge, publish, distribute, sublicense, and/or +// sell copies of the Software, and to permit persons to whom the Software is +// furnished to do so, subject to the following conditions: +// +// The above copyright notice and this permission notice shall be included in +// all copies or substantial portions of the Software. +// +// THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR +// IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, +// FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE +// AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER +// LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, +// OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN +// THE SOFTWARE. + +import { NgModule } from "@angular/core"; +import { RouterModule, Routes } from "@angular/router"; + +import { + NuiButtonModule, + NuiDocsModule, + NuiMessageModule, + NuiSwitchModule, + DEMO_PATH_TOKEN, +} from "@nova-ui/bits"; +import { NuiDashboardsModule } from "@nova-ui/dashboards"; + +import { ProportionalDocsComponent } from "./proportional-docs.component"; +import { ProportionalDonutContentDocsComponent } from "./proportional-donut-content-docs.component"; +import { ProportionalWidgetDonutContentFormattersExampleComponent } from "./proportional-donut-content-formatters/proportional-donut-content-formatters-example.component"; +import { ProportionalWidgetExampleComponent } from "./proportional-widget/proportional-widget-example.component"; +import { ProportionalWidgetInteractiveExampleComponent } from "./proportional-widget-interactive/proportional-widget-interactive-example.component"; +import { getDemoFiles } from "../../../../demo-files-factory"; + +const routes: Routes = [ + { + path: "", + component: ProportionalDocsComponent, + data: { + srlc: { + hideIndicator: true, + }, + showThemeSwitcher: true, + }, + }, + { + path: "example", + component: ProportionalWidgetExampleComponent, + data: { + srlc: { + hideIndicator: true, + }, + }, + }, + { + path: "donut-content-formatters", + component: ProportionalDonutContentDocsComponent, + data: { + srlc: { + hideIndicator: true, + }, + }, + }, + { + path: "donut-content-formatters-example", + component: ProportionalWidgetDonutContentFormattersExampleComponent, + data: { + srlc: { + hideIndicator: true, + }, + }, + }, + { + path: "proportional-widget-interactive-example", + component: ProportionalWidgetInteractiveExampleComponent, + data: { + srlc: { + hideIndicator: true, + }, + }, + }, +]; + +@NgModule({ + imports: [ + RouterModule.forChild(routes), + NuiButtonModule, + NuiDocsModule, + NuiDashboardsModule, + NuiMessageModule, + NuiSwitchModule, + ], + declarations: [ + ProportionalDocsComponent, + ProportionalWidgetExampleComponent, + ProportionalWidgetInteractiveExampleComponent, + ProportionalWidgetDonutContentFormattersExampleComponent, + ProportionalDonutContentDocsComponent, + ], + providers: [ + { + provide: DEMO_PATH_TOKEN, + useValue: getDemoFiles("proportional"), + }, + ], +}) +export default class ProportionalDocsModule {} +\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\`, + "widget-types/proportional/proportional-donut-content-docs.component.ts": \\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\`// © 2022 SolarWinds Worldwide, LLC. All rights reserved. +// +// Permission is hereby granted, free of charge, to any person obtaining a copy +// of this software and associated documentation files (the "Software"), to +// deal in the Software without restriction, including without limitation the +// rights to use, copy, modify, merge, publish, distribute, sublicense, and/or +// sell copies of the Software, and to permit persons to whom the Software is +// furnished to do so, subject to the following conditions: +// +// The above copyright notice and this permission notice shall be included in +// all copies or substantial portions of the Software. +// +// THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR +// IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, +// FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE +// AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER +// LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, +// OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN +// THE SOFTWARE. + +import { Component } from "@angular/core"; + +@Component({ + selector: "nui-proportional-donut-content-docs", + templateUrl: "./proportional-donut-content-docs.component.html", + standalone: false, +}) +export class ProportionalDonutContentDocsComponent { + public dataSourceDataFieldsConfig = \\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\` +public dataFieldsConfig: IProportionalDataFieldsConfig = { + dataFields$: new BehaviorSubject(this.dataFields), + chartSeriesDataFields$: new BehaviorSubject(this.chartSeriesDataFields), +}; + \\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\`; + + public widgetConfigSlice = \\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\` +"properties": { + "configuration": { + "chartOptions": { + donutContentConfig: { + formatter: { + componentType: SiUnitsFormatterComponent.lateLoadKey, + }, + aggregator: { + aggregatorType: sumAggregator.aggregatorType, + }, + }, + } + } +} + + \\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\`; +} +\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\`, + "widget-types/proportional/proportional-donut-content-formatters/proportional-donut-content-formatters-example.component.ts": \\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\`// © 2022 SolarWinds Worldwide, LLC. All rights reserved. +// +// Permission is hereby granted, free of charge, to any person obtaining a copy +// of this software and associated documentation files (the "Software"), to +// deal in the Software without restriction, including without limitation the +// rights to use, copy, modify, merge, publish, distribute, sublicense, and/or +// sell copies of the Software, and to permit persons to whom the Software is +// furnished to do so, subject to the following conditions: +// +// The above copyright notice and this permission notice shall be included in +// all copies or substantial portions of the Software. +// +// THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR +// IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, +// FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE +// AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER +// LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, +// OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN +// THE SOFTWARE. + +import { + ChangeDetectorRef, + Component, + Injectable, + OnDestroy, + OnInit, +} from "@angular/core"; +import { GridsterConfig, GridsterItem } from "angular-gridster2"; +import { BehaviorSubject } from "rxjs"; + +import { + DataSourceService, + IDataField, + IDataSource, + IFilteringOutputs, +} from "@nova-ui/bits"; +import { IAccessors, IChartAssistSeries } from "@nova-ui/charts"; +import { + DATA_SOURCE, + DEFAULT_LEGEND_FORMATTERS, + DEFAULT_PIZZAGNA_ROOT, + DEFAULT_PROPORTIONAL_CONTENT_AGGREGATORS, + DEFAULT_PROPORTIONAL_CONTENT_FORMATTERS, + DONUT_CONTENT_CONFIGURATION_SLICE, + IDashboard, + IDonutContentConfig, + IProportionalDataFieldsConfig, + IProportionalWidgetChartOptions, + IProportionalWidgetConfig, + IProviderConfiguration, + IWidget, + IWidgets, + LegendPlacement, + PizzagnaLayer, + ProportionalContentAggregatorsRegistryService, + ProportionalDonutContentFormattersRegistryService, + ProportionalLegendFormattersRegistryService, + ProportionalWidgetChartTypes, + ProviderRegistryService, + SiUnitsFormatterComponent, + sumAggregator, + WellKnownPathKey, + WellKnownProviders, + WidgetTypesService, +} from "@nova-ui/dashboards"; + +import { IMockBeerReview } from "../models"; + +/** + * A simple proportional data source to retrieve beer review counts by city + */ +@Injectable() +export class BeerReviewCountsByCityMockDataSource + extends DataSourceService> + implements IDataSource>, OnDestroy +{ + public static providerId = "BeerReviewCountsByCityMockDataSource"; + public busy = new BehaviorSubject(false); + + protected dataFields: IDataField[] = [ + { + id: "Brno", + label: "Brno", + // @ts-ignore + dataType: null, + }, + { + id: "kyiv", + label: "Kyiv", + // @ts-ignore + dataType: null, + }, + { + id: "austin", + label: "Austin", + // @ts-ignore + dataType: null, + }, + { + id: "lisbon", + label: "Lisbon", + // @ts-ignore + dataType: null, + }, + { + id: "sydney", + label: "Sydney", + // @ts-ignore + dataType: null, + }, + { + id: "nur-sultan", + label: "Nur-Sultan", + // @ts-ignore + dataType: null, + }, + ]; + protected chartSeriesDataFields: IDataField[] = [ + // default field in the chart series that is used for the aggregation + { + id: "data[0]", + label: "data", + // @ts-ignore + dataType: null, + }, + // any custom field in the chart series that is used for the aggregation + { + id: "customDonutContent", + label: "Custom Donut Content", + // @ts-ignore + dataType: null, + }, + ]; + + /** + * DataSource needs to implement the "IDataFieldsConfig" for this scenario. + * + * It's necessary to provide the "chartSeriesDataFields", + * that's why proportional widget dataSource has it's own interface for that - IProportionalDataFieldsConfig. + * + * dataFields$ - stands for possible series fields + * chartSeriesDataFields$ - stands for the fields IN the series + * + * see declaration of "dataFields" and "chartSeriesDataFields" for the example. + */ + public dataFieldsConfig: IProportionalDataFieldsConfig = { + dataFields$: new BehaviorSubject(this.dataFields), + chartSeriesDataFields$: new BehaviorSubject( + this.chartSeriesDataFields + ), + }; + + public async getFilteredData(): Promise { + this.busy.next(true); + return new Promise((resolve) => { + setTimeout(() => { + this.outputsSubject.next({ + result: getMockBeerReviewCountsByCity(), + }); + this.busy.next(false); + }, 300); + }); + } + + public ngOnDestroy(): void { + this.outputsSubject.complete(); + } +} + +/** + * A component that instantiates the dashboard + */ +@Component({ + selector: "proportional-widget-donut-content-formatters-example", + templateUrl: "./proportional-donut-content-formatters-example.component.html", + styleUrls: [ + "./proportional-donut-content-formatters-example.component.less", + ], + standalone: false, +}) +export class ProportionalWidgetDonutContentFormattersExampleComponent + implements OnInit +{ + public dashboard: IDashboard | undefined; + + // Angular gridster requires a configuration object even if it's empty. + // Pass this to the dashboard component's gridsterConfig input in the template. + public gridsterConfig: GridsterConfig = {}; + + // Boolean passed as an input to the dashboard. When true, widgets can be moved, resized, removed, or edited + public editMode: boolean = false; + + constructor( + // WidgetTypesService provides the widget's necessary structure information + private widgetTypesService: WidgetTypesService, + // In general, the ProviderRegistryService is used for making entities available for injection into dynamically loaded components. + private providerRegistry: ProviderRegistryService, + // registry for adding the formatter for donut content + contentFormattersRegistry: ProportionalDonutContentFormattersRegistryService, + // registry for adding the formatter for proportional legend + legendFormattersRegistry: ProportionalLegendFormattersRegistryService, + // registry for adding the aggregators for donut content + aggregatorRegistry: ProportionalContentAggregatorsRegistryService, + private changeDetectorRef: ChangeDetectorRef + ) { + // on the dashboard startup, it's necessary to add possible content formatters, legend formatters and content aggregators to the registry. + // using registry is a way for setting the available formatters. + legendFormattersRegistry.addItems(DEFAULT_LEGEND_FORMATTERS); + contentFormattersRegistry.addItems( + DEFAULT_PROPORTIONAL_CONTENT_FORMATTERS + ); + aggregatorRegistry.addItems(DEFAULT_PROPORTIONAL_CONTENT_AGGREGATORS); + } + + public ngOnInit(): void { + // Grabbing the widget's default template which will be needed as a parameter for setNode + const widgetTemplate = this.widgetTypesService.getWidgetType( + "proportional", + 1 + ); + + // Registering our data sources as dropdown options in the widget editor/configurator + // Note: This could also be done in the parent module's constructor so that + // multiple dashboards could have access to the same widget template modification. + this.widgetTypesService.setNode( + // This is the template we grabbed above with getWidgetType + widgetTemplate, + // We are setting the editor/configurator part of the widget template + "configurator", + // This indicates which node you are changing and we want to change + // the data source providers available for selection in the editor. + WellKnownPathKey.DataSourceProviders, + // We are setting the data sources available for selection in the editor + [BeerReviewCountsByCityMockDataSource.providerId] + ); + + // Setup of the configurator is done here + this.setupConfigurator(); + + // Registering the data source for injection into the Proportional widget. + this.providerRegistry.setProviders({ + [BeerReviewCountsByCityMockDataSource.providerId]: { + provide: DATA_SOURCE, + useClass: BeerReviewCountsByCityMockDataSource, + deps: [], + }, + }); + + this.initializeDashboard(); + } + + /** Used for restoring widgets state */ + public reInitializeDashboard(): void { + // destroys the components and their providers so the dashboard can re init data + this.dashboard = undefined; + this.changeDetectorRef.detectChanges(); + + this.initializeDashboard(); + } + + private initializeDashboard(): void { + // We're using a static configuration object for this example, but this is where + // the widget's configuration could potentially be populated from a database + const widgetIndex: IWidgets = { + // Complete the proportional widget with information coming from its type definition + [widgetConfig.id]: + this.widgetTypesService.mergeWithWidgetType(widgetConfig), + }; + + // Setting the widget dimensions and position (this is for gridster) + const positions: Record = { + [widgetConfig.id]: { + cols: 6, + rows: 6, + y: 0, + x: 0, + }, + }; + + // Finally, assigning the variables we created above to the dashboard + this.dashboard = { + positions, + widgets: widgetIndex, + }; + } + + /** + * Sets up the configurator sections for proportional donut + */ + private setupConfigurator() { + const widgetTemplate = this.widgetTypesService.getWidgetType( + "proportional", + 1 + ); + + // remove old "presentation", "chartOptionsEditor" and "donutContentConfiguration" sections from the configurator + delete widgetTemplate.configurator?.structure?.presentation; + delete widgetTemplate.configurator?.structure?.chartOptionsEditor; + delete widgetTemplate.configurator?.structure + ?.donutContentConfiguration; + + // add new "presentation" section + this.widgetTypesService.setNode( + widgetTemplate, + "configurator", + "presentation", + DONUT_CONTENT_CONFIGURATION_SLICE.presentation + ); + // add new "chartOptionsEditor" section + this.widgetTypesService.setNode( + widgetTemplate, + "configurator", + "chartOptionsEditor", + DONUT_CONTENT_CONFIGURATION_SLICE.chartOptionsEditor + ); + // add new "donutContentConfiguration" section + this.widgetTypesService.setNode( + widgetTemplate, + "configurator", + "donutContentConfiguration", + DONUT_CONTENT_CONFIGURATION_SLICE.donutContentConfiguration + ); + } +} + +const widgetConfig: IWidget = { + id: "proportionalWidgetId", + type: "proportional", + pizzagna: { + [PizzagnaLayer.Configuration]: { + [DEFAULT_PIZZAGNA_ROOT]: { + providers: {}, + }, + header: { + properties: { + title: "Beer Review Tally by City", + subtitle: "These People Love Beer", + }, + }, + chart: { + providers: { + [WellKnownProviders.DataSource]: { + // Setting the data source providerId for the chart + providerId: + BeerReviewCountsByCityMockDataSource.providerId, + } as IProviderConfiguration, + }, + properties: { + configuration: { + chartOptions: { + type: ProportionalWidgetChartTypes.DonutChart, + legendPlacement: LegendPlacement.Right, + // old configuration looks like this + // contentFormatter: { + // componentType: DonutContentSumFormatterComponent.lateLoadKey, + // }, + + // NEW configuration looks like this + donutContentConfig: { + formatter: { + componentType: + SiUnitsFormatterComponent.lateLoadKey, + }, + aggregator: { + aggregatorType: + sumAggregator.aggregatorType, + properties: { + // example of a default metric to be used for the percentage calculation + // activeMetricId: "austin", + }, + }, + } as IDonutContentConfig, + } as IProportionalWidgetChartOptions, + } as IProportionalWidgetConfig, + }, + }, + }, + }, +}; + +export function getMockBeerReviewCountsByCity(): IMockBeerReview[] { + return [ + { + id: "Brno", + name: "Brno", + data: [Math.round(Math.random() * 1000000)], + icon: "status_down", + link: "https://en.wikipedia.org/wiki/Brno", + value: "Brno", + customDonutContent: "Custom Brno", + }, + { + id: "kyiv", + name: "Kyiv", + data: [Math.round(Math.random() * 1000000)], + icon: "status_critical", + link: "https://en.wikipedia.org/wiki/Kyiv", + value: "Kyiv", + customDonutContent: "Custom Kyiv", + }, + { + id: "austin", + name: "Austin", + data: [Math.round(Math.random() * 1000000)], + icon: "status_warning", + link: "https://en.wikipedia.org/wiki/Austin", + value: "Austin", + customDonutContent: "Custom Austin", + }, + { + id: "lisbon", + name: "Lisbon", + data: [Math.round(Math.random() * 1000000)], + icon: "status_unknown", + link: "https://en.wikipedia.org/wiki/Lisbon", + value: "Lisbon", + customDonutContent: "Custom Lisbon", + }, + { + id: "sydney", + name: "Sydney", + data: [Math.round(Math.random() * 1000000)], + icon: "status_up", + link: "https://en.wikipedia.org/wiki/Sydney", + value: "Sydney", + customDonutContent: "Custom Sydney", + }, + { + id: "nur-sultan", + name: "Nur-Sultan", + data: [Math.round(Math.random() * 1000000)], + icon: "status_unmanaged", + link: "https://en.wikipedia.org/wiki/Nur-Sultan", + value: "Nur-Sultan", + customDonutContent: "Custom Nur-Sultan", + }, + ].sort((a, b) => a.data[0] - b.data[0]); +} +\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\`, + "widget-types/proportional/proportional-widget/proportional-widget-example.component.ts": \\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\`// © 2022 SolarWinds Worldwide, LLC. All rights reserved. +// +// Permission is hereby granted, free of charge, to any person obtaining a copy +// of this software and associated documentation files (the "Software"), to +// deal in the Software without restriction, including without limitation the +// rights to use, copy, modify, merge, publish, distribute, sublicense, and/or +// sell copies of the Software, and to permit persons to whom the Software is +// furnished to do so, subject to the following conditions: +// +// The above copyright notice and this permission notice shall be included in +// all copies or substantial portions of the Software. +// +// THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR +// IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, +// FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE +// AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER +// LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, +// OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN +// THE SOFTWARE. + +import { + ChangeDetectorRef, + Component, + Injectable, + OnDestroy, + OnInit, +} from "@angular/core"; +import { GridsterConfig, GridsterItem } from "angular-gridster2"; +import { BehaviorSubject } from "rxjs"; + +import { + DataSourceService, + IDataSource, + IFilteringOutputs, +} from "@nova-ui/bits"; +import { + DATA_SOURCE, + DEFAULT_PIZZAGNA_ROOT, + IDashboard, + IProportionalWidgetChartOptions, + IProportionalWidgetConfig, + IProportionalWidgetData, + IProviderConfiguration, + IRefresherProperties, + IWidget, + IWidgets, + LegendPlacement, + PizzagnaLayer, + ProportionalWidgetChartTypes, + ProviderRegistryService, + WellKnownPathKey, + WellKnownProviders, + WidgetTypesService, +} from "@nova-ui/dashboards"; + +import { IMockBeerReview } from "../models"; + +/** + * A simple proportional data source to retrieve beer review counts by city + */ +@Injectable() +export class BeerReviewCountsByCityMockDataSource + extends DataSourceService + implements IDataSource, OnDestroy +{ + // This is the ID we'll use to identify the provider + public static providerId = "BeerReviewCountsByCityMockDataSource"; + public busy = new BehaviorSubject(false); + + public async getFilteredData(): Promise { + this.busy.next(true); + return new Promise((resolve) => { + setTimeout(() => { + this.outputsSubject.next({ + result: getMockBeerReviewCountsByCity(), + }); + this.busy.next(false); + }, 300); + }); + } + + public ngOnDestroy(): void { + this.outputsSubject.complete(); + } +} + +/** + * A component that instantiates the dashboard + */ +@Component({ + selector: "proportional-widget-example", + templateUrl: "./proportional-widget-example.component.html", + styleUrls: ["./proportional-widget-example.component.less"], + standalone: false, +}) +export class ProportionalWidgetExampleComponent implements OnInit { + // This variable will hold all the data needed to define the layout and behavior of the widgets. + // Pass this to the dashboard component's dashboard input in the template. + public dashboard: IDashboard | undefined; + + // Angular gridster requires a configuration object even if it's empty. + // Pass this to the dashboard component's gridsterConfig input in the template. + public gridsterConfig: GridsterConfig = {}; + + // Boolean passed as an input to the dashboard. When true, widgets can be moved, resized, removed, or edited + public editMode: boolean = false; + + constructor( + // WidgetTypesService provides the widget's necessary structure information + private widgetTypesService: WidgetTypesService, + // In general, the ProviderRegistryService is used for making entities available for injection into dynamically loaded components. + private providerRegistry: ProviderRegistryService, + private changeDetectorRef: ChangeDetectorRef + ) {} + + public ngOnInit(): void { + // Grabbing the widget's default template which will be needed as a parameter for setNode + const widgetTemplate = this.widgetTypesService.getWidgetType( + "proportional", + 1 + ); + + // Registering our data sources as dropdown options in the widget editor/configurator + // Note: This could also be done in the parent module's constructor so that + // multiple dashboards could have access to the same widget template modification. + this.widgetTypesService.setNode( + // This is the template we grabbed above with getWidgetType + widgetTemplate, + // We are setting the editor/configurator part of the widget template + "configurator", + // This indicates which node you are changing and we want to change + // the data source providers available for selection in the editor. + WellKnownPathKey.DataSourceProviders, + // We are setting the data sources available for selection in the editor + [BeerReviewCountsByCityMockDataSource.providerId] + ); + + // Registering the data source for injection into the Proportional widget. + this.providerRegistry.setProviders({ + [BeerReviewCountsByCityMockDataSource.providerId]: { + provide: DATA_SOURCE, + useClass: BeerReviewCountsByCityMockDataSource, + deps: [], + }, + }); + + this.initializeDashboard(); + } + + /** Used for restoring widgets state */ + public reInitializeDashboard(): void { + // destroys the components and their providers so the dashboard can re init data + this.dashboard = undefined; + this.changeDetectorRef.detectChanges(); + + this.initializeDashboard(); + } + + public initializeDashboard(): void { + // We're using a static configuration object for this example, but this is where + // the widget's configuration could potentially be populated from a database + const widgetIndex: IWidgets = { + // Complete the proportional widget with information coming from its type definition + [widgetConfig.id]: + this.widgetTypesService.mergeWithWidgetType(widgetConfig), + }; + + // Setting the widget dimensions and position (this is for gridster) + const positions: Record = { + [widgetConfig.id]: { + cols: 5, + rows: 6, + y: 0, + x: 0, + }, + }; + + // Finally, assigning the variables we created above to the dashboard + this.dashboard = { + positions, + widgets: widgetIndex, + }; + } +} + +const widgetConfig: IWidget = { + id: "proportionalWidgetId", + type: "proportional", + pizzagna: { + [PizzagnaLayer.Configuration]: { + [DEFAULT_PIZZAGNA_ROOT]: { + providers: { + [WellKnownProviders.Refresher]: { + properties: { + // Configuring the refresher interval so that our data source is invoked every ten minutes + interval: 60 * 10, + enabled: true, + } as IRefresherProperties, + } as Partial, + }, + }, + header: { + properties: { + title: "Beer Review Tally by City", + subtitle: "These People Love Beer", + }, + }, + chart: { + providers: { + [WellKnownProviders.DataSource]: { + // Setting the data source providerId for the chart + providerId: + BeerReviewCountsByCityMockDataSource.providerId, + } as IProviderConfiguration, + }, + properties: { + configuration: { + chartOptions: { + type: ProportionalWidgetChartTypes.DonutChart, + legendPlacement: LegendPlacement.Right, + } as IProportionalWidgetChartOptions, + // You can optionally define custom colors for the chart by setting the 'chartColors' configuration property + // "chartColors": [ + // "var(--nui-color-chart-five)", + // "var(--nui-color-chart-six)", + // "var(--nui-color-chart-seven)", + // "var(--nui-color-chart-eight)", + // "var(--nui-color-chart-nine)", + // "var(--nui-color-chart-ten)", + // ], + // or use-mapped structure + chartColors: { + Brno: "var(--nui-color-chart-five)", + kyiv: "var(--nui-color-chart-six)", + austin: "var(--nui-color-chart-seven)", + lisbon: "var(--nui-color-chart-eight)", + sydney: "var(--nui-color-chart-nine)", + "nur-sultan": "var(--nui-color-chart-ten)", + }, + prioritizeWidgetColors: false, + } as IProportionalWidgetConfig, + }, + }, + }, + }, +}; + +export function getMockBeerReviewCountsByCity(): IMockBeerReview[] { + return [ + { + id: "Brno", + name: "Brno", + data: [Math.round(Math.random() * 100000)], + icon: "status_down", + link: "https://en.wikipedia.org/wiki/Brno", + value: "Brno", + color: "var(--nui-color-chart-one)", + }, + { + id: "kyiv", + name: "Kyiv", + data: [Math.round(Math.random() * 100000)], + icon: "status_critical", + link: "https://en.wikipedia.org/wiki/Kyiv", + value: "Kyiv", + color: "var(--nui-color-chart-two)", + }, + { + id: "austin", + name: "Austin", + data: [Math.round(Math.random() * 100000)], + icon: "status_warning", + link: "https://en.wikipedia.org/wiki/Austin", + value: "Austin", + color: "var(--nui-color-chart-three)", + }, + { + id: "lisbon", + name: "Lisbon", + data: [Math.round(Math.random() * 100000)], + icon: "status_unknown", + link: "https://en.wikipedia.org/wiki/Lisbon", + value: "Lisbon", + color: "var(--nui-color-chart-four)", + }, + { + id: "sydney", + name: "Sydney", + data: [Math.round(Math.random() * 100000)], + icon: "status_up", + link: "https://en.wikipedia.org/wiki/Sydney", + value: "Sydney", + color: "var(--nui-color-chart-five)", + }, + { + id: "nur-sultan", + name: "Nur-Sultan", + data: [Math.round(Math.random() * 100000)], + icon: "status_unmanaged", + link: "https://en.wikipedia.org/wiki/Nur-Sultan", + value: "Nur-Sultan", + color: "var(--nui-color-chart-six)", + }, + ].sort((a, b) => a.data[0] - b.data[0]); +} +\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\`, + "widget-types/proportional/proportional-widget-interactive/proportional-widget-interactive-example.component.ts": \\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\`// © 2022 SolarWinds Worldwide, LLC. All rights reserved. +// +// Permission is hereby granted, free of charge, to any person obtaining a copy +// of this software and associated documentation files (the "Software"), to +// deal in the Software without restriction, including without limitation the +// rights to use, copy, modify, merge, publish, distribute, sublicense, and/or +// sell copies of the Software, and to permit persons to whom the Software is +// furnished to do so, subject to the following conditions: +// +// The above copyright notice and this permission notice shall be included in +// all copies or substantial portions of the Software. +// +// THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR +// IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, +// FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE +// AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER +// LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, +// OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN +// THE SOFTWARE. + +import { + ChangeDetectorRef, + Component, + Injectable, + OnDestroy, + OnInit, +} from "@angular/core"; +import { GridsterConfig, GridsterItem } from "angular-gridster2"; +import keyBy from "lodash/keyBy"; +import { BehaviorSubject } from "rxjs"; + +import { + DataSourceService, + IDataSource, + IFilteringOutputs, +} from "@nova-ui/bits"; +import { + DATA_SOURCE, + DEFAULT_PIZZAGNA_ROOT, + IDashboard, + IProportionalWidgetChartOptions, + IProportionalWidgetConfig, + IProportionalWidgetData, + IProviderConfiguration, + IWidget, + LegendPlacement, + NOVA_URL_INTERACTION_HANDLER, + PizzagnaLayer, + ProportionalWidgetChartTypes, + ProviderRegistryService, + WellKnownPathKey, + WellKnownProviders, + WidgetTypesService, +} from "@nova-ui/dashboards"; + +import { IMockBeerReview } from "../models"; + +/** + * A simple proportional data source to retrieve beer review counts by city + */ +@Injectable() +export class ReviewCountsByCityMockDataSource + extends DataSourceService + implements IDataSource, OnDestroy +{ + // This is the ID we'll use to identify the provider + public static providerId = "ReviewCountsByCityMockDataSource"; + public busy = new BehaviorSubject(false); + + public async getFilteredData(): Promise { + this.busy.next(true); + return new Promise((resolve) => { + setTimeout(() => { + this.outputsSubject.next({ + result: getMockBeerReviewCountsByCity(), + }); + this.busy.next(false); + }, 300); + }); + } + + public ngOnDestroy(): void { + this.outputsSubject.complete(); + } +} + +/** + * A component that instantiates the dashboard + */ +@Component({ + selector: "proportional-widget-interactive-example", + templateUrl: "./proportional-widget-interactive-example.component.html", + styleUrls: ["./proportional-widget-interactive-example.component.less"], + standalone: false, +}) +export class ProportionalWidgetInteractiveExampleComponent implements OnInit { + // This variable will hold all the data needed to define the layout and behavior of the widgets. + // Pass this to the dashboard component's dashboard input in the template. + public dashboard: IDashboard | undefined; + + // Angular gridster requires a configuration object even if it's empty. + // Pass this to the dashboard component's gridsterConfig input in the template. + public gridsterConfig: GridsterConfig = {}; + + // Boolean passed as an input to the dashboard. When true, widgets can be moved, resized, removed, or edited + public editMode: boolean = false; + + constructor( + // WidgetTypesService provides the widget's necessary structure information + private widgetTypesService: WidgetTypesService, + // In general, the ProviderRegistryService is used for making entities available for injection into dynamically loaded components. + private providerRegistry: ProviderRegistryService, + private changeDetectorRef: ChangeDetectorRef + ) {} + + public ngOnInit(): void { + // Grabbing the widget's default template which will be needed as a parameter for setNode + const widgetTemplate = this.widgetTypesService.getWidgetType( + "proportional", + 1 + ); + + // Registering our data sources as dropdown options in the widget editor/configurator + // Note: This could also be done in the parent module's constructor so that + // multiple dashboards could have access to the same widget template modification. + this.widgetTypesService.setNode( + // This is the template we grabbed above with getWidgetType + widgetTemplate, + // We are setting the editor/configurator part of the widget template + "configurator", + // This indicates which node you are changing and we want to change + // the data source providers available for selection in the editor. + WellKnownPathKey.DataSourceProviders, + // We are setting the data sources available for selection in the editor + [ReviewCountsByCityMockDataSource.providerId] + ); + + // Registering the data source for injection into the Proportional widget. + this.providerRegistry.setProviders({ + [ReviewCountsByCityMockDataSource.providerId]: { + provide: DATA_SOURCE, + useClass: ReviewCountsByCityMockDataSource, + deps: [], + }, + }); + + this.initializeDashboard(); + } + + /** Used for restoring widgets state */ + public reInitializeDashboard(): void { + // destroys the components and their providers so the dashboard can re init data + this.dashboard = undefined; + this.changeDetectorRef.detectChanges(); + + this.initializeDashboard(); + } + + public initializeDashboard(): void { + // We're using a static configuration object for this example, but this is where + // the widget's configuration could potentially be populated from a database + const widgetsWithStructure = widgetConfigs.map((w) => + this.widgetTypesService.mergeWithWidgetType(w) + ); + const widgetsIndex = keyBy(widgetsWithStructure, (w: IWidget) => w.id); + + // Setting the widget dimensions and position (this is for gridster) + const positions: Record = { + [widgetConfigs[0].id]: { + cols: 6, + rows: 6, + y: 0, + x: 0, + }, + [widgetConfigs[1].id]: { + cols: 6, + rows: 6, + y: 0, + x: 6, + }, + }; + + // Finally, assigning the variables we created above to the dashboard + this.dashboard = { + positions, + widgets: widgetsIndex, + }; + } +} + +const widgetConfigs: IWidget[] = [ + { + id: "widget1", + type: "proportional", + pizzagna: { + [PizzagnaLayer.Configuration]: { + [DEFAULT_PIZZAGNA_ROOT]: { + providers: { + // Configuring the UrlInteractionHandler to handle interactions + [WellKnownProviders.InteractionHandler]: { + providerId: NOVA_URL_INTERACTION_HANDLER, + properties: { + // the 'url' property tells the handler what link to use when interaction occurs on the series + // if the series does not have a link we are passing one to the handler + url: "\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\${data.link || 'https://en.wikipedia.org/wiki/'+data.id}", + // by default the link is opened in the current window, set 'newWindow' to true to open in a new tab instead + // newWindow: true, + }, + }, + }, + }, + header: { + properties: { + title: "Proportional Widget", + subtitle: "With interaction handler", + }, + }, + chart: { + providers: { + [WellKnownProviders.DataSource]: { + // Setting the data source providerId for the chart + providerId: + ReviewCountsByCityMockDataSource.providerId, + } as IProviderConfiguration, + }, + properties: { + configuration: { + // Setting the interactive to true + interactive: true, + chartOptions: { + type: ProportionalWidgetChartTypes.VerticalBarChart, + legendPlacement: LegendPlacement.Bottom, + } as IProportionalWidgetChartOptions, + prioritizeWidgetColors: false, + } as IProportionalWidgetConfig, + }, + }, + }, + }, + }, + { + id: "widget2", + type: "proportional", + pizzagna: { + [PizzagnaLayer.Configuration]: { + header: { + properties: { + title: "Proportional Widget", + subtitle: "Without interaction handler", + }, + }, + chart: { + providers: { + [WellKnownProviders.DataSource]: { + // Setting the data source providerId for the chart + providerId: + ReviewCountsByCityMockDataSource.providerId, + } as IProviderConfiguration, + }, + properties: { + configuration: { + // interactive set to false so series without links are not styled like a link + interactive: false, + chartOptions: { + type: ProportionalWidgetChartTypes.HorizontalBarChart, + legendPlacement: LegendPlacement.Bottom, + } as IProportionalWidgetChartOptions, + prioritizeWidgetColors: false, + } as IProportionalWidgetConfig, + }, + }, + }, + }, + }, +]; + +export function getMockBeerReviewCountsByCity(): IMockBeerReview[] { + return [ + { + id: "Brno", + name: "Brno", + data: [Math.round(Math.random() * 100000)], + icon: "status_down", + link: "https://en.wikipedia.org/wiki/Brno", + value: "Brno", + color: "var(--nui-color-chart-one)", + }, + { + id: "kyiv", + name: "Kyiv", + data: [Math.round(Math.random() * 100000)], + icon: "status_critical", + link: "https://en.wikipedia.org/wiki/Kyiv", + value: "Kyiv", + color: "var(--nui-color-chart-two)", + }, + { + id: "austin", + name: "Austin", + data: [Math.round(Math.random() * 100000)], + icon: "status_warning", + value: "Austin", + color: "var(--nui-color-chart-three)", + }, + { + id: "lisbon", + name: "Lisbon", + data: [Math.round(Math.random() * 100000)], + icon: "status_unknown", + link: "https://en.wikipedia.org/wiki/Lisbon", + value: "Lisbon", + color: "var(--nui-color-chart-four)", + }, + { + id: "sydney", + name: "Sydney", + data: [Math.round(Math.random() * 100000)], + icon: "status_up", + value: "Sydney", + color: "var(--nui-color-chart-five)", + }, + { + id: "nur-sultan", + name: "Nur-Sultan", + data: [Math.round(Math.random() * 100000)], + icon: "status_unmanaged", + value: "Nur-Sultan", + color: "var(--nui-color-chart-six)", + }, + ].sort((a, b) => a.data[0] - b.data[0]); +} +\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\`, + "widget-types/risk-score/risk-score-docs.component.ts": \\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\`// © 2023 SolarWinds Worldwide, LLC. All rights reserved. +// +// Permission is hereby granted, free of charge, to any person obtaining a copy +// of this software and associated documentation files (the "Software"), to +// deal in the Software without restriction, including without limitation the +// rights to use, copy, modify, merge, publish, distribute, sublicense, and/or +// sell copies of the Software, and to permit persons to whom the Software is +// furnished to do so, subject to the following conditions: +// +// The above copyright notice and this permission notice shall be included in +// all copies or substantial portions of the Software. +// +// THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR +// IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, +// FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE +// AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER +// LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, +// OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN +// THE SOFTWARE. + +import { Component, OnInit } from "@angular/core"; + +import { mapContentFile } from "../../../../demo-files-factory"; + +@Component({ + selector: "nui-risk-score-docs", + templateUrl: "./risk-score-docs.component.html", + standalone: false, +}) +export class RiskScoreDocsComponent implements OnInit { + public riskScoreWidgetFileText = ""; + public riskScoreConfiguratorFileText = ""; + + public async ngOnInit(): Promise { + this.riskScoreWidgetFileText = await import( + "./../../../../../../src/lib/widget-types/risk-score/risk-score-widget" + ).then(mapContentFile); + this.riskScoreConfiguratorFileText = await import( + "./../../../../../../src/lib/widget-types/risk-score/risk-score-configurator" + ).then(mapContentFile); + } +} +\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\`, + "widget-types/risk-score/risk-score-docs.module.ts": \\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\`// © 2023 SolarWinds Worldwide, LLC. All rights reserved. +// +// Permission is hereby granted, free of charge, to any person obtaining a copy +// of this software and associated documentation files (the "Software"), to +// deal in the Software without restriction, including without limitation the +// rights to use, copy, modify, merge, publish, distribute, sublicense, and/or +// sell copies of the Software, and to permit persons to whom the Software is +// furnished to do so, subject to the following conditions: +// +// The above copyright notice and this permission notice shall be included in +// all copies or substantial portions of the Software. +// +// THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR +// IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, +// FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE +// AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER +// LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, +// OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN +// THE SOFTWARE. + +import { NgModule } from "@angular/core"; +import { RouterModule, Routes } from "@angular/router"; + +import { + DEMO_PATH_TOKEN, + NuiButtonModule, + NuiDocsModule, + NuiMessageModule, + NuiSwitchModule, +} from "@nova-ui/bits"; +import { NuiDashboardsModule } from "@nova-ui/dashboards"; + +import { RiskScoreDocsComponent } from "./risk-score-docs.component"; +import { RiskScoreWidgetExampleComponent } from "./risk-score-widget-example/risk-score-widget-example.component"; +import { getDemoFiles } from "../../../../demo-files-factory"; + +const routes: Routes = [ + { + path: "", + component: RiskScoreDocsComponent, + data: { + srlc: { + hideIndicator: true, + }, + showThemeSwitcher: true, + }, + }, + { + path: "example", + component: RiskScoreWidgetExampleComponent, + data: { + srlc: { + hideIndicator: true, + }, + }, + }, +]; + +@NgModule({ + imports: [ + RouterModule.forChild(routes), + NuiButtonModule, + NuiDocsModule, + NuiMessageModule, + NuiDashboardsModule, + NuiSwitchModule, + ], + declarations: [RiskScoreDocsComponent, RiskScoreWidgetExampleComponent], + providers: [ + { + provide: DEMO_PATH_TOKEN, + useValue: getDemoFiles("risk-score"), + }, + ], +}) +export default class RiskScoreDocsModule {} +\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\`, + "widget-types/risk-score/risk-score-widget-example/risk-score-widget-example.component.ts": \\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\`// © 2023 SolarWinds Worldwide, LLC. All rights reserved. +// +// Permission is hereby granted, free of charge, to any person obtaining a copy +// of this software and associated documentation files (the "Software"), to +// deal in the Software without restriction, including without limitation the +// rights to use, copy, modify, merge, publish, distribute, sublicense, and/or +// sell copies of the Software, and to permit persons to whom the Software is +// furnished to do so, subject to the following conditions: +// +// The above copyright notice and this permission notice shall be included in +// all copies or substantial portions of the Software. +// +// THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR +// IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, +// FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE +// AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER +// LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, +// OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN +// THE SOFTWARE. + +import { HttpClient, HttpErrorResponse } from "@angular/common/http"; +import { Component, Injectable, OnDestroy, OnInit } from "@angular/core"; +import { GridsterConfig, GridsterItem } from "angular-gridster2"; +import { BehaviorSubject } from "rxjs"; +import { finalize } from "rxjs/operators"; + +import { DataSourceService, IFilteringOutputs } from "@nova-ui/bits"; +import { + DATA_SOURCE, + DEFAULT_PIZZAGNA_ROOT, + IDashboard, + IRiskScoreData, + IProviderConfiguration, + IRefresherProperties, + IWidget, + IWidgets, + RiskScoreTileComponent, + NOVA_KPI_DATASOURCE_ADAPTER, + PizzagnaLayer, + ProviderRegistryService, + WellKnownPathKey, + WellKnownProviders, + WidgetTypesService, +} from "@nova-ui/dashboards"; + +/** + * A simple KPI data source to retrieve the average rating of Harry Potter and the Sorcerer's Stone (book) via googleapis + */ +@Injectable() +export class AverageRatingRiskScoreDataSource + extends DataSourceService + implements OnDestroy +{ + // This is the ID we'll use to identify the provider + public static providerId = "AverageRatingRiskScoreDataSource"; + + // Use this subject to communicate the data source's busy state + public busy = new BehaviorSubject(false); + + constructor(private http: HttpClient) { + super(); + } + + // In this example, getFilteredData is invoked every 10 minutes (Take a look at the refresher + // provider definition in the widget configuration below to see how the interval is set) + public async getFilteredData(): Promise { + this.busy.next(true); + return new Promise((resolve) => { + // *** Make a resource request to an external API (if needed) + this.http + .get("https://www.googleapis.com/books/v1/volumes/5MQFrgEACAAJ") + .pipe(finalize(() => this.busy.next(false))) + .subscribe({ + next: (data: any) => { + resolve({ + result: { + value: data.volumeInfo.averageRating, + }, + }); + }, + error: (error: HttpErrorResponse) => { + resolve({ + result: null, + error: { + type: error.status, + }, + }); + }, + }); + }); + } + + public ngOnDestroy(): void { + this.outputsSubject.complete(); + } +} + +/** + * A component that instantiates the dashboard + */ +@Component({ + selector: "risk-score-widget-example", + templateUrl: "./risk-score-widget-example.component.html", + styleUrls: ["./risk-score-widget-example.component.less"], + standalone: false, +}) +export class RiskScoreWidgetExampleComponent implements OnInit { + // This variable will hold all the data needed to define the layout and behavior of the widgets. + // Pass this to the dashboard component's dashboard input in the template. + public dashboard: IDashboard; + + // Angular gridster requires a configuration object even if it's empty. + // Pass this to the dashboard component's gridsterConfig input in the template. + public gridsterConfig: GridsterConfig = {}; + + // Boolean passed as an input to the dashboard. When true, widgets can be moved, resized, removed, or edited + public editMode: boolean = false; + + constructor( + // WidgetTypesService provides the widget's necessary structure information + private widgetTypesService: WidgetTypesService, + + // In general, the ProviderRegistryService is used for making entities available for injection into dynamically loaded components. + private providerRegistry: ProviderRegistryService + ) {} + + public ngOnInit(): void { + // Grabbing the widget's default template which will be needed as a parameter for setNode + const widgetTemplate = this.widgetTypesService.getWidgetType( + "risk-score", + 1 + ); + // Registering our data sources as dropdown options in the widget editor/configurator + // Note: This could also be done in the parent module's constructor so that + // multiple dashboards could have access to the same widget template modification. + this.widgetTypesService.setNode( + // This is the template we grabbed above with getWidgetType + widgetTemplate, + // We are setting the editor/configurator part of the widget template + "configurator", + // This indicates which node you are changing and we want to change + // the data source providers available for selection in the editor. + WellKnownPathKey.DataSourceProviders, + // We are setting the data sources available for selection in the editor + [AverageRatingRiskScoreDataSource.providerId] + ); + + // Registering the data source for injection into the KPI tile. + // Note: Each tile of a KPI widget is assigned its own instance of the data source + this.providerRegistry.setProviders({ + [AverageRatingRiskScoreDataSource.providerId]: { + provide: DATA_SOURCE, + useClass: AverageRatingRiskScoreDataSource, + // Any dependencies that need to be injected into the provider must be listed here + deps: [HttpClient], + }, + }); + + this.initializeDashboard(); + } + + public initializeDashboard(): void { + // We're using a static configuration object for this example, but this is where + // the widget's configuration could potentially be populated from a database + const riskScoreWidget = widgetConfig; + const widgetIndex: IWidgets = { + // Complete the KPI widget with information coming from its type definition + [riskScoreWidget.id]: + this.widgetTypesService.mergeWithWidgetType(riskScoreWidget), + }; + + // Setting the widget dimensions and position (this is for gridster) + const positions: Record = { + [riskScoreWidget.id]: { + cols: 4, + rows: 6, + y: 0, + x: 0, + }, + }; + + // Finally, assigning the variables we created above to the dashboard + this.dashboard = { + positions, + widgets: widgetIndex, + }; + } +} + +const widgetConfig: IWidget = { + id: "riskScoreWidgetId", + type: "risk-score", + pizzagna: { + [PizzagnaLayer.Configuration]: { + [DEFAULT_PIZZAGNA_ROOT]: { + providers: { + [WellKnownProviders.Refresher]: { + properties: { + // Configuring the refresher interval so that our data source is invoked every ten minutes + interval: 60 * 10, + enabled: true, + } as IRefresherProperties, + } as Partial, + }, + }, + header: { + properties: { + title: "Harry Potter and the Sorcerer's Stone", + subtitle: "By J. K. Rowling", + }, + }, + tiles: { + properties: { + nodes: ["riskScore1"], + }, + }, + riskScore1: { + id: "riskScore1", + componentType: RiskScoreTileComponent.lateLoadKey, + properties: { + widgetData: { + minValue: 0, + maxValue: 5, + useStaticLabel: false, + staticLabel: undefined, + label: \\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\`Average Rating\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\`, + description: \\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\`Harry Potter and the Sorcerer's Stone By J. K. Rowling Average Rating Risk Score\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\`, + }, + }, + providers: { + [WellKnownProviders.DataSource]: { + // Setting the data source providerId for the tile with id "kpi1" + providerId: AverageRatingRiskScoreDataSource.providerId, + } as IProviderConfiguration, + [WellKnownProviders.Adapter]: { + providerId: NOVA_KPI_DATASOURCE_ADAPTER, + properties: { + componentId: "riskScore1", + propertyPath: "widgetData", + }, + } as IProviderConfiguration, + }, + }, + }, + }, +}; +\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\`, + "widget-types/table/table-docs.component.ts": \\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\`// © 2022 SolarWinds Worldwide, LLC. All rights reserved. +// +// Permission is hereby granted, free of charge, to any person obtaining a copy +// of this software and associated documentation files (the "Software"), to +// deal in the Software without restriction, including without limitation the +// rights to use, copy, modify, merge, publish, distribute, sublicense, and/or +// sell copies of the Software, and to permit persons to whom the Software is +// furnished to do so, subject to the following conditions: +// +// The above copyright notice and this permission notice shall be included in +// all copies or substantial portions of the Software. +// +// THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR +// IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, +// FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE +// AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER +// LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, +// OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN +// THE SOFTWARE. + +import { Component, OnInit } from "@angular/core"; + +import { mapContentFile } from "../../../../demo-files-factory"; + +@Component({ + selector: "nui-table-docs", + templateUrl: "./table-docs.component.html", + standalone: false, +}) +export class TableDocsComponent implements OnInit { + public widgetFileText = ""; + public configuratorFileText = ""; + + public async ngOnInit(): Promise { + this.widgetFileText = await import( + "./../../../../../../src/lib/widget-types/table/table-widget" + ).then(mapContentFile); + this.configuratorFileText = await import( + "./../../../../../../src/lib/widget-types/table/table-configurator" + ).then(mapContentFile); + } +} +\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\`, + "widget-types/table/table-docs.module.ts": \\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\`// © 2022 SolarWinds Worldwide, LLC. All rights reserved. +// +// Permission is hereby granted, free of charge, to any person obtaining a copy +// of this software and associated documentation files (the "Software"), to +// deal in the Software without restriction, including without limitation the +// rights to use, copy, modify, merge, publish, distribute, sublicense, and/or +// sell copies of the Software, and to permit persons to whom the Software is +// furnished to do so, subject to the following conditions: +// +// The above copyright notice and this permission notice shall be included in +// all copies or substantial portions of the Software. +// +// THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR +// IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, +// FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE +// AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER +// LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, +// OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN +// THE SOFTWARE. + +import { NgModule } from "@angular/core"; +import { RouterModule, Routes } from "@angular/router"; + +import { DEMO_PATH_TOKEN } from "@nova-ui/bits"; +import { + NuiButtonModule, + NuiDocsModule, + NuiMessageModule, + NuiSwitchModule, +} from "@nova-ui/bits"; +import { + NuiDashboardsModule, + TableFormatterRegistryService, +} from "@nova-ui/dashboards"; + +import { TableDocsComponent } from "./table-docs.component"; +import { TablePaginatorDocsComponent } from "./table-paginator-docs.component"; +import { TableSelectableDocsComponent } from "./table-selectable-docs.component"; +import { TableWidgetExampleComponent } from "./table-widget/table-widget-example.component"; +import { TableWidgetInteractiveExampleComponent } from "./table-widget-interactive/table-widget-interactive-example.component"; +import { TableWidgetPaginatorExampleComponent } from "./table-widget-paginator/table-widget-paginator-example.component"; +import { TableWidgetSearchExampleComponent } from "./table-widget-search/table-widget-search-example.component"; +import { TableSearchDocsComponent } from "./table-widget-search-docs.component"; +import { TableWidgetSelectableMultiExampleComponent } from "./table-widget-selectable/table-widget-selectable-multi/table-widget-selectable-multi.example.component"; +import { TableWidgetSelectableRadioExampleComponent } from "./table-widget-selectable/table-widget-selectable-radio/table-widget-selectable-radio.example.component"; +import { TableWidgetSelectableSingleExampleComponent } from "./table-widget-selectable/table-widget-selectable-single/table-widget-selectable-single.example.component"; +import { TableWidgetSelectableExampleComponent } from "./table-widget-selectable/table-widget-selectable.example.component"; +import { DEFAULT_TABLE_FORMATTERS } from "../../../../../../src/lib/widget-types/table/default-table-formatters"; +import { getDemoFiles } from "../../../../demo-files-factory"; + +const routes: Routes = [ + { + path: "", + component: TableDocsComponent, + data: { + srlc: { + hideIndicator: true, + }, + showThemeSwitcher: true, + }, + }, + { + path: "example", + component: TableWidgetExampleComponent, + data: { + srlc: { + hideIndicator: true, + }, + }, + }, + { + path: "table-search", + component: TableSearchDocsComponent, + data: { + srlc: { + hideIndicator: true, + }, + showThemeSwitcher: true, + }, + }, + { + path: "table-paginator", + component: TablePaginatorDocsComponent, + data: { + srlc: { + hideIndicator: true, + }, + showThemeSwitcher: true, + }, + }, + { + path: "table-select", + component: TableSelectableDocsComponent, + data: { + srlc: { + hideIndicator: true, + }, + showThemeSwitcher: true, + }, + }, +]; + +@NgModule({ + imports: [ + RouterModule.forChild(routes), + NuiButtonModule, + NuiDocsModule, + NuiMessageModule, + NuiSwitchModule, + NuiDashboardsModule, + ], + declarations: [ + TableDocsComponent, + TableSearchDocsComponent, + TablePaginatorDocsComponent, + TableWidgetPaginatorExampleComponent, + TableSelectableDocsComponent, + TableWidgetInteractiveExampleComponent, + TableWidgetExampleComponent, + TableWidgetSearchExampleComponent, + TableWidgetSelectableExampleComponent, + TableWidgetSelectableMultiExampleComponent, + TableWidgetSelectableSingleExampleComponent, + TableWidgetSelectableRadioExampleComponent, + ], + providers: [ + { + provide: DEMO_PATH_TOKEN, + useValue: getDemoFiles("table"), + }, + ], +}) +export default class TableDocsModule { + constructor(tableFormattersRegistryService: TableFormatterRegistryService) { + tableFormattersRegistryService.addItems(DEFAULT_TABLE_FORMATTERS); + } +} +\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\`, + "widget-types/table/table-paginator-docs.component.ts": \\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\`// © 2022 SolarWinds Worldwide, LLC. All rights reserved. +// +// Permission is hereby granted, free of charge, to any person obtaining a copy +// of this software and associated documentation files (the "Software"), to +// deal in the Software without restriction, including without limitation the +// rights to use, copy, modify, merge, publish, distribute, sublicense, and/or +// sell copies of the Software, and to permit persons to whom the Software is +// furnished to do so, subject to the following conditions: +// +// The above copyright notice and this permission notice shall be included in +// all copies or substantial portions of the Software. +// +// THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR +// IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, +// FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE +// AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER +// LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, +// OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN +// THE SOFTWARE. + +import { Component } from "@angular/core"; + +@Component({ + selector: "nui-table-paginator-docs", + templateUrl: "./table-paginator-docs.component.html", + standalone: false, +}) +export class TablePaginatorDocsComponent { + public tableConfigurationText = \\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\` + "table": { + ... + properties: { + configuration: { + // define paginator configuration here + scrollType: ScrollType.paginator, + paginatorConfiguration: { + pageSize: 10, // Value have to be one of pageSizeSet values + pageSizeSet: [10, 20, 30], + }, + // If not specified, default is set to + // pageSize: 10, + // pageSizeSet: [10, 20, 50], + hasVirtualScroll: false, // Has to be speciefied because of backward compatibility + } as ITableWidgetConfig, + }, + }, + \\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\`; +} +\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\`, + "widget-types/table/table-selectable-docs.component.ts": \\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\`// © 2022 SolarWinds Worldwide, LLC. All rights reserved. +// +// Permission is hereby granted, free of charge, to any person obtaining a copy +// of this software and associated documentation files (the "Software"), to +// deal in the Software without restriction, including without limitation the +// rights to use, copy, modify, merge, publish, distribute, sublicense, and/or +// sell copies of the Software, and to permit persons to whom the Software is +// furnished to do so, subject to the following conditions: +// +// The above copyright notice and this permission notice shall be included in +// all copies or substantial portions of the Software. +// +// THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR +// IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, +// FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE +// AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER +// LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, +// OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN +// THE SOFTWARE. + +import { Component } from "@angular/core"; + +@Component({ + selector: "nui-table-selectable-docs", + templateUrl: "./table-selectable-docs.component.html", + standalone: false, +}) +export class TableSelectableDocsComponent { + public tableConfigurationText = \\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\` + "table": { + ... + properties: { + // enabling selection here + selectionConfiguration: { + // whether the selection is enabled or disabled + enabled: true, + // can be Multi | Radio | Single + selectionMode: TableSelectionMode.Multi, + // property that uniquely identifies row in a table + trackByProperty: "id", + // whether clicking on row should select it + clickableRow: true, + }, + }, + }, + \\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\`; + + public eventSubscriptionText = \\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\` +... +constructor(Inject(PIZZAGNA_EVENT_BUS) eventBus: EventBus) { + eventBus + .getStream(SELECTION) + // don't forget to unsubscribe! + .subscribe((selection: ISelection) => ...) +} +... + \\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\`; +} +\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\`, + "widget-types/table/table-widget/table-widget-example.component.ts": \\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\`// © 2022 SolarWinds Worldwide, LLC. All rights reserved. +// +// Permission is hereby granted, free of charge, to any person obtaining a copy +// of this software and associated documentation files (the "Software"), to +// deal in the Software without restriction, including without limitation the +// rights to use, copy, modify, merge, publish, distribute, sublicense, and/or +// sell copies of the Software, and to permit persons to whom the Software is +// furnished to do so, subject to the following conditions: +// +// The above copyright notice and this permission notice shall be included in +// all copies or substantial portions of the Software. +// +// THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR +// IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, +// FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE +// AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER +// LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, +// OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN +// THE SOFTWARE. + +import { ChangeDetectorRef, Component, OnInit } from "@angular/core"; +import { GridsterConfig, GridsterItem } from "angular-gridster2"; +import orderBy from "lodash/orderBy"; +import { BehaviorSubject, firstValueFrom, from } from "rxjs"; +import { map, tap } from "rxjs/operators"; + +import { + DataSourceService, + IDataField, + INovaFilteringOutputs, + INovaFilters, + nameof, +} from "@nova-ui/bits"; +import { + DATA_SOURCE, + IDashboard, + ITableWidgetColumnConfig, + IWidget, + IWidgets, + ProviderRegistryService, + WellKnownPathKey, + WellKnownProviders, + WidgetTypesService, +} from "@nova-ui/dashboards"; + +export const BREW_API_URL = "https://api.punkapi.com/v2/beers"; + +export interface IBrewInfo { + id: string; + name: string; + tagline: string; + first_brewed: string; + description: string; + brewers_tips: string; +} + +export interface IBrewDatasourceResponse { + brewInfo: IBrewInfo[]; + total: number; +} + +export class BeerDataSource extends DataSourceService { + public static providerId = "BeerDataSource"; + + private cache: IBrewInfo[] = []; + + public busy = new BehaviorSubject(false); + + public dataFields: Array = [ + { + id: nameof("id"), + label: "No", + dataType: "number", + sortable: true, + }, + // To indicate that a column should not be sortable, set the optional IDataField 'sortable' property to false + { + id: nameof("name"), + label: "Name", + dataType: "string", + sortable: true, + }, + { + id: nameof("tagline"), + label: "Tagline", + dataType: "string", + sortable: true, + }, + { + id: nameof("first_brewed"), + label: "First Brewed", + dataType: "string", + sortable: true, + }, + { + id: nameof("description"), + label: "Description", + dataType: "string", + sortable: false, + }, + { + id: nameof("brewers_tips"), + label: "Brewer's Tips", + dataType: "string", + sortable: false, + }, + ]; + + public async getFilteredData( + filters: INovaFilters + ): Promise { + const start = filters.virtualScroll?.value?.start ?? 0; + const end = filters.virtualScroll?.value?.end ?? 0; + + // Resetting cache on first page request + if (start === 0) { + this.cache = []; + } + + // extract sorter settings to send to the backend + // filters.sorterValue.sortBy; filters.sorterValue.direction + return firstValueFrom( + from(this.fetch(start, end)).pipe( + tap((response: IBrewDatasourceResponse | undefined) => { + if (!response) { + return; + } + this.cache = this.sortData( + this.cache.concat(response.brewInfo), + filters + ); + this.dataSubject.next(this.cache); + }), + map(() => ({ + repeat: { itemsSource: this.cache }, + dataFields: this.dataFields, + })) + ) + ); + } + + public async fetch( + start: number, + end: number + ): Promise { + const delta: number = end - start; + const currentPage: number = end / delta || 0; + const response: object | Array = await ( + await fetch( + \\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\`\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\${BREW_API_URL}/?page=\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\${currentPage}&per_page=\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\${delta}\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\` + ) + ).json(); + + // Note: In case request fails we should not proceed with mapping + if (!Array.isArray(response)) { + return undefined; + } + + return { + brewInfo: response.map((result: IBrewInfo) => ({ + id: result.id, + name: result.name, + tagline: result.tagline, + first_brewed: result.first_brewed, + description: result.description, + brewers_tips: result.brewers_tips, + })), + total: response.length, + }; + } + + private sortData(data: IBrewInfo[], filters: INovaFilters): IBrewInfo[] { + return orderBy( + data, + filters.sorter?.value?.sortBy, + filters.sorter?.value?.direction as "desc" | "asc" + ); + } +} + +/** + * A component that instantiates the dashboard + */ +@Component({ + selector: "table-widget-example", + templateUrl: "./table-widget-example.component.html", + styleUrls: ["./table-widget-example.component.less"], + standalone: false, +}) +export class TableWidgetExampleComponent implements OnInit { + // This variable will hold all the data needed to define the layout and behavior of the widgets. + // Pass this to the dashboard component's dashboard input in the template. + public dashboard: IDashboard | undefined; + + // Angular gridster requires a configuration object even if it's empty. + // Pass this to the dashboard component's gridsterConfig input in the template. + public gridsterConfig: GridsterConfig = {}; + + // Boolean passed as an input to the dashboard. When true, widgets can be moved, resized, removed, or edited + public editMode: boolean = false; + + constructor( + // WidgetTypesService provides the widget's necessary structure information + private widgetTypesService: WidgetTypesService, + // In general, the ProviderRegistryService is used for making entities available for injection into dynamically loaded components. + private providerRegistry: ProviderRegistryService, + private changeDetectorRef: ChangeDetectorRef + ) {} + + public ngOnInit(): void { + // Grabbing the widget's default template which will be needed as a parameter for setNode + const widgetTemplate = this.widgetTypesService.getWidgetType( + "table", + 1 + ); + + // Registering our data sources as dropdown options in the widget editor/configurator + // Note: This could also be done in the parent module's constructor so that + // multiple dashboards could have access to the same widget template modification. + this.widgetTypesService.setNode( + // This is the template we grabbed above with getWidgetType + widgetTemplate, + // We are setting the editor/configurator part of the widget template + "configurator", + // This indicates which node you are changing and we want to change + // the data source providers available for selection in the editor. + WellKnownPathKey.DataSourceProviders, + // We are setting the data sources available for selection in the editor + [BeerDataSource.providerId] + ); + + // Registering the data source for injection into the widget. + this.providerRegistry.setProviders({ + [BeerDataSource.providerId]: { + provide: DATA_SOURCE, + useClass: BeerDataSource, + // Any dependencies that need to be injected into the provider must be listed here + deps: [], + }, + }); + + this.initializeDashboard(); + } + + /** Used for restoring widgets state */ + public reInitializeDashboard(): void { + // destroys the components and their providers so the dashboard can re init data + this.dashboard = undefined; + this.changeDetectorRef.detectChanges(); + + this.initializeDashboard(); + } + + public initializeDashboard(): void { + // We're using a static configuration object for this example, but this is where + // the widget's configuration could potentially be populated from a database + const tableWidget = widgetConfig; + const widgetIndex: IWidgets = { + // Enhance the widget with information coming from it's type definition + [tableWidget.id]: + this.widgetTypesService.mergeWithWidgetType(tableWidget), + }; + + // Setting the widget dimensions and position (this is for gridster) + const positions: Record = { + [tableWidget.id]: { + cols: 12, + rows: 6, + y: 0, + x: 0, + }, + }; + + // Finally, assigning the variables we created above to the dashboard + this.dashboard = { + positions, + widgets: widgetIndex, + }; + } +} + +const TABLE_COLUMNS: ITableWidgetColumnConfig[] = [ + { + id: "column1", + label: $localize\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\`Beer Name\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\`, + isActive: true, + width: 185, + formatter: { + componentType: "RawFormatterComponent", + properties: { + dataFieldIds: { + value: "name", + }, + }, + }, + }, + { + id: "column2", + label: $localize\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\`Tagline\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\`, + isActive: true, + width: 250, + formatter: { + componentType: "RawFormatterComponent", + properties: { + dataFieldIds: { + value: "tagline", + }, + }, + }, + }, + { + id: "column3", + label: $localize\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\`First Brewed\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\`, + isActive: true, + width: 100, + formatter: { + componentType: "RawFormatterComponent", + properties: { + dataFieldIds: { + value: "first_brewed", + }, + }, + }, + }, + { + id: "column4", + label: $localize\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\`Description\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\`, + isActive: true, + formatter: { + componentType: "RawFormatterComponent", + properties: { + dataFieldIds: { + value: "description", + }, + }, + }, + }, +]; + +export const widgetConfig: IWidget = { + id: "tableWidgetId", + type: "table", + pizzagna: { + configuration: { + header: { + properties: { + title: "Stupendous Suds", + subtitle: "Try These Brilliant Brews", + }, + }, + table: { + providers: { + [WellKnownProviders.DataSource]: { + providerId: BeerDataSource.providerId, + }, + }, + properties: { + configuration: { + columns: TABLE_COLUMNS, + sortable: true, + sorterConfiguration: { + descendantSorting: false, + sortBy: "", + }, + hasVirtualScroll: true, + }, + }, + }, + }, + }, +}; +\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\`, + "widget-types/table/table-widget-interactive/table-widget-interactive-example.component.ts": \\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\`// © 2022 SolarWinds Worldwide, LLC. All rights reserved. +// +// Permission is hereby granted, free of charge, to any person obtaining a copy +// of this software and associated documentation files (the "Software"), to +// deal in the Software without restriction, including without limitation the +// rights to use, copy, modify, merge, publish, distribute, sublicense, and/or +// sell copies of the Software, and to permit persons to whom the Software is +// furnished to do so, subject to the following conditions: +// +// The above copyright notice and this permission notice shall be included in +// all copies or substantial portions of the Software. +// +// THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR +// IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, +// FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE +// AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER +// LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, +// OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN +// THE SOFTWARE. + +import { ChangeDetectorRef, Component, OnInit } from "@angular/core"; +import { GridsterConfig, GridsterItem } from "angular-gridster2"; +import orderBy from "lodash/orderBy"; +import { BehaviorSubject, firstValueFrom, from } from "rxjs"; +import { map, tap } from "rxjs/operators"; + +import { + DataSourceService, + IDataField, + INovaFilteringOutputs, + INovaFilters, + nameof, +} from "@nova-ui/bits"; +import { + DATA_SOURCE, + DEFAULT_PIZZAGNA_ROOT, + IDashboard, + ITableWidgetColumnConfig, + IWidget, + IWidgets, + NOVA_URL_INTERACTION_HANDLER, + ProviderRegistryService, + WellKnownPathKey, + WellKnownProviders, + WidgetTypesService, +} from "@nova-ui/dashboards"; + +export const BREW_API_URL = "https://api.punkapi.com/v2/beers"; + +export interface IBrewInfo { + id: string; + name: string; + tagline: string; + first_brewed: string; + description: string; + brewers_tips: string; +} + +export interface IBrewDatasourceResponse { + brewInfo: IBrewInfo[]; + total: number; +} + +export class MockBeerDataSource extends DataSourceService { + public static providerId = "MockBeerDataSource"; + + private cache: IBrewInfo[] = []; + + public busy = new BehaviorSubject(false); + + public dataFields: Array = [ + { + id: nameof("id"), + label: "No", + dataType: "number", + sortable: true, + }, + // To indicate that a column should not be sortable, set the optional IDataField 'sortable' property to false + { + id: nameof("name"), + label: "Name", + dataType: "string", + sortable: true, + }, + { + id: nameof("tagline"), + label: "Tagline", + dataType: "string", + sortable: true, + }, + { + id: nameof("first_brewed"), + label: "First Brewed", + dataType: "string", + sortable: true, + }, + { + id: nameof("description"), + label: "Description", + dataType: "string", + sortable: false, + }, + { + id: nameof("brewers_tips"), + label: "Brewer's Tips", + dataType: "string", + sortable: false, + }, + ]; + + public async getFilteredData( + filters: INovaFilters + ): Promise { + const start = filters.virtualScroll?.value?.start ?? 0; + const end = filters.virtualScroll?.value?.end ?? 0; + + // Resetting cache on first page request + if (start === 0) { + this.cache = []; + } + + // extract sorter settings to send to the backend + // filters.sorterValue.sortBy; filters.sorterValue.direction + return firstValueFrom( + from(this.fetch(start, end)).pipe( + tap((response) => { + if (!response) { + return; + } + this.cache = this.sortData( + this.cache.concat(response.brewInfo), + filters + ); + this.dataSubject.next(this.cache); + }), + map(() => ({ + repeat: { itemsSource: this.cache }, + dataFields: this.dataFields, + })) + ) + ); + } + + public async fetch( + start: number, + end: number + ): Promise { + const delta: number = end - start; + const currentPage: number = end / delta || 0; + const response: object | Array = await ( + await fetch( + \\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\`\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\${BREW_API_URL}/?page=\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\${currentPage}&per_page=\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\${delta}\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\` + ) + ).json(); + console.log( + "📘 table-widget-interactive-example.component: 85# -> response:", + response + ); + + // Note: In case request fails we should not proceed with mapping + if (!Array.isArray(response)) { + return undefined; + } + + return { + brewInfo: response.map((result: IBrewInfo) => ({ + id: result.id, + name: result.name, + tagline: result.tagline, + first_brewed: result.first_brewed, + description: result.description, + brewers_tips: result.brewers_tips, + })), + total: response.length, + }; + } + + private sortData(data: IBrewInfo[], filters: INovaFilters): IBrewInfo[] { + return orderBy( + data, + filters.sorter?.value?.sortBy, + filters.sorter?.value?.direction as "desc" | "asc" + ); + } +} + +/** + * A component that instantiates the dashboard + */ +@Component({ + selector: "table-widget-interactive-example", + templateUrl: "./table-widget-interactive-example.component.html", + styleUrls: ["./table-widget-interactive-example.component.less"], + standalone: false, +}) +export class TableWidgetInteractiveExampleComponent implements OnInit { + // This variable will hold all the data needed to define the layout and behavior of the widgets. + // Pass this to the dashboard component's dashboard input in the template. + public dashboard: IDashboard | undefined; + + // Angular gridster requires a configuration object even if it's empty. + // Pass this to the dashboard component's gridsterConfig input in the template. + public gridsterConfig: GridsterConfig = {}; + + // Boolean passed as an input to the dashboard. When true, widgets can be moved, resized, removed, or edited + public editMode: boolean = false; + + constructor( + // WidgetTypesService provides the widget's necessary structure information + private widgetTypesService: WidgetTypesService, + // In general, the ProviderRegistryService is used for making entities available for injection into dynamically loaded components. + private providerRegistry: ProviderRegistryService, + private changeDetectorRef: ChangeDetectorRef + ) {} + + public ngOnInit(): void { + // Grabbing the widget's default template which will be needed as a parameter for setNode + const widgetTemplate = this.widgetTypesService.getWidgetType( + "table", + 1 + ); + + // Registering our data sources as dropdown options in the widget editor/configurator + // Note: This could also be done in the parent module's constructor so that + // multiple dashboards could have access to the same widget template modification. + this.widgetTypesService.setNode( + // This is the template we grabbed above with getWidgetType + widgetTemplate, + // We are setting the editor/configurator part of the widget template + "configurator", + // This indicates which node you are changing and we want to change + // the data source providers available for selection in the editor. + WellKnownPathKey.DataSourceProviders, + // We are setting the data sources available for selection in the editor + [MockBeerDataSource.providerId] + ); + + // Registering the data source for injection into the widget. + this.providerRegistry.setProviders({ + [MockBeerDataSource.providerId]: { + provide: DATA_SOURCE, + useClass: MockBeerDataSource, + // Any dependencies that need to be injected into the provider must be listed here + deps: [], + }, + }); + + this.initializeDashboard(); + } + + /** Used for restoring widgets state */ + public reInitializeDashboard(): void { + // destroys the components and their providers so the dashboard can re init data + this.dashboard = undefined; + this.changeDetectorRef.detectChanges(); + + this.initializeDashboard(); + } + + public initializeDashboard(): void { + // We're using a static configuration object for this example, but this is where + // the widget's configuration could potentially be populated from a database + const tableWidget = widgetConfig; + const widgetIndex: IWidgets = { + // Enhance the widget with information coming from it's type definition + [tableWidget.id]: + this.widgetTypesService.mergeWithWidgetType(tableWidget), + }; + + // Setting the widget dimensions and position (this is for gridster) + const positions: Record = { + [tableWidget.id]: { + cols: 12, + rows: 6, + y: 0, + x: 0, + }, + }; + + // Finally, assigning the variables we created above to the dashboard + this.dashboard = { + positions, + widgets: widgetIndex, + }; + } +} + +const TABLE_COLUMNS: ITableWidgetColumnConfig[] = [ + { + id: "column1", + label: $localize\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\`Beer Name\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\`, + isActive: true, + width: 185, + formatter: { + componentType: "RawFormatterComponent", + properties: { + dataFieldIds: { + value: "name", + }, + }, + }, + }, + { + id: "column2", + label: $localize\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\`Tagline\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\`, + isActive: true, + width: 250, + formatter: { + componentType: "RawFormatterComponent", + properties: { + dataFieldIds: { + value: "tagline", + }, + }, + }, + }, + { + id: "column3", + label: $localize\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\`First Brewed\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\`, + isActive: true, + width: 100, + formatter: { + componentType: "RawFormatterComponent", + properties: { + dataFieldIds: { + value: "first_brewed", + }, + }, + }, + }, + { + id: "column4", + label: $localize\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\`Description\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\`, + isActive: true, + formatter: { + componentType: "RawFormatterComponent", + properties: { + dataFieldIds: { + value: "description", + }, + }, + }, + }, +]; + +export const widgetConfig: IWidget = { + id: "tableWidgetId", + type: "table", + pizzagna: { + configuration: { + [DEFAULT_PIZZAGNA_ROOT]: { + providers: { + [WellKnownProviders.InteractionHandler]: { + // Configuring the UrlInteractionHandler to handle interactions + providerId: NOVA_URL_INTERACTION_HANDLER, + properties: { + // the 'url' property tells the handler what link to use when interaction occurs on the series + url: "\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\${'https://untappd.com/search?q='+data.name}", + // by default the link is opened in the current window, set 'newWindow' to true to open in a new tab instead + newWindow: true, + }, + }, + }, + }, + header: { + properties: { + title: "Stupendous Suds", + subtitle: "Try These Brilliant Brews", + }, + }, + table: { + providers: { + [WellKnownProviders.DataSource]: { + providerId: MockBeerDataSource.providerId, + }, + }, + properties: { + configuration: { + // set interactions to true on the table + interactive: true, + columns: TABLE_COLUMNS, + sortable: true, + sorterConfiguration: { + descendantSorting: false, + sortBy: "", + }, + hasVirtualScroll: true, + }, + }, + }, + }, + }, +}; +\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\`, + "widget-types/table/table-widget-paginator/table-widget-paginator-example.component.ts": \\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\`// © 2022 SolarWinds Worldwide, LLC. All rights reserved. +// +// Permission is hereby granted, free of charge, to any person obtaining a copy +// of this software and associated documentation files (the "Software"), to +// deal in the Software without restriction, including without limitation the +// rights to use, copy, modify, merge, publish, distribute, sublicense, and/or +// sell copies of the Software, and to permit persons to whom the Software is +// furnished to do so, subject to the following conditions: +// +// The above copyright notice and this permission notice shall be included in +// all copies or substantial portions of the Software. +// +// THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR +// IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, +// FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE +// AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER +// LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, +// OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN +// THE SOFTWARE. + +import { HttpClient } from "@angular/common/http"; +import { ChangeDetectorRef, Component, OnInit } from "@angular/core"; +import { GridsterConfig, GridsterItem } from "angular-gridster2"; + +import { LoggerService } from "@nova-ui/bits"; +import { + DATA_SOURCE, + DEFAULT_PIZZAGNA_ROOT, + IDashboard, + IProviderConfiguration, + ITableWidgetConfig, + IWidget, + IWidgets, + NOVA_URL_INTERACTION_HANDLER, + PizzagnaLayer, + ProviderRegistryService, + RawFormatterComponent, + ScrollType, + WellKnownPathKey, + WellKnownProviders, + WidgetTypesService, +} from "@nova-ui/dashboards"; + +import { AcmeTableMockDataSource } from "../../../../prototypes/data/table/acme-table-mock-data-source.service"; + +/** + * A component that instantiates the dashboard + */ +@Component({ + selector: "table-widget-paginator-example", + templateUrl: "./table-widget-paginator-example.component.html", + styleUrls: ["./table-widget-paginator-example.component.less"], + standalone: false, +}) +export class TableWidgetPaginatorExampleComponent implements OnInit { + public dashboard: IDashboard | undefined; + public gridsterConfig: GridsterConfig = {}; + public editMode: boolean = false; + + constructor( + private widgetTypesService: WidgetTypesService, + private providerRegistry: ProviderRegistryService, + private changeDetectorRef: ChangeDetectorRef + ) {} + + public ngOnInit(): void { + const widgetTemplate = this.widgetTypesService.getWidgetType( + "table", + 1 + ); + this.widgetTypesService.setNode( + widgetTemplate, + "configurator", + WellKnownPathKey.DataSourceProviders, + [AcmeTableMockDataSource.providerId] + ); + + this.providerRegistry.setProviders({ + [AcmeTableMockDataSource.providerId]: { + provide: DATA_SOURCE, + useClass: AcmeTableMockDataSource, + deps: [LoggerService, HttpClient], + }, + }); + + this.initializeDashboard(); + } + + /** Used for restoring widgets state */ + public reInitializeDashboard(): void { + // destroys the components and their providers so the dashboard can re init data + this.dashboard = undefined; + this.changeDetectorRef.detectChanges(); + + this.initializeDashboard(); + } + + public initializeDashboard(): void { + const tableWithPaginator = tableWidgetWithPaginator; + const tableWithVirtualScroll = tableWidgetWithVirtualScroll; + + const widgetIndex: IWidgets = { + [tableWithPaginator.id]: + this.widgetTypesService.mergeWithWidgetType(tableWithPaginator), + [tableWithVirtualScroll.id]: + this.widgetTypesService.mergeWithWidgetType( + tableWithVirtualScroll + ), + }; + + const positions: Record = { + [tableWithPaginator.id]: { + cols: 6, + rows: 6, + y: 0, + x: 0, + }, + [tableWithVirtualScroll.id]: { + cols: 6, + rows: 6, + y: 0, + x: 0, + }, + }; + + this.dashboard = { + positions, + widgets: widgetIndex, + }; + } +} + +export const tableWidgetWithPaginator: IWidget = { + id: "widget1", + type: "table", + pizzagna: { + [PizzagnaLayer.Configuration]: { + [DEFAULT_PIZZAGNA_ROOT]: { + providers: { + [WellKnownProviders.InteractionHandler]: { + providerId: NOVA_URL_INTERACTION_HANDLER, + }, + }, + }, + header: { + properties: { + title: "Table Widget with paginator!", + subtitle: "Basic table widget", + collapsible: true, + }, + }, + table: { + providers: { + [WellKnownProviders.DataSource]: { + providerId: AcmeTableMockDataSource.providerId, + } as IProviderConfiguration, + }, + properties: { + configuration: { + interactive: true, + columns: [ + { + id: "column1", + label: "No.", + isActive: true, + formatter: { + componentType: + RawFormatterComponent.lateLoadKey, + properties: { + dataFieldIds: { + value: "position", + }, + }, + }, + }, + { + id: "column2", + label: "Name", + isActive: true, + formatter: { + componentType: + RawFormatterComponent.lateLoadKey, + properties: { + dataFieldIds: { + value: "name", + }, + }, + }, + }, + { + id: "column3", + label: "Status", + isActive: true, + formatter: { + componentType: + RawFormatterComponent.lateLoadKey, + properties: { + dataFieldIds: { + value: "status", + }, + }, + }, + }, + ], + sorterConfiguration: { + descendantSorting: false, + sortBy: "column1", + }, + scrollType: ScrollType.paginator, + paginatorConfiguration: { + pageSize: 5, + pageSizeSet: [5, 10, 20, 30], + }, + hasVirtualScroll: false, + searchConfiguration: { + enabled: true, + }, + } as ITableWidgetConfig, + }, + }, + }, + }, +}; + +export const tableWidgetWithVirtualScroll: IWidget = { + id: "widget2", + type: "table", + pizzagna: { + [PizzagnaLayer.Configuration]: { + [DEFAULT_PIZZAGNA_ROOT]: { + providers: { + [WellKnownProviders.InteractionHandler]: { + providerId: NOVA_URL_INTERACTION_HANDLER, + }, + }, + }, + header: { + properties: { + title: "Table Widget with virtual scroll!", + subtitle: "Basic table widget", + collapsible: true, + }, + }, + table: { + providers: { + [WellKnownProviders.DataSource]: { + providerId: AcmeTableMockDataSource.providerId, + } as IProviderConfiguration, + }, + properties: { + configuration: { + interactive: true, + columns: [ + { + id: "column1", + label: "No.", + isActive: true, + formatter: { + componentType: + RawFormatterComponent.lateLoadKey, + properties: { + dataFieldIds: { + value: "position", + }, + }, + }, + }, + { + id: "column2", + label: "Name", + isActive: true, + formatter: { + componentType: + RawFormatterComponent.lateLoadKey, + properties: { + dataFieldIds: { + value: "name", + }, + }, + }, + }, + { + id: "column3", + label: "Status", + isActive: true, + formatter: { + componentType: + RawFormatterComponent.lateLoadKey, + properties: { + dataFieldIds: { + value: "status", + }, + }, + }, + }, + ], + sorterConfiguration: { + descendantSorting: false, + sortBy: "column1", + }, + hasVirtualScroll: true, + searchConfiguration: { + enabled: true, + }, + } as ITableWidgetConfig, + }, + }, + }, + }, +}; +\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\`, + "widget-types/table/table-widget-search/table-widget-search-example.component.ts": \\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\`// © 2022 SolarWinds Worldwide, LLC. All rights reserved. +// +// Permission is hereby granted, free of charge, to any person obtaining a copy +// of this software and associated documentation files (the "Software"), to +// deal in the Software without restriction, including without limitation the +// rights to use, copy, modify, merge, publish, distribute, sublicense, and/or +// sell copies of the Software, and to permit persons to whom the Software is +// furnished to do so, subject to the following conditions: +// +// The above copyright notice and this permission notice shall be included in +// all copies or substantial portions of the Software. +// +// THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR +// IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, +// FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE +// AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER +// LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, +// OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN +// THE SOFTWARE. + +import { HttpClient } from "@angular/common/http"; +import { + ChangeDetectorRef, + Component, + Injectable, + OnInit, +} from "@angular/core"; +import { GridsterConfig, GridsterItem } from "angular-gridster2"; +import isEqual from "lodash/isEqual"; +import isNil from "lodash/isNil"; +import { BehaviorSubject, firstValueFrom, Observable, of, Subject } from "rxjs"; +import { + catchError, + delay, + finalize, + map, + // eslint-disable-next-line import/no-deprecated + switchMap, + tap, +} from "rxjs/operators"; + +import { + DataSourceFeatures, + DataSourceService, + IDataField, + IDataSource, + IDataSourceFeatures, + IDataSourceFeaturesConfiguration, + IDataSourceOutput, + IFilter, + IFilters, + INovaFilteringOutputs, + INovaFilters, + LoggerService, +} from "@nova-ui/bits"; +import { + DATA_SOURCE, + IDashboard, + ITableWidgetConfig, + IWidget, + IWidgets, + ProviderRegistryService, + WellKnownPathKey, + WellKnownProviders, + WidgetTypesService, +} from "@nova-ui/dashboards"; + +import { GBOOKS_API_URL } from "../../../../prototypes/data/table/constants"; + +interface IGBooksApiResponse { + kind: string; + totalItems: number; + items: IGBooksItemModel[]; + [key: string]: any; +} + +interface IGBooksItemModel { + id: string; + volumeInfo: { + title: string; + subtitle: string; + authors: string[]; + [key: string]: any; + }; + accessInfo: { [key: string]: any }; + saleInfo: { [key: string]: any }; +} + +interface IGBooksData { + books: IGBooksVolume[]; + totalItems: number; +} + +interface IGBooksVolume { + title: string; + authors: string; +} + +type searchableColumnType = "title" | "authors"; + +@Injectable() +export class AcmeTableGBooksDataSource + extends DataSourceService + implements IDataSource +{ + public static providerId = "AcmeTableGBooksDataSource"; + public static mockError = false; + + public searchableColumn: searchableColumnType = "title"; + + public page: number = 1; + public busy = new BehaviorSubject(false); + public features: IDataSourceFeaturesConfiguration; + + private cache = Array.from({ length: 0 }); + private previousFilters: INovaFilters; + // DataSource Features declared + private supportedFeatures: IDataSourceFeatures = { + search: { enabled: true }, + pagination: { enabled: true }, + }; + private columnToQueryParamMap: { [k in searchableColumnType]: string } = { + title: "intitle", + authors: "inauthor", + }; + + private applyFilters$ = new Subject(); + + public dataFields: Array = [ + { + id: "title", + label: $localize\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\`Title\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\`, + dataType: "string", + sortable: false, + }, + { + id: "authors", + label: $localize\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\`Authors\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\`, + dataType: "string", + sortable: false, + }, + ]; + + constructor(private logger: LoggerService, private http: HttpClient) { + super(); + // Using Nova DataSourceFeatures implementation for the features + this.features = new DataSourceFeatures(this.supportedFeatures); + + this.applyFilters$ + // eslint-disable-next-line import/no-deprecated + .pipe(switchMap((filters) => this.getData(filters))) + .subscribe(async (res) => { + this.outputsSubject.next(await this.getFilteredData(res)); + }); + } + + public async getFilteredData( + booksData: IGBooksData + ): Promise> { + return firstValueFrom( + of(booksData).pipe( + tap((response) => { + this.cache = this.cache.concat(response.books); + }), + map((response) => ({ + result: { + repeat: { itemsSource: this.cache }, + paginator: { total: response.totalItems }, + dataFields: this.dataFields, + }, + })) + ) + ); + } + + private getData(filters: INovaFilters): Observable { + if ( + this.isNewSearchTerm(filters.search) && + filters.virtualScroll?.value.start === 0 + ) { + this.cache = []; + } + + return this.http + .get(this.getComposedUrl(filters)) + .pipe( + tap(() => this.busy.next(true)), + delay(300), // mock + map((response) => ({ + books: + response.items?.map((volume) => ({ + title: volume.volumeInfo.title, + authors: + volume.volumeInfo.authors?.join(", ") || "", + })) || [], + totalItems: response.totalItems, + })), + catchError((e) => { + this.logger.error(e); + return of({ + books: [], + totalItems: 0, + }); + }), + finalize(() => { + this.busy.next(false); + this.previousFilters = filters; + }) + ); + } + + private getComposedUrl(filters: INovaFilters) { + const initialUrl = \\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\`\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\${GBOOKS_API_URL}?q=\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\`; + const maxResults = \\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\`maxResults=\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\${ + (filters.virtualScroll?.value.end || 0) - + (filters.virtualScroll?.value.start || 0) + }\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\`; + + const virtualScrollPart = filters.virtualScroll + ? \\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\`startIndex=\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\${filters.virtualScroll.value.start}\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\` + : ""; + + const searchQueryParam = + this.columnToQueryParamMap[this.searchableColumn]; + const searchPart = filters.search + ? \\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\`\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\${searchQueryParam}:\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\${filters.search.value}\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\` + : "_"; // google books api requires some criteria to do the search + + return \\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\`\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\${initialUrl}\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\${searchPart}&\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\${maxResults}&\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\${virtualScrollPart}&filter=full\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\`; + } + + private isNewSearchTerm(search: IFilter | undefined) { + return ( + !isNil(search?.value) && + !isEqual(search?.value, this.previousFilters?.search?.value) + ); + } + + // redefine parent method + public async applyFilters(): Promise { + this.applyFilters$.next(this.getFilters()); + } +} + +/** + * A component that instantiates the dashboard + */ +@Component({ + selector: "table-widget-search-example", + templateUrl: "./table-widget-search-example.component.html", + styleUrls: ["./table-widget-search-example.component.less"], + standalone: false, +}) +export class TableWidgetSearchExampleComponent implements OnInit { + public dashboard: IDashboard | undefined; + public gridsterConfig: GridsterConfig = {}; + public editMode: boolean = false; + + constructor( + private widgetTypesService: WidgetTypesService, + private providerRegistry: ProviderRegistryService, + private changeDetectorRef: ChangeDetectorRef + ) {} + + public ngOnInit(): void { + const widgetTemplate = this.widgetTypesService.getWidgetType( + "table", + 1 + ); + this.widgetTypesService.setNode( + widgetTemplate, + "configurator", + WellKnownPathKey.DataSourceProviders, + [AcmeTableGBooksDataSource.providerId] + ); + + this.providerRegistry.setProviders({ + [AcmeTableGBooksDataSource.providerId]: { + provide: DATA_SOURCE, + useClass: AcmeTableGBooksDataSource, + deps: [LoggerService, HttpClient], + }, + }); + + this.initializeDashboard(); + } + + /** Used for restoring widgets state */ + public reInitializeDashboard(): void { + // destroys the components and their providers so the dashboard can re init data + this.dashboard = undefined; + this.changeDetectorRef.detectChanges(); + + this.initializeDashboard(); + } + + public initializeDashboard(): void { + const tableWidget = widgetConfig; + const widgetIndex: IWidgets = { + [tableWidget.id]: + this.widgetTypesService.mergeWithWidgetType(tableWidget), + }; + + const positions: Record = { + [tableWidget.id]: { + cols: 12, + rows: 6, + y: 0, + x: 0, + }, + }; + + this.dashboard = { + positions, + widgets: widgetIndex, + }; + } +} + +export const widgetConfig: IWidget = { + id: "tableWidgetId", + type: "table", + pizzagna: { + configuration: { + header: { + properties: { + title: "Google Books", + }, + }, + table: { + providers: { + [WellKnownProviders.DataSource]: { + providerId: AcmeTableGBooksDataSource.providerId, + }, + }, + properties: { + configuration: { + columns: [ + { + id: "column1", + label: $localize\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\`Title\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\`, + isActive: true, + formatter: { + componentType: "RawFormatterComponent", + properties: { + dataFieldIds: { + value: "title", + }, + }, + }, + }, + { + id: "column2", + label: $localize\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\`Author\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\`, + isActive: true, + formatter: { + componentType: "RawFormatterComponent", + properties: { + dataFieldIds: { + value: "authors", + }, + }, + }, + }, + ], + sortable: false, + // define search configuration here + searchConfiguration: { + enabled: true, + // following properties below can be configured as well + // searchTerm: "search criteria here", + // searchDebounce: 300, + }, + hasVirtualScroll: true, + } as ITableWidgetConfig, + }, + }, + }, + }, +}; +\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\`, + "widget-types/table/table-widget-search-docs.component.ts": \\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\`// © 2022 SolarWinds Worldwide, LLC. All rights reserved. +// +// Permission is hereby granted, free of charge, to any person obtaining a copy +// of this software and associated documentation files (the "Software"), to +// deal in the Software without restriction, including without limitation the +// rights to use, copy, modify, merge, publish, distribute, sublicense, and/or +// sell copies of the Software, and to permit persons to whom the Software is +// furnished to do so, subject to the following conditions: +// +// The above copyright notice and this permission notice shall be included in +// all copies or substantial portions of the Software. +// +// THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR +// IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, +// FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE +// AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER +// LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, +// OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN +// THE SOFTWARE. + +import { Component } from "@angular/core"; + +@Component({ + selector: "nui-table-search-docs", + templateUrl: "./table-widget-search-docs.component.html", + standalone: false, +}) +export class TableSearchDocsComponent { + public featuredDeclaredText = \\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\` + private supportedFeatures: IDataSourceFeatures = { + search: { enabled: true }, + pagination: { enabled: true }, + };\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\`; + public featuresUsedText = \\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\` + this.features = new DataSourceFeatures(this.supportedFeatures); + \\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\`; + public tableConfigurationText = \\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\` + "table": { + ... + properties: { + configuration: { + // define search configuration here + searchConfiguration: { + enabled: true, + // following optional properties below can be configured as well + // searchTerm: "search criteria here", + // searchDebounce: 300, + }, + } as ITableWidgetConfig, + }, + }, + \\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\`; +} +\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\`, + "widget-types/table/table-widget-selectable/table-widget-selectable-multi/table-widget-selectable-multi.example.component.ts": \\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\`// © 2022 SolarWinds Worldwide, LLC. All rights reserved. +// +// Permission is hereby granted, free of charge, to any person obtaining a copy +// of this software and associated documentation files (the "Software"), to +// deal in the Software without restriction, including without limitation the +// rights to use, copy, modify, merge, publish, distribute, sublicense, and/or +// sell copies of the Software, and to permit persons to whom the Software is +// furnished to do so, subject to the following conditions: +// +// The above copyright notice and this permission notice shall be included in +// all copies or substantial portions of the Software. +// +// THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR +// IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, +// FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE +// AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER +// LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, +// OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN +// THE SOFTWARE. + +import { Component } from "@angular/core"; + +import { TableSelectionMode } from "@nova-ui/bits"; +import { TableWidgetSelectionConfig } from "@nova-ui/dashboards"; + +/** + * A component that instantiates the dashboard + */ +@Component({ + selector: "table-widget-selectable-multi-example", + templateUrl: "./table-widget-selectable-multi.example.component.html", + styleUrls: ["./table-widget-selectable-multi.example.component.less"], + standalone: false, +}) +export class TableWidgetSelectableMultiExampleComponent { + public selectionConfiguration: TableWidgetSelectionConfig = { + enabled: true, + selectionMode: TableSelectionMode.Multi, + trackByProperty: "id", + clickableRow: true, + }; +} +\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\`, + "widget-types/table/table-widget-selectable/table-widget-selectable-radio/table-widget-selectable-radio.example.component.ts": \\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\`// © 2022 SolarWinds Worldwide, LLC. All rights reserved. +// +// Permission is hereby granted, free of charge, to any person obtaining a copy +// of this software and associated documentation files (the "Software"), to +// deal in the Software without restriction, including without limitation the +// rights to use, copy, modify, merge, publish, distribute, sublicense, and/or +// sell copies of the Software, and to permit persons to whom the Software is +// furnished to do so, subject to the following conditions: +// +// The above copyright notice and this permission notice shall be included in +// all copies or substantial portions of the Software. +// +// THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR +// IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, +// FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE +// AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER +// LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, +// OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN +// THE SOFTWARE. + +import { Component } from "@angular/core"; + +import { TableSelectionMode } from "@nova-ui/bits"; +import { TableWidgetSelectionConfig } from "@nova-ui/dashboards"; + +/** + * A component that instantiates the dashboard + */ +@Component({ + selector: "table-widget-selectable-radio-example", + templateUrl: "./table-widget-selectable-radio.example.component.html", + styleUrls: ["./table-widget-selectable-radio.example.component.less"], + standalone: false, +}) +export class TableWidgetSelectableRadioExampleComponent { + public selectionConfiguration: TableWidgetSelectionConfig = { + enabled: true, + selectionMode: TableSelectionMode.Radio, + trackByProperty: "id", + clickableRow: true, + }; +} +\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\`, + "widget-types/table/table-widget-selectable/table-widget-selectable-single/table-widget-selectable-single.example.component.ts": \\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\`// © 2022 SolarWinds Worldwide, LLC. All rights reserved. +// +// Permission is hereby granted, free of charge, to any person obtaining a copy +// of this software and associated documentation files (the "Software"), to +// deal in the Software without restriction, including without limitation the +// rights to use, copy, modify, merge, publish, distribute, sublicense, and/or +// sell copies of the Software, and to permit persons to whom the Software is +// furnished to do so, subject to the following conditions: +// +// The above copyright notice and this permission notice shall be included in +// all copies or substantial portions of the Software. +// +// THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR +// IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, +// FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE +// AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER +// LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, +// OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN +// THE SOFTWARE. + +import { Component } from "@angular/core"; + +import { TableSelectionMode } from "@nova-ui/bits"; +import { TableWidgetSelectionConfig } from "@nova-ui/dashboards"; + +/** + * A component that instantiates the dashboard + */ +@Component({ + selector: "table-widget-selectable-single-example", + templateUrl: "./table-widget-selectable-single.example.component.html", + styleUrls: ["./table-widget-selectable-single.example.component.less"], + standalone: false, +}) +export class TableWidgetSelectableSingleExampleComponent { + public selectionConfiguration: TableWidgetSelectionConfig = { + enabled: true, + selectionMode: TableSelectionMode.Single, + trackByProperty: "id", + }; +} +\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\`, + "widget-types/table/table-widget-selectable/table-widget-selectable.example.component.ts": \\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\`// © 2022 SolarWinds Worldwide, LLC. All rights reserved. +// +// Permission is hereby granted, free of charge, to any person obtaining a copy +// of this software and associated documentation files (the "Software"), to +// deal in the Software without restriction, including without limitation the +// rights to use, copy, modify, merge, publish, distribute, sublicense, and/or +// sell copies of the Software, and to permit persons to whom the Software is +// furnished to do so, subject to the following conditions: +// +// The above copyright notice and this permission notice shall be included in +// all copies or substantial portions of the Software. +// +// THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR +// IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, +// FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE +// AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER +// LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, +// OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN +// THE SOFTWARE. + +import { HttpClient } from "@angular/common/http"; +import { ChangeDetectorRef, Component, Input, OnInit } from "@angular/core"; +import { GridsterConfig, GridsterItem } from "angular-gridster2"; + +import { LoggerService, TableSelectionMode } from "@nova-ui/bits"; +import { + DATA_SOURCE, + DEFAULT_PIZZAGNA_ROOT, + IDashboard, + IProviderConfiguration, + ITableWidgetConfig, + IWidget, + IWidgets, + NOVA_URL_INTERACTION_HANDLER, + PizzagnaLayer, + ProviderRegistryService, + RawFormatterComponent, + TableWidgetSelectionConfig, + WellKnownPathKey, + WellKnownProviders, + WidgetTypesService, +} from "@nova-ui/dashboards"; + +import { AcmeTableMockDataSource } from "../../../../prototypes/data/table/acme-table-mock-data-source.service"; + +/** + * A component that instantiates the dashboard + */ +@Component({ + selector: "table-widget-selectable-example", + templateUrl: "./table-widget-selectable.example.component.html", + styleUrls: ["./table-widget-selectable.example.component.less"], + standalone: false, +}) +export class TableWidgetSelectableExampleComponent implements OnInit { + public dashboard: IDashboard | undefined; + public gridsterConfig: GridsterConfig = {}; + public editMode: boolean = false; + + @Input() public selectionConfiguration: TableWidgetSelectionConfig = { + enabled: false, + selectionMode: TableSelectionMode.None, + }; + + constructor( + private widgetTypesService: WidgetTypesService, + private providerRegistry: ProviderRegistryService, + private changeDetectorRef: ChangeDetectorRef + ) {} + + public ngOnInit(): void { + const widgetTemplate = this.widgetTypesService.getWidgetType( + "table", + 1 + ); + this.widgetTypesService.setNode( + widgetTemplate, + "configurator", + WellKnownPathKey.DataSourceProviders, + [AcmeTableMockDataSource.providerId] + ); + + this.providerRegistry.setProviders({ + [AcmeTableMockDataSource.providerId]: { + provide: DATA_SOURCE, + useClass: AcmeTableMockDataSource, + deps: [LoggerService, HttpClient], + }, + }); + + this.initializeDashboard(); + } + + /** Used for restoring widgets state */ + public reInitializeDashboard(): void { + // destroys the components and their providers so the dashboard can re init data + this.dashboard = undefined; + this.changeDetectorRef.detectChanges(); + + this.initializeDashboard(); + } + + public initializeDashboard(): void { + const tableWidget = this.widgetConfig; + const widgetIndex: IWidgets = { + [tableWidget.id]: + this.widgetTypesService.mergeWithWidgetType(tableWidget), + }; + + const positions: Record = { + [tableWidget.id]: { + cols: 12, + rows: 6, + y: 0, + x: 0, + }, + }; + + this.dashboard = { + positions, + widgets: widgetIndex, + }; + } + + private get widgetConfig(): IWidget { + return { + id: "widget1", + type: "table", + pizzagna: { + [PizzagnaLayer.Configuration]: { + [DEFAULT_PIZZAGNA_ROOT]: { + providers: { + [WellKnownProviders.InteractionHandler]: { + providerId: NOVA_URL_INTERACTION_HANDLER, + }, + }, + }, + header: { + properties: { + title: "Table Widget with Selection!", + subtitle: "Basic table widget", + collapsible: true, + }, + }, + table: { + providers: { + [WellKnownProviders.DataSource]: { + providerId: AcmeTableMockDataSource.providerId, + } as IProviderConfiguration, + }, + properties: { + configuration: { + // enabling selection here + selectionConfiguration: + this.selectionConfiguration, + columns: [ + { + id: "column1", + label: "No.", + isActive: true, + formatter: { + componentType: + RawFormatterComponent.lateLoadKey, + properties: { + dataFieldIds: { + value: "position", + }, + }, + }, + }, + { + id: "column2", + label: "Name", + isActive: true, + formatter: { + componentType: + RawFormatterComponent.lateLoadKey, + properties: { + dataFieldIds: { + value: "name", + }, + }, + }, + }, + { + id: "column3", + label: "Status", + isActive: true, + formatter: { + componentType: + RawFormatterComponent.lateLoadKey, + properties: { + dataFieldIds: { + value: "status", + }, + }, + }, + }, + ], + } as ITableWidgetConfig, + }, + }, + }, + }, + }; + } +} +\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\`, + "widget-types/timeseries/timeseries-docs.component.ts": \\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\`// © 2022 SolarWinds Worldwide, LLC. All rights reserved. +// +// Permission is hereby granted, free of charge, to any person obtaining a copy +// of this software and associated documentation files (the "Software"), to +// deal in the Software without restriction, including without limitation the +// rights to use, copy, modify, merge, publish, distribute, sublicense, and/or +// sell copies of the Software, and to permit persons to whom the Software is +// furnished to do so, subject to the following conditions: +// +// The above copyright notice and this permission notice shall be included in +// all copies or substantial portions of the Software. +// +// THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR +// IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, +// FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE +// AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER +// LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, +// OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN +// THE SOFTWARE. + +import { Component, OnInit } from "@angular/core"; + +import { mapContentFile } from "../../../../demo-files-factory"; + +@Component({ + selector: "nui-timeseries-docs", + templateUrl: "./timeseries-docs.component.html", + standalone: false, +}) +export class TimeseriesDocsComponent implements OnInit { + public timeseriesWidgetFileText = ""; + public timeseriesConfiguratorFileText = ""; + + async ngOnInit(): Promise { + this.timeseriesWidgetFileText = await import( + "./../../../../../../src/lib/widget-types/timeseries/timeseries-widget" + ).then(mapContentFile); + this.timeseriesConfiguratorFileText = await import( + "./../../../../../../src/lib/widget-types/timeseries/timeseries-configurator" + ).then(mapContentFile); + } +} +\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\`, + "widget-types/timeseries/timeseries-docs.module.ts": \\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\`// © 2022 SolarWinds Worldwide, LLC. All rights reserved. +// +// Permission is hereby granted, free of charge, to any person obtaining a copy +// of this software and associated documentation files (the "Software"), to +// deal in the Software without restriction, including without limitation the +// rights to use, copy, modify, merge, publish, distribute, sublicense, and/or +// sell copies of the Software, and to permit persons to whom the Software is +// furnished to do so, subject to the following conditions: +// +// The above copyright notice and this permission notice shall be included in +// all copies or substantial portions of the Software. +// +// THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR +// IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, +// FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE +// AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER +// LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, +// OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN +// THE SOFTWARE. + +import { NgModule } from "@angular/core"; +import { RouterModule, Routes } from "@angular/router"; + +import { DEMO_PATH_TOKEN } from "@nova-ui/bits"; +import { + NuiButtonModule, + NuiDocsModule, + NuiMessageModule, + NuiSwitchModule, +} from "@nova-ui/bits"; +import { NuiDashboardsModule } from "@nova-ui/dashboards"; + +import { TimeseriesDocsComponent } from "./timeseries-docs.component"; +import { TimeseriesWidgetExampleComponent } from "./timeseries-widget-example/timeseries-widget-example.component"; +import { TimeseriesWidgetInteractiveExampleComponent } from "./timeseries-widget-interactive-example/timeseries-widget-interactive-example.component"; +import { TimeseriesWidgetStatusBarExampleComponent } from "./timeseries-widget-status-bar-example/timeseries-widget-status-bar-example.component"; +import { getDemoFiles } from "../../../../demo-files-factory"; + +const routes: Routes = [ + { + path: "", + component: TimeseriesDocsComponent, + data: { + srlc: { + hideIndicator: true, + }, + showThemeSwitcher: true, + }, + }, + { + path: "example", + component: TimeseriesWidgetExampleComponent, + data: { + srlc: { + hideIndicator: true, + }, + }, + }, +]; + +@NgModule({ + imports: [ + RouterModule.forChild(routes), + NuiButtonModule, + NuiDocsModule, + NuiMessageModule, + NuiSwitchModule, + NuiDashboardsModule, + ], + declarations: [ + TimeseriesDocsComponent, + TimeseriesWidgetExampleComponent, + TimeseriesWidgetInteractiveExampleComponent, + TimeseriesWidgetStatusBarExampleComponent, + ], + providers: [ + { + provide: DEMO_PATH_TOKEN, + useValue: getDemoFiles("timeseries"), + }, + ], +}) +export default class TimeseriesDocsModule {} +\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\`, + "widget-types/timeseries/timeseries-widget-example/timeseries-widget-example.component.ts": \\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\`// © 2022 SolarWinds Worldwide, LLC. All rights reserved. +// +// Permission is hereby granted, free of charge, to any person obtaining a copy +// of this software and associated documentation files (the "Software"), to +// deal in the Software without restriction, including without limitation the +// rights to use, copy, modify, merge, publish, distribute, sublicense, and/or +// sell copies of the Software, and to permit persons to whom the Software is +// furnished to do so, subject to the following conditions: +// +// The above copyright notice and this permission notice shall be included in +// all copies or substantial portions of the Software. +// +// THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR +// IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, +// FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE +// AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER +// LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, +// OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN +// THE SOFTWARE. + +import { + ChangeDetectorRef, + Component, + Injectable, + OnInit, +} from "@angular/core"; +import { GridsterConfig, GridsterItem } from "angular-gridster2"; +import cloneDeep from "lodash/cloneDeep"; +import keyBy from "lodash/keyBy"; +import moment, { Moment } from "moment/moment"; +import { BehaviorSubject } from "rxjs"; + +import { + DataSourceService, + IDataSource, + INovaFilters, + ITimeframe, +} from "@nova-ui/bits"; +import { + DATA_SOURCE, + DEFAULT_PIZZAGNA_ROOT, + IDashboard, + IDataSourceOutput, + IProviderConfiguration, + ISerializableTimeframe, + ITimeseriesItemConfiguration, + ITimeseriesOutput, + ITimeseriesScaleConfig, + ITimeseriesWidgetConfig, + ITimeseriesWidgetData, + ITimeseriesWidgetSeriesData, + IWidget, + LegendPlacement, + PizzagnaLayer, + ProviderRegistryService, + TimeseriesChartPreset, + TimeseriesScaleType, + WellKnownPathKey, + WellKnownProviders, + WidgetTypesService, +} from "@nova-ui/dashboards"; + +/** + * A simple Timeseries data source implementation + */ +@Injectable() +export class BeerVsReadingMockDataSource + extends DataSourceService + implements IDataSource +{ + public static providerId = "BeerVsReadingMockDataSource"; + + public busy = new BehaviorSubject(false); + + public async getFilteredData( + filters: INovaFilters + ): Promise> { + // In this example we're using some static mock data located at the bottom of this file. In a real-world + // scenario, the data for the chart would likely be retrieved via an asynchronous backend call. + let filteredData = getData(); + + this.busy.next(true); + + // Filtering using the filter registered by the TimeFrameBar + const timeframeFilter = filters.timeframe?.value as ITimeframe; + if (timeframeFilter) { + filteredData = filteredData.map((item: ITimeseriesWidgetData) => ({ + id: item.id, + name: item.name, + description: item.description, + data: item.data.filter( + (seriesData: ITimeseriesWidgetSeriesData) => + filterDates( + seriesData.x, + timeframeFilter.startDatetime, + timeframeFilter.endDatetime + ) + ), + })); + } + + this.busy.next(false); + + return { result: { series: filteredData } }; + } +} + +function filterDates(dateToCheck: Date, startDate: Moment, endDate: Moment) { + const mom = moment(dateToCheck); + return ( + mom.isBetween(startDate, endDate) || + mom.isSame(startDate) || + mom.isSame(endDate) + ); +} + +/** + * A component that instantiates the dashboard + */ +@Component({ + selector: "timeseries-widget-example", + templateUrl: "./timeseries-widget-example.component.html", + styleUrls: ["./timeseries-widget-example.component.less"], + standalone: false, +}) +export class TimeseriesWidgetExampleComponent implements OnInit { + // This variable will hold all the data needed to define the layout and behavior of the widgets. + // Pass this to the dashboard component's dashboard input in the template. + public dashboard: IDashboard | undefined; + + // Angular gridster requires a configuration object even if it's empty. + // Pass this to the dashboard component's gridsterConfig input in the template. + public gridsterConfig: GridsterConfig = {}; + + // Boolean passed as an input to the dashboard. When true, widgets can be moved, resized, removed, or edited + public editMode: boolean = false; + + constructor( + // WidgetTypesService provides the widget's necessary structure information + private widgetTypesService: WidgetTypesService, + + // In general, the ProviderRegistryService is used for making entities available for injection into dynamically loaded components. + private providerRegistry: ProviderRegistryService, + + // Angular's ChangeDetectorRef + private changeDetectorRef: ChangeDetectorRef + ) {} + + public ngOnInit(): void { + // Grabbing the widget's default template which will be needed as a parameter for setNode + const widgetTemplate = this.widgetTypesService.getWidgetType( + "timeseries", + 1 + ); + // Registering our data sources as dropdown options in the widget editor/configurator + // Note: This could also be done in the parent module's constructor so that + // multiple dashboards could have access to the same widget template modification. + this.widgetTypesService.setNode( + // This is the template we grabbed above with getWidgetType + widgetTemplate, + // We are setting the editor/configurator part of the widget template + "configurator", + // This indicates which node you are changing and we want to change + // the data source providers available for selection in the editor. + WellKnownPathKey.DataSourceProviders, + // We are setting the data sources available for selection in the editor + [BeerVsReadingMockDataSource.providerId] + ); + + // Registering the data source for injection into the widget. + this.providerRegistry.setProviders({ + [BeerVsReadingMockDataSource.providerId]: { + provide: DATA_SOURCE, + useClass: BeerVsReadingMockDataSource, + deps: [], + }, + }); + + this.initializeDashboard(); + } + + public initializeDashboard(): void { + // We're using a static configuration object for this example, but this is where + // the widget's configuration could potentially be populated from a database + const widgetsWithStructure = widgetConfigs.map((w) => + this.widgetTypesService.mergeWithWidgetType(w) + ); + const widgetsIndex = keyBy(widgetsWithStructure, (w: IWidget) => w.id); + + // Finally, assigning the variables we created above to the dashboard + this.dashboard = { + positions: cloneDeep(positions), + widgets: widgetsIndex, + }; + } + + /** Used for restoring widgets state */ + public reInitializeDashboard(): void { + // destroys the components and their providers so the dashboard can re init data + this.dashboard = undefined; + this.changeDetectorRef.detectChanges(); + + this.initializeDashboard(); + } +} + +const widgetConfigs: IWidget[] = [ + { + id: "lineWidgetId", + type: "timeseries", + pizzagna: { + [PizzagnaLayer.Configuration]: { + [DEFAULT_PIZZAGNA_ROOT]: { + providers: { + [WellKnownProviders.DataSource]: { + // Setting the initially selected data source providerId + providerId: BeerVsReadingMockDataSource.providerId, + } as IProviderConfiguration, + }, + }, + header: { + properties: { + title: "Line Chart", + subtitle: "Survey of 1000 Solarians", + }, + }, + chart: { + providers: { + [WellKnownProviders.Adapter]: { + properties: { + // Setting the series and corresponding labels to initially display on the chart + series: [ + { + id: "series-1", + label: "Beer Tasting", + selectedSeriesId: "series-1", + }, + { + id: "series-2", + label: "Reading", + selectedSeriesId: "series-2", + }, + ] as ITimeseriesItemConfiguration[], + }, + } as Partial, + }, + properties: { + // Setting the general chart configuration + configuration: { + legendPlacement: LegendPlacement.Right, + enableZoom: true, + leftAxisLabel: "Solarians (%)", + // You can optionally define custom colors for the chart by setting the 'chartColors' configuration property + // "chartColors": [ + // "var(--nui-color-chart-eight)", + // "var(--nui-color-chart-nine)", + // "var(--nui-color-chart-ten)", + // ], + } as ITimeseriesWidgetConfig, + }, + }, + timeframeSelection: { + properties: { + // Setting the initial timeframe selected in the timeframe bar + timeframe: { + selectedPresetId: "last7Days", + } as ISerializableTimeframe, + minDate: moment().subtract(60, "days").format(), + maxDate: moment().format(), + }, + }, + }, + }, + }, + { + id: "stackedAreaWidgetId", + type: "timeseries", + pizzagna: { + [PizzagnaLayer.Configuration]: { + [DEFAULT_PIZZAGNA_ROOT]: { + providers: { + [WellKnownProviders.DataSource]: { + // Setting the initially selected data source providerId + providerId: BeerVsReadingMockDataSource.providerId, + } as IProviderConfiguration, + }, + }, + header: { + properties: { + title: "Stacked Area Chart", + subtitle: "Survey of 1000 Solarians", + }, + }, + chart: { + providers: { + [WellKnownProviders.Adapter]: { + properties: { + // Setting the series and corresponding labels to initially display on the chart + series: [ + { + id: "series-1", + label: "Beer Tasting", + selectedSeriesId: "series-1", + }, + { + id: "series-2", + label: "Reading", + selectedSeriesId: "series-2", + }, + ] as ITimeseriesItemConfiguration[], + }, + } as Partial, + }, + properties: { + // Setting the general chart configuration + configuration: { + legendPlacement: LegendPlacement.Right, + enableZoom: true, + // Setting the preset to stacked area + preset: TimeseriesChartPreset.StackedArea, + leftAxisLabel: "Solarians (%)", + // You can optionally define custom colors for the chart by setting the 'chartColors' configuration property + // "chartColors": [ + // "var(--nui-color-chart-eight)", + // "var(--nui-color-chart-nine)", + // "var(--nui-color-chart-ten)", + // ], + } as ITimeseriesWidgetConfig, + }, + }, + timeframeSelection: { + properties: { + // Setting the initial timeframe selected in the timeframe bar + timeframe: { + selectedPresetId: "last7Days", + } as ISerializableTimeframe, + minDate: moment().subtract(60, "days").format(), + maxDate: moment().format(), + }, + }, + }, + }, + }, + { + id: "stackedPercentageAreaWidgetId", + type: "timeseries", + pizzagna: { + [PizzagnaLayer.Configuration]: { + [DEFAULT_PIZZAGNA_ROOT]: { + providers: { + [WellKnownProviders.DataSource]: { + // Setting the initially selected data source providerId + providerId: BeerVsReadingMockDataSource.providerId, + } as IProviderConfiguration, + }, + }, + header: { + properties: { + title: "Stacked Percentage Area Chart", + subtitle: "Survey of 1000 Solarians", + }, + }, + chart: { + providers: { + [WellKnownProviders.Adapter]: { + properties: { + // Setting the series and corresponding labels to initially display on the chart + series: [ + { + id: "series-1", + label: "Beer Tasting", + selectedSeriesId: "series-1", + }, + { + id: "series-2", + label: "Reading", + selectedSeriesId: "series-2", + }, + ] as ITimeseriesItemConfiguration[], + }, + } as Partial, + }, + properties: { + // Setting the general chart configuration + configuration: { + legendPlacement: LegendPlacement.Right, + enableZoom: true, + // Setting the preset to stacked percentage area + preset: TimeseriesChartPreset.StackedPercentageArea, + leftAxisLabel: "Solarians (%)", + // You can optionally define custom colors for the chart by setting the 'chartColors' configuration property + // "chartColors": [ + // "var(--nui-color-chart-eight)", + // "var(--nui-color-chart-nine)", + // "var(--nui-color-chart-ten)", + // ], + } as ITimeseriesWidgetConfig, + }, + }, + timeframeSelection: { + properties: { + // Setting the initial timeframe selected in the timeframe bar + timeframe: { + selectedPresetId: "last7Days", + } as ISerializableTimeframe, + minDate: moment().subtract(60, "days").format(), + maxDate: moment().format(), + }, + }, + }, + }, + }, + { + id: "stackedBarWidgetId", + type: "timeseries", + pizzagna: { + [PizzagnaLayer.Configuration]: { + [DEFAULT_PIZZAGNA_ROOT]: { + providers: { + [WellKnownProviders.DataSource]: { + // Setting the initially selected data source providerId + providerId: BeerVsReadingMockDataSource.providerId, + } as IProviderConfiguration, + }, + }, + header: { + properties: { + title: "Stacked Bar Chart", + subtitle: "Survey of 1000 Solarians", + }, + }, + chart: { + providers: { + [WellKnownProviders.Adapter]: { + properties: { + // Setting the series and corresponding labels to initially display on the chart + series: [ + { + id: "series-1", + label: "Beer Tasting", + selectedSeriesId: "series-1", + }, + { + id: "series-2", + label: "Reading", + selectedSeriesId: "series-2", + }, + ] as ITimeseriesItemConfiguration[], + }, + } as Partial, + }, + properties: { + configuration: { + legendPlacement: LegendPlacement.Right, + enableZoom: true, + leftAxisLabel: "Solarians (%)", + // Setting the preset to stacked bar + preset: TimeseriesChartPreset.StackedBar, + scales: { + x: { + type: TimeseriesScaleType.TimeInterval, + properties: { + interval: 24 * 60 * 60, + }, + } as ITimeseriesScaleConfig, + }, + } as ITimeseriesWidgetConfig, + }, + }, + timeframeSelection: { + properties: { + // Setting the initial timeframe selected in the timeframe bar + timeframe: { + selectedPresetId: "last7Days", + } as ISerializableTimeframe, + minDate: moment().subtract(60, "days").format(), + maxDate: moment().format(), + }, + }, + }, + }, + }, +]; + +// using startOf("day") so that each band for the bar chart corresponds to a calendar day +const now = moment().startOf("day"); + +export const getData = (): ITimeseriesWidgetData[] => [ + { + id: "series-1", + name: "Beer Tasting", + description: "Havin' some suds", + data: [ + { x: now.clone().subtract(20, "day").toDate(), y: 30 }, + { x: now.clone().subtract(19, "day").toDate(), y: 35 }, + { x: now.clone().subtract(18, "day").toDate(), y: 33 }, + { x: now.clone().subtract(17, "day").toDate(), y: 40 }, + { x: now.clone().subtract(16, "day").toDate(), y: 35 }, + { x: now.clone().subtract(15, "day").toDate(), y: 30 }, + { x: now.clone().subtract(14, "day").toDate(), y: 35 }, + { x: now.clone().subtract(13, "day").toDate(), y: 15 }, + { x: now.clone().subtract(12, "day").toDate(), y: 30 }, + { x: now.clone().subtract(11, "day").toDate(), y: 45 }, + { x: now.clone().subtract(10, "day").toDate(), y: 60 }, + { x: now.clone().subtract(9, "day").toDate(), y: 54 }, + { x: now.clone().subtract(8, "day").toDate(), y: 42 }, + { x: now.clone().subtract(7, "day").toDate(), y: 44 }, + { x: now.clone().subtract(6, "day").toDate(), y: 54 }, + { x: now.clone().subtract(5, "day").toDate(), y: 43 }, + { x: now.clone().subtract(4, "day").toDate(), y: 76 }, + { x: now.clone().subtract(3, "day").toDate(), y: 54 }, + { x: now.clone().subtract(2, "day").toDate(), y: 42 }, + { x: now.clone().subtract(1, "day").toDate(), y: 34 }, + ], + }, + { + id: "series-2", + name: "Reading", + description: "Hittin' the books", + data: [ + { x: now.clone().subtract(20, "day").toDate(), y: 60 }, + { x: now.clone().subtract(19, "day").toDate(), y: 64 }, + { x: now.clone().subtract(18, "day").toDate(), y: 70 }, + { x: now.clone().subtract(17, "day").toDate(), y: 55 }, + { x: now.clone().subtract(16, "day").toDate(), y: 55 }, + { x: now.clone().subtract(15, "day").toDate(), y: 45 }, + { x: now.clone().subtract(14, "day").toDate(), y: 60 }, + { x: now.clone().subtract(13, "day").toDate(), y: 65 }, + { x: now.clone().subtract(12, "day").toDate(), y: 63 }, + { x: now.clone().subtract(11, "day").toDate(), y: 60 }, + { x: now.clone().subtract(10, "day").toDate(), y: 61 }, + { x: now.clone().subtract(9, "day").toDate(), y: 65 }, + { x: now.clone().subtract(8, "day").toDate(), y: 63 }, + { x: now.clone().subtract(7, "day").toDate(), y: 58 }, + { x: now.clone().subtract(6, "day").toDate(), y: 64 }, + { x: now.clone().subtract(5, "day").toDate(), y: 63 }, + { x: now.clone().subtract(4, "day").toDate(), y: 60 }, + { x: now.clone().subtract(3, "day").toDate(), y: 62 }, + { x: now.clone().subtract(2, "day").toDate(), y: 61 }, + { x: now.clone().subtract(1, "day").toDate(), y: 62 }, + ], + }, +]; + +// Setting the widget dimensions and position (this is for gridster) +const positions: Record = { + [widgetConfigs[0].id]: { + cols: 6, + rows: 6, + y: 0, + x: 0, + }, + [widgetConfigs[1].id]: { + cols: 6, + rows: 6, + y: 0, + x: 6, + }, + [widgetConfigs[3].id]: { + cols: 6, + rows: 6, + y: 6, + x: 0, + }, + [widgetConfigs[2].id]: { + cols: 6, + rows: 6, + y: 6, + x: 6, + }, +}; +\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\`, + "widget-types/timeseries/timeseries-widget-interactive-example/timeseries-widget-interactive-example.component.ts": \\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\`// © 2022 SolarWinds Worldwide, LLC. All rights reserved. +// +// Permission is hereby granted, free of charge, to any person obtaining a copy +// of this software and associated documentation files (the "Software"), to +// deal in the Software without restriction, including without limitation the +// rights to use, copy, modify, merge, publish, distribute, sublicense, and/or +// sell copies of the Software, and to permit persons to whom the Software is +// furnished to do so, subject to the following conditions: +// +// The above copyright notice and this permission notice shall be included in +// all copies or substantial portions of the Software. +// +// THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR +// IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, +// FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE +// AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER +// LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, +// OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN +// THE SOFTWARE. + +import { + ChangeDetectorRef, + Component, + Injectable, + OnInit, +} from "@angular/core"; +import { GridsterConfig, GridsterItem } from "angular-gridster2"; +import cloneDeep from "lodash/cloneDeep"; +import keyBy from "lodash/keyBy"; +import moment, { Moment } from "moment/moment"; +import { BehaviorSubject } from "rxjs"; + +import { + DataSourceService, + IDataSource, + INovaFilters, + ITimeframe, +} from "@nova-ui/bits"; +import { + DATA_SOURCE, + DEFAULT_PIZZAGNA_ROOT, + IDashboard, + IDataSourceOutput, + IProviderConfiguration, + ISerializableTimeframe, + ITimeseriesItemConfiguration, + ITimeseriesOutput, + ITimeseriesScaleConfig, + ITimeseriesWidgetConfig, + ITimeseriesWidgetData, + ITimeseriesWidgetSeriesData, + IWidget, + NOVA_URL_INTERACTION_HANDLER, + LegendPlacement, + PizzagnaLayer, + ProviderRegistryService, + TimeseriesChartPreset, + TimeseriesScaleType, + WellKnownPathKey, + WellKnownProviders, + WidgetTypesService, +} from "@nova-ui/dashboards"; + +/** + * A simple Timeseries data source implementation + */ +@Injectable() +export class TimeseriesMockDataSource + extends DataSourceService + implements IDataSource +{ + public static providerId = "TimeseriesMockDataSource"; + + public busy = new BehaviorSubject(false); + + public async getFilteredData( + filters: INovaFilters + ): Promise> { + // In this example we're using some static mock data located at the bottom of this file. In a real-world + // scenario, the data for the chart would likely be retrieved via an asynchronous backend call. + let filteredData = getData(); + + this.busy.next(true); + + // Filtering using the filter registered by the TimeFrameBar + const timeframeFilter = filters.timeframe?.value as ITimeframe; + if (timeframeFilter) { + filteredData = filteredData.map((item: ITimeseriesWidgetData) => ({ + id: item.id, + name: item.name, + description: item.description, + // the filtered data should return the provided links if they are set. + link: item?.link, + secondaryLink: item?.secondaryLink, + data: item.data.filter( + (seriesData: ITimeseriesWidgetSeriesData) => + filterDates( + seriesData.x, + timeframeFilter.startDatetime, + timeframeFilter.endDatetime + ) + ), + })); + } + + this.busy.next(false); + + return { result: { series: filteredData } }; + } +} + +function filterDates(dateToCheck: Date, startDate: Moment, endDate: Moment) { + const mom = moment(dateToCheck); + return ( + mom.isBetween(startDate, endDate) || + mom.isSame(startDate) || + mom.isSame(endDate) + ); +} + +/** + * A component that instantiates the dashboard + */ +@Component({ + selector: "timeseries-widget-interactive-example", + templateUrl: "./timeseries-widget-interactive-example.component.html", + styleUrls: ["./timeseries-widget-interactive-example.component.less"], + standalone: false, +}) +export class TimeseriesWidgetInteractiveExampleComponent implements OnInit { + // This variable will hold all the data needed to define the layout and behavior of the widgets. + // Pass this to the dashboard component's dashboard input in the template. + public dashboard: IDashboard | undefined; + + // Angular gridster requires a configuration object even if it's empty. + // Pass this to the dashboard component's gridsterConfig input in the template. + public gridsterConfig: GridsterConfig = {}; + + // Boolean passed as an input to the dashboard. When true, widgets can be moved, resized, removed, or edited + public editMode: boolean = false; + + constructor( + // WidgetTypesService provides the widget's necessary structure information + private widgetTypesService: WidgetTypesService, + + // In general, the ProviderRegistryService is used for making entities available for injection into dynamically loaded components. + private providerRegistry: ProviderRegistryService, + + // Angular's ChangeDetectorRef + private changeDetectorRef: ChangeDetectorRef + ) {} + + public ngOnInit(): void { + // Grabbing the widget's default template which will be needed as a parameter for setNode + const widgetTemplate = this.widgetTypesService.getWidgetType( + "timeseries", + 1 + ); + // Registering our data sources as dropdown options in the widget editor/configurator + // Note: This could also be done in the parent module's constructor so that + // multiple dashboards could have access to the same widget template modification. + this.widgetTypesService.setNode( + // This is the template we grabbed above with getWidgetType + widgetTemplate, + // We are setting the editor/configurator part of the widget template + "configurator", + // This indicates which node you are changing and we want to change + // the data source providers available for selection in the editor. + WellKnownPathKey.DataSourceProviders, + // We are setting the data sources available for selection in the editor + [TimeseriesMockDataSource.providerId] + ); + + // Registering the data source for injection into the widget. + this.providerRegistry.setProviders({ + [TimeseriesMockDataSource.providerId]: { + provide: DATA_SOURCE, + useClass: TimeseriesMockDataSource, + deps: [], + }, + }); + + this.initializeDashboard(); + } + + public initializeDashboard(): void { + // We're using a static configuration object for this example, but this is where + // the widget's configuration could potentially be populated from a database + const widgetsWithStructure = widgetConfigs.map((w) => + this.widgetTypesService.mergeWithWidgetType(w) + ); + const widgetsIndex = keyBy(widgetsWithStructure, (w: IWidget) => w.id); + + // Finally, assigning the variables we created above to the dashboard + this.dashboard = { + positions: cloneDeep(positions), + widgets: widgetsIndex, + }; + } + + /** Used for restoring widgets state */ + public reInitializeDashboard(): void { + // destroys the components and their providers so the dashboard can re init data + this.dashboard = undefined; + this.changeDetectorRef.detectChanges(); + + this.initializeDashboard(); + } +} + +const widgetConfigs: IWidget[] = [ + { + id: "lineWidgetId", + type: "timeseries", + pizzagna: { + [PizzagnaLayer.Configuration]: { + [DEFAULT_PIZZAGNA_ROOT]: { + providers: { + [WellKnownProviders.DataSource]: { + // Setting the initially selected data source providerId + providerId: TimeseriesMockDataSource.providerId, + } as IProviderConfiguration, + [WellKnownProviders.InteractionHandler]: { + // Setting the UrlInteractionHandler as an interactionHandler + providerId: NOVA_URL_INTERACTION_HANDLER, + properties: { + // the 'url' property tells the handler what link to use when interaction occurs on the series + url: "\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\${data.link || 'https://en.wikipedia.org/wiki/'+data.legendDescriptionPrimary}", + // by default the link is opened in the current window, set 'newWindow' to true to open in a new tab instead + // newWindow: true, + }, + }, + }, + }, + header: { + properties: { + title: "Line Chart", + subtitle: "Basic Timeseries with Interaction", + }, + }, + chart: { + providers: { + [WellKnownProviders.Adapter]: { + properties: { + // Setting the series and corresponding labels to initially display on the chart + series: [ + { + id: "series-1", + label: "Nur-Sultan", + selectedSeriesId: "series-1", + }, + { + id: "series-2", + label: "Brno", + selectedSeriesId: "series-2", + }, + { + id: "series-3", + label: "Lisbon", + selectedSeriesId: "series-3", + }, + { + id: "series-4", + label: "Austin", + selectedSeriesId: "series-4", + }, + ] as ITimeseriesItemConfiguration[], + }, + } as Partial, + }, + properties: { + // Setting the general chart configuration + configuration: { + // setting interaction to 'series' will make all series in the chart interactable + interaction: "series", + legendPlacement: LegendPlacement.Right, + enableZoom: true, + } as ITimeseriesWidgetConfig, + }, + }, + timeframeSelection: { + properties: { + timeframe: { + selectedPresetId: "last7Days", + } as ISerializableTimeframe, + minDate: moment().subtract(60, "days").format(), + maxDate: moment().format(), + }, + }, + }, + }, + }, + { + id: "stackedBarWidgetId", + type: "timeseries", + pizzagna: { + [PizzagnaLayer.Configuration]: { + [DEFAULT_PIZZAGNA_ROOT]: { + providers: { + [WellKnownProviders.DataSource]: { + // Setting the initially selected data source providerId + providerId: TimeseriesMockDataSource.providerId, + } as IProviderConfiguration, + }, + }, + header: { + properties: { + title: "Stacked Bar Chart", + subtitle: + "Basic Timeseries without Interaction Handler", + }, + }, + chart: { + providers: { + [WellKnownProviders.Adapter]: { + properties: { + // Setting the series and corresponding labels to initially display on the chart + series: [ + { + id: "series-1", + label: "Nur-Sultan", + selectedSeriesId: "series-1", + }, + { + id: "series-2", + label: "Brno", + selectedSeriesId: "series-2", + }, + { + id: "series-3", + label: "Lisbon", + selectedSeriesId: "series-3", + }, + { + id: "series-4", + label: "Austin", + selectedSeriesId: "series-4", + }, + ] as ITimeseriesItemConfiguration[], + }, + } as Partial, + }, + properties: { + // Setting the general chart configuration + configuration: { + legendPlacement: LegendPlacement.Right, + enableZoom: true, + // Setting the preset to stacked bar + preset: TimeseriesChartPreset.StackedBar, + scales: { + x: { + type: TimeseriesScaleType.TimeInterval, + properties: { + interval: 24 * 60 * 60, + }, + } as ITimeseriesScaleConfig, + }, + } as ITimeseriesWidgetConfig, + }, + }, + timeframeSelection: { + properties: { + // Setting the initial timeframe selected in the timeframe bar + timeframe: { + selectedPresetId: "last7Days", + } as ISerializableTimeframe, + minDate: moment().subtract(60, "days").format(), + maxDate: moment().format(), + }, + }, + }, + }, + }, +]; + +// using startOf("day") so that each band for the bar chart corresponds to a calendar day +const startOfToday = moment().startOf("day").toDate(); + +export const getData = (): ITimeseriesWidgetData[] => [ + { + id: "series-1", + name: "Nur-Sultan", + description: "'link' only", + link: "https://en.wikipedia.org/wiki/Nur-Sultan", + data: [ + { x: moment(startOfToday).subtract(59, "day").toDate(), y: 35 }, + { x: moment(startOfToday).subtract(58, "day").toDate(), y: 33 }, + { x: moment(startOfToday).subtract(57, "day").toDate(), y: 40 }, + { x: moment(startOfToday).subtract(56, "day").toDate(), y: 35 }, + { x: moment(startOfToday).subtract(55, "day").toDate(), y: 30 }, + { x: moment(startOfToday).subtract(54, "day").toDate(), y: 35 }, + { x: moment(startOfToday).subtract(53, "day").toDate(), y: 15 }, + { x: moment(startOfToday).subtract(52, "day").toDate(), y: 30 }, + { x: moment(startOfToday).subtract(51, "day").toDate(), y: 35 }, + { x: moment(startOfToday).subtract(50, "day").toDate(), y: 34 }, + { x: moment(startOfToday).subtract(49, "day").toDate(), y: 35 }, + { x: moment(startOfToday).subtract(48, "day").toDate(), y: 33 }, + { x: moment(startOfToday).subtract(47, "day").toDate(), y: 40 }, + { x: moment(startOfToday).subtract(46, "day").toDate(), y: 35 }, + { x: moment(startOfToday).subtract(45, "day").toDate(), y: 30 }, + { x: moment(startOfToday).subtract(44, "day").toDate(), y: 35 }, + { x: moment(startOfToday).subtract(43, "day").toDate(), y: 15 }, + { x: moment(startOfToday).subtract(42, "day").toDate(), y: 30 }, + { x: moment(startOfToday).subtract(41, "day").toDate(), y: 35 }, + { x: moment(startOfToday).subtract(40, "day").toDate(), y: 34 }, + { x: moment(startOfToday).subtract(39, "day").toDate(), y: 35 }, + { x: moment(startOfToday).subtract(38, "day").toDate(), y: 33 }, + { x: moment(startOfToday).subtract(37, "day").toDate(), y: 40 }, + { x: moment(startOfToday).subtract(36, "day").toDate(), y: 35 }, + { x: moment(startOfToday).subtract(35, "day").toDate(), y: 30 }, + { x: moment(startOfToday).subtract(34, "day").toDate(), y: 35 }, + { x: moment(startOfToday).subtract(33, "day").toDate(), y: 15 }, + { x: moment(startOfToday).subtract(32, "day").toDate(), y: 30 }, + { x: moment(startOfToday).subtract(31, "day").toDate(), y: 35 }, + { x: moment(startOfToday).subtract(30, "day").toDate(), y: 34 }, + { x: moment(startOfToday).subtract(29, "day").toDate(), y: 35 }, + { x: moment(startOfToday).subtract(28, "day").toDate(), y: 33 }, + { x: moment(startOfToday).subtract(27, "day").toDate(), y: 40 }, + { x: moment(startOfToday).subtract(26, "day").toDate(), y: 35 }, + { x: moment(startOfToday).subtract(25, "day").toDate(), y: 30 }, + { x: moment(startOfToday).subtract(24, "day").toDate(), y: 35 }, + { x: moment(startOfToday).subtract(23, "day").toDate(), y: 15 }, + { x: moment(startOfToday).subtract(22, "day").toDate(), y: 30 }, + { x: moment(startOfToday).subtract(21, "day").toDate(), y: 35 }, + { x: moment(startOfToday).subtract(20, "day").toDate(), y: 34 }, + { x: moment(startOfToday).subtract(19, "day").toDate(), y: 35 }, + { x: moment(startOfToday).subtract(18, "day").toDate(), y: 33 }, + { x: moment(startOfToday).subtract(17, "day").toDate(), y: 40 }, + { x: moment(startOfToday).subtract(16, "day").toDate(), y: 35 }, + { x: moment(startOfToday).subtract(15, "day").toDate(), y: 30 }, + { x: moment(startOfToday).subtract(14, "day").toDate(), y: 35 }, + { x: moment(startOfToday).subtract(13, "day").toDate(), y: 15 }, + { x: moment(startOfToday).subtract(12, "day").toDate(), y: 30 }, + { x: moment(startOfToday).subtract(11, "day").toDate(), y: 35 }, + { x: moment(startOfToday).subtract(10, "day").toDate(), y: 34 }, + { x: moment(startOfToday).subtract(9, "day").toDate(), y: 33 }, + { x: moment(startOfToday).subtract(8, "day").toDate(), y: 35 }, + { x: moment(startOfToday).subtract(7, "day").toDate(), y: 36 }, + { x: moment(startOfToday).subtract(6, "day").toDate(), y: 34 }, + { x: moment(startOfToday).subtract(5, "day").toDate(), y: 33 }, + { x: moment(startOfToday).subtract(4, "day").toDate(), y: 30 }, + { x: moment(startOfToday).subtract(3, "day").toDate(), y: 32 }, + { x: moment(startOfToday).subtract(2, "day").toDate(), y: 31 }, + { x: moment(startOfToday).subtract(1, "day").toDate(), y: 34 }, + { x: moment(startOfToday).toDate(), y: 25 }, + ], + }, + { + id: "series-2", + name: "Brno", + description: "'link' and 'secondaryLink'", + link: "https://en.wikipedia.org/wiki/Brno", + secondaryLink: "https://en.wikipedia.org/wiki/Europe", + data: [ + { x: moment(startOfToday).subtract(59, "day").toDate(), y: 35 }, + { x: moment(startOfToday).subtract(58, "day").toDate(), y: 33 }, + { x: moment(startOfToday).subtract(57, "day").toDate(), y: 40 }, + { x: moment(startOfToday).subtract(56, "day").toDate(), y: 35 }, + { x: moment(startOfToday).subtract(55, "day").toDate(), y: 30 }, + { x: moment(startOfToday).subtract(54, "day").toDate(), y: 35 }, + { x: moment(startOfToday).subtract(53, "day").toDate(), y: 15 }, + { x: moment(startOfToday).subtract(52, "day").toDate(), y: 30 }, + { x: moment(startOfToday).subtract(51, "day").toDate(), y: 35 }, + { x: moment(startOfToday).subtract(50, "day").toDate(), y: 34 }, + { x: moment(startOfToday).subtract(49, "day").toDate(), y: 35 }, + { x: moment(startOfToday).subtract(48, "day").toDate(), y: 33 }, + { x: moment(startOfToday).subtract(47, "day").toDate(), y: 40 }, + { x: moment(startOfToday).subtract(46, "day").toDate(), y: 35 }, + { x: moment(startOfToday).subtract(45, "day").toDate(), y: 30 }, + { x: moment(startOfToday).subtract(44, "day").toDate(), y: 35 }, + { x: moment(startOfToday).subtract(43, "day").toDate(), y: 15 }, + { x: moment(startOfToday).subtract(42, "day").toDate(), y: 30 }, + { x: moment(startOfToday).subtract(41, "day").toDate(), y: 35 }, + { x: moment(startOfToday).subtract(40, "day").toDate(), y: 34 }, + { x: moment(startOfToday).subtract(39, "day").toDate(), y: 35 }, + { x: moment(startOfToday).subtract(38, "day").toDate(), y: 33 }, + { x: moment(startOfToday).subtract(37, "day").toDate(), y: 40 }, + { x: moment(startOfToday).subtract(36, "day").toDate(), y: 35 }, + { x: moment(startOfToday).subtract(35, "day").toDate(), y: 30 }, + { x: moment(startOfToday).subtract(34, "day").toDate(), y: 35 }, + { x: moment(startOfToday).subtract(33, "day").toDate(), y: 15 }, + { x: moment(startOfToday).subtract(32, "day").toDate(), y: 30 }, + { x: moment(startOfToday).subtract(31, "day").toDate(), y: 35 }, + { x: moment(startOfToday).subtract(30, "day").toDate(), y: 34 }, + { x: moment(startOfToday).subtract(29, "day").toDate(), y: 35 }, + { x: moment(startOfToday).subtract(28, "day").toDate(), y: 33 }, + { x: moment(startOfToday).subtract(27, "day").toDate(), y: 40 }, + { x: moment(startOfToday).subtract(26, "day").toDate(), y: 35 }, + { x: moment(startOfToday).subtract(25, "day").toDate(), y: 30 }, + { x: moment(startOfToday).subtract(24, "day").toDate(), y: 35 }, + { x: moment(startOfToday).subtract(23, "day").toDate(), y: 15 }, + { x: moment(startOfToday).subtract(22, "day").toDate(), y: 30 }, + { x: moment(startOfToday).subtract(21, "day").toDate(), y: 35 }, + { x: moment(startOfToday).subtract(20, "day").toDate(), y: 34 }, + { x: moment(startOfToday).subtract(19, "day").toDate(), y: 64 }, + { x: moment(startOfToday).subtract(18, "day").toDate(), y: 70 }, + { x: moment(startOfToday).subtract(17, "day").toDate(), y: 55 }, + { x: moment(startOfToday).subtract(16, "day").toDate(), y: 55 }, + { x: moment(startOfToday).subtract(15, "day").toDate(), y: 45 }, + { x: moment(startOfToday).subtract(14, "day").toDate(), y: 10 }, + { x: moment(startOfToday).subtract(13, "day").toDate(), y: 65 }, + { x: moment(startOfToday).subtract(12, "day").toDate(), y: 35 }, + { x: moment(startOfToday).subtract(11, "day").toDate(), y: 60 }, + { x: moment(startOfToday).subtract(10, "day").toDate(), y: 61 }, + { x: moment(startOfToday).subtract(9, "day").toDate(), y: 65 }, + { x: moment(startOfToday).subtract(8, "day").toDate(), y: 63 }, + { x: moment(startOfToday).subtract(7, "day").toDate(), y: 58 }, + { x: moment(startOfToday).subtract(6, "day").toDate(), y: 64 }, + { x: moment(startOfToday).subtract(5, "day").toDate(), y: 63 }, + { x: moment(startOfToday).subtract(4, "day").toDate(), y: 60 }, + { x: moment(startOfToday).subtract(3, "day").toDate(), y: 62 }, + { x: moment(startOfToday).subtract(2, "day").toDate(), y: 61 }, + { x: moment(startOfToday).subtract(1, "day").toDate(), y: 62 }, + { x: moment(startOfToday).toDate(), y: 55 }, + ], + }, + { + id: "series-3", + name: "Lisbon", + description: "'secondaryLink' only", + secondaryLink: "https://en.wikipedia.org/wiki/Lisbon", + data: [ + { x: moment(startOfToday).subtract(59, "day").toDate(), y: 35 }, + { x: moment(startOfToday).subtract(58, "day").toDate(), y: 33 }, + { x: moment(startOfToday).subtract(57, "day").toDate(), y: 40 }, + { x: moment(startOfToday).subtract(56, "day").toDate(), y: 35 }, + { x: moment(startOfToday).subtract(55, "day").toDate(), y: 30 }, + { x: moment(startOfToday).subtract(54, "day").toDate(), y: 35 }, + { x: moment(startOfToday).subtract(53, "day").toDate(), y: 15 }, + { x: moment(startOfToday).subtract(52, "day").toDate(), y: 30 }, + { x: moment(startOfToday).subtract(51, "day").toDate(), y: 35 }, + { x: moment(startOfToday).subtract(50, "day").toDate(), y: 34 }, + { x: moment(startOfToday).subtract(49, "day").toDate(), y: 35 }, + { x: moment(startOfToday).subtract(48, "day").toDate(), y: 33 }, + { x: moment(startOfToday).subtract(47, "day").toDate(), y: 40 }, + { x: moment(startOfToday).subtract(46, "day").toDate(), y: 35 }, + { x: moment(startOfToday).subtract(45, "day").toDate(), y: 30 }, + { x: moment(startOfToday).subtract(44, "day").toDate(), y: 35 }, + { x: moment(startOfToday).subtract(43, "day").toDate(), y: 15 }, + { x: moment(startOfToday).subtract(42, "day").toDate(), y: 30 }, + { x: moment(startOfToday).subtract(41, "day").toDate(), y: 35 }, + { x: moment(startOfToday).subtract(40, "day").toDate(), y: 34 }, + { x: moment(startOfToday).subtract(39, "day").toDate(), y: 35 }, + { x: moment(startOfToday).subtract(38, "day").toDate(), y: 33 }, + { x: moment(startOfToday).subtract(37, "day").toDate(), y: 40 }, + { x: moment(startOfToday).subtract(36, "day").toDate(), y: 35 }, + { x: moment(startOfToday).subtract(35, "day").toDate(), y: 30 }, + { x: moment(startOfToday).subtract(34, "day").toDate(), y: 35 }, + { x: moment(startOfToday).subtract(33, "day").toDate(), y: 15 }, + { x: moment(startOfToday).subtract(32, "day").toDate(), y: 30 }, + { x: moment(startOfToday).subtract(31, "day").toDate(), y: 35 }, + { x: moment(startOfToday).subtract(30, "day").toDate(), y: 34 }, + { x: moment(startOfToday).subtract(29, "day").toDate(), y: 35 }, + { x: moment(startOfToday).subtract(28, "day").toDate(), y: 33 }, + { x: moment(startOfToday).subtract(27, "day").toDate(), y: 40 }, + { x: moment(startOfToday).subtract(26, "day").toDate(), y: 35 }, + { x: moment(startOfToday).subtract(25, "day").toDate(), y: 30 }, + { x: moment(startOfToday).subtract(24, "day").toDate(), y: 35 }, + { x: moment(startOfToday).subtract(23, "day").toDate(), y: 15 }, + { x: moment(startOfToday).subtract(22, "day").toDate(), y: 30 }, + { x: moment(startOfToday).subtract(21, "day").toDate(), y: 35 }, + { x: moment(startOfToday).subtract(20, "day").toDate(), y: 34 }, + { x: moment(startOfToday).subtract(19, "day").toDate(), y: 80 }, + { x: moment(startOfToday).subtract(18, "day").toDate(), y: 70 }, + { x: moment(startOfToday).subtract(17, "day").toDate(), y: 95 }, + { x: moment(startOfToday).subtract(16, "day").toDate(), y: 90 }, + { x: moment(startOfToday).subtract(15, "day").toDate(), y: 85 }, + { x: moment(startOfToday).subtract(14, "day").toDate(), y: 70 }, + { x: moment(startOfToday).subtract(13, "day").toDate(), y: 75 }, + { x: moment(startOfToday).subtract(12, "day").toDate(), y: 69 }, + { x: moment(startOfToday).subtract(11, "day").toDate(), y: 75 }, + { x: moment(startOfToday).subtract(10, "day").toDate(), y: 81 }, + { x: moment(startOfToday).subtract(9, "day").toDate(), y: 93 }, + { x: moment(startOfToday).subtract(8, "day").toDate(), y: 83 }, + { x: moment(startOfToday).subtract(7, "day").toDate(), y: 70 }, + { x: moment(startOfToday).subtract(6, "day").toDate(), y: 74 }, + { x: moment(startOfToday).subtract(5, "day").toDate(), y: 73 }, + { x: moment(startOfToday).subtract(4, "day").toDate(), y: 68 }, + { x: moment(startOfToday).subtract(3, "day").toDate(), y: 72 }, + { x: moment(startOfToday).subtract(2, "day").toDate(), y: 61 }, + { x: moment(startOfToday).subtract(1, "day").toDate(), y: 69 }, + { x: moment(startOfToday).toDate(), y: 60 }, + ], + }, + { + id: "series-4", + name: "Austin", + description: "No links", + data: [ + { x: moment(startOfToday).subtract(59, "day").toDate(), y: 25 }, + { x: moment(startOfToday).subtract(58, "day").toDate(), y: 43 }, + { x: moment(startOfToday).subtract(57, "day").toDate(), y: 40 }, + { x: moment(startOfToday).subtract(56, "day").toDate(), y: 65 }, + { x: moment(startOfToday).subtract(55, "day").toDate(), y: 30 }, + { x: moment(startOfToday).subtract(54, "day").toDate(), y: 25 }, + { x: moment(startOfToday).subtract(53, "day").toDate(), y: 45 }, + { x: moment(startOfToday).subtract(52, "day").toDate(), y: 30 }, + { x: moment(startOfToday).subtract(51, "day").toDate(), y: 85 }, + { x: moment(startOfToday).subtract(50, "day").toDate(), y: 74 }, + { x: moment(startOfToday).subtract(49, "day").toDate(), y: 55 }, + { x: moment(startOfToday).subtract(48, "day").toDate(), y: 23 }, + { x: moment(startOfToday).subtract(47, "day").toDate(), y: 40 }, + { x: moment(startOfToday).subtract(46, "day").toDate(), y: 15 }, + { x: moment(startOfToday).subtract(45, "day").toDate(), y: 20 }, + { x: moment(startOfToday).subtract(44, "day").toDate(), y: 65 }, + { x: moment(startOfToday).subtract(43, "day").toDate(), y: 25 }, + { x: moment(startOfToday).subtract(42, "day").toDate(), y: 40 }, + { x: moment(startOfToday).subtract(41, "day").toDate(), y: 25 }, + { x: moment(startOfToday).subtract(40, "day").toDate(), y: 54 }, + { x: moment(startOfToday).subtract(39, "day").toDate(), y: 65 }, + { x: moment(startOfToday).subtract(38, "day").toDate(), y: 33 }, + { x: moment(startOfToday).subtract(37, "day").toDate(), y: 50 }, + { x: moment(startOfToday).subtract(36, "day").toDate(), y: 45 }, + { x: moment(startOfToday).subtract(35, "day").toDate(), y: 20 }, + { x: moment(startOfToday).subtract(34, "day").toDate(), y: 25 }, + { x: moment(startOfToday).subtract(33, "day").toDate(), y: 35 }, + { x: moment(startOfToday).subtract(32, "day").toDate(), y: 20 }, + { x: moment(startOfToday).subtract(31, "day").toDate(), y: 35 }, + { x: moment(startOfToday).subtract(30, "day").toDate(), y: 14 }, + { x: moment(startOfToday).subtract(29, "day").toDate(), y: 55 }, + { x: moment(startOfToday).subtract(28, "day").toDate(), y: 23 }, + { x: moment(startOfToday).subtract(27, "day").toDate(), y: 10 }, + { x: moment(startOfToday).subtract(26, "day").toDate(), y: 5 }, + { x: moment(startOfToday).subtract(25, "day").toDate(), y: 20 }, + { x: moment(startOfToday).subtract(24, "day").toDate(), y: 35 }, + { x: moment(startOfToday).subtract(23, "day").toDate(), y: 15 }, + { x: moment(startOfToday).subtract(22, "day").toDate(), y: 30 }, + { x: moment(startOfToday).subtract(21, "day").toDate(), y: 35 }, + { x: moment(startOfToday).subtract(20, "day").toDate(), y: 34 }, + { x: moment(startOfToday).subtract(19, "day").toDate(), y: 50 }, + { x: moment(startOfToday).subtract(18, "day").toDate(), y: 60 }, + { x: moment(startOfToday).subtract(17, "day").toDate(), y: 95 }, + { x: moment(startOfToday).subtract(16, "day").toDate(), y: 80 }, + { x: moment(startOfToday).subtract(15, "day").toDate(), y: 65 }, + { x: moment(startOfToday).subtract(14, "day").toDate(), y: 80 }, + { x: moment(startOfToday).subtract(13, "day").toDate(), y: 85 }, + { x: moment(startOfToday).subtract(12, "day").toDate(), y: 69 }, + { x: moment(startOfToday).subtract(11, "day").toDate(), y: 65 }, + { x: moment(startOfToday).subtract(10, "day").toDate(), y: 71 }, + { x: moment(startOfToday).subtract(9, "day").toDate(), y: 73 }, + { x: moment(startOfToday).subtract(8, "day").toDate(), y: 43 }, + { x: moment(startOfToday).subtract(7, "day").toDate(), y: 70 }, + { x: moment(startOfToday).subtract(6, "day").toDate(), y: 84 }, + { x: moment(startOfToday).subtract(5, "day").toDate(), y: 73 }, + { x: moment(startOfToday).subtract(4, "day").toDate(), y: 38 }, + { x: moment(startOfToday).subtract(3, "day").toDate(), y: 72 }, + { x: moment(startOfToday).subtract(2, "day").toDate(), y: 81 }, + { x: moment(startOfToday).subtract(1, "day").toDate(), y: 59 }, + { x: moment(startOfToday).toDate(), y: 60 }, + ], + }, +]; +// Setting the widget dimensions and position (this is for gridster) +const positions: Record = { + [widgetConfigs[0].id]: { + cols: 6, + rows: 6, + y: 0, + x: 0, + }, + [widgetConfigs[1].id]: { + cols: 6, + rows: 6, + y: 0, + x: 6, + }, +}; +\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\`, + "widget-types/timeseries/timeseries-widget-status-bar-example/timeseries-widget-status-bar-example.component.ts": \\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\`// © 2022 SolarWinds Worldwide, LLC. All rights reserved. +// +// Permission is hereby granted, free of charge, to any person obtaining a copy +// of this software and associated documentation files (the "Software"), to +// deal in the Software without restriction, including without limitation the +// rights to use, copy, modify, merge, publish, distribute, sublicense, and/or +// sell copies of the Software, and to permit persons to whom the Software is +// furnished to do so, subject to the following conditions: +// +// The above copyright notice and this permission notice shall be included in +// all copies or substantial portions of the Software. +// +// THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR +// IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, +// FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE +// AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER +// LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, +// OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN +// THE SOFTWARE. + +import { + ChangeDetectorRef, + Component, + Injectable, + OnInit, +} from "@angular/core"; +import { GridsterConfig, GridsterItem } from "angular-gridster2"; +import keyBy from "lodash/keyBy"; +import moment, { Moment } from "moment/moment"; +import { BehaviorSubject } from "rxjs"; + +import { + DataSourceService, + IDataSource, + IDataSourceOutput, + INovaFilters, + ITimeframe, +} from "@nova-ui/bits"; +import { CHART_PALETTE_CS_S_EXTENDED } from "@nova-ui/charts"; +import { + applyStatusEndpoints, + DATA_SOURCE, + DEFAULT_PIZZAGNA_ROOT, + IDashboard, + IProviderConfiguration, + ISerializableTimeframe, + ITimeseriesItemConfiguration, + ITimeseriesOutput, + ITimeseriesScaleConfig, + ITimeseriesWidgetConfig, + ITimeseriesWidgetData, + ITimeseriesWidgetSeriesData, + ITimeseriesWidgetStatusData, + IWidget, + LegendPlacement, + PizzagnaLayer, + ProviderRegistryService, + TimeseriesChartPreset, + TimeseriesScaleType, + WellKnownPathKey, + WellKnownProviders, + WidgetTypesService, +} from "@nova-ui/dashboards"; + +/** + * A simple Timeseries data source implementation with continuous (non-interval-based) output + */ +@Injectable() +export class TimeseriesStatusContinuousDataSource + extends DataSourceService + implements IDataSource> +{ + public static providerId = "TimeseriesStatusContinuousDataSource"; + + public busy = new BehaviorSubject(false); + + public async getFilteredData( + filters: INovaFilters + ): Promise< + IDataSourceOutput> + > { + // In this example we're using some static mock data located at the bottom of this file. In a real-world + // scenario, the data for the chart would likely be retrieved via an asynchronous backend call. + const data = getContinuousData(); + let filteredData = data; + + this.busy.next(true); + + // Filtering using the filter registered by the TimeFrameBar + const timeframeFilter = filters.timeframe?.value as ITimeframe; + if (timeframeFilter) { + filteredData = filteredData.map((item: ITimeseriesWidgetData) => ({ + id: item.id, + name: item.name, + description: item.description, + data: item.data.filter( + (seriesData: ITimeseriesWidgetSeriesData) => + filterDates( + seriesData.x, + timeframeFilter.startDatetime, + timeframeFilter.endDatetime + ) + ), + })); + + // apply endpoints on the filtered status data so that when the status chart is zoomed (filtered), + // each status visualizations is ensured to have valid start and end values + filteredData = applyStatusEndpoints( + timeframeFilter, + filteredData, + data + ); + } + + this.busy.next(false); + return { result: { series: filteredData } }; + } +} + +/** + * A simple Timeseries data source implementation with interval-based output + */ +@Injectable() +export class TimeseriesStatusIntervalDataSource + extends DataSourceService + implements IDataSource> +{ + public static providerId = "TimeseriesStatusIntervalDataSource"; + + public busy = new BehaviorSubject(false); + + public async getFilteredData( + filters: INovaFilters + ): Promise< + IDataSourceOutput> + > { + // In this example we're using some static mock data located at the bottom of this file. In a real-world + // scenario, the data for the chart would likely be retrieved via an asynchronous backend call. + const data = getIntervalData(); + let filteredData = data; + + this.busy.next(true); + + // Filtering using the filter registered by the TimeFrameBar + const timeframeFilter = filters.timeframe?.value as ITimeframe; + if (timeframeFilter) { + filteredData = filteredData.map((item: ITimeseriesWidgetData) => ({ + id: item.id, + name: item.name, + description: item.description, + data: item.data.filter( + (seriesData: ITimeseriesWidgetSeriesData) => + filterDates( + seriesData.x, + timeframeFilter.startDatetime, + timeframeFilter.endDatetime + ) + ), + })); + + // Note: There's no need to apply filter endpoints to the status data in this case since we know it's visualized in regular intervals + } + + this.busy.next(false); + return { result: { series: filteredData } }; + } +} + +function filterDates(dateToCheck: Date, startDate: Moment, endDate: Moment) { + const mom = moment(dateToCheck); + return ( + mom.isBetween(startDate, endDate) || + mom.isSame(startDate) || + mom.isSame(endDate) + ); +} + +/** + * A component that instantiates the dashboard + */ +@Component({ + selector: "timeseries-widget-status-bar-example", + templateUrl: "./timeseries-widget-status-bar-example.component.html", + styleUrls: ["./timeseries-widget-status-bar-example.component.less"], + standalone: false, +}) +export class TimeseriesWidgetStatusBarExampleComponent implements OnInit { + // This variable will hold all the data needed to define the layout and behavior of the widgets. + // Pass this to the dashboard component's dashboard input in the template. + public dashboard: IDashboard | undefined; + + // Angular gridster requires a configuration object even if it's empty. + // Pass this to the dashboard component's gridsterConfig input in the template. + public gridsterConfig: GridsterConfig = {}; + + // Boolean passed as an input to the dashboard. When true, widgets can be moved, resized, removed, or edited + public editMode: boolean = false; + + constructor( + // WidgetTypesService provides the widget's necessary structure information + private widgetTypesService: WidgetTypesService, + + // In general, the ProviderRegistryService is used for making entities available for injection into dynamically loaded components. + private providerRegistry: ProviderRegistryService, + private changeDetectorRef: ChangeDetectorRef + ) {} + + public ngOnInit(): void { + // Grabbing the widget's default template which will be needed as a parameter for setNode + const widgetTemplate = this.widgetTypesService.getWidgetType( + "timeseries", + 1 + ); + // Registering our data sources as dropdown options in the widget editor/configurator + // Note: This could also be done in the parent module's constructor so that + // multiple dashboards could have access to the same widget template modification. + this.widgetTypesService.setNode( + // This is the template we grabbed above with getWidgetType + widgetTemplate, + // We are setting the editor/configurator part of the widget template + "configurator", + // This indicates which node you are changing and we want to change + // the data source providers available for selection in the editor. + WellKnownPathKey.DataSourceProviders, + // We are setting the data sources available for selection in the editor + [ + TimeseriesStatusContinuousDataSource.providerId, + TimeseriesStatusIntervalDataSource.providerId, + ] + ); + + // Registering the data source for injection into the widget. + this.providerRegistry.setProviders({ + [TimeseriesStatusContinuousDataSource.providerId]: { + provide: DATA_SOURCE, + useClass: TimeseriesStatusContinuousDataSource, + deps: [], + }, + [TimeseriesStatusIntervalDataSource.providerId]: { + provide: DATA_SOURCE, + useClass: TimeseriesStatusIntervalDataSource, + deps: [], + }, + }); + + this.initializeDashboard(); + } + + /** Used for restoring widgets state */ + public reInitializeDashboard(): void { + // destroys the components and their providers so the dashboard can re init data + this.dashboard = undefined; + this.changeDetectorRef.detectChanges(); + + this.initializeDashboard(); + } + + public initializeDashboard(): void { + // We're using a static configuration object for this example, but this is where + // the widget's configuration could potentially be populated from a database + const widgetsWithStructure = widgetConfigs.map((w) => + this.widgetTypesService.mergeWithWidgetType(w) + ); + const widgetsIndex = keyBy(widgetsWithStructure, (w: IWidget) => w.id); + + // Finally, assigning the variables we created above to the dashboard + this.dashboard = { + positions, + widgets: widgetsIndex, + }; + } +} + +const widgetConfigs: IWidget[] = [ + { + id: "statusChartWidgetId", + type: "timeseries", + pizzagna: { + [PizzagnaLayer.Configuration]: { + [DEFAULT_PIZZAGNA_ROOT]: { + providers: { + [WellKnownProviders.DataSource]: { + // Setting the initially selected data source providerId + providerId: + TimeseriesStatusContinuousDataSource.providerId, + } as IProviderConfiguration, + }, + }, + header: { + properties: { + title: "Status Bar Chart with Continuous (Non-Interval) Scale", + subtitle: "Basic Timeseries Widget", + }, + }, + chart: { + providers: { + [WellKnownProviders.Adapter]: { + properties: { + // Setting the series and corresponding labels to initially display on the chart + series: [ + { + id: "series-1", + label: "Node Status", + selectedSeriesId: "series-1", + }, + { + id: "series-2", + label: "Node Status", + selectedSeriesId: "series-2", + }, + ] as ITimeseriesItemConfiguration[], + }, + } as Partial, + }, + properties: { + configuration: { + legendPlacement: LegendPlacement.Right, + enableZoom: true, + // Setting the preset to status bar + preset: TimeseriesChartPreset.StatusBar, + } as ITimeseriesWidgetConfig, + }, + }, + timeframeSelection: { + properties: { + // Setting the initial timeframe selected in the timeframe bar + timeframe: { + selectedPresetId: "last7Days", + } as ISerializableTimeframe, + maxDate: moment().format(), + }, + }, + }, + }, + }, + { + id: "statusIntervalChartWidgetId", + type: "timeseries", + pizzagna: { + [PizzagnaLayer.Configuration]: { + [DEFAULT_PIZZAGNA_ROOT]: { + providers: { + [WellKnownProviders.DataSource]: { + // Setting the initially selected data source providerId + providerId: + TimeseriesStatusIntervalDataSource.providerId, + } as IProviderConfiguration, + }, + }, + header: { + properties: { + title: "Status Bar Chart with Interval Scale", + subtitle: "Basic Timeseries Widget", + }, + }, + chart: { + providers: { + [WellKnownProviders.Adapter]: { + properties: { + // Setting the series and corresponding labels to initially display on the chart + series: [ + { + id: "series-1", + label: "Node Status", + selectedSeriesId: "series-1", + }, + { + id: "series-2", + label: "Node Status", + selectedSeriesId: "series-2", + }, + ] as ITimeseriesItemConfiguration[], + }, + } as Partial, + }, + properties: { + configuration: { + legendPlacement: LegendPlacement.Right, + enableZoom: true, + // Setting the preset to status bar + preset: TimeseriesChartPreset.StatusBar, + scales: { + x: { + type: TimeseriesScaleType.TimeInterval, + properties: { + // one-day interval in seconds + interval: 24 * 60 * 60, + }, + } as ITimeseriesScaleConfig, + }, + } as ITimeseriesWidgetConfig, + }, + }, + timeframeSelection: { + properties: { + // Setting the initial timeframe selected in the timeframe bar + timeframe: { + selectedPresetId: "last7Days", + } as ISerializableTimeframe, + maxDate: moment().format(), + }, + }, + }, + }, + }, +]; + +export const startOfToday = (): Moment => moment().startOf("day"); + +export const getContinuousData = + (): ITimeseriesWidgetData[] => { + const series: ITimeseriesWidgetData[] = [ + { + id: "series-1", + name: "Node Status", + description: "lastchance.demo.lab", + data: [ + // the 'x' value is set to the time and 'y' to the status at that given time + { + x: startOfToday().subtract(20, "day").toDate(), + y: Status.Warning, + }, + { + x: startOfToday().subtract(19, "day").toDate(), + y: Status.Down, + }, + { + x: startOfToday().subtract(17, "day").toDate(), + y: Status.Critical, + }, + { + x: startOfToday().subtract(16, "day").toDate(), + y: Status.Warning, + }, + { + x: startOfToday().subtract(15, "day").toDate(), + y: Status.Down, + }, + { + x: startOfToday().subtract(14, "day").toDate(), + y: Status.Critical, + }, + { + x: startOfToday().subtract(12, "day").toDate(), + y: Status.Unknown, + }, + { + x: startOfToday().subtract(10, "day").toDate(), + y: Status.Up, + }, + { + x: startOfToday().subtract(9, "day").toDate(), + y: Status.Critical, + }, + { + x: startOfToday().subtract(6, "day").toDate(), + y: Status.Up, + }, + { + x: startOfToday().subtract(3, "day").toDate(), + y: Status.Warning, + }, + { + x: startOfToday().subtract(2, "day").toDate(), + y: Status.Critical, + }, + { + x: startOfToday().subtract(1, "day").toDate(), + y: Status.Up, + }, + // This data point will be ignored and is only here to provide an endpoint for the previous status. + { x: moment().toDate(), y: Status.Up }, + ], + }, + { + id: "series-2", + name: "Node Status", + description: "newhope.demo.lab", + data: [ + { + x: startOfToday().subtract(19, "day").toDate(), + y: Status.Critical, + }, + { + x: startOfToday().subtract(18, "day").toDate(), + y: Status.Unknown, + }, + { + x: startOfToday().subtract(17, "day").toDate(), + y: Status.Warning, + }, + { + x: startOfToday().subtract(15, "day").toDate(), + y: Status.Down, + }, + { + x: startOfToday().subtract(8, "day").toDate(), + y: Status.Critical, + }, + { + x: startOfToday().subtract(7, "day").toDate(), + y: Status.Down, + }, + { + x: startOfToday().subtract(6, "day").toDate(), + y: Status.Up, + }, + { + x: startOfToday().subtract(5, "day").toDate(), + y: Status.Critical, + }, + { + x: startOfToday().subtract(4, "day").toDate(), + y: Status.Up, + }, + { + x: startOfToday().subtract(3, "day").toDate(), + y: Status.Warning, + }, + { + x: startOfToday().subtract(2, "day").toDate(), + y: Status.Up, + }, + { + x: startOfToday().subtract(1, "day").toDate(), + y: Status.Down, + }, + // This data point will be ignored and is only here to provide an endpoint for the previous status. + { x: moment().toDate(), y: Status.Down }, + ], + }, + ]; + + for (const s of series) { + // here are we setting the color and icon associated to the status for each data point + s.data = s.data.map((d: any, i: number) => ({ + ...d, + color: statusColors[d.y as Status], + // The thickness of the line is dependant on the status. If the status equals 'Up' then 'thick' is set to false. + thick: d.y !== Status.Up, + icon: "status_" + d.y, + })); + } + + return series; + }; + +// Note that the output of this function is spaced evenly at one-day intervals +export const getIntervalData = + (): ITimeseriesWidgetData[] => { + const series: ITimeseriesWidgetData[] = [ + { + id: "series-1", + name: "Node Status", + description: "lastchance.demo.lab", + data: [ + // the 'x' value is set to the time and 'y' to the status at that given time + { + x: startOfToday().subtract(20, "day").toDate(), + y: Status.Up, + }, + { + x: startOfToday().subtract(19, "day").toDate(), + y: Status.Critical, + }, + { + x: startOfToday().subtract(18, "day").toDate(), + y: Status.Warning, + }, + { + x: startOfToday().subtract(17, "day").toDate(), + y: Status.Down, + }, + { + x: startOfToday().subtract(16, "day").toDate(), + y: Status.Critical, + }, + { + x: startOfToday().subtract(15, "day").toDate(), + y: Status.Down, + }, + { + x: startOfToday().subtract(14, "day").toDate(), + y: Status.Up, + }, + { + x: startOfToday().subtract(13, "day").toDate(), + y: Status.Warning, + }, + { + x: startOfToday().subtract(12, "day").toDate(), + y: Status.Up, + }, + { + x: startOfToday().subtract(11, "day").toDate(), + y: Status.Critical, + }, + { + x: startOfToday().subtract(10, "day").toDate(), + y: Status.Up, + }, + { + x: startOfToday().subtract(9, "day").toDate(), + y: Status.Down, + }, + { + x: startOfToday().subtract(8, "day").toDate(), + y: Status.Critical, + }, + { + x: startOfToday().subtract(7, "day").toDate(), + y: Status.Warning, + }, + { + x: startOfToday().subtract(6, "day").toDate(), + y: Status.Down, + }, + { + x: startOfToday().subtract(5, "day").toDate(), + y: Status.Critical, + }, + { + x: startOfToday().subtract(4, "day").toDate(), + y: Status.Down, + }, + { + x: startOfToday().subtract(3, "day").toDate(), + y: Status.Up, + }, + { + x: startOfToday().subtract(2, "day").toDate(), + y: Status.Warning, + }, + { + x: startOfToday().subtract(1, "day").toDate(), + y: Status.Critical, + }, + { x: startOfToday().toDate(), y: Status.Up }, + ], + }, + { + id: "series-2", + name: "Node Status", + description: "newhope.demo.lab", + data: [ + { + x: startOfToday().subtract(20, "day").toDate(), + y: Status.Up, + }, + { + x: startOfToday().subtract(19, "day").toDate(), + y: Status.Up, + }, + { + x: startOfToday().subtract(18, "day").toDate(), + y: Status.Down, + }, + { + x: startOfToday().subtract(17, "day").toDate(), + y: Status.Critical, + }, + { + x: startOfToday().subtract(16, "day").toDate(), + y: Status.Down, + }, + { + x: startOfToday().subtract(15, "day").toDate(), + y: Status.Up, + }, + { + x: startOfToday().subtract(14, "day").toDate(), + y: Status.Critical, + }, + { + x: startOfToday().subtract(13, "day").toDate(), + y: Status.Up, + }, + { + x: startOfToday().subtract(12, "day").toDate(), + y: Status.Critical, + }, + { + x: startOfToday().subtract(11, "day").toDate(), + y: Status.Warning, + }, + { + x: startOfToday().subtract(10, "day").toDate(), + y: Status.Up, + }, + { + x: startOfToday().subtract(9, "day").toDate(), + y: Status.Down, + }, + { + x: startOfToday().subtract(8, "day").toDate(), + y: Status.Up, + }, + { + x: startOfToday().subtract(7, "day").toDate(), + y: Status.Down, + }, + { + x: startOfToday().subtract(6, "day").toDate(), + y: Status.Critical, + }, + { + x: startOfToday().subtract(5, "day").toDate(), + y: Status.Down, + }, + { + x: startOfToday().subtract(4, "day").toDate(), + y: Status.Up, + }, + { + x: startOfToday().subtract(3, "day").toDate(), + y: Status.Critical, + }, + { + x: startOfToday().subtract(2, "day").toDate(), + y: Status.Up, + }, + { + x: startOfToday().subtract(1, "day").toDate(), + y: Status.Warning, + }, + { x: startOfToday().toDate(), y: Status.Critical }, + ], + }, + ]; + + for (const s of series) { + // here are we setting the color and icon associated to the status for each data point + s.data = s.data.map((d: any, i: number) => ({ + ...d, + color: statusColors[d.y as Status], + // The thickness of the line is dependant on the status. If the status equals 'Up' then 'thick' is set to false. + thick: d.y !== Status.Up, + icon: "status_" + d.y, + })); + } + + return series; + }; + +// An enumeration of statuses +enum Status { + Unknown = "unknown", + Up = "up", + Warning = "warning", + Down = "down", + Critical = "critical", +} + +// This is the map used for setting the color of each status bar +const statusColors: Record = { + [Status.Unknown]: CHART_PALETTE_CS_S_EXTENDED[6], + [Status.Up]: CHART_PALETTE_CS_S_EXTENDED[8], + [Status.Warning]: CHART_PALETTE_CS_S_EXTENDED[4], + [Status.Down]: CHART_PALETTE_CS_S_EXTENDED[0], + [Status.Critical]: CHART_PALETTE_CS_S_EXTENDED[2], +}; + +// Setting the widget dimensions and position (this is for gridster) +const positions: Record = { + [widgetConfigs[0].id]: { + cols: 12, + rows: 4, + y: 0, + x: 0, + }, + [widgetConfigs[1].id]: { + cols: 12, + rows: 4, + y: 4, + x: 0, + }, +}; +\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\`, + "widget-types/view-components/kpi-tile-view-basic/kpi-tile-view-basic-example.component.ts": \\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\`// © 2022 SolarWinds Worldwide, LLC. All rights reserved. +// +// Permission is hereby granted, free of charge, to any person obtaining a copy +// of this software and associated documentation files (the "Software"), to +// deal in the Software without restriction, including without limitation the +// rights to use, copy, modify, merge, publish, distribute, sublicense, and/or +// sell copies of the Software, and to permit persons to whom the Software is +// furnished to do so, subject to the following conditions: +// +// The above copyright notice and this permission notice shall be included in +// all copies or substantial portions of the Software. +// +// THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR +// IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, +// FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE +// AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER +// LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, +// OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN +// THE SOFTWARE. + +import { Component } from "@angular/core"; +import { FormControl } from "@angular/forms"; + +type KpiTileState = "normal" | "loading" | "empty"; + +/** + * KPI Tile View - Playground example. + * Switch between all visual states (normal / loading / empty) and toggle + * interactivity to explore every variant the standalone tile supports. + */ +@Component({ + selector: "kpi-tile-view-basic-example", + templateUrl: "./kpi-tile-view-basic-example.component.html", + standalone: false, +}) +export class KpiTileViewBasicExampleComponent { + public readonly stateControl = new FormControl("normal", { + nonNullable: true, + }); + public interactive = false; + public lastClicked = ""; + + public readonly stateOptions: KpiTileState[] = ["normal", "loading", "empty"]; + + public onTileClick(label: string): void { + this.lastClicked = label; + } +} +\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\`, + "widget-types/view-components/kpi-tile-view-interactive/kpi-tile-view-interactive-example.component.ts": \\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\`// © 2022 SolarWinds Worldwide, LLC. All rights reserved. +// +// Permission is hereby granted, free of charge, to any person obtaining a copy +// of this software and associated documentation files (the "Software"), to +// deal in the Software without restriction, including without limitation the +// rights to use, copy, modify, merge, publish, distribute, sublicense, and/or +// sell copies of the Software, and to permit persons to whom the Software is +// furnished to do so, subject to the following conditions: +// +// The above copyright notice and this permission notice shall be included in +// all copies or substantial portions of the Software. +// +// THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR +// IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, +// FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE +// AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER +// LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, +// OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN +// THE SOFTWARE. + +import { Component, TemplateRef, ViewChild } from "@angular/core"; + +/** + * Interactive KPI Tile View example with custom value formatting + * and click event handling. + */ +@Component({ + selector: "kpi-tile-view-interactive-example", + templateUrl: "./kpi-tile-view-interactive-example.component.html", + standalone: false, +}) +export class KpiTileViewInteractiveExampleComponent { + public currentValue = 1_247; + public lastClickedTile = ""; + + @ViewChild("customValueTpl", { static: true }) + public customValueTpl: TemplateRef; + + public onTileClick(): void { + this.lastClickedTile = "Active Sessions"; + } + + public onUptimeClick(): void { + this.lastClickedTile = "Uptime"; + } +} +\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\`, + "widget-types/view-components/proportional-chart-view-playground/proportional-chart-view-playground-example.component.ts": \\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\`// © 2022 SolarWinds Worldwide, LLC. All rights reserved. +// +// Permission is hereby granted, free of charge, to any person obtaining a copy +// of this software and associated documentation files (the "Software"), to +// deal in the Software without restriction, including without limitation the +// rights to use, copy, modify, merge, publish, distribute, sublicense, and/or +// sell copies of the Software, and to permit persons to whom the Software is +// furnished to do so, subject to the following conditions: +// +// The above copyright notice and this permission notice shall be included in +// all copies or substantial portions of the Software. +// +// THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR +// IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, +// FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE +// AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER +// LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, +// OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN +// THE SOFTWARE. + +import { Component } from "@angular/core"; +import { FormControl } from "@angular/forms"; + +import { IProportionalDataItem } from "@nova-ui/dashboards"; + +type ProportionalChartType = "donut" | "pie" | "verticalBar" | "horizontalBar"; +type LegendPlacement = "right" | "bottom" | "none"; + +/** + * Proportional Chart View - Playground example. + * Lets you switch between all supported chart types and legend placements + * to see every visual variant the standalone view component provides. + */ +@Component({ + selector: "proportional-chart-view-playground-example", + templateUrl: "./proportional-chart-view-playground-example.component.html", + standalone: false, +}) +export class ProportionalChartViewPlaygroundExampleComponent { + public readonly chartTypeControl = new FormControl( + "donut", + { nonNullable: true } + ); + public readonly legendPlacementControl = new FormControl( + "right", + { nonNullable: true } + ); + + public readonly chartTypeOptions: ProportionalChartType[] = [ + "donut", + "pie", + "verticalBar", + "horizontalBar", + ]; + public readonly legendPlacementOptions: LegendPlacement[] = [ + "right", + "bottom", + "none", + ]; + + public colors: Record = { + down: "#dc3545", + up: "#2cc079", + warning: "#f3a002", + unknown: "#707070", + }; + + public chartData: Array = [ + { id: "up", name: "Up", value: 78 }, + { id: "down", name: "Down", value: 8 }, + { id: "warning", name: "Warning", value: 12 }, + { id: "unknown", name: "Unknown", value: 2 }, + ]; +} +\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\`, + "widget-types/view-components/view-components-docs.component.ts": \\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\`// © 2022 SolarWinds Worldwide, LLC. All rights reserved. +// +// Permission is hereby granted, free of charge, to any person obtaining a copy +// of this software and associated documentation files (the "Software"), to +// deal in the Software without restriction, including without limitation the +// rights to use, copy, modify, merge, publish, distribute, sublicense, and/or +// sell copies of the Software, and to permit persons to whom the Software is +// furnished to do so, subject to the following conditions: +// +// The above copyright notice and this permission notice shall be included in +// all copies or substantial portions of the Software. +// +// THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR +// IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, +// FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE +// AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER +// LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, +// OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN +// THE SOFTWARE. + +import { Component } from "@angular/core"; + +@Component({ + selector: "nui-view-components-docs", + templateUrl: "./view-components-docs.component.html", + standalone: false, +}) +export class ViewComponentsDocsComponent {} +\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\`, + "widget-types/view-components/view-components-docs.module.ts": \\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\`// © 2022 SolarWinds Worldwide, LLC. All rights reserved. +// +// Permission is hereby granted, free of charge, to any person obtaining a copy +// of this software and associated documentation files (the "Software"), to +// deal in the Software without restriction, including without limitation the +// rights to use, copy, modify, merge, publish, distribute, sublicense, and/or +// sell copies of the Software, and to permit persons to whom the Software is +// furnished to do so, subject to the following conditions: +// +// The above copyright notice and this permission notice shall be included in +// all copies or substantial portions of the Software. +// +// THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR +// IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, +// FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE +// AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER +// LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, +// OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN +// THE SOFTWARE. + +import { CommonModule } from "@angular/common"; +import { NgModule } from "@angular/core"; +import { ReactiveFormsModule } from "@angular/forms"; +import { RouterModule, Routes } from "@angular/router"; + +import { + NuiDocsModule, + NuiIconModule, + NuiMessageModule, + NuiFormFieldModule, + NuiSelectV2Module, + NuiSwitchModule, + DEMO_PATH_TOKEN, +} from "@nova-ui/bits"; +import { NuiDashboardViewsModule } from "@nova-ui/dashboards"; + +import { getDemoFiles } from "../../../../demo-files-factory"; +import { KpiTileViewBasicExampleComponent } from "./kpi-tile-view-basic/kpi-tile-view-basic-example.component"; +import { KpiTileViewInteractiveExampleComponent } from "./kpi-tile-view-interactive/kpi-tile-view-interactive-example.component"; +import { ProportionalChartViewPlaygroundExampleComponent } from "./proportional-chart-view-playground/proportional-chart-view-playground-example.component"; +import { ViewComponentsDocsComponent } from "./view-components-docs.component"; + +const routes: Routes = [ + { + path: "", + component: ViewComponentsDocsComponent, + data: { + srlc: { + hideIndicator: true, + }, + showThemeSwitcher: true, + }, + }, + { + path: "kpi-tile-view-basic", + component: KpiTileViewBasicExampleComponent, + data: { + srlc: { + hideIndicator: true, + }, + }, + }, + { + path: "proportional-chart-view-playground", + component: ProportionalChartViewPlaygroundExampleComponent, + data: { + srlc: { + hideIndicator: true, + }, + }, + }, +]; + +@NgModule({ + imports: [ + CommonModule, + ReactiveFormsModule, + RouterModule.forChild(routes), + NuiDocsModule, + NuiMessageModule, + NuiIconModule, + NuiFormFieldModule, + NuiSelectV2Module, + NuiSwitchModule, + NuiDashboardViewsModule, + ], + declarations: [ + ViewComponentsDocsComponent, + KpiTileViewBasicExampleComponent, + KpiTileViewInteractiveExampleComponent, + ProportionalChartViewPlaygroundExampleComponent, + ], + providers: [ + { + provide: DEMO_PATH_TOKEN, + useValue: getDemoFiles("view-components"), + }, + ], +}) +export default class ViewComponentsDocsModule {} +\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\`, + "widget-types/widget-types.module.ts": \\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\`// © 2022 SolarWinds Worldwide, LLC. All rights reserved. +// +// Permission is hereby granted, free of charge, to any person obtaining a copy +// of this software and associated documentation files (the "Software"), to +// deal in the Software without restriction, including without limitation the +// rights to use, copy, modify, merge, publish, distribute, sublicense, and/or +// sell copies of the Software, and to permit persons to whom the Software is +// furnished to do so, subject to the following conditions: +// +// The above copyright notice and this permission notice shall be included in +// all copies or substantial portions of the Software. +// +// THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR +// IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, +// FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE +// AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER +// LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, +// OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN +// THE SOFTWARE. + +import { NgModule, Type } from "@angular/core"; +import { RouterModule, Routes } from "@angular/router"; + +import { NuiDocsModule } from "@nova-ui/bits"; +import { + ConfiguratorHeadingService, + NuiDashboardsModule, +} from "@nova-ui/dashboards"; + +export enum WidgetTypesRoute { + kpi = "kpi", + riskScore = "risk-score", + timeseries = "timeseries", + table = "table", + proportional = "proportional", + embedded = "embedded", + drilldown = "drilldown", + viewComponents = "view-components", +} + +const routes: Routes = [ + { + path: WidgetTypesRoute.kpi, + loadChildren: async () => + import("./kpi/kpi-docs.module") as object as Promise>, + data: { + srlc: { + hideIndicator: true, + }, + }, + }, + { + path: WidgetTypesRoute.riskScore, + loadChildren: async () => + import("./risk-score/risk-score-docs.module") as object as Promise< + Type + >, + data: { + srlc: { + hideIndicator: true, + }, + }, + }, + { + path: WidgetTypesRoute.timeseries, + loadChildren: async () => + import("./timeseries/timeseries-docs.module") as object as Promise< + Type + >, + data: { + srlc: { + hideIndicator: true, + }, + }, + }, + { + path: WidgetTypesRoute.table, + loadChildren: async () => + import("./table/table-docs.module") as object as Promise>, + data: { + srlc: { + hideIndicator: true, + }, + }, + }, + { + path: WidgetTypesRoute.proportional, + loadChildren: async () => + import( + "./proportional/proportional-docs.module" + ) as object as Promise>, + data: { + srlc: { + hideIndicator: true, + }, + }, + }, + { + path: WidgetTypesRoute.embedded, + loadChildren: async () => + import( + "./embedded-content/embedded-content-docs.module" + ) as object as Promise>, + data: { + srlc: { + hideIndicator: true, + }, + }, + }, + { + path: WidgetTypesRoute.drilldown, + loadChildren: async () => + import( + "./drilldown/drilldown-widget-docs.module" + ) as object as Promise>, + data: { + srlc: { + hideIndicator: true, + }, + }, + }, + { + path: WidgetTypesRoute.viewComponents, + loadChildren: async () => + import( + "./view-components/view-components-docs.module" + ) as object as Promise>, + data: { + srlc: { + hideIndicator: true, + }, + }, + }, +]; + +@NgModule({ + imports: [ + RouterModule.forChild(routes), + NuiDocsModule, + NuiDashboardsModule, + ], + providers: [ConfiguratorHeadingService], +}) +export default class WidgetTypesModule {} +\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\`, +}; +\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\`, + "overview/hero/dashboard/hero-dashboard.component.ts": \\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\`// © 2022 SolarWinds Worldwide, LLC. All rights reserved. +// +// Permission is hereby granted, free of charge, to any person obtaining a copy +// of this software and associated documentation files (the "Software"), to +// deal in the Software without restriction, including without limitation the +// rights to use, copy, modify, merge, publish, distribute, sublicense, and/or +// sell copies of the Software, and to permit persons to whom the Software is +// furnished to do so, subject to the following conditions: +// +// The above copyright notice and this permission notice shall be included in +// all copies or substantial portions of the Software. +// +// THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR +// IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, +// FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE +// AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER +// LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, +// OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN +// THE SOFTWARE. + +import { HttpClient } from "@angular/common/http"; +import { + ChangeDetectionStrategy, + Component, + OnInit, + ViewEncapsulation, +} from "@angular/core"; +import keyBy from "lodash/keyBy"; + +import { LoggerService, ThemeSwitchService } from "@nova-ui/bits"; +import { + DATA_SOURCE, + IDashboard, + IWidget, + ProviderRegistryService, + WidgetTypesService, +} from "@nova-ui/dashboards"; + +import { positions, widgets } from "./widget-configs"; +import { + HarryPotterAverageRatingDataSource, + HarryPotterRatingsCountDataSource, +} from "../data/kpi-datasources"; +import { + BeerReviewCountsByCityMockDataSource, + BeerReviewCountsByCityMockDataSource2, +} from "../data/proportional-datasources"; +import { BeerDataSource } from "../data/table/beer-data-source"; +import { RandomUserDataSource } from "../data/table/random-user-data-source"; +import { + BeerVsReadingMockDataSource, + LoungingVsFrisbeeGolfMockDataSource, +} from "../data/timeseries-data-sources"; + +/** + * A component that instantiates the dashboard + */ +@Component({ + selector: "hero-dashboard", + templateUrl: "./hero-dashboard.component.html", + styleUrls: ["./hero-dashboard.component.less"], + encapsulation: ViewEncapsulation.Emulated, + changeDetection: ChangeDetectionStrategy.Default, + standalone: false, +}) +export class HeroDashboardComponent implements OnInit { + public dashboard: IDashboard = { + positions: {}, + widgets: {}, + }; + + public gridsterConfig = {}; + public editMode = false; + + constructor( + private providerRegistry: ProviderRegistryService, + public themeSwitcherService: ThemeSwitchService, + private widgetTypesService: WidgetTypesService + ) { + this.providerRegistry.setProviders({ + [HarryPotterAverageRatingDataSource.providerId]: { + provide: DATA_SOURCE, + useClass: HarryPotterAverageRatingDataSource, + deps: [HttpClient], + }, + [HarryPotterRatingsCountDataSource.providerId]: { + provide: DATA_SOURCE, + useClass: HarryPotterRatingsCountDataSource, + deps: [HttpClient], + }, + [BeerReviewCountsByCityMockDataSource.providerId]: { + provide: DATA_SOURCE, + useClass: BeerReviewCountsByCityMockDataSource, + deps: [], + }, + [BeerReviewCountsByCityMockDataSource2.providerId]: { + provide: DATA_SOURCE, + useClass: BeerReviewCountsByCityMockDataSource2, + deps: [], + }, + [RandomUserDataSource.providerId]: { + provide: DATA_SOURCE, + useClass: RandomUserDataSource, + deps: [LoggerService], + }, + [BeerDataSource.providerId]: { + provide: DATA_SOURCE, + useClass: BeerDataSource, + deps: [LoggerService], + }, + [BeerVsReadingMockDataSource.providerId]: { + provide: DATA_SOURCE, + useClass: BeerVsReadingMockDataSource, + deps: [], + }, + [LoungingVsFrisbeeGolfMockDataSource.providerId]: { + provide: DATA_SOURCE, + useClass: LoungingVsFrisbeeGolfMockDataSource, + deps: [], + }, + }); + } + + public ngOnInit(): void { + const widgetsWithStructure = widgets.map((w) => ({ + ...w, + pizzagna: { + ...this.widgetTypesService.getWidgetType(w.type, w.version) + .widget, + ...w.pizzagna, + }, + })); + const widgetsIndex = keyBy(widgetsWithStructure, (w: IWidget) => w.id); + + this.dashboard = { + positions: positions, + widgets: widgetsIndex, + }; + } +} +\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\`, + "overview/hero/dashboard/widget-configs.ts": \\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\`// © 2022 SolarWinds Worldwide, LLC. All rights reserved. +// +// Permission is hereby granted, free of charge, to any person obtaining a copy +// of this software and associated documentation files (the "Software"), to +// deal in the Software without restriction, including without limitation the +// rights to use, copy, modify, merge, publish, distribute, sublicense, and/or +// sell copies of the Software, and to permit persons to whom the Software is +// furnished to do so, subject to the following conditions: +// +// The above copyright notice and this permission notice shall be included in +// all copies or substantial portions of the Software. +// +// THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR +// IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, +// FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE +// AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER +// LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, +// OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN +// THE SOFTWARE. + +import { GridsterItem } from "angular-gridster2"; + +import { IWidget } from "@nova-ui/dashboards"; + +import { kpiConfig } from "../widget-configs/kpi"; +import { proportionalConfig } from "../widget-configs/proportional"; +import { tableConfig } from "../widget-configs/table"; +import { timeseriesConfig } from "../widget-configs/timeseries"; + +export const positions: Record = { + [tableConfig.id]: { + cols: 7, + rows: 7, + y: 0, + x: 0, + }, + [proportionalConfig.id]: { + cols: 5, + rows: 7, + y: 0, + x: 7, + }, + [kpiConfig.id]: { + cols: 6, + rows: 7, + y: 7, + x: 0, + }, + [timeseriesConfig.id]: { + cols: 6, + rows: 7, + y: 7, + x: 6, + }, +}; + +export const widgets: IWidget[] = [ + { + ...tableConfig, + }, + { + ...proportionalConfig, + }, + { + ...kpiConfig, + }, + { + ...timeseriesConfig, + }, +]; +\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\`, + "overview/hero/data/kpi-datasources.ts": \\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\`// © 2022 SolarWinds Worldwide, LLC. All rights reserved. +// +// Permission is hereby granted, free of charge, to any person obtaining a copy +// of this software and associated documentation files (the "Software"), to +// deal in the Software without restriction, including without limitation the +// rights to use, copy, modify, merge, publish, distribute, sublicense, and/or +// sell copies of the Software, and to permit persons to whom the Software is +// furnished to do so, subject to the following conditions: +// +// The above copyright notice and this permission notice shall be included in +// all copies or substantial portions of the Software. +// +// THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR +// IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, +// FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE +// AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER +// LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, +// OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN +// THE SOFTWARE. + +import { HttpClient, HttpErrorResponse } from "@angular/common/http"; +import { Injectable, OnDestroy } from "@angular/core"; +import { BehaviorSubject } from "rxjs"; +import { finalize } from "rxjs/operators"; + +import { DataSourceService, IFilteringOutputs } from "@nova-ui/bits"; +import { IKpiData } from "@nova-ui/dashboards"; + +import { GOOGLE_BOOKS_URL } from "./table/constants"; + +@Injectable() +export class HarryPotterAverageRatingDataSource + extends DataSourceService + implements OnDestroy +{ + public static providerId = "HarryPotterAverageRatingDataSource"; + + public busy = new BehaviorSubject(false); + + constructor(private http: HttpClient) { + super(); + } + + public async getFilteredData(): Promise { + this.busy.next(true); + return new Promise((resolve) => { + // *** Make a resource request to an external API (if needed) + this.http + .get(\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\`\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\${GOOGLE_BOOKS_URL}/5MQFrgEACAAJ\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\`) + .pipe(finalize(() => this.busy.next(false))) + .subscribe({ + next: (data: any) => { + resolve({ + result: { + value: data.volumeInfo.averageRating, + }, + }); + }, + error: (error: HttpErrorResponse) => { + resolve({ + result: null, + error: { + type: error.status, + }, + }); + }, + }); + }); + } + + public ngOnDestroy(): void { + this.outputsSubject.complete(); + } +} + +@Injectable() +export class HarryPotterRatingsCountDataSource + extends DataSourceService + implements OnDestroy +{ + public static providerId = "HarryPotterRatingsCountDataSource"; + + public busy = new BehaviorSubject(false); + + constructor(private http: HttpClient) { + super(); + } + + public async getFilteredData(): Promise { + this.busy.next(true); + return new Promise((resolve) => { + this.http + .get(\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\`\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\${GOOGLE_BOOKS_URL}/5MQFrgEACAAJ\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\`) + .pipe(finalize(() => this.busy.next(false))) + .subscribe({ + next: (data: any) => { + resolve({ + result: { + value: data.volumeInfo.ratingsCount, + }, + }); + }, + error: (error: HttpErrorResponse) => { + resolve({ + result: null, + error: { + type: error.status, + }, + }); + }, + }); + }); + } + + public ngOnDestroy(): void { + this.outputsSubject.complete(); + } +} +\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\`, + "overview/hero/data/proportional-datasources.ts": \\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\`// © 2022 SolarWinds Worldwide, LLC. All rights reserved. +// +// Permission is hereby granted, free of charge, to any person obtaining a copy +// of this software and associated documentation files (the "Software"), to +// deal in the Software without restriction, including without limitation the +// rights to use, copy, modify, merge, publish, distribute, sublicense, and/or +// sell copies of the Software, and to permit persons to whom the Software is +// furnished to do so, subject to the following conditions: +// +// The above copyright notice and this permission notice shall be included in +// all copies or substantial portions of the Software. +// +// THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR +// IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, +// FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE +// AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER +// LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, +// OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN +// THE SOFTWARE. + +import { Injectable, OnDestroy } from "@angular/core"; +import { BehaviorSubject } from "rxjs"; + +import { + DataSourceService, + IDataSource, + IFilteringOutputs, +} from "@nova-ui/bits"; + +import { + getMockBeerReviewCountsByCity, + getMockBeerReviewCountsByCity2, + IProportionalWidgetData, +} from "./widget-data"; + +@Injectable() +export class BeerReviewCountsByCityMockDataSource + extends DataSourceService + implements IDataSource, OnDestroy +{ + // This is the ID we'll use to identify the provider + public static providerId = "BeerReviewCountsByCityMockDataSource"; + public busy = new BehaviorSubject(false); + + public async getFilteredData(): Promise { + this.busy.next(true); + return new Promise((resolve) => { + setTimeout(() => { + this.outputsSubject.next({ + result: getMockBeerReviewCountsByCity(), + }); + this.busy.next(false); + }, 300); + }); + } + + public ngOnDestroy(): void { + this.outputsSubject.complete(); + } +} + +@Injectable() +export class BeerReviewCountsByCityMockDataSource2 + extends DataSourceService + implements IDataSource, OnDestroy +{ + // This is the ID we'll use to identify the provider + public static providerId = "BeerReviewCountsByCityMockDataSource2"; + public busy = new BehaviorSubject(false); + + public async getFilteredData(): Promise { + this.busy.next(true); + return new Promise((resolve) => { + setTimeout(() => { + this.outputsSubject.next({ + result: getMockBeerReviewCountsByCity2(), + }); + this.busy.next(false); + }, 1500); + }); + } + + public ngOnDestroy(): void { + this.outputsSubject.complete(); + } +} +\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\`, + "overview/hero/data/table/beer-data-source.ts": \\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\`// © 2022 SolarWinds Worldwide, LLC. All rights reserved. +// +// Permission is hereby granted, free of charge, to any person obtaining a copy +// of this software and associated documentation files (the "Software"), to +// deal in the Software without restriction, including without limitation the +// rights to use, copy, modify, merge, publish, distribute, sublicense, and/or +// sell copies of the Software, and to permit persons to whom the Software is +// furnished to do so, subject to the following conditions: +// +// The above copyright notice and this permission notice shall be included in +// all copies or substantial portions of the Software. +// +// THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR +// IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, +// FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE +// AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER +// LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, +// OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN +// THE SOFTWARE. + +import { ListRange } from "@angular/cdk/collections"; +import { Injectable } from "@angular/core"; +import isEqual from "lodash/isEqual"; +import orderBy from "lodash/orderBy"; +import { BehaviorSubject } from "rxjs"; + +import { + DataSourceService, + IDataField, + INovaFilteringOutputs, + INovaFilters, + ISorterFilter, + LoggerService, +} from "@nova-ui/bits"; + +import { IBrewDatasourceResponse, IBrewInfo } from "../types"; +import { BREW_API_URL } from "./constants"; + +@Injectable() +export class BeerDataSource extends DataSourceService { + public static providerId = "BeerDataSource"; + + private cache = Array.from({ length: 0 }); + private lastSortValue?: ISorterFilter; + private lastVirtualScroll?: ListRange; + private totalItems: number = 325; + + public page: number = 1; + public busy = new BehaviorSubject(false); + + public dataFields: Array = [ + { id: "id", label: "No", dataType: "number" }, + { id: "name", label: "Name", dataType: "string" }, + { id: "tagline", label: "Tagline", dataType: "string" }, + { id: "first_brewed", label: "First Brewed", dataType: "string" }, + { id: "description", label: "Description", dataType: "string" }, + { id: "brewers_tips", label: "Brewer's Tips", dataType: "string" }, + ]; + + constructor(private logger: LoggerService) { + super(); + } + + public async getFilteredData( + filters: INovaFilters + ): Promise { + const start = filters.virtualScroll?.value?.start ?? 0; + const end = filters.virtualScroll?.value?.end ?? 0; + const delta = end - start; + + // This condition handles sorting. We want to sort columns without fetching another chunk of data. + // Since the data is being fetched when scrolled, we compare virtual scroll indexes here in the condition as well. + if (filters.sorter?.value) { + if ( + !isEqual(this.lastSortValue, filters.sorter.value) && + isEqual(this.lastVirtualScroll, filters.virtualScroll?.value) + ) { + const totalPages = Math.ceil( + delta ? this.totalItems / delta : 1 + ); + const itemsPerPage: number = Math.max( + delta < 80 ? delta : 80, + 1 + ); + let response: Array | null = null; + let map: IBrewDatasourceResponse; + + if (filters.sorter?.value?.direction === "desc") { + this.cache = []; + for (let i = 0; i < this.page; ++i) { + response = await ( + await fetch( + \\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\`\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\${BREW_API_URL}/?page=\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\${ + totalPages - i || 1 + }&per_page=\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\${itemsPerPage}\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\` + ) + ).json(); + + // since the last page contains only 5 items we need to fetch another page to give virtual scroll enough space to work + if (response && response.length < itemsPerPage) { + this.page++; + } + map = { + brewInfo: response?.map((result: IBrewInfo) => ({ + id: result.id, + name: result.name, + tagline: result.tagline, + first_brewed: result.first_brewed, + description: result.description, + brewers_tips: result.brewers_tips, + })), + total: response?.length, + } as IBrewDatasourceResponse; + this.cache = + totalPages - i !== 0 + ? this.cache.concat(map.brewInfo) + : this.cache; + } + } + + if (filters.sorter?.value?.direction === "asc") { + this.cache = []; + for (let i = 0; i < this.page; i++) { + response = await ( + await fetch( + \\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\`\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\${BREW_API_URL}/?page=\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\${ + i + 1 + }&per_page=\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\${itemsPerPage}\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\` + ) + ).json(); + map = { + brewInfo: response?.map((result: IBrewInfo) => ({ + id: result.id, + name: result.name, + tagline: result.tagline, + first_brewed: result.first_brewed, + description: result.description, + brewers_tips: result.brewers_tips, + })), + total: response?.length, + } as IBrewDatasourceResponse; + this.cache = this.cache.concat(map.brewInfo); + } + } + + this.lastSortValue = filters.sorter?.value; + this.lastVirtualScroll = filters.virtualScroll?.value; + + return { + repeat: { itemsSource: this.sortData(this.cache, filters) }, + paginator: { total: this.totalItems }, + dataFields: this.dataFields, + }; + } + } + + this.busy.next(true); + return new Promise((resolve) => { + setTimeout(() => { + this.getData(start, end, filters).then( + (response: INovaFilteringOutputs) => { + if (!response) { + return; + } + + this.cache = this.cache.concat(response.brewInfo); + + this.dataSubject.next(this.cache); + resolve({ + repeat: { + itemsSource: this.sortData(this.cache, filters), + }, + paginator: { total: this.totalItems }, + dataFields: this.dataFields, + }); + + this.lastSortValue = filters.sorter?.value; + this.lastVirtualScroll = filters.virtualScroll?.value; + this.busy.next(false); + } + ); + }, 500); + }); + } + + public async getData( + start: number = 0, + end: number = 20, + filters: INovaFilters + ): Promise { + const delta = end - start; + const totalPages = Math.ceil(delta ? this.totalItems / delta : 1); + let response: Array | null = null; + // The api.punk.com is able to return only 80 items per page + const itemsPerPage: number = Math.max(delta < 80 ? delta : 80, 1); + + if (filters.sorter?.value?.direction === "asc") { + response = await ( + await fetch( + \\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\`\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\${BREW_API_URL}/?page=\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\${this.page}&per_page=\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\${itemsPerPage}\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\` + ) + ).json(); + } + + if (filters.sorter?.value?.direction === "desc") { + response = await ( + await fetch( + \\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\`\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\${BREW_API_URL}/?page=\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\${ + totalPages - this.page + }&per_page=\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\${itemsPerPage}\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\` + ) + ).json(); + } + + if (!filters.sorter) { + response = await ( + await fetch( + \\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\`\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\${BREW_API_URL}/?page=\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\${this.page}&per_page=\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\${itemsPerPage}\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\` + ) + ).json(); + } + return { + brewInfo: response?.map((result: IBrewInfo, i: number) => ({ + id: result.id, + name: result.name, + tagline: result.tagline, + first_brewed: result.first_brewed, + description: result.description, + brewers_tips: result.brewers_tips, + })), + total: response?.length, + } as IBrewDatasourceResponse; + } + + private sortData(data: IBrewInfo[], filters: INovaFilters) { + return orderBy( + data, + filters.sorter?.value?.sortBy, + filters.sorter?.value?.direction as "desc" | "asc" + ); + } +} +\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\`, + "overview/hero/data/table/constants.ts": \\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\`// © 2022 SolarWinds Worldwide, LLC. All rights reserved. +// +// Permission is hereby granted, free of charge, to any person obtaining a copy +// of this software and associated documentation files (the "Software"), to +// deal in the Software without restriction, including without limitation the +// rights to use, copy, modify, merge, publish, distribute, sublicense, and/or +// sell copies of the Software, and to permit persons to whom the Software is +// furnished to do so, subject to the following conditions: +// +// The above copyright notice and this permission notice shall be included in +// all copies or substantial portions of the Software. +// +// THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR +// IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, +// FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE +// AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER +// LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, +// OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN +// THE SOFTWARE. + +export const corsProxy = "https://cors-anywhere.herokuapp.com"; +export const RANDOMUSER_API_URL = "https://randomuser.me"; +export const BREW_API_URL = "https://api.punkapi.com/v2/beers"; +export const GOOGLE_BOOKS_URL = "https://www.googleapis.com/books/v1/volumes"; +export const apiRoute = "api/1.3"; +export const responseError = \\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\`Error responding from server. Please visit \\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\${RANDOMUSER_API_URL} and \\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\${corsProxy} to see if they're available\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\`; +\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\`, + "overview/hero/data/table/random-user-data-source.ts": \\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\`// © 2022 SolarWinds Worldwide, LLC. All rights reserved. +// +// Permission is hereby granted, free of charge, to any person obtaining a copy +// of this software and associated documentation files (the "Software"), to +// deal in the Software without restriction, including without limitation the +// rights to use, copy, modify, merge, publish, distribute, sublicense, and/or +// sell copies of the Software, and to permit persons to whom the Software is +// furnished to do so, subject to the following conditions: +// +// The above copyright notice and this permission notice shall be included in +// all copies or substantial portions of the Software. +// +// THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR +// IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, +// FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE +// AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER +// LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, +// OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN +// THE SOFTWARE. + +import { ListRange } from "@angular/cdk/collections"; +import { Injectable } from "@angular/core"; +import isEqual from "lodash/isEqual"; +import orderBy from "lodash/orderBy"; +import { BehaviorSubject } from "rxjs"; + +import { + DataSourceService, + IDataField, + INovaFilteringOutputs, + INovaFilters, + ISorterFilter, + LoggerService, +} from "@nova-ui/bits"; + +import { + IRandomUserResponse, + IRandomUserResults, + IRandomUserTableModel, + UsersQueryResponse, +} from "../types"; +import { + apiRoute, + corsProxy, + RANDOMUSER_API_URL, + responseError, +} from "./constants"; + +@Injectable() +export class RandomUserDataSource extends DataSourceService { + public static providerId = "RandomUserDataSource"; + + private readonly seed = "sw"; + + private cache = Array.from({ length: 0 }); + private lastSortValue?: ISorterFilter; + private lastVirtualScroll?: ListRange; + + public page: number = 1; + public busy = new BehaviorSubject(false); + + public dataFields: Array = [ + { id: "no", label: $localize\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\`No\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\`, dataType: "number" }, + { id: "nameTitle", label: $localize\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\`Title\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\`, dataType: "string" }, + { id: "nameFirst", label: $localize\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\`First\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\`, dataType: "string" }, + { id: "nameLast", label: $localize\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\`Last\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\`, dataType: "string" }, + { id: "gender", label: $localize\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\`Gender\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\`, dataType: "string" }, + { id: "country", label: $localize\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\`Country\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\`, dataType: "string" }, + { id: "city", label: $localize\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\`City\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\`, dataType: "string" }, + { id: "postcode", label: $localize\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\`Postcode\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\`, dataType: "number" }, + { id: "email", label: $localize\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\`E-Mail\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\`, dataType: "string" }, + { id: "cell", label: $localize\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\`Cell\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\`, dataType: "string" }, + ]; + + constructor(private logger: LoggerService) { + super(); + } + + public async getFilteredData( + filters: INovaFilters + ): Promise { + // This condition handles sorting. We want to sort columns without fetching another chunk of data. + // Since the data is being fetched when scrolled, we compare virtual scroll indexes here in the condition as well. + if (filters.sorter?.value) { + if ( + !isEqual(this.lastSortValue, filters.sorter.value) && + isEqual(this.lastVirtualScroll, filters.virtualScroll?.value) + ) { + this.lastSortValue = filters.sorter?.value; + this.lastVirtualScroll = filters.virtualScroll?.value; + + return { + repeat: { itemsSource: this.sortData(this.cache, filters) }, + paginator: { total: 200 }, + dataFields: this.dataFields, + }; + } + } + this.busy.next(true); + + const virtualScrollFilter = + filters.virtualScroll && filters.virtualScroll.value; + const start = virtualScrollFilter + ? filters.virtualScroll?.value.start + : 0; + const end = virtualScrollFilter ? filters.virtualScroll?.value.end : 0; + + // We're returning Promise with setTimeout here to make the response from the server longer, as the API being used sends responses + // almost immediately. We need it longer to be able the show the spinner component on data load + return new Promise((resolve) => { + setTimeout(() => { + this.getData(start, end).then( + (response: INovaFilteringOutputs | undefined) => { + if (!response) { + return; + } + + this.cache = this.cache.concat(response.users); + + this.dataSubject.next(this.cache); + resolve({ + repeat: { + itemsSource: this.sortData(this.cache, filters), + }, + // This API can return thousands of results, however doesn't return the max number of results, + // so we set the max number of result manually here. + paginator: { total: 200 }, + dataFields: this.dataFields, + }); + + this.lastSortValue = filters.sorter?.value; + this.lastVirtualScroll = filters.virtualScroll?.value; + this.busy.next(false); + } + ); + }, 300); + }); + } + + public async getData( + start: number = 0, + end: number = 20 + ): Promise { + let response: IRandomUserResponse | null = null; + try { + response = await ( + await fetch( + \\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\`\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\${corsProxy}/\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\${RANDOMUSER_API_URL}/\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\${apiRoute}/?page=\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\${ + this.page + }&results=\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\${end - start}&seed=\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\${this.seed}\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\` + ) + ).json(); + return { + users: response?.results.map( + (result: IRandomUserResults, i: number) => ({ + no: this.cache.length + i + 1, + nameTitle: result.name.title, + nameFirst: result.name.first, + nameLast: result.name.last, + gender: result.gender, + country: result.location.country, + city: result.location.city, + postcode: result.location.postcode, + email: result.email, + cell: result.cell, + }) + ), + total: response?.results.length, + start: start, + } as UsersQueryResponse; + } catch (e) { + this.logger.error(responseError); + } + } + + private sortData(data: IRandomUserTableModel[], filters: INovaFilters) { + return orderBy( + data, + filters.sorter?.value?.sortBy, + filters.sorter?.value?.direction as "desc" | "asc" + ); + } +} +\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\`, + "overview/hero/data/table/types.ts": \\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\`// © 2022 SolarWinds Worldwide, LLC. All rights reserved. +// +// Permission is hereby granted, free of charge, to any person obtaining a copy +// of this software and associated documentation files (the "Software"), to +// deal in the Software without restriction, including without limitation the +// rights to use, copy, modify, merge, publish, distribute, sublicense, and/or +// sell copies of the Software, and to permit persons to whom the Software is +// furnished to do so, subject to the following conditions: +// +// The above copyright notice and this permission notice shall be included in +// all copies or substantial portions of the Software. +// +// THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR +// IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, +// FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE +// AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER +// LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, +// OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN +// THE SOFTWARE. + +import { IDataField, INovaFilteringOutputs } from "@nova-ui/bits"; +export interface BasicTableModel { + position: number; + name: string; + features: any; + status: string; + checks: any; + "cpu-load": number; + firstUrl: string; + firstUrlLabel: string; + secondUrl: string; + secondUrlLabel: string; +} + +export interface ITableDataSourceOutput extends INovaFilteringOutputs { + dataFields: IDataField[]; +} +\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\`, + "overview/hero/data/timeseries-data-sources.ts": \\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\`// © 2022 SolarWinds Worldwide, LLC. All rights reserved. +// +// Permission is hereby granted, free of charge, to any person obtaining a copy +// of this software and associated documentation files (the "Software"), to +// deal in the Software without restriction, including without limitation the +// rights to use, copy, modify, merge, publish, distribute, sublicense, and/or +// sell copies of the Software, and to permit persons to whom the Software is +// furnished to do so, subject to the following conditions: +// +// The above copyright notice and this permission notice shall be included in +// all copies or substantial portions of the Software. +// +// THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR +// IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, +// FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE +// AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER +// LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, +// OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN +// THE SOFTWARE. + +import { Injectable } from "@angular/core"; +import { Moment } from "moment/moment"; +import { BehaviorSubject } from "rxjs"; + +import { DataSourceService, IDataSource, INovaFilters } from "@nova-ui/bits"; +import { + ITimeseriesOutput, + ITimeseriesWidgetData, + ITimeseriesWidgetSeriesData, +} from "@nova-ui/dashboards"; + +import { + BEER_VS_READING_DATA, + LOUNGING_VS_ULTIMATE_FRISBEE_DATA, +} from "./widget-data"; + +@Injectable() +export class BeerVsReadingMockDataSource + extends DataSourceService + implements IDataSource +{ + public static providerId = "BeerVsReadingMockDataSource"; + + public busy = new BehaviorSubject(false); + + constructor() { + super(); + } + + public async getFilteredData( + filters: INovaFilters + ): Promise { + this.busy.next(true); + const result = await delay( + { series: getData(filters, BEER_VS_READING_DATA) }, + 1000 + ); + this.busy.next(false); + return result; + } +} + +@Injectable() +export class LoungingVsFrisbeeGolfMockDataSource + extends DataSourceService + implements IDataSource +{ + public static providerId = "LoungingVsFrisbeeGolfMockDataSource"; + + public busy = new BehaviorSubject(false); + + constructor() { + super(); + } + + public async getFilteredData( + filters: INovaFilters + ): Promise { + this.busy.next(true); + const result = await delay( + { series: getData(filters, LOUNGING_VS_ULTIMATE_FRISBEE_DATA) }, + 1000 + ); + this.busy.next(false); + return result; + } +} + +function getData( + filters: INovaFilters, + data: ITimeseriesWidgetData[] +): ITimeseriesWidgetData[] { + const timeframeFilter = filters.timeframe; + let filteredData = data; + // TIME FRAME PICKER FILTERING + if (timeframeFilter) { + filteredData = filteredData.map((item: ITimeseriesWidgetData) => ({ + id: item.id, + name: item.name, + description: item.description, + data: item.data.filter((seriesData: ITimeseriesWidgetSeriesData) => + filterDates( + seriesData.x, + timeframeFilter.value.startDatetime, + timeframeFilter.value.endDatetime + ) + ), + })); + } + + return filteredData; +} + +function filterDates(dateToCheck: Moment, startDate: Moment, endDate: Moment) { + return ( + dateToCheck.isBetween(startDate, endDate) || + dateToCheck.isSame(startDate) || + dateToCheck.isSame(endDate) + ); +} + +async function delay( + value: ITimeseriesOutput, + ms: number +): Promise { + return new Promise((resolve) => setTimeout(() => resolve(value), ms)); +} +\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\`, + "overview/hero/data/types.ts": \\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\`// © 2022 SolarWinds Worldwide, LLC. All rights reserved. +// +// Permission is hereby granted, free of charge, to any person obtaining a copy +// of this software and associated documentation files (the "Software"), to +// deal in the Software without restriction, including without limitation the +// rights to use, copy, modify, merge, publish, distribute, sublicense, and/or +// sell copies of the Software, and to permit persons to whom the Software is +// furnished to do so, subject to the following conditions: +// +// The above copyright notice and this permission notice shall be included in +// all copies or substantial portions of the Software. +// +// THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR +// IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, +// FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE +// AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER +// LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, +// OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN +// THE SOFTWARE. + +export interface UsersQueryResponse { + users: IRandomUserTableModel[]; + total: number; + start: number; +} + +export interface IRandomUserResponse { + info: Array; + results: Array; +} + +export interface IRandomUserInfo { + page: number; + results: number; + seed: string; + version: string; +} + +export interface IRandomUserResults { + cell: string; + dob: { + age: number; + date: string; + }; + email: string; + gender: string; + id: any; + location: IRandomUserLocation; + login: { + md5: string; + password: string; + salt: string; + sha1: string; + sha256: string; + username: string; + uuid: string; + }; + name: { + title: string; + first: string; + last: string; + }; + nat: string; + phone: string; + picture: { + large: string; + medium: string; + thumbnail: string; + }; + registered: { + date: string; + age: number; + }; +} + +export interface IRandomUserTableModel { + no: number; + nameTitle: string; + nameFirst: string; + nameLast: string; + gender: string; + country: string; + city: string; + postcode: number; + email: string; + cell: string; +} + +export interface IRandomUserLocation { + city: string; + coordinates: { latitude: string; longitude: string }; + country: string; + postcode: number; + state: string; + street: { number: number; name: string }; + timezone: any; +} + +export interface IBrewInfo { + id: number; + name: string; + tagline: string; + first_brewed: string; + description: string; + brewers_tips: string; +} + +export interface IBrewDatasourceResponse { + brewInfo: IBrewInfo[]; + total: number; +} +\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\`, + "overview/hero/data/widget-data.ts": \\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\`// © 2022 SolarWinds Worldwide, LLC. All rights reserved. +// +// Permission is hereby granted, free of charge, to any person obtaining a copy +// of this software and associated documentation files (the "Software"), to +// deal in the Software without restriction, including without limitation the +// rights to use, copy, modify, merge, publish, distribute, sublicense, and/or +// sell copies of the Software, and to permit persons to whom the Software is +// furnished to do so, subject to the following conditions: +// +// The above copyright notice and this permission notice shall be included in +// all copies or substantial portions of the Software. +// +// THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR +// IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, +// FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE +// AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER +// LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, +// OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN +// THE SOFTWARE. + +import moment from "moment/moment"; + +import { ITimeseriesWidgetData } from "@nova-ui/dashboards"; + +import { BasicTableModel } from "./table/types"; + +export interface IProportionalWidgetData { + id: string; + name: string; + data: number[]; + link: string; + value: string; +} + +export function getMockBeerReviewCountsByCity(): IProportionalWidgetData[] { + return [ + { + id: "Brno", + name: "Brno", + data: [Math.round(Math.random() * 100000)], + link: "https://en.wikipedia.org/wiki/Brno", + value: "Brno", + }, + { + id: "kyiv", + name: "Kyiv", + data: [Math.round(Math.random() * 100000)], + link: "https://en.wikipedia.org/wiki/Kyiv", + value: "Kyiv", + }, + { + id: "austin", + name: "Austin", + data: [Math.round(Math.random() * 100000)], + link: "https://en.wikipedia.org/wiki/Austin", + value: "Austin", + }, + { + id: "lisbon", + name: "Lisbon", + data: [Math.round(Math.random() * 100000)], + link: "https://en.wikipedia.org/wiki/Lisbon", + value: "Lisbon", + }, + { + id: "sydney", + name: "Sydney", + data: [Math.round(Math.random() * 100000)], + link: "https://en.wikipedia.org/wiki/Sydney", + value: "Sydney", + }, + { + id: "nur-sultan", + name: "Nur-Sultan", + data: [Math.round(Math.random() * 100000)], + link: "https://en.wikipedia.org/wiki/Nur-Sultan", + value: "Nur-Sultan", + }, + ].sort((a, b) => a.data[0] - b.data[0]); +} + +export function getMockBeerReviewCountsByCity2(): IProportionalWidgetData[] { + return [ + { + id: "london", + name: "London", + data: [Math.round(Math.random() * 100000)], + link: "https://en.wikipedia.org/wiki/London", + value: "London", + }, + { + id: "paris", + name: "Paris", + data: [Math.round(Math.random() * 100000)], + link: "https://en.wikipedia.org/wiki/Paris", + value: "Paris", + }, + { + id: "rio", + name: "Rio", + data: [Math.round(Math.random() * 100000)], + link: "https://en.wikipedia.org/wiki/Rio_de_Janeiro", + value: "Rio", + }, + ].sort((a, b) => a.data[0] - b.data[0]); +} + +export const BEER_VS_READING_DATA: ITimeseriesWidgetData[] = [ + { + id: "series-1", + name: "Beer Tasting", + description: "Havin' some suds", + data: [ + { x: moment().subtract(10, "day"), y: 30 }, + { x: moment().subtract(9, "day"), y: 35 }, + { x: moment().subtract(8, "day"), y: 33 }, + { x: moment().subtract(7, "day"), y: 40 }, + { x: moment().subtract(6, "day"), y: 35 }, + { x: moment().subtract(5, "day"), y: 30 }, + { x: moment().subtract(4, "day"), y: 35 }, + { x: moment().subtract(3, "day"), y: 15 }, + { x: moment().subtract(2, "day"), y: 30 }, + { x: moment().subtract(1, "day"), y: 35 }, + { x: moment().subtract(24, "hour"), y: 34 }, + { x: moment().subtract(15, "hour"), y: 33 }, + { x: moment().subtract(10, "hour"), y: 35 }, + { x: moment().subtract(5, "hour"), y: 36 }, + { x: moment().subtract(1, "hour"), y: 34 }, + { x: moment().subtract(50, "minute"), y: 33 }, + { x: moment().subtract(40, "minute"), y: 30 }, + { x: moment().subtract(30, "minute"), y: 32 }, + { x: moment().subtract(20, "minute"), y: 31 }, + { x: moment().subtract(10, "minute"), y: 34 }, + ], + }, + { + id: "series-2", + name: "Reading", + description: "Hittin' the books", + data: [ + { x: moment().subtract(10, "day"), y: 60 }, + { x: moment().subtract(9, "day"), y: 64 }, + { x: moment().subtract(8, "day"), y: 70 }, + { x: moment().subtract(7, "day"), y: 55 }, + { x: moment().subtract(6, "day"), y: 55 }, + { x: moment().subtract(5, "day"), y: 45 }, + { x: moment().subtract(4, "day"), y: 10 }, + { x: moment().subtract(3, "day"), y: 65 }, + { x: moment().subtract(2, "day"), y: 35 }, + { x: moment().subtract(1, "day"), y: 60 }, + { x: moment().subtract(24, "hour"), y: 61 }, + { x: moment().subtract(15, "hour"), y: 65 }, + { x: moment().subtract(10, "hour"), y: 63 }, + { x: moment().subtract(5, "hour"), y: 58 }, + { x: moment().subtract(1, "hour"), y: 64 }, + { x: moment().subtract(50, "minute"), y: 63 }, + { x: moment().subtract(40, "minute"), y: 60 }, + { x: moment().subtract(30, "minute"), y: 62 }, + { x: moment().subtract(20, "minute"), y: 61 }, + { x: moment().subtract(10, "minute"), y: 62 }, + ], + }, +]; + +export const LOUNGING_VS_ULTIMATE_FRISBEE_DATA: ITimeseriesWidgetData[] = [ + { + id: "series-a", + name: "Lounging", + description: "Shootin' the Breeze", + data: [ + { x: moment().subtract(10, "day"), y: 10 }, + { x: moment().subtract(9, "day"), y: 15 }, + { x: moment().subtract(8, "day"), y: 13 }, + { x: moment().subtract(7, "day"), y: 20 }, + { x: moment().subtract(6, "day"), y: 15 }, + { x: moment().subtract(5, "day"), y: 10 }, + { x: moment().subtract(4, "day"), y: 15 }, + { x: moment().subtract(3, "day"), y: 5 }, + { x: moment().subtract(2, "day"), y: 10 }, + { x: moment().subtract(1, "day"), y: 15 }, + { x: moment().subtract(24, "hour"), y: 14 }, + { x: moment().subtract(15, "hour"), y: 13 }, + { x: moment().subtract(10, "hour"), y: 15 }, + { x: moment().subtract(5, "hour"), y: 16 }, + { x: moment().subtract(1, "hour"), y: 14 }, + { x: moment().subtract(50, "minute"), y: 13 }, + { x: moment().subtract(40, "minute"), y: 10 }, + { x: moment().subtract(30, "minute"), y: 12 }, + { x: moment().subtract(20, "minute"), y: 11 }, + { x: moment().subtract(10, "minute"), y: 14 }, + ], + }, + { + id: "series-b", + name: "Frisbee Golfing", + description: "Golfin' with a disc", + data: [ + { x: moment().subtract(10, "day"), y: 80 }, + { x: moment().subtract(9, "day"), y: 84 }, + { x: moment().subtract(8, "day"), y: 80 }, + { x: moment().subtract(7, "day"), y: 75 }, + { x: moment().subtract(6, "day"), y: 95 }, + { x: moment().subtract(5, "day"), y: 85 }, + { x: moment().subtract(4, "day"), y: 80 }, + { x: moment().subtract(3, "day"), y: 85 }, + { x: moment().subtract(2, "day"), y: 85 }, + { x: moment().subtract(1, "day"), y: 80 }, + { x: moment().subtract(24, "hour"), y: 81 }, + { x: moment().subtract(15, "hour"), y: 85 }, + { x: moment().subtract(10, "hour"), y: 83 }, + { x: moment().subtract(5, "hour"), y: 88 }, + { x: moment().subtract(1, "hour"), y: 84 }, + { x: moment().subtract(50, "minute"), y: 83 }, + { x: moment().subtract(40, "minute"), y: 80 }, + { x: moment().subtract(30, "minute"), y: 82 }, + { x: moment().subtract(20, "minute"), y: 81 }, + { x: moment().subtract(10, "minute"), y: 82 }, + ], + }, +]; + +export const TABLE_DATA: BasicTableModel[] = [ + { + position: 1, + name: "FOCUS-SVR-02258", + features: ["remote-access-vpn-tunnel", "patch-manager01"], + status: "Active", + checks: { + icon: "status_up", + num: 25, + }, + "cpu-load": 86, + firstUrl: "https://en.wikipedia.org/wiki/Brno", + firstUrlLabel: "Brno", + secondUrl: "https://en.wikipedia.org/wiki/VMware_Workstation", + secondUrlLabel: "Workstation", + }, + { + position: 2, + name: "FOCUS-SVR-03312", + features: ["tools", "database", "orion-ape-backup"], + status: "Active", + checks: { + icon: "status_critical", + num: 25, + }, + "cpu-load": 47, + firstUrl: "https://en.wikipedia.org/wiki/Brno", + firstUrlLabel: "Brno", + secondUrl: "https://en.wikipedia.org/wiki/VMware_Workstation", + secondUrlLabel: "Workstation", + }, + { + position: 3, + name: "FOCUS-SVR-02258", + features: [ + "remote-access-vpn-tunnel", + "database", + "orion-ape-backup", + "patch-manager01", + ], + status: "Active", + checks: { + icon: "status_down", + num: 25, + }, + "cpu-load": 53, + firstUrl: "https://en.wikipedia.org/wiki/Kyiv", + firstUrlLabel: "Kyiv", + secondUrl: "https://en.wikipedia.org/wiki/VMware_Workstation", + secondUrlLabel: "Workstation", + }, + { + position: 4, + name: "Man-LT-JYJ4AD5", + features: [ + "remote-access-vpn-tunnel", + "tools", + "database", + "orion-ape-backup", + ], + status: "Active", + checks: { + icon: "status_up", + num: 25, + }, + "cpu-load": 32, + firstUrl: "https://en.wikipedia.org/wiki/Kyiv", + firstUrlLabel: "Kyiv", + secondUrl: "https://en.wikipedia.org/wiki/VirtualBox", + secondUrlLabel: "VirtualBox", + }, + { + position: 5, + name: "Man-LT-JYJ425", + features: [ + "remote-access-vpn-tunnel", + "tools", + "database", + "orion-ape-backup", + ], + status: "Active", + checks: { + icon: "status_up", + num: 25, + }, + "cpu-load": 22, + firstUrl: "https://en.wikipedia.org/wiki/Kyiv", + firstUrlLabel: "Kyiv", + secondUrl: "https://en.wikipedia.org/wiki/VirtualBox", + secondUrlLabel: "VirtualBox", + }, + { + position: 6, + name: "Man-LT-JYJ4333", + features: [ + "remote-access-vpn-tunnel", + "tools", + "database", + "orion-ape-backup", + ], + status: "Active", + checks: { + icon: "status_up", + num: 25, + }, + "cpu-load": 12, + firstUrl: "https://en.wikipedia.org/wiki/Kyiv", + firstUrlLabel: "Kyiv", + secondUrl: "https://en.wikipedia.org/wiki/VirtualBox", + secondUrlLabel: "VirtualBox", + }, + { + position: 7, + name: "FOCUS-SVR-02258", + features: ["remote-access-vpn-tunnel", "patch-manager01"], + status: "Active", + checks: { + icon: "status_up", + num: 25, + }, + "cpu-load": 86, + firstUrl: "https://en.wikipedia.org/wiki/Austin", + firstUrlLabel: "Austin", + secondUrl: "https://en.wikipedia.org/wiki/VMware_Workstation", + secondUrlLabel: "Workstation", + }, + { + position: 8, + name: "Man-LT-JYJ4AD5", + features: [ + "remote-access-vpn-tunnel", + "tools", + "database", + "orion-ape-backup", + "patch-manager01", + ], + status: "Active", + checks: { + icon: "status_inactive", + num: 25, + }, + "cpu-load": 35, + firstUrl: "https://en.wikipedia.org/wiki/Austin", + firstUrlLabel: "Austin", + secondUrl: "https://en.wikipedia.org/wiki/VMware_Workstation", + secondUrlLabel: "Workstation", + }, + { + position: 9, + name: "Man-LT-JYJ4AD5", + features: [ + "remote-access-vpn-tunnel", + "tools", + "database", + "patch-manager01", + ], + status: "Active", + checks: { + icon: "status_up", + num: 25, + }, + "cpu-load": 32, + firstUrl: "https://en.wikipedia.org/wiki/Brno", + firstUrlLabel: "Brno", + secondUrl: "https://en.wikipedia.org/wiki/VMware_Workstation", + secondUrlLabel: "Workstation", + }, + { + position: 10, + name: "Man-LT-JYJ4AD5", + features: [ + "remote-access-vpn-tunnel", + "database", + "orion-ape-backup", + "patch-manager01", + ], + status: "Active", + checks: { + icon: "status_up", + num: 25, + }, + "cpu-load": 64, + firstUrl: "https://en.wikipedia.org/wiki/Kyiv", + firstUrlLabel: "Kyiv", + secondUrl: "https://en.wikipedia.org/wiki/VMware_Workstation", + secondUrlLabel: "Workstation", + }, + { + position: 11, + name: "Man-LT-111", + features: [], + status: "Active", + checks: { + icon: "status_external", + num: 25, + }, + "cpu-load": 55, + firstUrl: "https://en.wikipedia.org/wiki/Brno", + firstUrlLabel: "Brno", + secondUrl: "https://en.wikipedia.org/wiki/VMware_Workstation", + secondUrlLabel: "Workstation", + }, + { + position: 12, + name: "Man-LT-2222", + features: [ + "remote-access-vpn-tunnel", + "tools", + "database", + "orion-ape-backup", + "patch-manager01", + ], + status: "Active", + checks: { + icon: "status_inactive", + num: 25, + }, + "cpu-load": 34, + firstUrl: "https://en.wikipedia.org/wiki/Brno", + firstUrlLabel: "Brno", + secondUrl: "https://en.wikipedia.org/wiki/VMware_Workstation", + secondUrlLabel: "Workstation", + }, + { + position: 13, + name: "Man-LT-333333", + features: [ + "remote-access-vpn-tunnel", + "tools", + "database", + "patch-manager01", + ], + status: "Active", + checks: { + icon: "status_up", + num: 25, + }, + "cpu-load": 56, + firstUrl: "https://en.wikipedia.org/wiki/Kyiv", + firstUrlLabel: "Kyiv", + secondUrl: "https://en.wikipedia.org/wiki/VMware_Workstation", + secondUrlLabel: "Workstation", + }, + { + position: 14, + name: "Man-LT-444444", + features: [ + "remote-access-vpn-tunnel", + "database", + "orion-ape-backup", + "patch-manager01", + ], + status: "Active", + checks: { + icon: "status_up", + num: 25, + }, + "cpu-load": 26, + firstUrl: "https://en.wikipedia.org/wiki/Kyiv", + firstUrlLabel: "Kyiv", + secondUrl: "https://en.wikipedia.org/wiki/VMware_Workstation", + secondUrlLabel: "Workstation", + }, + { + position: 15, + name: "Man-LT-555555", + features: [ + "remote-access-vpn-tunnel", + "database", + "orion-ape-backup", + "patch-manager01", + ], + status: "Active", + checks: { + icon: "status_up", + num: 25, + }, + "cpu-load": 76, + firstUrl: "https://en.wikipedia.org/wiki/Austin", + firstUrlLabel: "Austin", + secondUrl: "https://en.wikipedia.org/wiki/VMware_Workstation", + secondUrlLabel: "Workstation", + }, + { + position: 16, + name: "FOCUS-SVR-02258", + features: ["remote-access-vpn-tunnel", "patch-manager01"], + status: "Active", + checks: { + icon: "status_up", + num: 25, + }, + "cpu-load": 86, + firstUrl: "https://en.wikipedia.org/wiki/Brno", + firstUrlLabel: "Brno", + secondUrl: "https://en.wikipedia.org/wiki/VMware_Workstation", + secondUrlLabel: "Workstation", + }, + { + position: 17, + name: "FOCUS-SVR-03312", + features: ["tools", "database", "orion-ape-backup"], + status: "Active", + checks: { + icon: "status_critical", + num: 25, + }, + "cpu-load": 47, + firstUrl: "https://en.wikipedia.org/wiki/Brno", + firstUrlLabel: "Brno", + secondUrl: "https://en.wikipedia.org/wiki/VMware_Workstation", + secondUrlLabel: "Workstation", + }, + { + position: 18, + name: "FOCUS-SVR-02258", + features: [ + "remote-access-vpn-tunnel", + "database", + "orion-ape-backup", + "patch-manager01", + ], + status: "Active", + checks: { + icon: "status_down", + num: 25, + }, + "cpu-load": 53, + firstUrl: "https://en.wikipedia.org/wiki/Kyiv", + firstUrlLabel: "Kyiv", + secondUrl: "https://en.wikipedia.org/wiki/VMware_Workstation", + secondUrlLabel: "Workstation", + }, + { + position: 19, + name: "Man-LT-JYJ4AD5", + features: [ + "remote-access-vpn-tunnel", + "tools", + "database", + "orion-ape-backup", + ], + status: "Active", + checks: { + icon: "status_up", + num: 25, + }, + "cpu-load": 32, + firstUrl: "https://en.wikipedia.org/wiki/Kyiv", + firstUrlLabel: "Kyiv", + secondUrl: "https://en.wikipedia.org/wiki/VirtualBox", + secondUrlLabel: "VirtualBox", + }, + { + position: 20, + name: "Man-LT-JYJ425", + features: [ + "remote-access-vpn-tunnel", + "tools", + "database", + "orion-ape-backup", + ], + status: "Active", + checks: { + icon: "status_up", + num: 25, + }, + "cpu-load": 22, + firstUrl: "https://en.wikipedia.org/wiki/Kyiv", + firstUrlLabel: "Kyiv", + secondUrl: "https://en.wikipedia.org/wiki/VirtualBox", + secondUrlLabel: "VirtualBox", + }, + { + position: 21, + name: "Man-LT-JYJ4333", + features: [ + "remote-access-vpn-tunnel", + "tools", + "database", + "orion-ape-backup", + ], + status: "Active", + checks: { + icon: "status_up", + num: 25, + }, + "cpu-load": 12, + firstUrl: "https://en.wikipedia.org/wiki/Kyiv", + firstUrlLabel: "Kyiv", + secondUrl: "https://en.wikipedia.org/wiki/VirtualBox", + secondUrlLabel: "VirtualBox", + }, + { + position: 22, + name: "FOCUS-SVR-02258", + features: ["remote-access-vpn-tunnel", "patch-manager01"], + status: "Active", + checks: { + icon: "status_up", + num: 25, + }, + "cpu-load": 86, + firstUrl: "https://en.wikipedia.org/wiki/Austin", + firstUrlLabel: "Austin", + secondUrl: "https://en.wikipedia.org/wiki/VMware_Workstation", + secondUrlLabel: "Workstation", + }, + { + position: 23, + name: "Man-LT-JYJ4AD5", + features: [ + "remote-access-vpn-tunnel", + "tools", + "database", + "orion-ape-backup", + "patch-manager01", + ], + status: "Active", + checks: { + icon: "status_inactive", + num: 25, + }, + "cpu-load": 35, + firstUrl: "https://en.wikipedia.org/wiki/Austin", + firstUrlLabel: "Austin", + secondUrl: "https://en.wikipedia.org/wiki/VMware_Workstation", + secondUrlLabel: "Workstation", + }, + { + position: 24, + name: "Man-LT-JYJ4AD5", + features: [ + "remote-access-vpn-tunnel", + "tools", + "database", + "patch-manager01", + ], + status: "Active", + checks: { + icon: "status_up", + num: 25, + }, + "cpu-load": 32, + firstUrl: "https://en.wikipedia.org/wiki/Brno", + firstUrlLabel: "Brno", + secondUrl: "https://en.wikipedia.org/wiki/VMware_Workstation", + secondUrlLabel: "Workstation", + }, + { + position: 25, + name: "Man-LT-JYJ4AD5", + features: [ + "remote-access-vpn-tunnel", + "database", + "orion-ape-backup", + "patch-manager01", + ], + status: "Active", + checks: { + icon: "status_up", + num: 25, + }, + "cpu-load": 64, + firstUrl: "https://en.wikipedia.org/wiki/Kyiv", + firstUrlLabel: "Kyiv", + secondUrl: "https://en.wikipedia.org/wiki/VMware_Workstation", + secondUrlLabel: "Workstation", + }, + { + position: 26, + name: "Man-LT-111", + features: [], + status: "Active", + checks: { + icon: "status_external", + num: 25, + }, + "cpu-load": 55, + firstUrl: "https://en.wikipedia.org/wiki/Brno", + firstUrlLabel: "Brno", + secondUrl: "https://en.wikipedia.org/wiki/VMware_Workstation", + secondUrlLabel: "Workstation", + }, + { + position: 27, + name: "Man-LT-2222", + features: [ + "remote-access-vpn-tunnel", + "tools", + "database", + "orion-ape-backup", + "patch-manager01", + ], + status: "Active", + checks: { + icon: "status_inactive", + num: 25, + }, + "cpu-load": 34, + firstUrl: "https://en.wikipedia.org/wiki/Brno", + firstUrlLabel: "Brno", + secondUrl: "https://en.wikipedia.org/wiki/VMware_Workstation", + secondUrlLabel: "Workstation", + }, + { + position: 28, + name: "Man-LT-333333", + features: [ + "remote-access-vpn-tunnel", + "tools", + "database", + "patch-manager01", + ], + status: "Active", + checks: { + icon: "status_up", + num: 25, + }, + "cpu-load": 56, + firstUrl: "https://en.wikipedia.org/wiki/Kyiv", + firstUrlLabel: "Kyiv", + secondUrl: "https://en.wikipedia.org/wiki/VMware_Workstation", + secondUrlLabel: "Workstation", + }, + { + position: 29, + name: "Man-LT-444444", + features: [ + "remote-access-vpn-tunnel", + "database", + "orion-ape-backup", + "patch-manager01", + ], + status: "Active", + checks: { + icon: "status_up", + num: 25, + }, + "cpu-load": 26, + firstUrl: "https://en.wikipedia.org/wiki/Kyiv", + firstUrlLabel: "Kyiv", + secondUrl: "https://en.wikipedia.org/wiki/VMware_Workstation", + secondUrlLabel: "Workstation", + }, + { + position: 30, + name: "Man-LT-555555", + features: [ + "remote-access-vpn-tunnel", + "database", + "orion-ape-backup", + "patch-manager01", + ], + status: "Active", + checks: { + icon: "status_up", + num: 25, + }, + "cpu-load": 76, + firstUrl: "https://en.wikipedia.org/wiki/Austin", + firstUrlLabel: "Austin", + secondUrl: "https://en.wikipedia.org/wiki/VMware_Workstation", + secondUrlLabel: "Workstation", + }, + { + position: 31, + name: "FOCUS-SVR-02258", + features: ["remote-access-vpn-tunnel", "patch-manager01"], + status: "Active", + checks: { + icon: "status_up", + num: 25, + }, + "cpu-load": 86, + firstUrl: "https://en.wikipedia.org/wiki/Brno", + firstUrlLabel: "Brno", + secondUrl: "https://en.wikipedia.org/wiki/VMware_Workstation", + secondUrlLabel: "Workstation", + }, + { + position: 32, + name: "FOCUS-SVR-03312", + features: ["tools", "database", "orion-ape-backup"], + status: "Active", + checks: { + icon: "status_critical", + num: 25, + }, + "cpu-load": 47, + firstUrl: "https://en.wikipedia.org/wiki/Brno", + firstUrlLabel: "Brno", + secondUrl: "https://en.wikipedia.org/wiki/VMware_Workstation", + secondUrlLabel: "Workstation", + }, + { + position: 33, + name: "FOCUS-SVR-02258", + features: [ + "remote-access-vpn-tunnel", + "database", + "orion-ape-backup", + "patch-manager01", + ], + status: "Active", + checks: { + icon: "status_down", + num: 25, + }, + "cpu-load": 53, + firstUrl: "https://en.wikipedia.org/wiki/Kyiv", + firstUrlLabel: "Kyiv", + secondUrl: "https://en.wikipedia.org/wiki/VMware_Workstation", + secondUrlLabel: "Workstation", + }, + { + position: 34, + name: "Man-LT-JYJ4AD5", + features: [ + "remote-access-vpn-tunnel", + "tools", + "database", + "orion-ape-backup", + ], + status: "Active", + checks: { + icon: "status_up", + num: 25, + }, + "cpu-load": 32, + firstUrl: "https://en.wikipedia.org/wiki/Kyiv", + firstUrlLabel: "Kyiv", + secondUrl: "https://en.wikipedia.org/wiki/VirtualBox", + secondUrlLabel: "VirtualBox", + }, + { + position: 35, + name: "Man-LT-JYJ425", + features: [ + "remote-access-vpn-tunnel", + "tools", + "database", + "orion-ape-backup", + ], + status: "Active", + checks: { + icon: "status_up", + num: 25, + }, + "cpu-load": 22, + firstUrl: "https://en.wikipedia.org/wiki/Kyiv", + firstUrlLabel: "Kyiv", + secondUrl: "https://en.wikipedia.org/wiki/VirtualBox", + secondUrlLabel: "VirtualBox", + }, + { + position: 36, + name: "Man-LT-JYJ4333", + features: [ + "remote-access-vpn-tunnel", + "tools", + "database", + "orion-ape-backup", + ], + status: "Active", + checks: { + icon: "status_up", + num: 25, + }, + "cpu-load": 12, + firstUrl: "https://en.wikipedia.org/wiki/Kyiv", + firstUrlLabel: "Kyiv", + secondUrl: "https://en.wikipedia.org/wiki/VirtualBox", + secondUrlLabel: "VirtualBox", + }, + { + position: 37, + name: "FOCUS-SVR-02258", + features: ["remote-access-vpn-tunnel", "patch-manager01"], + status: "Active", + checks: { + icon: "status_up", + num: 25, + }, + "cpu-load": 86, + firstUrl: "https://en.wikipedia.org/wiki/Austin", + firstUrlLabel: "Austin", + secondUrl: "https://en.wikipedia.org/wiki/VMware_Workstation", + secondUrlLabel: "Workstation", + }, + { + position: 38, + name: "Man-LT-JYJ4AD5", + features: [ + "remote-access-vpn-tunnel", + "tools", + "database", + "orion-ape-backup", + "patch-manager01", + ], + status: "Active", + checks: { + icon: "status_inactive", + num: 25, + }, + "cpu-load": 35, + firstUrl: "https://en.wikipedia.org/wiki/Austin", + firstUrlLabel: "Austin", + secondUrl: "https://en.wikipedia.org/wiki/VMware_Workstation", + secondUrlLabel: "Workstation", + }, + { + position: 39, + name: "Man-LT-JYJ4AD5", + features: [ + "remote-access-vpn-tunnel", + "tools", + "database", + "patch-manager01", + ], + status: "Active", + checks: { + icon: "status_up", + num: 25, + }, + "cpu-load": 32, + firstUrl: "https://en.wikipedia.org/wiki/Brno", + firstUrlLabel: "Brno", + secondUrl: "https://en.wikipedia.org/wiki/VMware_Workstation", + secondUrlLabel: "Workstation", + }, + { + position: 40, + name: "Man-LT-JYJ4AD5", + features: [ + "remote-access-vpn-tunnel", + "database", + "orion-ape-backup", + "patch-manager01", + ], + status: "Active", + checks: { + icon: "status_up", + num: 25, + }, + "cpu-load": 64, + firstUrl: "https://en.wikipedia.org/wiki/Kyiv", + firstUrlLabel: "Kyiv", + secondUrl: "https://en.wikipedia.org/wiki/VMware_Workstation", + secondUrlLabel: "Workstation", + }, + { + position: 41, + name: "Man-LT-111", + features: [], + status: "Active", + checks: { + icon: "status_external", + num: 25, + }, + "cpu-load": 55, + firstUrl: "https://en.wikipedia.org/wiki/Brno", + firstUrlLabel: "Brno", + secondUrl: "https://en.wikipedia.org/wiki/VMware_Workstation", + secondUrlLabel: "Workstation", + }, + { + position: 42, + name: "Man-LT-2222", + features: [ + "remote-access-vpn-tunnel", + "tools", + "database", + "orion-ape-backup", + "patch-manager01", + ], + status: "Active", + checks: { + icon: "status_inactive", + num: 25, + }, + "cpu-load": 34, + firstUrl: "https://en.wikipedia.org/wiki/Brno", + firstUrlLabel: "Brno", + secondUrl: "https://en.wikipedia.org/wiki/VMware_Workstation", + secondUrlLabel: "Workstation", + }, + { + position: 43, + name: "Man-LT-333333", + features: [ + "remote-access-vpn-tunnel", + "tools", + "database", + "patch-manager01", + ], + status: "Active", + checks: { + icon: "status_up", + num: 25, + }, + "cpu-load": 56, + firstUrl: "https://en.wikipedia.org/wiki/Kyiv", + firstUrlLabel: "Kyiv", + secondUrl: "https://en.wikipedia.org/wiki/VMware_Workstation", + secondUrlLabel: "Workstation", + }, + { + position: 44, + name: "Man-LT-444444", + features: [ + "remote-access-vpn-tunnel", + "database", + "orion-ape-backup", + "patch-manager01", + ], + status: "Active", + checks: { + icon: "status_up", + num: 25, + }, + "cpu-load": 26, + firstUrl: "https://en.wikipedia.org/wiki/Kyiv", + firstUrlLabel: "Kyiv", + secondUrl: "https://en.wikipedia.org/wiki/VMware_Workstation", + secondUrlLabel: "Workstation", + }, + { + position: 45, + name: "Man-LT-555555", + features: [ + "remote-access-vpn-tunnel", + "database", + "orion-ape-backup", + "patch-manager01", + ], + status: "Active", + checks: { + icon: "status_up", + num: 25, + }, + "cpu-load": 76, + firstUrl: "https://en.wikipedia.org/wiki/Austin", + firstUrlLabel: "Austin", + secondUrl: "https://en.wikipedia.org/wiki/VMware_Workstation", + secondUrlLabel: "Workstation", + }, +]; +\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\`, + "overview/hero/widget-configs/kpi.ts": \\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\`// © 2022 SolarWinds Worldwide, LLC. All rights reserved. +// +// Permission is hereby granted, free of charge, to any person obtaining a copy +// of this software and associated documentation files (the "Software"), to +// deal in the Software without restriction, including without limitation the +// rights to use, copy, modify, merge, publish, distribute, sublicense, and/or +// sell copies of the Software, and to permit persons to whom the Software is +// furnished to do so, subject to the following conditions: +// +// The above copyright notice and this permission notice shall be included in +// all copies or substantial portions of the Software. +// +// THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR +// IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, +// FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE +// AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER +// LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, +// OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN +// THE SOFTWARE. + +import { + DEFAULT_PIZZAGNA_ROOT, + IProviderConfiguration, + IRefresherProperties, + IWidget, + KpiComponent, + NOVA_KPI_DATASOURCE_ADAPTER, + PizzagnaLayer, + WellKnownProviders, +} from "@nova-ui/dashboards"; + +import { + HarryPotterAverageRatingDataSource, + HarryPotterRatingsCountDataSource, +} from "../data/kpi-datasources"; + +export const kpiConfig: IWidget = { + id: "kpiWidgetId", + type: "kpi", + pizzagna: { + [PizzagnaLayer.Configuration]: { + [DEFAULT_PIZZAGNA_ROOT]: { + providers: { + [WellKnownProviders.Refresher]: { + properties: { + // Configuring the refresher interval so that our data source is invoked every ten minutes + interval: 60 * 10, + enabled: true, + } as IRefresherProperties, + } as Partial, + }, + }, + header: { + properties: { + title: "Harry Potter and the Sorcerer's Stone", + subtitle: "By J. K. Rowling", + }, + }, + tiles: { + properties: { + nodes: ["kpi1", "kpi2"], + }, + }, + kpi1: { + id: "kpi1", + componentType: KpiComponent.lateLoadKey, + properties: { + widgetData: { + label: "Average Rating", + backgroundColor: "var(--nui-color-chart-three)", + units: "out of 5 Stars", + }, + }, + providers: { + [WellKnownProviders.DataSource]: { + // Setting the data source providerId for the tile with id "kpi1" + providerId: + HarryPotterAverageRatingDataSource.providerId, + } as IProviderConfiguration, + [WellKnownProviders.Adapter]: { + providerId: NOVA_KPI_DATASOURCE_ADAPTER, + properties: { + componentId: "kpi1", + propertyPath: "widgetData", + }, + } as IProviderConfiguration, + }, + }, + kpi2: { + id: "kpi2", + componentType: KpiComponent.lateLoadKey, + properties: { + widgetData: { + label: "Reader Feedback", + units: "Ratings", + }, + }, + providers: { + [WellKnownProviders.DataSource]: { + // Setting the data source providerId for the tile with id "kpi" + providerId: + HarryPotterRatingsCountDataSource.providerId, + } as IProviderConfiguration, + [WellKnownProviders.Adapter]: { + providerId: NOVA_KPI_DATASOURCE_ADAPTER, + properties: { + componentId: "kpi2", + propertyPath: "widgetData", + }, + } as IProviderConfiguration, + }, + }, + }, + }, +}; +\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\`, + "overview/hero/widget-configs/proportional.ts": \\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\`// © 2022 SolarWinds Worldwide, LLC. All rights reserved. +// +// Permission is hereby granted, free of charge, to any person obtaining a copy +// of this software and associated documentation files (the "Software"), to +// deal in the Software without restriction, including without limitation the +// rights to use, copy, modify, merge, publish, distribute, sublicense, and/or +// sell copies of the Software, and to permit persons to whom the Software is +// furnished to do so, subject to the following conditions: +// +// The above copyright notice and this permission notice shall be included in +// all copies or substantial portions of the Software. +// +// THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR +// IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, +// FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE +// AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER +// LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, +// OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN +// THE SOFTWARE. + +import { + DEFAULT_PIZZAGNA_ROOT, + IProportionalWidgetChartOptions, + IProviderConfiguration, + IWidget, + LegendPlacement, + PizzagnaLayer, + ProportionalWidgetChartTypes, + WellKnownProviders, +} from "@nova-ui/dashboards"; + +import { BeerReviewCountsByCityMockDataSource } from "../data/proportional-datasources"; + +export const proportionalConfig: IWidget = { + id: "proportionalWidgetId", + type: "proportional", + pizzagna: { + [PizzagnaLayer.Configuration]: { + [DEFAULT_PIZZAGNA_ROOT]: { + providers: { + [WellKnownProviders.Refresher]: { + properties: { + interval: 0, + }, + }, + }, + }, + header: { + properties: { + title: "Beer Review Tally by City", + subtitle: "These People Love Beer", + }, + }, + chart: { + providers: { + [WellKnownProviders.DataSource]: { + providerId: + BeerReviewCountsByCityMockDataSource.providerId, + } as IProviderConfiguration, + }, + properties: { + configuration: { + chartOptions: { + type: ProportionalWidgetChartTypes.DonutChart, + legendPlacement: LegendPlacement.Right, + } as IProportionalWidgetChartOptions, + }, + }, + }, + }, + }, +}; +\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\`, + "overview/hero/widget-configs/risk-score.ts": \\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\`// © 2023 SolarWinds Worldwide, LLC. All rights reserved. +// +// Permission is hereby granted, free of charge, to any person obtaining a copy +// of this software and associated documentation files (the "Software"), to +// deal in the Software without restriction, including without limitation the +// rights to use, copy, modify, merge, publish, distribute, sublicense, and/or +// sell copies of the Software, and to permit persons to whom the Software is +// furnished to do so, subject to the following conditions: +// +// The above copyright notice and this permission notice shall be included in +// all copies or substantial portions of the Software. +// +// THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR +// IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, +// FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE +// AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER +// LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, +// OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN +// THE SOFTWARE. + +import { + DEFAULT_PIZZAGNA_ROOT, + IProviderConfiguration, + IRefresherProperties, + IWidget, + RiskScoreTileComponent, + NOVA_KPI_DATASOURCE_ADAPTER, + PizzagnaLayer, + WellKnownProviders, +} from "@nova-ui/dashboards"; + +import { HarryPotterAverageRatingDataSource } from "../data/kpi-datasources"; + +export const riskScoreConfig: IWidget = { + id: "riskScoreWidgetId", + type: "risk-score", + pizzagna: { + [PizzagnaLayer.Configuration]: { + [DEFAULT_PIZZAGNA_ROOT]: { + providers: { + [WellKnownProviders.Refresher]: { + properties: { + // Configuring the refresher interval so that our data source is invoked every ten minutes + interval: 60 * 10, + enabled: true, + } as IRefresherProperties, + } as Partial, + }, + }, + header: { + properties: { + title: "Harry Potter and the Sorcerer's Stone", + subtitle: "By J. K. Rowling", + }, + }, + tiles: { + properties: { + nodes: ["riskScore1"], + }, + }, + riskScore1: { + id: "riskScore1", + componentType: RiskScoreTileComponent.lateLoadKey, + properties: { + widgetData: { + minValue: 0, + maxValue: 5, + useStaticLabel: false, + staticLabel: undefined, + label: \\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\`Average Rating\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\`, + description: \\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\`Harry Potter and the Sorcerer's Stone By J. K. Rowling Average Rating Risk Score\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\`, + }, + }, + providers: { + [WellKnownProviders.DataSource]: { + // Setting the data source providerId for the tile with id "riskScore1" + providerId: + HarryPotterAverageRatingDataSource.providerId, + } as IProviderConfiguration, + [WellKnownProviders.Adapter]: { + providerId: NOVA_KPI_DATASOURCE_ADAPTER, + properties: { + componentId: "riskScore1", + propertyPath: "widgetData", + }, + } as IProviderConfiguration, + }, + }, + }, + }, +}; +\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\`, + "overview/hero/widget-configs/table.ts": \\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\`// © 2022 SolarWinds Worldwide, LLC. All rights reserved. +// +// Permission is hereby granted, free of charge, to any person obtaining a copy +// of this software and associated documentation files (the "Software"), to +// deal in the Software without restriction, including without limitation the +// rights to use, copy, modify, merge, publish, distribute, sublicense, and/or +// sell copies of the Software, and to permit persons to whom the Software is +// furnished to do so, subject to the following conditions: +// +// The above copyright notice and this permission notice shall be included in +// all copies or substantial portions of the Software. +// +// THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR +// IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, +// FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE +// AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER +// LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, +// OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN +// THE SOFTWARE. + +import { + ITableWidgetColumnConfig, + ITableWidgetSorterConfig, + IWidget, + PizzagnaLayer, + RawFormatterComponent, + WellKnownProviders, +} from "@nova-ui/dashboards"; + +import { BeerDataSource } from "../data/table/beer-data-source"; + +export const tableConfig: IWidget = { + id: "tableWidgetId", + type: "table", + pizzagna: { + [PizzagnaLayer.Configuration]: { + header: { + properties: { + title: "Stupendous Suds", + subtitle: "Try These Brilliant Brews", + }, + }, + table: { + providers: { + [WellKnownProviders.DataSource]: { + providerId: BeerDataSource.providerId, + }, + }, + properties: { + configuration: { + columns: [ + { + id: "column1", + label: "Beer Name", + isActive: true, + width: 185, + formatter: { + componentType: + RawFormatterComponent.lateLoadKey, + properties: { + dataFieldIds: { + value: "name", + }, + }, + }, + }, + { + id: "column2", + label: "Tagline", + isActive: true, + width: 250, + formatter: { + componentType: + RawFormatterComponent.lateLoadKey, + properties: { + dataFieldIds: { + value: "tagline", + }, + }, + }, + }, + { + id: "column3", + label: "First Brewed", + isActive: true, + formatter: { + componentType: + RawFormatterComponent.lateLoadKey, + properties: { + dataFieldIds: { + value: "first_brewed", + }, + }, + }, + }, + { + id: "column4", + label: "Description", + isActive: true, + width: 275, + formatter: { + componentType: + RawFormatterComponent.lateLoadKey, + properties: { + dataFieldIds: { + value: "description", + }, + }, + }, + }, + ] as ITableWidgetColumnConfig[], + sorterConfiguration: { + descendantSorting: false, + sortBy: "", + } as ITableWidgetSorterConfig, + hasVirtualScroll: true, + }, + }, + }, + }, + }, +}; +\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\`, + "overview/hero/widget-configs/timeseries.ts": \\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\`// © 2022 SolarWinds Worldwide, LLC. All rights reserved. +// +// Permission is hereby granted, free of charge, to any person obtaining a copy +// of this software and associated documentation files (the "Software"), to +// deal in the Software without restriction, including without limitation the +// rights to use, copy, modify, merge, publish, distribute, sublicense, and/or +// sell copies of the Software, and to permit persons to whom the Software is +// furnished to do so, subject to the following conditions: +// +// The above copyright notice and this permission notice shall be included in +// all copies or substantial portions of the Software. +// +// THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR +// IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, +// FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE +// AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER +// LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, +// OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN +// THE SOFTWARE. + +import moment from "moment/moment"; + +import { + DEFAULT_PIZZAGNA_ROOT, + IProviderConfiguration, + ISerializableTimeframe, + ITimeseriesItemConfiguration, + IWidget, + LegendPlacement, + WellKnownProviders, +} from "@nova-ui/dashboards"; + +import { BeerVsReadingMockDataSource } from "../data/timeseries-data-sources"; + +export const timeseriesConfig: IWidget = { + id: "timeseriesWidgetId", + type: "timeseries", + pizzagna: { + configuration: { + [DEFAULT_PIZZAGNA_ROOT]: { + providers: { + [WellKnownProviders.DataSource]: { + providerId: BeerVsReadingMockDataSource.providerId, + } as IProviderConfiguration, + }, + }, + header: { + properties: { + title: "Primary Leisure Activity Over Time", + subtitle: "Survey of 1000 Solarians", + }, + }, + chart: { + providers: { + [WellKnownProviders.Adapter]: { + properties: { + series: [ + { + id: "series-1", + label: "Beer Tasting", + selectedSeriesId: "series-1", + }, + { + id: "series-2", + label: "Reading", + selectedSeriesId: "series-2", + }, + ] as ITimeseriesItemConfiguration[], + }, + } as Partial, + }, + properties: { + configuration: { + legendPlacement: LegendPlacement.Right, + enableZoom: true, + leftAxisLabel: "Solarians (%)", + }, + }, + }, + timeframeSelection: { + properties: { + timeframe: { + selectedPresetId: "last7Days", + } as ISerializableTimeframe, + minDate: moment().subtract(10, "days").format(), + maxDate: moment().format(), + }, + }, + }, + }, +}; +\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\`, + "overview/overview-docs.component.ts": \\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\`// © 2022 SolarWinds Worldwide, LLC. All rights reserved. +// +// Permission is hereby granted, free of charge, to any person obtaining a copy +// of this software and associated documentation files (the "Software"), to +// deal in the Software without restriction, including without limitation the +// rights to use, copy, modify, merge, publish, distribute, sublicense, and/or +// sell copies of the Software, and to permit persons to whom the Software is +// furnished to do so, subject to the following conditions: +// +// The above copyright notice and this permission notice shall be included in +// all copies or substantial portions of the Software. +// +// THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR +// IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, +// FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE +// AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER +// LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, +// OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN +// THE SOFTWARE. + +import { Component } from "@angular/core"; + +@Component({ + selector: "nui-dashboard-overview-docs", + templateUrl: "./overview-docs.component.html", + standalone: false, +}) +export class OverviewDocsComponent {} +\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\`, + "overview/overview.module.ts": \\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\`// © 2022 SolarWinds Worldwide, LLC. All rights reserved. +// +// Permission is hereby granted, free of charge, to any person obtaining a copy +// of this software and associated documentation files (the "Software"), to +// deal in the Software without restriction, including without limitation the +// rights to use, copy, modify, merge, publish, distribute, sublicense, and/or +// sell copies of the Software, and to permit persons to whom the Software is +// furnished to do so, subject to the following conditions: +// +// The above copyright notice and this permission notice shall be included in +// all copies or substantial portions of the Software. +// +// THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR +// IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, +// FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE +// AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER +// LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, +// OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN +// THE SOFTWARE. + +import { CommonModule } from "@angular/common"; +import { NgModule } from "@angular/core"; +import { RouterModule } from "@angular/router"; + +import { + NuiBusyModule, + NuiButtonModule, + NuiDocsModule, + NuiIconModule, + NuiMessageModule, + NuiSwitchModule, +} from "@nova-ui/bits"; +import { + ConfiguratorHeadingService, + IFormatterDefinition, + LinkFormatterComponent, + NuiDashboardsModule, + WellKnownPathKey, + WidgetTypesService, +} from "@nova-ui/dashboards"; + +import { HeroDashboardComponent } from "./hero/dashboard/hero-dashboard.component"; +import { + HarryPotterAverageRatingDataSource, + HarryPotterRatingsCountDataSource, +} from "./hero/data/kpi-datasources"; +import { + BeerReviewCountsByCityMockDataSource, + BeerReviewCountsByCityMockDataSource2, +} from "./hero/data/proportional-datasources"; +import { BeerDataSource } from "./hero/data/table/beer-data-source"; +import { RandomUserDataSource } from "./hero/data/table/random-user-data-source"; +import { + BeerVsReadingMockDataSource, + LoungingVsFrisbeeGolfMockDataSource, +} from "./hero/data/timeseries-data-sources"; +import { OverviewDocsComponent } from "./overview-docs.component"; + +const routes = [ + { + path: "", + component: OverviewDocsComponent, + data: { + srlc: { + hideIndicator: true, + }, + showThemeSwitcher: true, + }, + }, + { + path: "hero", + component: HeroDashboardComponent, + data: { + srlc: { + hideIndicator: true, + }, + }, + }, +]; + +@NgModule({ + imports: [ + CommonModule, + NuiDashboardsModule, + NuiBusyModule, + NuiButtonModule, + NuiDocsModule, + NuiMessageModule, + NuiSwitchModule, + NuiIconModule, + RouterModule.forChild(routes), + ], + declarations: [OverviewDocsComponent, HeroDashboardComponent], + providers: [ConfiguratorHeadingService], +}) +export default class OverviewModule { + constructor(private widgetTypesService: WidgetTypesService) { + this.setupDataSourceProviders(); + this.setupProportionalLegendFormatters(); + } + + private setupDataSourceProviders() { + this.setDataSourceProviders("table", [ + RandomUserDataSource.providerId, + BeerDataSource.providerId, + ]); + this.setDataSourceProviders("kpi", [ + HarryPotterAverageRatingDataSource.providerId, + HarryPotterRatingsCountDataSource.providerId, + ]); + this.setDataSourceProviders("risk-score", [ + HarryPotterAverageRatingDataSource.providerId, + HarryPotterRatingsCountDataSource.providerId, + ]); + this.setDataSourceProviders("proportional", [ + BeerReviewCountsByCityMockDataSource.providerId, + BeerReviewCountsByCityMockDataSource2.providerId, + ]); + this.setDataSourceProviders("timeseries", [ + BeerVsReadingMockDataSource.providerId, + LoungingVsFrisbeeGolfMockDataSource.providerId, + ]); + } + + private setDataSourceProviders(type: string, providers: string[]) { + const widgetTemplate = this.widgetTypesService.getWidgetType(type, 1); + this.widgetTypesService.setNode( + widgetTemplate, + "configurator", + WellKnownPathKey.DataSourceProviders, + providers + ); + } + + private setupProportionalLegendFormatters() { + const formatters: IFormatterDefinition[] = [ + { + componentType: LinkFormatterComponent.lateLoadKey, + label: $localize\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\`Link\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\`, + dataTypes: { + value: "label", + link: "link", + }, + }, + ]; + + const widgetTemplate = this.widgetTypesService.getWidgetType( + "proportional", + 1 + ); + this.widgetTypesService.setNode( + widgetTemplate, + "configurator", + WellKnownPathKey.Formatters, + formatters + ); + } +} +\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\`, + "tutorials/customization/configurator-section/custom-configurator-section/custom-configurator-section.example.component.ts": \\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\`// © 2022 SolarWinds Worldwide, LLC. All rights reserved. +// +// Permission is hereby granted, free of charge, to any person obtaining a copy +// of this software and associated documentation files (the "Software"), to +// deal in the Software without restriction, including without limitation the +// rights to use, copy, modify, merge, publish, distribute, sublicense, and/or +// sell copies of the Software, and to permit persons to whom the Software is +// furnished to do so, subject to the following conditions: +// +// The above copyright notice and this permission notice shall be included in +// all copies or substantial portions of the Software. +// +// THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR +// IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, +// FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE +// AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER +// LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, +// OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN +// THE SOFTWARE. + +import { HttpClient, HttpErrorResponse } from "@angular/common/http"; +import { + ChangeDetectionStrategy, + ChangeDetectorRef, + Component, + EventEmitter, + Injectable, + Input, + OnChanges, + OnDestroy, + OnInit, + Output, + SimpleChanges, +} from "@angular/core"; +import { FormBuilder, FormGroup, Validators } from "@angular/forms"; +import { GridsterConfig, GridsterItem } from "angular-gridster2"; +// eslint-disable-next-line import/no-deprecated +import { BehaviorSubject, combineLatest, Observable } from "rxjs"; +// eslint-disable-next-line import/no-deprecated +import { finalize, map, startWith } from "rxjs/operators"; + +import { DataSourceService, IFilteringOutputs } from "@nova-ui/bits"; +import { + ComponentRegistryService, + DATA_SOURCE, + DEFAULT_PIZZAGNA_ROOT, + IDashboard, + IHasChangeDetector, + IHasForm, + IKpiData, + IProviderConfiguration, + IRefresherProperties, + IWidget, + IWidgets, + KpiComponent, + NOVA_KPI_DATASOURCE_ADAPTER, + PizzagnaLayer, + ProviderRegistryService, + WellKnownPathKey, + WellKnownProviders, + WidgetTypesService, +} from "@nova-ui/dashboards"; + +/** + * A custom version of the KpiDescriptionConfigurationComponent provided by the dashboards framework. + * --- + * For this example, the existing background color selection functionality has been replaced by custom + * template content. + */ +@Component({ + selector: "custom-kpi-description-configuration", + template: \\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\` + + +
+
+ + + +
+ + +
+
+ Custom Content +
+
+ The default version of this configurator section + displays a background color selector here. +
+
+ + +
+ + + +
+
+
+ \\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\`, + styleUrls: ["./custom-configurator-section.example.component.less"], + changeDetection: ChangeDetectionStrategy.OnPush, + standalone: false, +}) +// Remember to declare this class in the parent module +export class CustomKpiDescriptionConfigurationComponent + implements OnInit, OnChanges, IHasChangeDetector, IHasForm +{ + // Ensure that the lateLoadKey value matches class name + public static lateLoadKey = "CustomKpiDescriptionConfigurationComponent"; + + @Input() componentId: string; + @Input() configurableUnits: boolean; + + @Input() label: string = ""; + @Input() units: string = ""; + + @Output() formReady = new EventEmitter(); + + public form: FormGroup; + public subtitle$: Observable; + + constructor( + public changeDetector: ChangeDetectorRef, + private formBuilder: FormBuilder + ) {} + + public ngOnInit(): void { + this.form = this.formBuilder.group({ + label: [this.label, [Validators.required]], + }); + + if (this.configurableUnits) { + this.form.addControl("units", this.formBuilder.control(this.units)); + } + + const label = this.form.get("label"); + // eslint-disable-next-line import/no-deprecated + const labelValue = label?.valueChanges.pipe(startWith(label?.value)); + + // eslint-disable-next-line import/no-deprecated + this.subtitle$ = combineLatest([ + labelValue?.pipe(map((t) => t || $localize\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\`no label\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\`)), + ]).pipe(map((labels) => labels.join(", "))); + + this.formReady.emit(this.form); + } + + public ngOnChanges(changes: SimpleChanges): void { + if (changes.label) { + this.form.patchValue({ label: changes.label.currentValue }); + } + if (changes.units) { + this.form.patchValue({ units: changes.units.currentValue }); + } + } +} + +/** + * A simple KPI data source to retrieve the average rating of Harry Potter and the Sorcerer's Stone (book) via googleapis + */ +@Injectable() +export class AverageRatingKpiDataSource + extends DataSourceService + implements OnDestroy +{ + // This is the ID we'll use to identify the provider + public static providerId = "AverageRatingKpiDataSource"; + + // Use this subject to communicate the data source's busy state + public busy = new BehaviorSubject(false); + + constructor(private http: HttpClient) { + super(); + } + + // In this example, getFilteredData is invoked every 10 minutes (Take a look at the refresher + // provider definition in the widget configuration below to see how the interval is set) + public async getFilteredData(): Promise { + this.busy.next(true); + return new Promise((resolve) => { + // *** Make a resource request to an external API (if needed) + this.http + .get("https://www.googleapis.com/books/v1/volumes/5MQFrgEACAAJ") + .pipe(finalize(() => this.busy.next(false))) + .subscribe({ + next: (data: any) => { + resolve({ + result: { + value: data.volumeInfo.averageRating, + }, + }); + }, + error: (error: HttpErrorResponse) => { + resolve({ + result: null, + error: { + type: error.status, + }, + }); + }, + }); + }); + } + + public ngOnDestroy(): void { + this.outputsSubject.complete(); + } +} + +/** + * A simple KPI data source to retrieve the ratings count of Harry Potter and the Sorcerer's Stone (book) via googleapis + */ +@Injectable() +export class RatingsCountKpiDataSource + extends DataSourceService + implements OnDestroy +{ + public static providerId = "RatingsCountKpiDataSource"; + + // Use this subject to communicate the data source's busy state + public busy = new BehaviorSubject(false); + + constructor(private http: HttpClient) { + super(); + } + + public async getFilteredData(): Promise { + this.busy.next(true); + return new Promise((resolve) => { + this.http + .get("https://www.googleapis.com/books/v1/volumes/5MQFrgEACAAJ") + .pipe(finalize(() => this.busy.next(false))) + .subscribe({ + next: (data: any) => { + resolve({ + result: { + value: data.volumeInfo.ratingsCount, + }, + }); + }, + error: (error: HttpErrorResponse) => { + resolve({ + result: null, + error: { + type: error.status, + }, + }); + }, + }); + }); + } + + public ngOnDestroy(): void { + this.outputsSubject.complete(); + } +} + +/** + * A component that instantiates the dashboard + */ +@Component({ + selector: "custom-configurator-section-example", + templateUrl: "./custom-configurator-section.example.component.html", + styleUrls: ["./custom-configurator-section.example.component.less"], + standalone: false, +}) +export class CustomConfiguratorSectionExampleComponent implements OnInit { + // This variable will hold all the data needed to define the layout and behavior of the widgets. + // Pass this to the dashboard component's dashboard input in the template. + public dashboard: IDashboard | undefined; + + // Angular gridster requires a configuration object even if it's empty. + // Pass this to the dashboard component's gridsterConfig input in the template. + public gridsterConfig: GridsterConfig = {}; + + // Boolean which dashboard takes in as an input if its true it allows you to move widgets around. + public editMode: boolean = false; + + constructor( + // WidgetTypesService provides the widget's necessary structure information + private widgetTypesService: WidgetTypesService, + + // In general, the ProviderRegistryService is used for making entities available for injection into dynamically loaded components. + private providerRegistry: ProviderRegistryService, + + // Inject the ComponentRegistryService to make our custom component available for late loading by the dashboards framework + private componentRegistry: ComponentRegistryService, + + private changeDetectorRef: ChangeDetectorRef + ) {} + + public ngOnInit(): void { + // Grab the widget's default template which will be needed as a parameter for setNode. + const widgetTemplate = this.widgetTypesService.getWidgetType("kpi", 1); + + // Replace the default KPI description configuration component with our custom one. + // Note: This could also be done in the parent module's constructor to give + // multiple dashboards access to the same custom configurator section. + this.widgetTypesService.setNode( + widgetTemplate, + "configurator", + WellKnownPathKey.TileDescriptionConfigComponentType, + CustomKpiDescriptionConfigurationComponent.lateLoadKey + ); + + // Register the custom configurator section with the component registry to make it available + // for late loading by the dashboards framework. + this.componentRegistry.registerByLateLoadKey( + CustomKpiDescriptionConfigurationComponent + ); + + // Register our data sources as dropdown options in the widget editor/configurator + // Note: This could also be done in the parent module's constructor so that + // multiple dashboards could have access to the same widget template modification. + this.widgetTypesService.setNode( + // This is the template we grabbed above with getWidgetType + widgetTemplate, + // We are setting the editor/configurator part of the widget template + "configurator", + // This indicates which node you are changing and we want to change + // the data source providers available for selection in the editor. + WellKnownPathKey.DataSourceProviders, + // We are setting the data sources available for selection in the editor + [ + AverageRatingKpiDataSource.providerId, + RatingsCountKpiDataSource.providerId, + ] + ); + + // Register the data sources available for injection into the KPI tiles. + // Note: Each tile of a KPI widget is assigned its own instance of a data source + this.providerRegistry.setProviders({ + [AverageRatingKpiDataSource.providerId]: { + provide: DATA_SOURCE, + useClass: AverageRatingKpiDataSource, + // Any dependencies that need to be injected into the provider must be listed here + deps: [HttpClient], + }, + [RatingsCountKpiDataSource.providerId]: { + provide: DATA_SOURCE, + useClass: RatingsCountKpiDataSource, + deps: [HttpClient], + }, + }); + + this.initializeDashboard(); + } + + public initializeDashboard(): void { + // We're using a static configuration object for this example (see widgetConfig at the bottom of the file), + // but this is where the widget's configuration could potentially be populated from a database + const kpiWidget = widgetConfig; + const widgetIndex: IWidgets = { + // Complete the KPI widget with information coming from its type definition + [kpiWidget.id]: + this.widgetTypesService.mergeWithWidgetType(kpiWidget), + }; + + // Setting the widget dimensions and position (this is for gridster) + const positions: Record = { + [kpiWidget.id]: { + cols: 4, + rows: 6, + y: 0, + x: 0, + }, + }; + + // Finally, assigning the variables we created above to the dashboard + this.dashboard = { + positions, + widgets: widgetIndex, + }; + } + + /** Used for restoring widgets state */ + public reInitializeDashboard(): void { + // destroys the components and their providers so the dashboard can re init data + this.dashboard = undefined; + this.changeDetectorRef.detectChanges(); + + this.initializeDashboard(); + } +} + +const widgetConfig: IWidget = { + id: "widget1", + type: "kpi", + pizzagna: { + [PizzagnaLayer.Configuration]: { + [DEFAULT_PIZZAGNA_ROOT]: { + providers: { + [WellKnownProviders.Refresher]: { + properties: { + // Configuring the refresher interval so that our data source is invoked every ten minutes + interval: 60 * 10, + enabled: true, + } as IRefresherProperties, + } as Partial, + }, + }, + header: { + properties: { + title: "Harry Potter and the Sorcerer's Stone", + subtitle: "By J. K. Rowling", + }, + }, + tiles: { + properties: { + nodes: ["kpi1"], + }, + }, + kpi1: { + id: "kpi1", + componentType: KpiComponent.lateLoadKey, + properties: { + widgetData: { + units: "out of 5 Stars", + label: "Average Rating", + }, + }, + providers: { + [WellKnownProviders.DataSource]: { + // Setting the data source providerId for the tile with id "kpi1" + providerId: AverageRatingKpiDataSource.providerId, + } as IProviderConfiguration, + [WellKnownProviders.Adapter]: { + providerId: NOVA_KPI_DATASOURCE_ADAPTER, + properties: { + componentId: "kpi1", + propertyPath: "widgetData", + }, + } as IProviderConfiguration, + }, + }, + }, + }, +}; +\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\`, + "tutorials/customization/configurator-section/custom-configurator-section-docs.component.ts": \\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\`// © 2022 SolarWinds Worldwide, LLC. All rights reserved. +// +// Permission is hereby granted, free of charge, to any person obtaining a copy +// of this software and associated documentation files (the "Software"), to +// deal in the Software without restriction, including without limitation the +// rights to use, copy, modify, merge, publish, distribute, sublicense, and/or +// sell copies of the Software, and to permit persons to whom the Software is +// furnished to do so, subject to the following conditions: +// +// The above copyright notice and this permission notice shall be included in +// all copies or substantial portions of the Software. +// +// THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR +// IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, +// FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE +// AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER +// LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, +// OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN +// THE SOFTWARE. + +import { Component } from "@angular/core"; + +@Component({ + selector: "custom-configurator-section-docs", + templateUrl: "./custom-configurator-section-docs.component.html", + standalone: false, +}) +export class CustomConfiguratorSectionDocsComponent {} +\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\`, + "tutorials/customization/configurator-section/custom-configurator-section.module.ts": \\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\`// © 2022 SolarWinds Worldwide, LLC. All rights reserved. +// +// Permission is hereby granted, free of charge, to any person obtaining a copy +// of this software and associated documentation files (the "Software"), to +// deal in the Software without restriction, including without limitation the +// rights to use, copy, modify, merge, publish, distribute, sublicense, and/or +// sell copies of the Software, and to permit persons to whom the Software is +// furnished to do so, subject to the following conditions: +// +// The above copyright notice and this permission notice shall be included in +// all copies or substantial portions of the Software. +// +// THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR +// IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, +// FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE +// AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER +// LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, +// OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN +// THE SOFTWARE. + +import { CommonModule } from "@angular/common"; +import { HttpClientModule } from "@angular/common/http"; +import { NgModule } from "@angular/core"; +import { ReactiveFormsModule } from "@angular/forms"; +import { RouterModule } from "@angular/router"; + +import { + NuiButtonModule, + NuiDocsModule, + NuiFormFieldModule, + NuiIconModule, + NuiMessageModule, + NuiSwitchModule, + NuiTextboxModule, + DEMO_PATH_TOKEN, +} from "@nova-ui/bits"; +import { + NuiDashboardConfiguratorModule, + NuiDashboardsModule, +} from "@nova-ui/dashboards"; + +import { + CustomConfiguratorSectionExampleComponent, + CustomKpiDescriptionConfigurationComponent, +} from "./custom-configurator-section/custom-configurator-section.example.component"; +import { CustomConfiguratorSectionDocsComponent } from "./custom-configurator-section-docs.component"; +import { getDemoFiles } from "../../../../../demo-files-factory"; + +const routes = [ + { + path: "", + component: CustomConfiguratorSectionDocsComponent, + data: { + srlc: { + hideIndicator: true, + }, + showThemeSwitcher: true, + }, + }, + { + path: "example", + component: CustomConfiguratorSectionExampleComponent, + data: { + srlc: { + hideIndicator: true, + }, + }, + }, +]; + +@NgModule({ + imports: [ + CommonModule, + ReactiveFormsModule, + HttpClientModule, + NuiDashboardsModule, + NuiDashboardConfiguratorModule, + NuiDocsModule, + NuiFormFieldModule, + NuiIconModule, + NuiMessageModule, + NuiSwitchModule, + NuiTextboxModule, + NuiButtonModule, + RouterModule.forChild(routes), + ], + declarations: [ + CustomConfiguratorSectionDocsComponent, + CustomKpiDescriptionConfigurationComponent, + CustomConfiguratorSectionExampleComponent, + ], + providers: [ + { + provide: DEMO_PATH_TOKEN, + useValue: getDemoFiles("configurator-section"), + }, + ], +}) +export default class CustomConfiguratorSectionModule {} +\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\`, + "tutorials/customization/customization.module.ts": \\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\`// © 2022 SolarWinds Worldwide, LLC. All rights reserved. +// +// Permission is hereby granted, free of charge, to any person obtaining a copy +// of this software and associated documentation files (the "Software"), to +// deal in the Software without restriction, including without limitation the +// rights to use, copy, modify, merge, publish, distribute, sublicense, and/or +// sell copies of the Software, and to permit persons to whom the Software is +// furnished to do so, subject to the following conditions: +// +// The above copyright notice and this permission notice shall be included in +// all copies or substantial portions of the Software. +// +// THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR +// IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, +// FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE +// AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER +// LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, +// OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN +// THE SOFTWARE. + +import { NgModule, Type } from "@angular/core"; +import { RouterModule, Routes } from "@angular/router"; + +import { ConfiguratorHeadingService } from "@nova-ui/dashboards"; + +enum CustomizationModuleRoute { + ConfiguratorSection = "configurator-section", + Widget = "widget", + Formatter = "formatter", + DataSourceConfigurator = "data-source-configurator", +} + +const routes: Routes = [ + { + path: CustomizationModuleRoute.ConfiguratorSection, + loadChildren: async () => + import( + "./configurator-section/custom-configurator-section.module" + ) as object as Promise>, + }, + { + path: CustomizationModuleRoute.Widget, + loadChildren: async () => + import("./widget/custom-widget.module") as object as Promise< + Type + >, + }, + { + path: CustomizationModuleRoute.Formatter, + loadChildren: async () => + import("./formatter/custom-formatter.module") as object as Promise< + Type + >, + }, + { + path: CustomizationModuleRoute.DataSourceConfigurator, + loadChildren: async () => + import( + "./data-source-configurator/custom-data-source-configurator.module" + ) as object as Promise>, + }, +]; + +@NgModule({ + imports: [RouterModule.forChild(routes)], + providers: [ConfiguratorHeadingService], +}) +export default class CustomizationModule {} +\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\`, + "tutorials/customization/data-source-configurator/custom-data-source-configurator-docs.component.ts": \\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\`// © 2022 SolarWinds Worldwide, LLC. All rights reserved. +// +// Permission is hereby granted, free of charge, to any person obtaining a copy +// of this software and associated documentation files (the "Software"), to +// deal in the Software without restriction, including without limitation the +// rights to use, copy, modify, merge, publish, distribute, sublicense, and/or +// sell copies of the Software, and to permit persons to whom the Software is +// furnished to do so, subject to the following conditions: +// +// The above copyright notice and this permission notice shall be included in +// all copies or substantial portions of the Software. +// +// THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR +// IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, +// FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE +// AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER +// LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, +// OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN +// THE SOFTWARE. + +import { Component } from "@angular/core"; + +@Component({ + selector: "nui-custom-data-source-configurator-docs", + templateUrl: "./custom-data-source-configurator-docs.component.html", + standalone: false, +}) +export class CustomDataSourceConfiguratorDocComponent {} +\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\`, + "tutorials/customization/data-source-configurator/custom-data-source-configurator.module.ts": \\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\`// © 2022 SolarWinds Worldwide, LLC. All rights reserved. +// +// Permission is hereby granted, free of charge, to any person obtaining a copy +// of this software and associated documentation files (the "Software"), to +// deal in the Software without restriction, including without limitation the +// rights to use, copy, modify, merge, publish, distribute, sublicense, and/or +// sell copies of the Software, and to permit persons to whom the Software is +// furnished to do so, subject to the following conditions: +// +// The above copyright notice and this permission notice shall be included in +// all copies or substantial portions of the Software. +// +// THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR +// IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, +// FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE +// AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER +// LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, +// OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN +// THE SOFTWARE. + +import { NgModule } from "@angular/core"; +import { ReactiveFormsModule } from "@angular/forms"; +import { RouterModule, Routes } from "@angular/router"; + +// eslint-disable-next-line max-len +import { + NuiButtonModule, + NuiDocsModule, + NuiFormFieldModule, + NuiIconModule, + NuiMessageModule, + NuiSelectV2Module, + NuiSwitchModule, + NuiTextboxModule, + NuiValidationMessageModule, + DEMO_PATH_TOKEN, +} from "@nova-ui/bits"; +import { + NuiDashboardConfiguratorModule, + NuiDashboardsModule, +} from "@nova-ui/dashboards"; + +import { CustomDataSourceConfiguratorDocComponent } from "./custom-data-source-configurator-docs.component"; +import { + CustomDataSourceConfiguratorExampleComponent, + HarryPotterDataSourceConfiguratorComponent, +} from "./example/custom-data-source-configurator-example.component"; +import { getDemoFiles } from "../../../../../demo-files-factory"; + +const routes: Routes = [ + { + path: "", + component: CustomDataSourceConfiguratorDocComponent, + data: { + srlc: { + hideIndicator: true, + }, + showThemeSwitcher: true, + }, + }, +]; + +@NgModule({ + imports: [ + RouterModule.forChild(routes), + NuiDocsModule, + NuiButtonModule, + NuiMessageModule, + NuiDashboardConfiguratorModule, + NuiDashboardsModule, + NuiFormFieldModule, + NuiTextboxModule, + NuiSwitchModule, + NuiSelectV2Module, + NuiValidationMessageModule, + NuiIconModule, + ReactiveFormsModule, + ], + declarations: [ + CustomDataSourceConfiguratorDocComponent, + CustomDataSourceConfiguratorExampleComponent, + HarryPotterDataSourceConfiguratorComponent, + ], + providers: [ + { + provide: DEMO_PATH_TOKEN, + useValue: getDemoFiles("data-source-configurator"), + }, + ], +}) +export default class CustomDataSourceConfiguratorModuleRoute {} +\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\`, + "tutorials/customization/data-source-configurator/example/custom-data-source-configurator-example.component.ts": \\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\`// © 2022 SolarWinds Worldwide, LLC. All rights reserved. +// +// Permission is hereby granted, free of charge, to any person obtaining a copy +// of this software and associated documentation files (the "Software"), to +// deal in the Software without restriction, including without limitation the +// rights to use, copy, modify, merge, publish, distribute, sublicense, and/or +// sell copies of the Software, and to permit persons to whom the Software is +// furnished to do so, subject to the following conditions: +// +// The above copyright notice and this permission notice shall be included in +// all copies or substantial portions of the Software. +// +// THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR +// IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, +// FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE +// AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER +// LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, +// OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN +// THE SOFTWARE. + +import { HttpClient, HttpErrorResponse } from "@angular/common/http"; +import { + ChangeDetectorRef, + Component, + Inject, + Injectable, + Injector, + OnDestroy, + OnInit, +} from "@angular/core"; +import { FormBuilder, Validators } from "@angular/forms"; +import { GridsterConfig, GridsterItem } from "angular-gridster2"; +import { BehaviorSubject } from "rxjs"; +import { finalize } from "rxjs/operators"; + +import { + DataSourceService, + EventBus, + IEvent, + IFilteringOutputs, + LoggerService, +} from "@nova-ui/bits"; +import { + ComponentRegistryService, + ConfiguratorHeadingService, + DataSourceConfigurationV2Component, + DATA_SOURCE, + DEFAULT_PIZZAGNA_ROOT, + IConfigurable, + IDashboard, + IKpiData, + IProperties, + IProviderConfiguration, + IRefresherProperties, + IWidget, + IWidgets, + KpiComponent, + NOVA_KPI_DATASOURCE_ADAPTER, + PizzagnaLayer, + PIZZAGNA_EVENT_BUS, + ProviderRegistryService, + WellKnownPathKey, + WellKnownProviders, + WidgetTypesService, +} from "@nova-ui/dashboards"; + +/** + * This component will serve as the data source accordion in the configurator. + */ +@Component({ + selector: "harry-potter-data-source-configurator", + styleUrls: ["./custom-data-source-configurator-example.component.less"], + template: \\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\` + +
+ +
+ Data Source +
+ Harry Potter Books +
+
+
+
+ + + + {{ book.title }} + + + +
+
+ + + + {{ metric.label }} + + + +
+
+ \\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\`, + standalone: false, +}) +@Injectable() +export class HarryPotterDataSourceConfiguratorComponent + extends DataSourceConfigurationV2Component + implements OnInit +{ + // This lateLoadKey allows the component to be able to be registered by the componentRegistry + public static lateLoadKey = "HarryPotterDataSourceConfiguratorComponent"; + + // Array of books that will populate the book select + public books = [ + { + id: "5MQFrgEACAAJ", + title: $localize\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\`Harry Potter and the Sorcerer's Stone\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\`, + }, + { + id: "5iTebBW-w7QC", + title: $localize\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\`Harry Potter and the Chamber of Secrets\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\`, + }, + ]; + + // Array of metrics that will populate the metric select + public metrics = [ + { + id: "averageRating", + label: $localize\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\`Average Rating\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\`, + }, + { + id: "ratingsCount", + label: $localize\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\`Ratings Count\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\`, + }, + ]; + + // These need to be injected because DataSourceConfigurationV2Component uses them + constructor( + changeDetector: ChangeDetectorRef, + configuratorHeading: ConfiguratorHeadingService, + formBuilder: FormBuilder, + providerRegistryService: ProviderRegistryService, + @Inject(PIZZAGNA_EVENT_BUS) eventBus: EventBus, + injector: Injector, + logger: LoggerService + ) { + super( + changeDetector, + configuratorHeading, + formBuilder, + providerRegistryService, + eventBus, + injector, + logger + ); + } + + // Overriding 'ngOnInit' to add custom controls to the 'properties' form group + public ngOnInit(): void { + super.ngOnInit(); + + // Overriding the 'properties' control on the form to create a form group that accommodates our custom properties + this.form.setControl( + "properties", + this.formBuilder.group({ + bookId: [this.properties?.bookId ?? "", Validators.required], + metric: [this.properties?.metric ?? "", Validators.required], + }) + ); + // The default data source control has a required validator we're removing that validator here since we aren't using it. + this.form.setControl("dataSource", this.formBuilder.control(null)); + // Here we set the providerId to our only data source so when a new tile gets created it will default to it. + this.form.get("providerId")?.setValue(AcmeKpiDataSource.providerId); + // Here we subscribe to the form and if there are any changes we invoke the data source + this.form.valueChanges.subscribe((value) => { + if (!value.providerId) { + return; + } + this.invokeDataSource(value); + }); + } +} + +/** + * A simple KPI data source to retrieve the average rating of Harry Potter and the Sorcerer's Stone (book) via googleapis + */ +@Injectable() +export class AcmeKpiDataSource + extends DataSourceService + implements OnDestroy, IConfigurable +{ + // This is the ID we'll use to identify the provider + public static providerId = "AcmeKpiDataSource"; + + // Use this subject to communicate the data source's busy state + public busy = new BehaviorSubject(false); + + public properties: IProperties; + + constructor(private http: HttpClient) { + super(); + } + + // This function MUST be implemented in order to receive property updates from our configurator + public updateConfiguration(properties: IProperties): void { + // Saving the properties because we will need it for this data source. + this.properties = properties; + } + + // In this example, getFilteredData is invoked every 10 minutes (Take a look at the refresher + // provider definition in the widget configuration below to see how the interval is set) + public async getFilteredData(): Promise { + // For loading indicator to show + this.busy.next(true); + return new Promise((resolve) => { + // *** Make a resource request to an external API (if needed) + this.http + .get( + \\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\`https://www.googleapis.com/books/v1/volumes/\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\${this.properties?.bookId}\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\` + ) + // For loading indicator to be hidden + .pipe(finalize(() => this.busy.next(false))) + .subscribe({ + next: (data: any) => { + resolve({ + result: { + value: data.volumeInfo[this.properties?.metric], + }, + }); + }, + error: (error: HttpErrorResponse) => { + resolve({ + result: null, + error: { + type: error.status, + }, + }); + }, + }); + }); + } + + public ngOnDestroy(): void { + this.outputsSubject.complete(); + } +} + +/** + * A component that instantiates the dashboard + */ +@Component({ + selector: "custom-data-source-configurator-example", + templateUrl: "./custom-data-source-configurator-example.component.html", + styleUrls: ["./custom-data-source-configurator-example.component.less"], + standalone: false, +}) +export class CustomDataSourceConfiguratorExampleComponent implements OnInit { + // This variable will hold all the data needed to define the layout and behavior of the widgets. + // Pass this to the dashboard component's dashboard input in the template. + public dashboard: IDashboard; + + // Angular gridster requires a configuration object even if it's empty. + // Pass this to the dashboard component's gridsterConfig input in the template. + public gridsterConfig: GridsterConfig = {}; + + // Boolean which dashboard takes in as an input if its true it allows you to move widgets around. + public editMode: boolean = false; + + constructor( + // WidgetTypesService provides the widget's necessary structure information + private widgetTypesService: WidgetTypesService, + + // In general, the ProviderRegistryService is used for making entities available for injection into dynamically loaded components. + private providerRegistry: ProviderRegistryService, + + // Inject the ComponentRegistryService to make our custom component available for late loading by the dashboards framework + private componentRegistry: ComponentRegistryService + ) {} + + public ngOnInit(): void { + // Registering the new data source configurator so it can be used. + this.componentRegistry.registerByLateLoadKey( + HarryPotterDataSourceConfiguratorComponent + ); + // Registering the data source for injection into the KPI tile. + // Note: Each tile of a KPI widget is assigned its own instance of the data source + this.providerRegistry.setProviders({ + [AcmeKpiDataSource.providerId]: { + provide: DATA_SOURCE, + useClass: AcmeKpiDataSource, + // Any dependencies that need to be injected into the provider must be listed here + deps: [HttpClient], + }, + }); + + const kpiWidgetTemplate = this.widgetTypesService.getWidgetType( + "kpi", + 1 + ); + + this.widgetTypesService.setNode( + // This is the template we grabbed above with getWidgetType + kpiWidgetTemplate, + // We are setting the editor/configurator part of the widget template + "configurator", + // This is the path to go to the data source config component type. + WellKnownPathKey.DataSourceConfigComponentType, + // We are changing it to use the component we just created above instead of the default. + HarryPotterDataSourceConfiguratorComponent.lateLoadKey + ); + + // We're using a static configuration object for this example, but this is where + // the widget's configuration could potentially be populated from a database + const kpiWidget = widgetConfig; + const widgetIndex: IWidgets = { + // Complete the KPI widget with information coming from its type definition + [kpiWidget.id]: + this.widgetTypesService.mergeWithWidgetType(kpiWidget), + }; + + // Setting the widget dimensions and position (this is for gridster) + const positions: Record = { + [kpiWidget.id]: { + cols: 4, + rows: 6, + y: 0, + x: 0, + }, + }; + + // Finally, assigning the variables we created above to the dashboard + this.dashboard = { + positions, + widgets: widgetIndex, + }; + } +} + +const widgetConfig: IWidget = { + id: "widget1", + type: "kpi", + pizzagna: { + [PizzagnaLayer.Configuration]: { + [DEFAULT_PIZZAGNA_ROOT]: { + providers: { + [WellKnownProviders.Refresher]: { + properties: { + // Configuring the refresher interval so that our data source is invoked every ten minutes + interval: 60 * 10, + enabled: true, + } as IRefresherProperties, + } as Partial, + }, + }, + header: { + properties: { + title: "Harry Potter and the Sorcerer's Stone", + subtitle: "By J. K. Rowling", + }, + }, + tiles: { + properties: { + nodes: ["kpi1"], + }, + }, + kpi1: { + id: "kpi1", + componentType: KpiComponent.lateLoadKey, + properties: { + widgetData: { + units: "out of 5 Stars", + label: "Average Rating", + }, + }, + providers: { + [WellKnownProviders.DataSource]: { + // Setting the data source providerId for the tile with id "kpi1" + providerId: AcmeKpiDataSource.providerId, + properties: { + bookId: "5MQFrgEACAAJ", + metric: "averageRating", + }, + } as IProviderConfiguration, + [WellKnownProviders.Adapter]: { + providerId: NOVA_KPI_DATASOURCE_ADAPTER, + properties: { + componentId: "kpi1", + propertyPath: "widgetData", + }, + } as IProviderConfiguration, + }, + }, + }, + }, +}; +\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\`, + "tutorials/customization/formatter/custom-formatter.module.ts": \\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\`// © 2022 SolarWinds Worldwide, LLC. All rights reserved. +// +// Permission is hereby granted, free of charge, to any person obtaining a copy +// of this software and associated documentation files (the "Software"), to +// deal in the Software without restriction, including without limitation the +// rights to use, copy, modify, merge, publish, distribute, sublicense, and/or +// sell copies of the Software, and to permit persons to whom the Software is +// furnished to do so, subject to the following conditions: +// +// The above copyright notice and this permission notice shall be included in +// all copies or substantial portions of the Software. +// +// THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR +// IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, +// FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE +// AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER +// LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, +// OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN +// THE SOFTWARE. + +import { NgModule } from "@angular/core"; +import { ReactiveFormsModule } from "@angular/forms"; +import { RouterModule, Routes } from "@angular/router"; + +// eslint-disable-next-line max-len +import { + NuiButtonModule, + NuiDocsModule, + NuiFormFieldModule, + NuiIconModule, + NuiMessageModule, + NuiSelectV2Module, + NuiSwitchModule, + NuiTextboxModule, + NuiValidationMessageModule, + DEMO_PATH_TOKEN, +} from "@nova-ui/bits"; +import { NuiDashboardsModule } from "@nova-ui/dashboards"; + +import { CustomDonutContentFormatterDocComponent } from "./donut-content-formatter-example/custom-donut-content-formatter-docs.component"; +import { + CustomDonutContentFormatterComponent, + CustomDonutContentFormatterConfiguratorComponent, + CustomDonutContentFormatterExampleComponent, +} from "./donut-content-formatter-example/custom-donut-content-formatter-example.component"; +import { CustomFormatterDocComponent } from "./formatter-example/custom-formatter-docs.component"; +import { + CustomFormatterComponent, + CustomFormatterConfiguratorComponent, + CustomFormatterExampleComponent, +} from "./formatter-example/custom-formatter-example.component"; +import { getDemoFiles } from "../../../../../demo-files-factory"; + +const routes: Routes = [ + { + path: "table-formatter", + component: CustomFormatterDocComponent, + data: { + srlc: { + hideIndicator: true, + }, + showThemeSwitcher: true, + }, + }, + { + path: "donut-content-formatter", + component: CustomDonutContentFormatterDocComponent, + data: { + srlc: { + hideIndicator: true, + }, + showThemeSwitcher: true, + }, + }, +]; + +@NgModule({ + imports: [ + RouterModule.forChild(routes), + NuiDocsModule, + NuiButtonModule, + NuiMessageModule, + NuiDashboardsModule, + NuiFormFieldModule, + NuiTextboxModule, + NuiSwitchModule, + NuiSelectV2Module, + NuiValidationMessageModule, + NuiIconModule, + ReactiveFormsModule, + ], + declarations: [ + CustomDonutContentFormatterComponent, + CustomDonutContentFormatterExampleComponent, + CustomDonutContentFormatterConfiguratorComponent, + CustomDonutContentFormatterDocComponent, + CustomFormatterDocComponent, + CustomFormatterExampleComponent, + CustomFormatterConfiguratorComponent, + CustomFormatterComponent, + ], + providers: [ + { + provide: DEMO_PATH_TOKEN, + useValue: getDemoFiles("formatter"), + }, + ], +}) +export default class CustomFormatterModuleRoute {} +\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\`, + "tutorials/customization/formatter/donut-content-formatter-example/custom-donut-content-formatter-docs.component.ts": \\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\`// © 2022 SolarWinds Worldwide, LLC. All rights reserved. +// +// Permission is hereby granted, free of charge, to any person obtaining a copy +// of this software and associated documentation files (the "Software"), to +// deal in the Software without restriction, including without limitation the +// rights to use, copy, modify, merge, publish, distribute, sublicense, and/or +// sell copies of the Software, and to permit persons to whom the Software is +// furnished to do so, subject to the following conditions: +// +// The above copyright notice and this permission notice shall be included in +// all copies or substantial portions of the Software. +// +// THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR +// IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, +// FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE +// AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER +// LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, +// OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN +// THE SOFTWARE. + +import { Component } from "@angular/core"; + +@Component({ + selector: "nui-custom-donut-content-formatter-docs", + templateUrl: "./custom-donut-content-formatter-docs.component.html", + standalone: false, +}) +export class CustomDonutContentFormatterDocComponent {} +\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\`, + "tutorials/customization/formatter/donut-content-formatter-example/custom-donut-content-formatter-example.component.ts": \\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\`// © 2022 SolarWinds Worldwide, LLC. All rights reserved. +// +// Permission is hereby granted, free of charge, to any person obtaining a copy +// of this software and associated documentation files (the "Software"), to +// deal in the Software without restriction, including without limitation the +// rights to use, copy, modify, merge, publish, distribute, sublicense, and/or +// sell copies of the Software, and to permit persons to whom the Software is +// furnished to do so, subject to the following conditions: +// +// The above copyright notice and this permission notice shall be included in +// all copies or substantial portions of the Software. +// +// THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR +// IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, +// FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE +// AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER +// LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, +// OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN +// THE SOFTWARE. + +import { HttpClient } from "@angular/common/http"; +import { + ChangeDetectorRef, + Component, + Injectable, + Input, + OnChanges, + OnDestroy, + OnInit, + SimpleChanges, +} from "@angular/core"; +import { FormBuilder, FormGroup, Validators } from "@angular/forms"; +import { GridsterConfig, GridsterItem } from "angular-gridster2"; +import { Subject } from "rxjs"; +import { takeUntil, tap } from "rxjs/operators"; + +import { + DataSourceService, + IconService, + IDataSource, + IFilteringOutputs, + LoggerService, +} from "@nova-ui/bits"; +import { + ChartAssist, + IAccessors, + IChartAssistEvent, + IChartAssistSeries, +} from "@nova-ui/charts"; +import { + ComponentRegistryService, + ConfiguratorHeadingService, + DATA_SOURCE, + DonutChartFormatterConfiguratorComponent, + DonutContentPercentageConfigurationComponent, + DonutContentPercentageFormatterComponent, + DonutContentSumFormatterComponent, + IDashboard, + IFormatterDefinition, + IHasChangeDetector, + IProperties, + IProportionalWidgetChartOptions, + IProportionalWidgetConfig, + IProviderConfiguration, + IWidget, + IWidgets, + LegendPlacement, + PizzagnaLayer, + ProportionalWidgetChartTypes, + ProviderRegistryService, + WellKnownPathKey, + WellKnownProviders, + WidgetTypesService, +} from "@nova-ui/dashboards"; + +export enum Units { + Days = "Day(s)", + Weeks = "Week(s)", + Hours = "Hour(s)", +} + +@Component({ + selector: "custom-donut-content-formatter", + host: { class: "d-flex flex-column align-items-center" }, + template: \\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\` +
+ + {{ chartMetric || properties?.currentMetric || data[0].id }} +
+
+ {{ chartContent }} +
+
+ {{ units }} +
+
\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\`, + styleUrls: ["./custom-donut-content-formatter-example.component.less"], + standalone: false, +}) +export class CustomDonutContentFormatterComponent + implements IHasChangeDetector, OnInit, OnChanges +{ + public static lateLoadKey = "CustomDonutContentFormatterComponent"; + + // Used to emphasize the chart series when user interacts either with the chart legend, or chart segments. + public emphasizedSeriesData: IChartAssistSeries | undefined; + + // Current raw value of the metric to display + public currentMetricData: number; + + // Metric value rendered inside the template, when user selects a metric, and gets automatically recalculated depending on selected units + public chartContent: number; + + // Metric value rendered inside the template, when user interacts with either chart legend, or chart segments + public chartMetric: number; + + // Units which user can select from the configuration + public units: Units = Units.Days; + + private readonly destroy$ = new Subject(); + + constructor(public changeDetector: ChangeDetectorRef) {} + + // The data we receive from the chart, including metrics names and their values + @Input() data: IChartAssistSeries[]; + + // We use this chart assist instance to subscribe to the events triggered when an interaction with the chart occurs + @Input() chartAssist: ChartAssist; + + // These are the current properties from pizzagna. Used to use data set at the configuration layer + @Input() properties: IProperties; + + public ngOnChanges(changes: SimpleChanges): void { + if (changes.properties || !this.properties) { + // If current metric is not in the list of metrics any more we fall back to the very first one from the list we get from the datasource + this.currentMetricData = + this.data.find( + (item) => item.id === this.properties?.currentMetric + )?.data[0] || this.data[0].data[0]; + + // We either take the selected value, or fall back to the preselected default one + this.units = this.properties?.units || this.units; + } + + this.setContentValue(); + } + + public ngOnInit(): void { + // Here 'chartAssistSubject' is the entity that emits events every time user interacts with either chart legend, or chart segments. + // Subscribing to properly react on these kind of events + this.chartAssist.chartAssistSubject + .pipe( + tap( + (data: IChartAssistEvent) => + (this.emphasizedSeriesData = this.data.find( + (item) => item.id === data.payload.seriesId + )) + ), + tap(() => this.setContentValue()), + tap(() => this.setMetricValue()), + takeUntil(this.destroy$) + ) + .subscribe(); + } + + public getConvertedData(emphData: number): number { + // Recalculating data depending on the units user selected from the configuration view + switch (this.units) { + case Units.Weeks: + return this.emphasizedSeriesData + ? this.convertToWeeks(emphData) + : this.convertToWeeks(this.currentMetricData); + + case Units.Hours: + return this.emphasizedSeriesData + ? this.convertToHours(emphData) + : this.convertToHours(this.currentMetricData); + + default: + return this.emphasizedSeriesData + ? emphData + : this.currentMetricData; + } + } + + public setContentValue(): void { + this.chartContent = this.getConvertedData( + this.emphasizedSeriesData?.data[0] + ); + } + + public setMetricValue(): void { + this.chartMetric = this.emphasizedSeriesData + ? this.data.find( + (item) => + this.getConvertedData(item.data[0]) === + this.getConvertedData(this.emphasizedSeriesData?.data[0]) + )?.id + : // if metric was not initially selected we fall back to the very first one + this.properties?.currentMetric || this.data[0].id; + } + + private convertToWeeks(days: number | undefined): number { + return days ? Number((days / 7).toFixed(2)) : 0; + } + + private convertToHours(days: number | undefined): number { + return days ? Number((days * 24).toFixed(2)) : 0; + } +} + +@Component({ + selector: "custom-donut-content-formatter-configurator", + styleUrls: ["./custom-donut-content-formatter-example.component.less"], + template: \\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\` +
+
+ + + + {{ itemValue?.name }} + + + + This field is required + + +
+
+ + + + {{ itemValue }} + + + + This field is required + + +
+
+ \\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\`, + standalone: false, +}) +export class CustomDonutContentFormatterConfiguratorComponent + extends DonutChartFormatterConfiguratorComponent + implements OnChanges, OnInit, IHasChangeDetector +{ + public static lateLoadKey = "CustomFormatterConfiguratorComponent"; + + constructor( + changeDetector: ChangeDetectorRef, + formBuilder: FormBuilder, + logger: LoggerService, + public iconService: IconService, + public configuratorHeading: ConfiguratorHeadingService + ) { + super(changeDetector, formBuilder, logger); + } + + public availableUnits: Units[] = [Units.Days, Units.Hours, Units.Weeks]; + + protected addCustomFormControls(form: FormGroup): void { + form.addControl( + "units", + this.formBuilder.control(Units.Days, Validators.required) + ); + } +} + +/** + * A component that instantiates the dashboard + */ +@Component({ + selector: "custom-donut-content-formatter-example", + templateUrl: "./custom-donut-content-formatter-example.component.html", + styleUrls: ["./custom-donut-content-formatter-example.component.less"], + standalone: false, +}) +export class CustomDonutContentFormatterExampleComponent implements OnInit { + public editMode: boolean = false; + // This variable will hold all the data needed to define the layout and behavior of the widgets. + // Pass this to the dashboard component's dashboard input in the template. + public dashboard: IDashboard | undefined; + + // Angular gridster requires a configuration object even if it's empty. + // Pass this to the dashboard component's gridsterConfig input in the template. + public gridsterConfig: GridsterConfig = {}; + + constructor( + // WidgetTypesService provides the widget's necessary structure information + private widgetTypesService: WidgetTypesService, + // In general, the ProviderRegistryService is used for making entities available for injection into dynamically loaded components. + private providerRegistry: ProviderRegistryService, + // Inject the ComponentRegistryService to make our custom component available for late loading by the dashboards framework + private componentRegistry: ComponentRegistryService, + private changeDetectorRef: ChangeDetectorRef + ) { + // Register the custom configurator component with the component registry to make it available + // for late loading by the dashboard framework. + this.componentRegistry.registerByLateLoadKey( + CustomDonutContentFormatterConfiguratorComponent + ); + // Register the custom formatter component with the component registry to make it available + // for late loading by the dashboard framework. + this.componentRegistry.registerByLateLoadKey( + CustomDonutContentFormatterComponent + ); + + // Grab the widget's default template which will be needed as a parameter for setNode below. + const proportional = this.widgetTypesService.getWidgetType( + "proportional", + 1 + ); + + const donutFormatters: IFormatterDefinition[] = [ + { + componentType: DonutContentSumFormatterComponent.lateLoadKey, + label: $localize\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\`Sum\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\`, + } as IFormatterDefinition, + { + componentType: + DonutContentPercentageFormatterComponent.lateLoadKey, + label: $localize\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\`Percentage\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\`, + configurationComponent: + DonutContentPercentageConfigurationComponent.lateLoadKey, + } as IFormatterDefinition, + { + componentType: CustomDonutContentFormatterComponent.lateLoadKey, + label: $localize\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\`Custom\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\`, + // This is a custom configurator that will pop up below the formatter once it gets selected + configurationComponent: + CustomDonutContentFormatterConfiguratorComponent.lateLoadKey, + } as IFormatterDefinition, + ]; + + this.widgetTypesService.setNode( + // This is the template we grabbed above with getWidgetType + proportional, + // We are setting the editor/configurator part of the widget template + "configurator", + // This indicates which node you are changing and we want to change the formatters available for selection in the editor. + WellKnownPathKey.Formatters, + // We are setting the available formatters with the array we created above. + donutFormatters + ); + + // This sets the donut chart's datasource to have the StatusesExampleDatasource so the drop down is filled similar to the line above. + this.widgetTypesService.setNode( + proportional, + "configurator", + WellKnownPathKey.DataSourceProviders, + [StatusesExampleDatasource.providerId] + ); + + // Registering the data source for injection into the widget. + this.providerRegistry.setProviders({ + [StatusesExampleDatasource.providerId]: { + provide: DATA_SOURCE, + useClass: StatusesExampleDatasource, + // Any dependencies that need to be injected into the provider must be listed here + deps: [], + }, + }); + } + + public ngOnInit(): void { + this.initializeDashboard(); + } + + /** Used for restoring widgets state */ + public reInitializeDashboard(): void { + // destroys the components and their providers so the dashboard can re init data + this.dashboard = undefined; + this.changeDetectorRef.detectChanges(); + + this.initializeDashboard(); + } + + public initializeDashboard(): void { + // We're using a static configuration object for this example, but this is where + // the widget's configuration could potentially be populated from a database + const proportionalWidget = widgetConfig; + const widgetIndex: IWidgets = { + // Enhance the widget with information coming from it's type definition + [proportionalWidget.id]: + this.widgetTypesService.mergeWithWidgetType(proportionalWidget), + }; + + // Setting the widget dimensions and position (this is for gridster) + const positions: Record = { + [proportionalWidget.id]: { + cols: 12, + rows: 6, + y: 0, + x: 0, + }, + }; + + // Finally, assigning the variables we created above to the dashboard + this.dashboard = { + positions, + widgets: widgetIndex, + }; + } +} + +export interface IStatusesWidgetData { + id: string; + name: string; + data: number[]; +} + +export const randomStatusesWidgetData: IStatusesWidgetData[] = [ + { + id: "Down", + name: "Down", + data: [Math.round(Math.random() * 100)], + }, + { + id: "Critical", + name: "Critical", + data: [Math.round(Math.random() * 100)], + }, + { + id: "Warning", + name: "Warning", + data: [Math.round(Math.random() * 100)], + }, + { + id: "Unknown", + name: "Unknown", + data: [Math.round(Math.random() * 100)], + }, + { + id: "Up", + name: "Up", + data: [Math.round(Math.random() * 100)], + }, + { + id: "Unmanaged", + name: "Unmanaged", + data: [Math.round(Math.random() * 100)], + }, +]; + +@Injectable() +export class StatusesExampleDatasource + extends DataSourceService + implements IDataSource, OnDestroy +{ + public static providerId = "StatusesExampleDatasource"; + + public busy = new Subject(); + + constructor(private http: HttpClient) { + super(); + } + + public async getFilteredData(): Promise { + this.busy.next(true); + + return new Promise((resolve) => { + setTimeout(() => { + resolve({ + result: randomStatusesWidgetData, + }); + this.busy.next(false); + }, 1000); + }); + } + + public ngOnDestroy(): void { + this.outputsSubject.complete(); + } +} + +export const widgetConfig: IWidget = { + id: "proportionalWidgetId", + type: "proportional", + pizzagna: { + [PizzagnaLayer.Configuration]: { + header: { + properties: { + title: "Proportional Widget!", + subtitle: "Proportional widget with legend formatters", + }, + }, + chart: { + providers: { + [WellKnownProviders.DataSource]: { + providerId: StatusesExampleDatasource.providerId, + } as IProviderConfiguration, + }, + properties: { + configuration: { + interactive: true, + chartOptions: { + type: ProportionalWidgetChartTypes.DonutChart, + legendPlacement: LegendPlacement.Right, + contentFormatter: { + componentType: + CustomDonutContentFormatterComponent.lateLoadKey, + properties: { + // here you can set the default value for the metric you receive. If not set the first one from the list will be taken + currentMetric: "Down", + // here you set the default value for your custom controls. If not set the first one from the list will be taken + units: Units.Weeks, + }, + }, + } as IProportionalWidgetChartOptions, + chartColors: [ + "var(--nui-color-chart-eight)", + "var(--nui-color-chart-nine)", + "var(--nui-color-chart-ten)", + ], + } as IProportionalWidgetConfig, + }, + }, + }, + }, +}; +\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\`, + "tutorials/customization/formatter/formatter-example/custom-formatter-docs.component.ts": \\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\`// © 2022 SolarWinds Worldwide, LLC. All rights reserved. +// +// Permission is hereby granted, free of charge, to any person obtaining a copy +// of this software and associated documentation files (the "Software"), to +// deal in the Software without restriction, including without limitation the +// rights to use, copy, modify, merge, publish, distribute, sublicense, and/or +// sell copies of the Software, and to permit persons to whom the Software is +// furnished to do so, subject to the following conditions: +// +// The above copyright notice and this permission notice shall be included in +// all copies or substantial portions of the Software. +// +// THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR +// IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, +// FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE +// AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER +// LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, +// OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN +// THE SOFTWARE. + +import { Component } from "@angular/core"; + +@Component({ + selector: "nui-custom-formatter-docs", + templateUrl: "./custom-formatter-docs.component.html", + standalone: false, +}) +export class CustomFormatterDocComponent {} +\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\`, + "tutorials/customization/formatter/formatter-example/custom-formatter-example.component.ts": \\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\`// © 2022 SolarWinds Worldwide, LLC. All rights reserved. +// +// Permission is hereby granted, free of charge, to any person obtaining a copy +// of this software and associated documentation files (the "Software"), to +// deal in the Software without restriction, including without limitation the +// rights to use, copy, modify, merge, publish, distribute, sublicense, and/or +// sell copies of the Software, and to permit persons to whom the Software is +// furnished to do so, subject to the following conditions: +// +// The above copyright notice and this permission notice shall be included in +// all copies or substantial portions of the Software. +// +// THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR +// IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, +// FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE +// AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER +// LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, +// OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN +// THE SOFTWARE. + +import { ListRange } from "@angular/cdk/collections"; +import { ChangeDetectorRef, Component, Input, OnInit } from "@angular/core"; +import { FormBuilder, FormGroup, Validators } from "@angular/forms"; +import { GridsterConfig, GridsterItem } from "angular-gridster2"; +import isEqual from "lodash/isEqual"; +import orderBy from "lodash/orderBy"; +import { BehaviorSubject } from "rxjs"; + +import { + DataSourceService, + IconService, + IDataField, + INovaFilteringOutputs, + INovaFilters, + ISorterFilter, + LoggerService, +} from "@nova-ui/bits"; +import { + ComponentRegistryService, + ConfiguratorHeadingService, + DATA_SOURCE, + FormatterConfiguratorComponent, + IDashboard, + IDataSourceOutput, + IFormatterDefinition, + IHasChangeDetector, + ITableWidgetColumnConfig, + ITableWidgetSorterConfig, + IWidget, + IWidgets, + PizzagnaLayer, + ProviderRegistryService, + RawFormatterComponent, + TableFormatterRegistryService, + WellKnownPathKey, + WellKnownProviders, + WidgetTypesService, +} from "@nova-ui/dashboards"; + +export const BREW_API_URL = "https://api.punkapi.com/v2/beers"; + +@Component({ + selector: "custom-formatter", + host: { class: "d-flex" }, + template: \\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\` +
+
+ +
+
+ {{ data.value }} +
+
+ \\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\`, + styleUrls: ["./custom-formatter-example.component.less"], + standalone: false, +}) +export class CustomFormatterComponent implements IHasChangeDetector { + public static lateLoadKey = "CustomFormatterComponent"; + + constructor(public changeDetector: ChangeDetectorRef) {} + + @Input() public data: any; + @Input() public icon: string; + @Input() public threshold: string; + + public isAboveThreshold(): boolean { + return parseFloat(this.threshold) <= this.data.value; + } +} + +@Component({ + selector: "custom-formatter-configurator", + styleUrls: ["./custom-formatter-example.component.less"], + template: \\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\` +
+
+ + + + {{ item.label }} + + + + This field is required + + +
+
+ + + + + + + + This field is required + + +
+
+ + + + + This field is required + + +
+
+ +
+
+ +
+ + +
+ + + Select Item + +
+ \\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\`, + standalone: false, +}) +export class CustomFormatterConfiguratorComponent + extends FormatterConfiguratorComponent + implements OnInit, IHasChangeDetector +{ + public static lateLoadKey = "CustomFormatterConfiguratorComponent"; + + constructor( + changeDetector: ChangeDetectorRef, + configuratorHeading: ConfiguratorHeadingService, + formBuilder: FormBuilder, + logger: LoggerService, + public iconService: IconService + ) { + super(changeDetector, configuratorHeading, formBuilder, logger); + } + + public formatterFormGroup: FormGroup; + // This array is where the icon names will be stored + public options: string[] = []; + + public ngOnInit(): void { + for (const icon of this.iconService.icons) { + if (icon.category === "severity") { + this.options.push(icon.name); + } + } + } + + protected addCustomFormControls(form: FormGroup): void { + form.addControl( + "icon", + this.formBuilder.control("", Validators.required) + ); + form.addControl( + "threshold", + this.formBuilder.control(null, Validators.required) + ); + } +} + +/** + * A component that instantiates the dashboard + */ +@Component({ + selector: "custom-formatter-example", + templateUrl: "./custom-formatter-example.component.html", + styleUrls: ["./custom-formatter-example.component.less"], + standalone: false, +}) +export class CustomFormatterExampleComponent implements OnInit { + public editMode: boolean = false; + // This variable will hold all the data needed to define the layout and behavior of the widgets. + // Pass this to the dashboard component's dashboard input in the template. + public dashboard: IDashboard | undefined; + + // Angular gridster requires a configuration object even if it's empty. + // Pass this to the dashboard component's gridsterConfig input in the template. + public gridsterConfig: GridsterConfig = {}; + + constructor( + // WidgetTypesService provides the widget's necessary structure information + private widgetTypesService: WidgetTypesService, + // In general, the ProviderRegistryService is used for making entities available for injection into dynamically loaded components. + private providerRegistry: ProviderRegistryService, + // Inject the ComponentRegistryService to make our custom component available for late loading by the dashboards framework + private componentRegistry: ComponentRegistryService, + private tableFormatterRegistryService: TableFormatterRegistryService, + private changeDetectorRef: ChangeDetectorRef + ) { + // Register the custom configurator component with the component registry to make it available + // for late loading by the dashboard framework. + this.componentRegistry.registerByLateLoadKey( + CustomFormatterConfiguratorComponent + ); + // Register the custom formatter component with the component registry to make it available + // for late loading by the dashboard framework. + this.componentRegistry.registerByLateLoadKey(CustomFormatterComponent); + + // Grab the widget's default template which will be needed as a parameter for setNode below. + const table = this.widgetTypesService.getWidgetType("table", 1); + + const tableFormatters: IFormatterDefinition[] = [ + { + // This will be the component that will format the data + componentType: RawFormatterComponent.lateLoadKey, + // This is the label for what the formatter is selected in the drop down + label: $localize\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\`:table formatter|:No formatter\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\`, + // This says what datatype the formatter supports. If the value node is null, it accepts any data type. + dataTypes: { + // @ts-ignore: Ignoring compiler error to keep the same flow + value: null, + }, + }, + { + componentType: CustomFormatterComponent.lateLoadKey, + label: $localize\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\`:table formatter|:Custom formatter\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\`, + // This is a custom configurator that will pop up below the formatter once it gets selected + configurationComponent: + CustomFormatterConfiguratorComponent.lateLoadKey, + // This says what data types the formatter supports. + // In this case, it supports abv values only. + // If you look below in the table data source you'll see where we define our column's data types. + dataTypes: { + value: ["abv"], + }, + }, + ]; + + // Registering the formatters + this.tableFormatterRegistryService.addItems(tableFormatters); + + // This sets the table's datasource to have the BeerDataSource so the drop down is filled similar to the line above. + this.widgetTypesService.setNode( + table, + "configurator", + WellKnownPathKey.DataSourceProviders, + [BeerDataSource.providerId] + ); + + // Registering the data source for injection into the widget. + this.providerRegistry.setProviders({ + [BeerDataSource.providerId]: { + provide: DATA_SOURCE, + useClass: BeerDataSource, + // Any dependencies that need to be injected into the provider must be listed here + deps: [], + }, + }); + } + + public ngOnInit(): void { + this.initializeDashboard(); + } + + /** Used for restoring widgets state */ + public reInitializeDashboard(): void { + // destroys the components and their providers so the dashboard can re init data + this.dashboard = undefined; + this.changeDetectorRef.detectChanges(); + + this.initializeDashboard(); + } + + public initializeDashboard(): void { + // We're using a static configuration object for this example, but this is where + // the widget's configuration could potentially be populated from a database + const tableWidget = widgetConfig; + const widgetIndex: IWidgets = { + // Enhance the widget with information coming from it's type definition + [tableWidget.id]: + this.widgetTypesService.mergeWithWidgetType(tableWidget), + }; + + // Setting the widget dimensions and position (this is for gridster) + const positions: Record = { + [tableWidget.id]: { + cols: 12, + rows: 6, + y: 0, + x: 0, + }, + }; + + // Finally, assigning the variables we created above to the dashboard + this.dashboard = { + positions, + widgets: widgetIndex, + }; + } +} + +export interface IBrewInfo { + id: number; + name: string; + tagline: string; + first_brewed: string; + description: string; + brewers_tips: string; + abv: number; +} + +export interface IBrewDatasourceResponse { + brewInfo: IBrewInfo[]; + total: number; +} + +export class BeerDataSource extends DataSourceService { + public static providerId = "BeerDataSource"; + + private cache = Array.from({ length: 0 }); + private lastSortValue?: ISorterFilter; + private lastVirtualScroll?: ListRange; + // For simplicity, the totalItems value is hard-coded here, but in a real-world scenario the value would likely be retrieved via an async backend call + private totalItems: number = 325; + + public page: number = 1; + public busy = new BehaviorSubject(false); + + public dataFields: Array = [ + { id: "id", label: "No", dataType: "number" }, + { id: "name", label: "Name", dataType: "string" }, + { id: "tagline", label: "Tagline", dataType: "string" }, + { id: "first_brewed", label: "First Brewed", dataType: "string" }, + { id: "description", label: "Description", dataType: "string" }, + { id: "brewers_tips", label: "Brewer's Tips", dataType: "string" }, + // We are giving this field a custom data type of 'abv' so the dropdown in the custom formatter configurator can use it to filter out other data types + { id: "abv", label: "Alcohol By Volume", dataType: "abv" }, + ]; + + constructor(private logger: LoggerService) { + super(); + } + + public async getFilteredData( + filters: INovaFilters + ): Promise> { + const start = filters.virtualScroll?.value?.start ?? 0; + const end = filters.virtualScroll?.value?.end ?? 0; + const delta = end - start; + + // Note: We should start with a clean cache every time first page is requested + if (start === 0) { + this.cache = []; + } + + // This condition handles sorting. We want to sort columns without fetching another chunk of data. + // Since the data is being fetched when scrolled, we compare virtual scroll indexes here in the condition as well. + if (filters.sorter?.value) { + if ( + !isEqual(this.lastSortValue, filters.sorter.value) && + filters.virtualScroll?.value.start === 0 && + !!this.lastVirtualScroll + ) { + const totalPages = Math.ceil( + delta ? this.totalItems / delta : 1 + ); + const itemsPerPage: number = Math.max( + delta < 80 ? delta : 80, + 1 + ); + let response: Array | null = null; + let map: IBrewDatasourceResponse; + + if (filters.sorter?.value?.direction === "desc") { + this.cache = []; + for (let i = 0; i < this.page; ++i) { + response = await ( + await fetch( + \\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\`\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\${BREW_API_URL}/?page=\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\${ + totalPages - i || 1 + }&per_page=\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\${itemsPerPage}\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\` + ) + ).json(); + + // since the last page contains only 5 items we need to fetch another page to give virtual scroll enough space to work + if (response && response.length < itemsPerPage) { + this.page++; + } + map = { + brewInfo: response?.map((result: IBrewInfo) => ({ + id: result.id, + name: result.name, + tagline: result.tagline, + first_brewed: result.first_brewed, + description: result.description, + brewers_tips: result.brewers_tips, + })), + total: response?.length, + } as IBrewDatasourceResponse; + this.cache = + totalPages - i !== 0 + ? this.cache.concat(map.brewInfo) + : this.cache; + } + } + + if (filters.sorter?.value?.direction === "asc") { + this.cache = []; + for (let i = 0; i < this.page; i++) { + response = await ( + await fetch( + \\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\`\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\${BREW_API_URL}/?page=\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\${ + i + 1 + }&per_page=\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\${itemsPerPage}\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\` + ) + ).json(); + map = { + brewInfo: response?.map((result: IBrewInfo) => ({ + id: result.id, + name: result.name, + tagline: result.tagline, + first_brewed: result.first_brewed, + description: result.description, + brewers_tips: result.brewers_tips, + })), + total: response?.length, + } as IBrewDatasourceResponse; + this.cache = this.cache.concat(map.brewInfo); + } + } + + this.lastSortValue = filters.sorter?.value; + this.lastVirtualScroll = filters.virtualScroll?.value; + + return { + result: { + repeat: { + itemsSource: this.sortData(this.cache, filters), + }, + paginator: { total: this.totalItems }, + dataFields: this.dataFields, + }, + }; + } + } + + this.busy.next(true); + return new Promise((resolve) => { + setTimeout(() => { + this.getData(start, end, filters).then( + (response: INovaFilteringOutputs) => { + if (!response) { + return; + } + + this.cache = this.cache.concat(response.brewInfo); + + this.dataSubject.next(this.cache); + resolve({ + result: { + repeat: { + itemsSource: this.sortData( + this.cache, + filters + ), + }, + paginator: { total: this.totalItems }, + dataFields: this.dataFields, + }, + }); + + this.lastSortValue = filters.sorter?.value; + this.lastVirtualScroll = filters.virtualScroll?.value; + this.busy.next(false); + } + ); + }, 500); + }); + } + + public async getData( + start: number = 0, + end: number = 20, + filters: INovaFilters + ): Promise { + const delta = end - start; + const totalPages = Math.ceil(delta ? this.totalItems / delta : 1); + let response: Array | null = null; + // The api.punk.com is able to return only 80 items per page + const itemsPerPage: number = Math.max(delta < 80 ? delta : 80, 1); + + if (filters.sorter?.value?.direction === "asc") { + response = await ( + await fetch( + \\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\`\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\${BREW_API_URL}/?page=\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\${this.page}&per_page=\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\${itemsPerPage}\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\` + ) + ).json(); + } + + if (filters.sorter?.value?.direction === "desc") { + response = await ( + await fetch( + \\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\`\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\${BREW_API_URL}/?page=\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\${ + totalPages - this.page + }&per_page=\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\${itemsPerPage}\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\` + ) + ).json(); + } + + if (!filters.sorter) { + response = await ( + await fetch( + \\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\`\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\${BREW_API_URL}/?page=\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\${this.page}&per_page=\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\${itemsPerPage}\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\` + ) + ).json(); + } + return { + brewInfo: response?.map((result: IBrewInfo, i: number) => ({ + id: result.id, + abv: result.abv, + name: result.name, + tagline: result.tagline, + first_brewed: result.first_brewed, + description: result.description, + brewers_tips: result.brewers_tips, + })), + total: response?.length, + } as IBrewDatasourceResponse; + } + + private sortData(data: IBrewInfo[], filters: INovaFilters) { + return orderBy( + data, + filters.sorter?.value?.sortBy, + filters.sorter?.value?.direction as "desc" | "asc" + ); + } +} + +export const widgetConfig: IWidget = { + id: "tableWidgetId", + type: "table", + pizzagna: { + [PizzagnaLayer.Configuration]: { + header: { + properties: { + title: "Stupendous Suds", + subtitle: "Try These Brilliant Brews", + }, + }, + table: { + providers: { + [WellKnownProviders.DataSource]: { + providerId: BeerDataSource.providerId, + }, + }, + properties: { + configuration: { + columns: [ + { + id: "column1", + label: "Beer Name", + isActive: true, + width: 185, + formatter: { + componentType: + RawFormatterComponent.lateLoadKey, + properties: { + dataFieldIds: { + value: "name", + }, + }, + }, + }, + { + id: "column2", + label: "Tagline", + isActive: true, + width: 250, + formatter: { + componentType: + RawFormatterComponent.lateLoadKey, + properties: { + dataFieldIds: { + value: "tagline", + }, + }, + }, + }, + { + id: "column3", + label: "Alcohol By Volume", + isActive: true, + width: 150, + formatter: { + componentType: + CustomFormatterComponent.lateLoadKey, + properties: { + dataFieldIds: { + value: "abv", + }, + icon: "severity_error", + threshold: "5", + }, + }, + }, + { + id: "column4", + label: "Description", + isActive: true, + formatter: { + componentType: + RawFormatterComponent.lateLoadKey, + properties: { + dataFieldIds: { + value: "description", + }, + }, + }, + }, + ] as ITableWidgetColumnConfig[], + sorterConfiguration: { + descendantSorting: false, + sortBy: "", + } as ITableWidgetSorterConfig, + hasVirtualScroll: true, + }, + }, + }, + }, + }, +}; +\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\`, + "tutorials/customization/widget/custom-widget-docs.component.ts": \\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\`// © 2022 SolarWinds Worldwide, LLC. All rights reserved. +// +// Permission is hereby granted, free of charge, to any person obtaining a copy +// of this software and associated documentation files (the "Software"), to +// deal in the Software without restriction, including without limitation the +// rights to use, copy, modify, merge, publish, distribute, sublicense, and/or +// sell copies of the Software, and to permit persons to whom the Software is +// furnished to do so, subject to the following conditions: +// +// The above copyright notice and this permission notice shall be included in +// all copies or substantial portions of the Software. +// +// THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR +// IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, +// FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE +// AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER +// LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, +// OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN +// THE SOFTWARE. + +import { Component } from "@angular/core"; + +@Component({ + selector: "custom-widget-docs", + templateUrl: "./custom-widget-docs.component.html", + standalone: false, +}) +export class CustomWidgetDocsComponent {} +\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\`, + "tutorials/customization/widget/custom-widget.component.ts": \\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\`// © 2022 SolarWinds Worldwide, LLC. All rights reserved. +// +// Permission is hereby granted, free of charge, to any person obtaining a copy +// of this software and associated documentation files (the "Software"), to +// deal in the Software without restriction, including without limitation the +// rights to use, copy, modify, merge, publish, distribute, sublicense, and/or +// sell copies of the Software, and to permit persons to whom the Software is +// furnished to do so, subject to the following conditions: +// +// The above copyright notice and this permission notice shall be included in +// all copies or substantial portions of the Software. +// +// THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR +// IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, +// FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE +// AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER +// LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, +// OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN +// THE SOFTWARE. + +import { + ChangeDetectionStrategy, + ChangeDetectorRef, + Component, + EventEmitter, + HostBinding, + Input, + OnChanges, + OnInit, + Output, + SimpleChanges, +} from "@angular/core"; +import { FormBuilder, FormGroup, Validators } from "@angular/forms"; +import { GridsterConfig, GridsterItem } from "angular-gridster2"; + +import { IMenuItem } from "@nova-ui/bits"; +import { + ComponentRegistryService, + ConfiguratorHeadingService, + DEFAULT_PIZZAGNA_ROOT, + EVENT_PROXY, + FormStackComponent, + IConverterFormPartsProperties, + IDashboard, + IHasChangeDetector, + IHasForm, + IProviderConfiguration, + IWidget, + IWidgets, + IWidgetTypeDefinition, + NOVA_GENERIC_CONVERTER, + NOVA_TITLE_AND_DESCRIPTION_CONVERTER, + PizzagnaLayer, + refresher, + StackComponent, + TitleAndDescriptionConfigurationComponent, + WellKnownPathKey, + WellKnownProviders, + widgetBodyContentNodes, + WidgetConfiguratorSectionComponent, + WidgetTypesService, + WIDGET_BODY, + WIDGET_HEADER, + WIDGET_LOADING, +} from "@nova-ui/dashboards"; + +// The custom widget type name we'll use +const CUSTOM_WIDGET_TYPENAME = "example-custom-widget"; +// The path key we'll use for image selection in the configurator definition +const IMAGE_SELECTION_CONFIGURATOR_PATH_KEY = "imageSelection"; + +@Component({ + selector: "custom-widget-body", + // A simple template for our custom widget + template: \\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\`\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\`, + styleUrls: ["./custom-widget.component.less"], + changeDetection: ChangeDetectionStrategy.OnPush, + standalone: false, +}) +// Remember to declare this class in the parent module +export class CustomWidgetBodyContentComponent implements IHasChangeDetector { + // Ensure that the lateLoadKey value matches class name + public static lateLoadKey = "CustomWidgetBodyContentComponent"; + + // Optionally, providing an input for styling of the host element + @Input() @HostBinding("class") public elementClass = ""; + + // We'll map this input with the configurator form using the NOVA_GENERIC_CONVERTER. + // See the customWidget definition at the bottom of the file. + @Input() public imageSource: string; + + // Injecting the ChangeDetectorRef to implement IHasChangeDetector. + // This allows the dashboard framework to reliably propagate component property changes to the DOM. + constructor(public changeDetector: ChangeDetectorRef) {} +} + +/** + * A custom configurator section component for selecting the image source for the custom widget + */ +@Component({ + selector: "custom-configurator-section", + template: \\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\` + + + + +
+ + +
+ Image Selection +
+ {{ imageDisplayValue }} +
+
+
+
+ + + + + {{ item.title }} + + + +
+
+ \\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\`, + styleUrls: ["./custom-widget.component.less"], + changeDetection: ChangeDetectionStrategy.OnPush, + standalone: false, +}) +// Remember to declare this class in the parent module +export class CustomConfiguratorSectionComponent + implements OnInit, OnChanges, IHasChangeDetector, IHasForm +{ + // Ensure that the lateLoadKey value matches the class name + public static lateLoadKey = "CustomConfiguratorSectionComponent"; + + /** + * This input serves as the itemsSource a user can select an image from. + */ + @Input() imageItems: IMenuItem[] = []; + /** + * This property holds the currently selected image source string. + */ + @Input() imageSource: string; + + /** + * An output for emitting formReady to allow the immediate parent formGroup component to register us as a form control + * in the larger form. In this case, the immediate parent would be the WidgetConfiguratorSectionComponent as specified + * in the customWidget configurator definition at the bottom of this file. + */ + @Output() formReady = new EventEmitter(); + + public form: FormGroup; + public imageDisplayValue: string; + + constructor( + public changeDetector: ChangeDetectorRef, + private formBuilder: FormBuilder, + public configuratorHeading: ConfiguratorHeadingService + ) {} + + public ngOnInit(): void { + // Initializing the form + this.form = this.formBuilder.group({ + // Note: When using the NOVA_GENERIC_CONVERTER, the form control name, in this case 'imageSource', must match the input name on + // this component as well as that of the corresponding property on the custom widget body component. + imageSource: [{}, [Validators.required]], + }); + + // Emitting the formReady as described above. + this.formReady.emit(this.form); + } + + public ngOnChanges(changes: SimpleChanges): void { + if (changes.imageSource && !changes.imageSource.isFirstChange()) { + const previousValue: string = changes.imageSource.previousValue; + if (previousValue !== this.imageSource) { + // Setting the display value according to the current imageSource value + this.imageDisplayValue = this.imageItems.find( + (item: IMenuItem) => item.url === this.imageSource + )?.title; + + // Updating the form when the imageSource input gets updated + this.form.get("imageSource")?.setValue(this.imageSource); + } + } + } + + public onChanged(newValue: string): void { + // Keeping the display value updated as the user changes the dropdown selection + this.imageDisplayValue = this.imageItems.find( + (item: IMenuItem) => item.url === newValue + )?.title; + } +} + +/** + * The component that instantiates the dashboard + */ +@Component({ + selector: "custom-widget", + templateUrl: "./custom-widget.component.html", + styleUrls: ["./custom-widget.component.less"], + standalone: false, +}) +export class CustomWidgetComponent implements OnInit { + // This variable will hold all the data needed to define the layout and behavior of the widgets. + // Pass this to the dashboard component's dashboard input in the template. + public dashboard: IDashboard | undefined; + + // Angular gridster requires a configuration object even if it's empty. + // Pass this to the dashboard component's gridsterConfig input in the template. + public gridsterConfig: GridsterConfig = {}; + + // Boolean which dashboard takes in as an input if its true it allows you to move widgets around. + public editMode: boolean = false; + + constructor( + // WidgetTypesService provides the widget's necessary structure information + private widgetTypesService: WidgetTypesService, + + // Inject the ComponentRegistryService to make our custom component available for late loading by the dashboards framework + private componentRegistry: ComponentRegistryService, + private changeDetectorRef: ChangeDetectorRef + ) {} + + public ngOnInit(): void { + // Register the custom widget type and custom components + // Note: This could also be done in the parent module's constructor so that + // multiple dashboards could have access to the same registrations. + this.prepareNovaDashboards(); + + // Register some image items as dropdown options in the widget editor/configurator + // Note: This could also be done in the parent module's constructor so that + // multiple dashboards could have access to the same dropdown options. + this.registerImageOptions(); + + // Initialize our current instance of a dashboard with an instance of our custom widget + this.initializeDashboard(); + } + + /** Used for restoring widgets state */ + public reInitializeDashboard(): void { + // destroys the components and their providers so the dashboard can re init data + this.dashboard = undefined; + this.changeDetectorRef.detectChanges(); + + this.initializeDashboard(); + } + + public initializeDashboard(): void { + // We're using a static configuration object for this example (see widgetConfig at the bottom of the file), + // but this is where the widget's configuration could potentially be populated from a database + const widget = widgetConfig; + + // Create an index of widgets complete with structure and configuration to assign to the dashboard + const widgets: IWidgets = { + // Complete the custom widget with structure information coming from its type definition + [widget.id]: this.widgetTypesService.mergeWithWidgetType(widget), + }; + + // Setting the widget dimensions and position (this is for gridster) + const positions: Record = { + [widget.id]: { + cols: 4, + rows: 11, + y: 0, + x: 0, + }, + }; + + // Finally, assigning the variables we created above to the dashboard + this.dashboard = { positions, widgets }; + } + + private prepareNovaDashboards() { + // Register the custom widget type + this.widgetTypesService.registerWidgetType( + CUSTOM_WIDGET_TYPENAME, + 1, + customWidget + ); + + // Register the custom widget body component with the component registry to make it available + // for late loading by the dashboard framework. + this.componentRegistry.registerByLateLoadKey( + CustomWidgetBodyContentComponent + ); + + // Register the custom configurator section with the component registry to make it available + // for late loading by the dashboard framework. + this.componentRegistry.registerByLateLoadKey( + CustomConfiguratorSectionComponent + ); + } + + private registerImageOptions() { + // Grab the widget's default template which will be needed as a parameter for setNode below. + const widgetTemplate = this.widgetTypesService.getWidgetType( + CUSTOM_WIDGET_TYPENAME, + 1 + ); + + // Register some image items as dropdown options in the widget editor/configurator + this.widgetTypesService.setNode( + // This is the template we grabbed above with getWidgetType + widgetTemplate, + // We are setting the editor/configurator part of the widget template + "configurator", + // This indicates which node you are changing and we want to change the image items available for selection in the editor. + // For reference, see the 'paths' property of the custom widget's IWidgetTypeDefinition at the bottom of this file. + IMAGE_SELECTION_CONFIGURATOR_PATH_KEY, + // We are setting the image items available for selection in the editor. 'imageItems' is defined + // at the bottom of this file. + imageItems + ); + } +} + +/*************************************************************************************************** + * This is the type definition of our custom widget + ***************************************************************************************************/ +const customWidget: IWidgetTypeDefinition = { + /*************************************************************************************************** + * Paths to important settings in this type definition + ***************************************************************************************************/ + paths: { + widget: { + [WellKnownPathKey.Root]: DEFAULT_PIZZAGNA_ROOT, + }, + configurator: { + [WellKnownPathKey.Root]: DEFAULT_PIZZAGNA_ROOT, + // for the custom configuration component, this is the path for the list of image items available for selection + [IMAGE_SELECTION_CONFIGURATOR_PATH_KEY]: + "imageSelection.properties.imageItems", + }, + }, + /*************************************************************************************************** + * Widget section describes the structural part of the custom widget + ***************************************************************************************************/ + widget: { + [PizzagnaLayer.Structure]: { + [DEFAULT_PIZZAGNA_ROOT]: { + id: DEFAULT_PIZZAGNA_ROOT, + // base layout of the widget - all components referenced herein will be stacked in a column + componentType: StackComponent.lateLoadKey, + providers: { + // When enabled, this provider emits the REFRESH event on the pizzagna event bus every X seconds + [WellKnownProviders.Refresher]: refresher(), + // event proxy manages the transmission of events between widget and dashboard such as the WIDGET_EDIT and WIDGET_REMOVE events + [WellKnownProviders.EventProxy]: EVENT_PROXY, + }, + properties: { + // these values reference child components in the widget structure defined below + nodes: ["header", "loading", "body"], + }, + }, + // standard widget header + header: WIDGET_HEADER, + // this is the loading bar below the header + loading: WIDGET_LOADING, + // the body node + body: WIDGET_BODY, + + // retrieving the definitions for the body content nodes. the argument corresponds to the main content node key + ...widgetBodyContentNodes("mainContent"), + + // the component that supplies the content of our custom widget + mainContent: { + id: "mainContent", + componentType: CustomWidgetBodyContentComponent.lateLoadKey, + properties: { + elementClass: "d-flex w-100 justify-content-center", + }, + }, + }, + [PizzagnaLayer.Configuration]: { + [DEFAULT_PIZZAGNA_ROOT]: { + id: DEFAULT_PIZZAGNA_ROOT, + providers: { + // default refresher configuration + [WellKnownProviders.Refresher]: refresher(false, 60), + }, + }, + // default header configuration + header: { + properties: { + title: $localize\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\`Empty Custom Widget\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\`, + }, + }, + }, + }, + /*************************************************************************************************** + * Configurator section describes the form that's used to configure the widget + ***************************************************************************************************/ + configurator: { + [PizzagnaLayer.Structure]: { + [DEFAULT_PIZZAGNA_ROOT]: { + id: DEFAULT_PIZZAGNA_ROOT, + // base layout of the configurator - all form components referenced herein will be stacked in a column + componentType: FormStackComponent.lateLoadKey, + properties: { + elementClass: + "flex-grow-1 overflow-auto nui-scroll-shadows", + // these values reference child components laid out in this form (defined below) + nodes: ["presentation", "customConfig"], + }, + }, + // /presentation + presentation: { + id: "presentation", + componentType: WidgetConfiguratorSectionComponent.lateLoadKey, + properties: { + headerText: $localize\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\`Presentation\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\`, + nodes: ["titleAndDescription"], + }, + }, + // /presentation/titleAndDescription + titleAndDescription: { + id: "titleAndDescription", + componentType: + TitleAndDescriptionConfigurationComponent.lateLoadKey, + providers: { + converter: { + providerId: NOVA_TITLE_AND_DESCRIPTION_CONVERTER, + } as IProviderConfiguration, + }, + }, + // /customConfig + customConfig: { + id: "customConfig", + componentType: WidgetConfiguratorSectionComponent.lateLoadKey, + properties: { + headerText: $localize\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\`Custom Widget Configuration\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\`, + nodes: ["imageSelection"], + }, + }, + // /customConfig/imageSelection + imageSelection: { + id: "imageSelection", + // Here's where we set the configurator to use our custom configurator section + componentType: CustomConfiguratorSectionComponent.lateLoadKey, + properties: { + // This corresponds to the 'imageItems' input on the custom configurator section component + // which defines the list of image items to pick from. The empty value shown here is overridden + // in the 'registerImageOptions' method above. + imageItems: [] as IMenuItem[], + }, + providers: { + // Using the generic converter to map the selected image source between the widget and the form + [WellKnownProviders.Converter]: { + providerId: NOVA_GENERIC_CONVERTER, + properties: { + formParts: [ + { + // Setting up the generic converter to update the 'imageSource' property of the custom widget 'mainContent' component + previewPath: "mainContent.properties", + // Note: To use the NOVA_GENERIC_CONVERTER, the linked properties must have the same name between the configurator + // section component and the widget 'mainContent' component. Additionally, the property name must match the formControl + // name used in the configurator section component. In this case, the common name among all three is 'imageSource'. + keys: ["imageSource"], + }, + ] as IConverterFormPartsProperties[], + }, + } as IProviderConfiguration, + }, + }, + }, + }, +}; + +// For this example, we're using static items for the image selection dropdown. In a more realistic scenario, +// the items available for selection might come from a backend database. +const imageItems = [ + { + title: "Harry Potter Book Cover", + url: "https://imgc.allpostersimages.com/img/print/u-g-F8PQ9I0.jpg?w=550&h=550&p=0", + }, + { + title: "Harry Potter Movie Poster", + url: "https://images-na.ssl-images-amazon.com/images/I/81gpmMdKOHL._AC_SY741_.jpg", + }, +] as IMenuItem[]; + +// We're using a static configuration object for this example. In a more realistic scenario, +// a widget's configuration would likely be stored in a database. +const widgetConfig: IWidget = { + id: "widget1", + // This custom type is registered in the 'prepareNovaDashboards' method above. + type: CUSTOM_WIDGET_TYPENAME, + pizzagna: { + [PizzagnaLayer.Configuration]: { + header: { + // Setting the initial property values for the WidgetHeaderComponent + properties: { + title: "Harry Potter and the Sorcerer's Stone", + subtitle: "By J. K. Rowling", + }, + }, + mainContent: { + properties: { + // Setting the initial value for the 'imageSource' property on our custom widget body + imageSource: imageItems[0].url, + }, + }, + }, + }, +}; +\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\`, + "tutorials/customization/widget/custom-widget.module.ts": \\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\`// © 2022 SolarWinds Worldwide, LLC. All rights reserved. +// +// Permission is hereby granted, free of charge, to any person obtaining a copy +// of this software and associated documentation files (the "Software"), to +// deal in the Software without restriction, including without limitation the +// rights to use, copy, modify, merge, publish, distribute, sublicense, and/or +// sell copies of the Software, and to permit persons to whom the Software is +// furnished to do so, subject to the following conditions: +// +// The above copyright notice and this permission notice shall be included in +// all copies or substantial portions of the Software. +// +// THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR +// IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, +// FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE +// AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER +// LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, +// OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN +// THE SOFTWARE. + +import { CommonModule } from "@angular/common"; +import { HttpClientModule } from "@angular/common/http"; +import { NgModule } from "@angular/core"; +import { ReactiveFormsModule } from "@angular/forms"; +import { RouterModule } from "@angular/router"; + +import { + NuiButtonModule, + NuiDocsModule, + NuiFormFieldModule, + NuiIconModule, + NuiImageModule, + NuiMessageModule, + NuiSelectV2Module, + NuiSwitchModule, + DEMO_PATH_TOKEN, +} from "@nova-ui/bits"; +import { + NuiDashboardConfiguratorModule, + NuiDashboardsModule, +} from "@nova-ui/dashboards"; + +import { CustomWidgetDocsComponent } from "./custom-widget-docs.component"; +import { + CustomConfiguratorSectionComponent, + CustomWidgetBodyContentComponent, + CustomWidgetComponent, +} from "./custom-widget.component"; +import { getDemoFiles } from "../../../../../demo-files-factory"; + +const routes = [ + { + path: "", + component: CustomWidgetDocsComponent, + data: { + srlc: { + hideIndicator: true, + }, + showThemeSwitcher: true, + }, + }, + { + path: "example", + component: CustomWidgetComponent, + data: { + srlc: { + hideIndicator: true, + }, + }, + }, +]; + +@NgModule({ + imports: [ + CommonModule, + ReactiveFormsModule, + HttpClientModule, + NuiDashboardsModule, + NuiDashboardConfiguratorModule, + NuiDocsModule, + NuiFormFieldModule, + NuiIconModule, + NuiImageModule, + NuiMessageModule, + NuiSelectV2Module, + NuiSwitchModule, + NuiButtonModule, + RouterModule.forChild(routes), + ], + declarations: [ + CustomWidgetDocsComponent, + CustomConfiguratorSectionComponent, + CustomWidgetBodyContentComponent, + CustomWidgetComponent, + ], + providers: [ + { + provide: DEMO_PATH_TOKEN, + useValue: getDemoFiles("widget"), + }, + ], +}) +export default class CustomWidgetModule {} +\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\`, + "tutorials/data-source-setup/data-source-setup-docs.component.ts": \\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\`// © 2022 SolarWinds Worldwide, LLC. All rights reserved. +// +// Permission is hereby granted, free of charge, to any person obtaining a copy +// of this software and associated documentation files (the "Software"), to +// deal in the Software without restriction, including without limitation the +// rights to use, copy, modify, merge, publish, distribute, sublicense, and/or +// sell copies of the Software, and to permit persons to whom the Software is +// furnished to do so, subject to the following conditions: +// +// The above copyright notice and this permission notice shall be included in +// all copies or substantial portions of the Software. +// +// THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR +// IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, +// FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE +// AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER +// LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, +// OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN +// THE SOFTWARE. + +import { Component } from "@angular/core"; + +@Component({ + selector: "nui-dashboard-data-source-docs", + templateUrl: "./data-source-setup-docs.component.html", + standalone: false, +}) +export class DataSourceDocsComponent {} +\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\`, + "tutorials/data-source-setup/data-source-setup.component.ts": \\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\`// © 2022 SolarWinds Worldwide, LLC. All rights reserved. +// +// Permission is hereby granted, free of charge, to any person obtaining a copy +// of this software and associated documentation files (the "Software"), to +// deal in the Software without restriction, including without limitation the +// rights to use, copy, modify, merge, publish, distribute, sublicense, and/or +// sell copies of the Software, and to permit persons to whom the Software is +// furnished to do so, subject to the following conditions: +// +// The above copyright notice and this permission notice shall be included in +// all copies or substantial portions of the Software. +// +// THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR +// IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, +// FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE +// AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER +// LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, +// OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN +// THE SOFTWARE. + +import { HttpClient, HttpErrorResponse } from "@angular/common/http"; +import { Component, Injectable, OnDestroy, OnInit } from "@angular/core"; +import { GridsterConfig, GridsterItem } from "angular-gridster2"; +import { BehaviorSubject } from "rxjs"; +import { finalize } from "rxjs/operators"; + +import { DataSourceService, IFilteringOutputs } from "@nova-ui/bits"; +import { + DATA_SOURCE, + DEFAULT_PIZZAGNA_ROOT, + IDashboard, + IKpiData, + IProviderConfiguration, + IRefresherProperties, + IWidget, + IWidgets, + KpiComponent, + NOVA_KPI_DATASOURCE_ADAPTER, + PizzagnaLayer, + ProviderRegistryService, + WellKnownProviders, + WidgetTypesService, +} from "@nova-ui/dashboards"; + +/** + * A simple KPI data source to retrieve the average rating of Harry Potter and the Sorcerer's Stone (book) via googleapis + */ +@Injectable() +export class AverageRatingKpiDataSource + extends DataSourceService + implements OnDestroy +{ + // This is the ID we'll use to identify the provider + public static providerId = "AverageRatingKpiDataSource"; + + // Use this subject to communicate the data source's busy state + public busy = new BehaviorSubject(false); + + constructor(private http: HttpClient) { + super(); + } + + // In this example, getFilteredData is invoked every 10 minutes (Take a look at the refresher + // provider definition in the widget configuration below to see how the interval is set) + public async getFilteredData(): Promise { + this.busy.next(true); + return new Promise((resolve) => { + // *** Make a resource request to an external API (if needed) + this.http + .get("https://www.googleapis.com/books/v1/volumes/5MQFrgEACAAJ") + .pipe(finalize(() => this.busy.next(false))) + .subscribe({ + next: (data: any) => { + resolve({ + result: { + value: data.volumeInfo.averageRating, + }, + }); + }, + error: (error: HttpErrorResponse) => { + resolve({ + result: null, + error: { + type: error.status, + }, + }); + }, + }); + }); + } + + public ngOnDestroy(): void { + this.outputsSubject.complete(); + } +} + +/** + * A component that instantiates the dashboard + */ +@Component({ + selector: "data-source-setup", + templateUrl: "./data-source-setup.component.html", + styleUrls: ["./data-source-setup.component.less"], + standalone: false, +}) +export class DataSourceSetupComponent implements OnInit { + // This variable will hold all the data needed to define the layout and behavior of the widgets. + // Pass this to the dashboard component's dashboard input in the template. + public dashboard: IDashboard; + + // Angular gridster requires a configuration object even if it's empty. + // Pass this to the dashboard component's gridsterConfig input in the template. + public gridsterConfig: GridsterConfig = {}; + + constructor( + // WidgetTypesService provides the widget's necessary structure information + private widgetTypesService: WidgetTypesService, + + // In general, the ProviderRegistryService is used for making entities available for injection into dynamically loaded components. + private providerRegistry: ProviderRegistryService + ) {} + + public ngOnInit(): void { + // Registering the data source for injection into the KPI tile. + // Note: Each tile of a KPI widget is assigned its own instance of the data source + this.providerRegistry.setProviders({ + [AverageRatingKpiDataSource.providerId]: { + provide: DATA_SOURCE, + useClass: AverageRatingKpiDataSource, + // Any dependencies that need to be injected into the provider must be listed here + deps: [HttpClient], + }, + }); + // We're using a static configuration object for this example, but this is where + // the widget's configuration could potentially be populated from a database + const kpiWidget = widgetConfig; + const widgetIndex: IWidgets = { + // Complete the KPI widget with information coming from its type definition + [kpiWidget.id]: + this.widgetTypesService.mergeWithWidgetType(kpiWidget), + }; + + // Setting the widget dimensions and position (this is for gridster) + const positions: Record = { + [kpiWidget.id]: { + cols: 4, + rows: 6, + y: 0, + x: 0, + }, + }; + + // Finally, assigning the variables we created above to the dashboard + this.dashboard = { + positions, + widgets: widgetIndex, + }; + } +} + +const widgetConfig: IWidget = { + id: "widget1", + type: "kpi", + pizzagna: { + [PizzagnaLayer.Configuration]: { + [DEFAULT_PIZZAGNA_ROOT]: { + providers: { + [WellKnownProviders.Refresher]: { + properties: { + // Configuring the refresher interval so that our data source is invoked every ten minutes + interval: 60 * 10, + enabled: true, + } as IRefresherProperties, + } as Partial, + }, + }, + header: { + properties: { + title: "Harry Potter and the Sorcerer's Stone", + subtitle: "By J. K. Rowling", + }, + }, + tiles: { + properties: { + nodes: ["kpi1"], + }, + }, + kpi1: { + id: "kpi1", + componentType: KpiComponent.lateLoadKey, + properties: { + widgetData: { + units: "out of 5 Stars", + label: "Average Rating", + }, + }, + providers: { + [WellKnownProviders.DataSource]: { + // Setting the data source providerId for the tile with id "kpi1" + providerId: AverageRatingKpiDataSource.providerId, + } as IProviderConfiguration, + [WellKnownProviders.Adapter]: { + providerId: NOVA_KPI_DATASOURCE_ADAPTER, + properties: { + componentId: "kpi1", + propertyPath: "widgetData", + }, + } as IProviderConfiguration, + }, + }, + }, + }, +}; +\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\`, + "tutorials/data-source-setup/data-source-setup.module.ts": \\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\`// © 2022 SolarWinds Worldwide, LLC. All rights reserved. +// +// Permission is hereby granted, free of charge, to any person obtaining a copy +// of this software and associated documentation files (the "Software"), to +// deal in the Software without restriction, including without limitation the +// rights to use, copy, modify, merge, publish, distribute, sublicense, and/or +// sell copies of the Software, and to permit persons to whom the Software is +// furnished to do so, subject to the following conditions: +// +// The above copyright notice and this permission notice shall be included in +// all copies or substantial portions of the Software. +// +// THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR +// IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, +// FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE +// AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER +// LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, +// OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN +// THE SOFTWARE. + +import { CommonModule } from "@angular/common"; +import { HttpClientModule } from "@angular/common/http"; +import { NgModule } from "@angular/core"; +import { RouterModule } from "@angular/router"; + +import { + NuiDocsModule, + NuiMessageModule, + DEMO_PATH_TOKEN, +} from "@nova-ui/bits"; +import { NuiDashboardsModule } from "@nova-ui/dashboards"; + +import { DataSourceDocsComponent } from "./data-source-setup-docs.component"; +import { DataSourceSetupComponent } from "./data-source-setup.component"; +import { getDemoFiles } from "../../../../demo-files-factory"; + +const routes = [ + { + path: "", + component: DataSourceDocsComponent, + data: { + srlc: { + hideIndicator: true, + }, + showThemeSwitcher: true, + }, + }, + { + path: "example", + component: DataSourceSetupComponent, + data: { + srlc: { + hideIndicator: true, + }, + }, + }, +]; + +@NgModule({ + imports: [ + CommonModule, + HttpClientModule, + NuiDashboardsModule, + NuiDocsModule, + NuiMessageModule, + RouterModule.forChild(routes), + ], + declarations: [DataSourceDocsComponent, DataSourceSetupComponent], + providers: [ + { + provide: DEMO_PATH_TOKEN, + useValue: getDemoFiles("data-source-setup"), + }, + ], +}) +export default class DataSourceSetupModule {} +\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\`, + "tutorials/dynamic-header-links/dynamic-header-links-docs.component.ts": \\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\`// © 2022 SolarWinds Worldwide, LLC. All rights reserved. +// +// Permission is hereby granted, free of charge, to any person obtaining a copy +// of this software and associated documentation files (the "Software"), to +// deal in the Software without restriction, including without limitation the +// rights to use, copy, modify, merge, publish, distribute, sublicense, and/or +// sell copies of the Software, and to permit persons to whom the Software is +// furnished to do so, subject to the following conditions: +// +// The above copyright notice and this permission notice shall be included in +// all copies or substantial portions of the Software. +// +// THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR +// IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, +// FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE +// AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER +// LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, +// OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN +// THE SOFTWARE. + +import { Component } from "@angular/core"; + +@Component({ + selector: "nui-dynamic-header-links-docs", + templateUrl: "./dynamic-header-links-docs.component.html", + standalone: false, +}) +export class DynamicHeaderLinksDocsComponent {} +\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\`, + "tutorials/dynamic-header-links/dynamic-header-links-docs.module.ts": \\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\`// © 2022 SolarWinds Worldwide, LLC. All rights reserved. +// +// Permission is hereby granted, free of charge, to any person obtaining a copy +// of this software and associated documentation files (the "Software"), to +// deal in the Software without restriction, including without limitation the +// rights to use, copy, modify, merge, publish, distribute, sublicense, and/or +// sell copies of the Software, and to permit persons to whom the Software is +// furnished to do so, subject to the following conditions: +// +// The above copyright notice and this permission notice shall be included in +// all copies or substantial portions of the Software. +// +// THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR +// IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, +// FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE +// AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER +// LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, +// OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN +// THE SOFTWARE. + +import { NgModule } from "@angular/core"; +import { RouterModule } from "@angular/router"; + +import { DynamicHeaderLinksDocsComponent } from "./dynamic-header-links-docs.component"; + +const routes = [ + { + path: "", + component: DynamicHeaderLinksDocsComponent, + data: { + srlc: { + hideIndicator: true, + }, + showThemeSwitcher: true, + }, + }, +]; + +@NgModule({ + imports: [RouterModule.forChild(routes)], + declarations: [DynamicHeaderLinksDocsComponent], +}) +export default class DynamicHeaderLinksDocsModule {} +\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\`, + "tutorials/hello-dashboards/hello-dashboards-docs.component.ts": \\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\`// © 2022 SolarWinds Worldwide, LLC. All rights reserved. +// +// Permission is hereby granted, free of charge, to any person obtaining a copy +// of this software and associated documentation files (the "Software"), to +// deal in the Software without restriction, including without limitation the +// rights to use, copy, modify, merge, publish, distribute, sublicense, and/or +// sell copies of the Software, and to permit persons to whom the Software is +// furnished to do so, subject to the following conditions: +// +// The above copyright notice and this permission notice shall be included in +// all copies or substantial portions of the Software. +// +// THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR +// IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, +// FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE +// AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER +// LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, +// OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN +// THE SOFTWARE. + +import { Component } from "@angular/core"; + +@Component({ + selector: "nui-dashboard-hello-dashboards-docs", + templateUrl: "./hello-dashboards-docs.component.html", + standalone: false, +}) +export class HelloDashboardsDocsComponent {} +\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\`, + "tutorials/hello-dashboards/hello-dashboards-example/hello-dashboards-example.component.ts": \\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\`// © 2022 SolarWinds Worldwide, LLC. All rights reserved. +// +// Permission is hereby granted, free of charge, to any person obtaining a copy +// of this software and associated documentation files (the "Software"), to +// deal in the Software without restriction, including without limitation the +// rights to use, copy, modify, merge, publish, distribute, sublicense, and/or +// sell copies of the Software, and to permit persons to whom the Software is +// furnished to do so, subject to the following conditions: +// +// The above copyright notice and this permission notice shall be included in +// all copies or substantial portions of the Software. +// +// THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR +// IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, +// FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE +// AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER +// LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, +// OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN +// THE SOFTWARE. + +import { Component, OnInit } from "@angular/core"; +import { GridsterConfig, GridsterItem } from "angular-gridster2"; + +import { + IDashboard, + IWidget, + IWidgets, + KpiComponent, + PizzagnaLayer, + WidgetTypesService, +} from "@nova-ui/dashboards"; + +/** + * A component that instantiates the dashboard + */ +@Component({ + selector: "hello-dashboards-example", + templateUrl: "./hello-dashboards-example.component.html", + styleUrls: ["./hello-dashboards-example.component.less"], + standalone: false, +}) +export class HelloDashboardsExampleComponent implements OnInit { + // This variable will have all the data needed to render the widgets widgets. + // Pass this to the dashboard component's dashboard input. + public dashboard: IDashboard; + // Angular gridster requires a configuration object even if its empty. + // Pass this to the dashboard component's gridsterConfig input. + public gridsterConfig: GridsterConfig = {}; + + // WidgetTypesService provides the widget's necessary structure information + constructor(private widgetTypesService: WidgetTypesService) {} + + public ngOnInit(): void { + // Here we are hard-coding the widget config for this example, but this is where you + // could potentially populate the widget's configuration from a database + const kpiWidget = widgetConfig; + const widgetIndex: IWidgets = { + // Complete the KPI widget with information coming from its type definition + [kpiWidget.id]: + this.widgetTypesService.mergeWithWidgetType(kpiWidget), + }; + // Setting widget position and dimensions (this is for gridster) + const positions: Record = { + [kpiWidget.id]: { + cols: 4, + rows: 6, + y: 0, + x: 0, + }, + }; + // Finally, assigning the variables we created above to the dashboard + this.dashboard = { + positions, + widgets: widgetIndex, + }; + } +} + +// In a real-world scenario, this configuration would typically be fetched from a database or at least live in another file +const widgetConfig: IWidget = { + id: "widget1", + type: "kpi", + pizzagna: { + [PizzagnaLayer.Configuration]: { + header: { + properties: { + title: "Hello, KPI Widget!", + subtitle: "A Venue for Meaningful Values", + }, + }, + tiles: { + properties: { + nodes: ["kpi1"], + }, + }, + kpi1: { + id: "kpi1", + componentType: KpiComponent.lateLoadKey, + properties: { + widgetData: { + id: "totalStorage", + value: 1, + label: "Total storage", + units: "TB", + }, + }, + }, + }, + }, +}; +\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\`, + "tutorials/hello-dashboards/hello-dashboards.module.ts": \\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\`// © 2022 SolarWinds Worldwide, LLC. All rights reserved. +// +// Permission is hereby granted, free of charge, to any person obtaining a copy +// of this software and associated documentation files (the "Software"), to +// deal in the Software without restriction, including without limitation the +// rights to use, copy, modify, merge, publish, distribute, sublicense, and/or +// sell copies of the Software, and to permit persons to whom the Software is +// furnished to do so, subject to the following conditions: +// +// The above copyright notice and this permission notice shall be included in +// all copies or substantial portions of the Software. +// +// THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR +// IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, +// FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE +// AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER +// LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, +// OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN +// THE SOFTWARE. + +import { CommonModule } from "@angular/common"; +import { NgModule } from "@angular/core"; +import { RouterModule } from "@angular/router"; + +import { + NuiDocsModule, + NuiMessageModule, + DEMO_PATH_TOKEN, +} from "@nova-ui/bits"; +import { NuiDashboardsModule } from "@nova-ui/dashboards"; + +import { HelloDashboardsDocsComponent } from "./hello-dashboards-docs.component"; +import { HelloDashboardsExampleComponent } from "./hello-dashboards-example/hello-dashboards-example.component"; +import { getDemoFiles } from "../../../../demo-files-factory"; + +const routes = [ + { + path: "", + component: HelloDashboardsDocsComponent, + data: { + srlc: { + hideIndicator: true, + }, + showThemeSwitcher: true, + }, + }, + { + path: "example", + component: HelloDashboardsExampleComponent, + data: { + srlc: { + hideIndicator: true, + }, + }, + }, +]; + +@NgModule({ + imports: [ + CommonModule, + NuiDashboardsModule, + NuiDocsModule, + NuiMessageModule, + RouterModule.forChild(routes), + ], + declarations: [ + HelloDashboardsDocsComponent, + HelloDashboardsExampleComponent, + ], + providers: [ + { + provide: DEMO_PATH_TOKEN, + useValue: getDemoFiles("hello-dashboards"), + }, + ], +}) +export default class HelloDashboardsModule {} +\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\`, + "tutorials/persistence-handler-setup/persistence-handler-setup-docs.component.ts": \\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\`// © 2022 SolarWinds Worldwide, LLC. All rights reserved. +// +// Permission is hereby granted, free of charge, to any person obtaining a copy +// of this software and associated documentation files (the "Software"), to +// deal in the Software without restriction, including without limitation the +// rights to use, copy, modify, merge, publish, distribute, sublicense, and/or +// sell copies of the Software, and to permit persons to whom the Software is +// furnished to do so, subject to the following conditions: +// +// The above copyright notice and this permission notice shall be included in +// all copies or substantial portions of the Software. +// +// THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR +// IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, +// FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE +// AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER +// LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, +// OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN +// THE SOFTWARE. + +import { Component } from "@angular/core"; + +@Component({ + selector: "nui-dashboard-persistence-handler-setup-docs", + templateUrl: "./persistence-handler-setup-docs.component.html", + standalone: false, +}) +export class PersistenceHandlerSetupDocsComponent {} +\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\`, + "tutorials/persistence-handler-setup/persistence-handler-setup.component.ts": \\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\`// © 2022 SolarWinds Worldwide, LLC. All rights reserved. +// +// Permission is hereby granted, free of charge, to any person obtaining a copy +// of this software and associated documentation files (the "Software"), to +// deal in the Software without restriction, including without limitation the +// rights to use, copy, modify, merge, publish, distribute, sublicense, and/or +// sell copies of the Software, and to permit persons to whom the Software is +// furnished to do so, subject to the following conditions: +// +// The above copyright notice and this permission notice shall be included in +// all copies or substantial portions of the Software. +// +// THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR +// IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, +// FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE +// AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER +// LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, +// OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN +// THE SOFTWARE. + +import { HttpClient, HttpErrorResponse } from "@angular/common/http"; +import { + ChangeDetectorRef, + Component, + Injectable, + OnDestroy, + OnInit, +} from "@angular/core"; +import { GridsterConfig, GridsterItem } from "angular-gridster2"; +import { BehaviorSubject, Observable, Subject } from "rxjs"; +import { finalize } from "rxjs/operators"; + +import { + DataSourceService, + IFilteringOutputs, + ToastService, + uuid, +} from "@nova-ui/bits"; +import { + DATA_SOURCE, + DEFAULT_PIZZAGNA_ROOT, + IDashboard, + IDashboardPersistenceHandler, + IKpiData, + IProviderConfiguration, + IRefresherProperties, + IWidget, + IWidgets, + KpiComponent, + NOVA_KPI_DATASOURCE_ADAPTER, + PizzagnaLayer, + ProviderRegistryService, + WellKnownPathKey, + WellKnownProviders, + WidgetTypesService, +} from "@nova-ui/dashboards"; + +/** + * A simple persistence handler that is tied to the widget editor directive + */ +@Injectable() +// The realizer of IDashboardPersistenceHandler may implement a trySubmit and/or a tryRemove method. +export class PersistenceHandler implements IDashboardPersistenceHandler { + // This variable is just to show how to handle error handling. + private persistenceSucceeded: boolean = true; + + // The example uses the toast service to demonstrate the + // invocation of each of the persistence handler callbacks + constructor(private toastService: ToastService) { + // toastService options to let it sit on the page for 2 seconds. + this.toastService.setConfig({ + timeOut: 2000, + }); + } + + // This method will be invoked anytime the widget editor form gets submitted. + public trySubmit = (widget: IWidget): Observable => { + // Since we are working asynchronously, we'll return a subject. So, after the submit attempt + // succeeds or fails, we can let the subscriber know the result. + const subject = new Subject(); + + if (!widget.id) { + // Creates an id if the widget has no id. + // (This step will make more sense in the context of the widget cloning tutorial + // in which we handle the persistence of a newly created widget.) + widget.id = uuid(); + } + + // For this example, we're using a setTimeout to mock an asynchronous persistence request to a backend + setTimeout(() => { + if (this.persistenceSucceeded) { + // Passes along the new widget after one second. + subject.next(widget); + // Toast on the page on success. + this.toastService.success({ + title: $localize\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\`Submit succeeded.\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\`, + }); + } else { + const errorText = $localize\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\`Submit failed.\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\`; + // Toast on the page on failure. + this.toastService.error({ title: errorText }); + // Makes the subject say there is an error. + subject.error(errorText); + } + // Completes the subject so whoever subscribes to it knows its finished. + subject.complete(); + }, 1000); + + // Returns the subject as an observable. + return subject.asObservable(); + }; + + // This method will be invoked anytime there's a widget removal attempt. + public tryRemove = (widgetId: string): Observable => { + const subject = new Subject(); + + setTimeout(() => { + if (this.persistenceSucceeded) { + // Pass through the id of the widget that was removed. + subject.next(widgetId); + this.toastService.success({ + title: $localize\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\`Removal succeeded.\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\`, + }); + } else { + const errorText = $localize\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\`Removal failed.\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\`; + this.toastService.error({ title: errorText }); + subject.error(errorText); + } + subject.complete(); + }, 1000); + + return subject.asObservable(); + }; +} + +/** + * A component that instantiates the dashboard + */ +@Component({ + selector: "persistence-handler-setup", + templateUrl: "./persistence-handler-setup.component.html", + styleUrls: ["./persistence-handler-setup.component.less"], + // Here we provide our persistence handler at the component level; this can also be done in the module. + providers: [PersistenceHandler], + standalone: false, +}) +export class PersistenceHandlerSetupComponent implements OnInit { + // This variable will hold all the data needed to define the layout and behavior of the widgets. + // Pass this to the dashboard component's dashboard input in the template. + public dashboard: IDashboard | undefined; + + // Angular gridster requires a configuration object even if it's empty. + // Pass this to the dashboard component's gridsterConfig input in the template. + public gridsterConfig: GridsterConfig = {}; + + // Boolean which dashboard takes in as an input if its true it allows you to move widgets around. + public editMode: boolean = false; + + constructor( + // WidgetTypesService provides the widget's necessary structure information + private widgetTypesService: WidgetTypesService, + + // In general, the ProviderRegistryService is used for making entities available for injection into dynamically loaded components. + private providerRegistry: ProviderRegistryService, + + // We are injecting the PersistenceHandler we created and assigning it to a property we use in the template. + public persistenceHandler: PersistenceHandler, + private changeDetectorRef: ChangeDetectorRef + ) {} + + public ngOnInit(): void { + // Grabbing the widget's default template which will be needed as a parameter for setNode + const widgetTemplate = this.widgetTypesService.getWidgetType("kpi", 1); + // Registering our data sources as dropdown options in the widget editor/configurator + // Note: This could also be done in the parent module's constructor so that + // multiple dashboards could have access to the same widget template modification. + this.widgetTypesService.setNode( + // This is the template we grabbed above with getWidgetType + widgetTemplate, + // We are setting the editor/configurator part of the widget template + "configurator", + // This indicates which node you are changing and we want to change + // the data source providers available for selection in the editor. + WellKnownPathKey.DataSourceProviders, + // We are setting the data sources available for selection in the editor + [ + AverageRatingKpiDataSource.providerId, + RatingsCountKpiDataSource.providerId, + ] + ); + + // Registering the data sources available for injection into the KPI tiles. + // Note: Each tile of a KPI widget is assigned its own instance of a data source + this.providerRegistry.setProviders({ + [AverageRatingKpiDataSource.providerId]: { + provide: DATA_SOURCE, + useClass: AverageRatingKpiDataSource, + // Any dependencies that need to be injected into the provider must be listed here + deps: [HttpClient], + }, + [RatingsCountKpiDataSource.providerId]: { + provide: DATA_SOURCE, + useClass: RatingsCountKpiDataSource, + deps: [HttpClient], + }, + }); + + this.initializeDashboard(); + } + + /** Used for restoring widgets state */ + public reInitializeDashboard(): void { + // destroys the components and their providers so the dashboard can re init data + this.dashboard = undefined; + this.changeDetectorRef.detectChanges(); + + this.initializeDashboard(); + } + + public initializeDashboard(): void { + // We're using a static configuration object for this example (see widgetConfig at the bottom of the file), + // but this is where the widget's configuration could potentially be populated from a database + const kpiWidget = widgetConfig; + const widgetIndex: IWidgets = { + // Complete the KPI widget with information coming from its type definition + [kpiWidget.id]: + this.widgetTypesService.mergeWithWidgetType(kpiWidget), + }; + + // Setting the widget dimensions and position (this is for gridster) + const positions: Record = { + [kpiWidget.id]: { + cols: 4, + rows: 6, + y: 0, + x: 0, + }, + }; + + // Finally, assigning the variables we created above to the dashboard + this.dashboard = { + positions, + widgets: widgetIndex, + }; + } +} + +/** + * A simple KPI data source to retrieve the average rating of Harry Potter and the Sorcerer's Stone (book) via googleapis + */ +@Injectable() +export class AverageRatingKpiDataSource + extends DataSourceService + implements OnDestroy +{ + // This is the ID we'll use to identify the provider + public static providerId = "AverageRatingKpiDataSource"; + + // Use this subject to communicate the data source's busy state + public busy = new BehaviorSubject(false); + + constructor(private http: HttpClient) { + super(); + } + + // In this example, getFilteredData is invoked every 10 minutes (Take a look at the refresher + // provider definition in the widget configuration below to see how the interval is set) + public async getFilteredData(): Promise { + this.busy.next(true); + return new Promise((resolve) => { + // *** Make a resource request to an external API (if needed) + this.http + .get("https://www.googleapis.com/books/v1/volumes/5MQFrgEACAAJ") + .pipe(finalize(() => this.busy.next(false))) + .subscribe({ + next: (data: any) => { + resolve({ + result: { + value: data.volumeInfo.averageRating, + }, + }); + }, + error: (error: HttpErrorResponse) => { + resolve({ + result: null, + error: { + type: error.status, + }, + }); + }, + }); + }); + } + + public ngOnDestroy(): void { + this.outputsSubject.complete(); + } +} + +/** + * A simple KPI data source to retrieve the ratings count of Harry Potter and the Sorcerer's Stone (book) via googleapis + */ +@Injectable() +export class RatingsCountKpiDataSource + extends DataSourceService + implements OnDestroy +{ + public static providerId = "RatingsCountKpiDataSource"; + + // Use this subject to communicate the data source's busy state + public busy = new BehaviorSubject(false); + + constructor(private http: HttpClient) { + super(); + } + + public async getFilteredData(): Promise { + this.busy.next(true); + return new Promise((resolve) => { + this.http + .get("https://www.googleapis.com/books/v1/volumes/5MQFrgEACAAJ") + .pipe(finalize(() => this.busy.next(false))) + .subscribe({ + next: (data: any) => { + resolve({ + result: { + value: data.volumeInfo.ratingsCount, + }, + }); + }, + error: (error: HttpErrorResponse) => { + resolve({ + result: null, + error: { + type: error.status, + }, + }); + }, + }); + }); + } + + public ngOnDestroy(): void { + this.outputsSubject.complete(); + } +} + +const widgetConfig: IWidget = { + id: "widget1", + type: "kpi", + pizzagna: { + [PizzagnaLayer.Configuration]: { + [DEFAULT_PIZZAGNA_ROOT]: { + providers: { + [WellKnownProviders.Refresher]: { + properties: { + // Configuring the refresher interval so that our data source is invoked every ten minutes + interval: 60 * 10, + enabled: true, + } as IRefresherProperties, + } as Partial, + }, + }, + header: { + properties: { + title: "Harry Potter and the Sorcerer's Stone", + subtitle: "By J. K. Rowling", + }, + }, + tiles: { + properties: { + nodes: ["kpi1"], + }, + }, + kpi1: { + id: "kpi1", + componentType: KpiComponent.lateLoadKey, + properties: { + widgetData: { + units: "out of 5 Stars", + label: "Average Rating", + }, + }, + providers: { + [WellKnownProviders.DataSource]: { + // Setting the data source providerId for the tile with id "kpi1" + providerId: AverageRatingKpiDataSource.providerId, + } as IProviderConfiguration, + [WellKnownProviders.Adapter]: { + providerId: NOVA_KPI_DATASOURCE_ADAPTER, + properties: { + componentId: "kpi1", + propertyPath: "widgetData", + }, + } as IProviderConfiguration, + }, + }, + }, + }, +}; +\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\`, + "tutorials/persistence-handler-setup/persistence-handler-setup.module.ts": \\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\`// © 2022 SolarWinds Worldwide, LLC. All rights reserved. +// +// Permission is hereby granted, free of charge, to any person obtaining a copy +// of this software and associated documentation files (the "Software"), to +// deal in the Software without restriction, including without limitation the +// rights to use, copy, modify, merge, publish, distribute, sublicense, and/or +// sell copies of the Software, and to permit persons to whom the Software is +// furnished to do so, subject to the following conditions: +// +// The above copyright notice and this permission notice shall be included in +// all copies or substantial portions of the Software. +// +// THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR +// IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, +// FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE +// AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER +// LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, +// OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN +// THE SOFTWARE. + +import { CommonModule } from "@angular/common"; +import { HttpClientModule } from "@angular/common/http"; +import { NgModule } from "@angular/core"; +import { RouterModule } from "@angular/router"; + +import { + NuiButtonModule, + NuiDocsModule, + NuiMessageModule, + NuiSwitchModule, + NuiToastModule, + DEMO_PATH_TOKEN, +} from "@nova-ui/bits"; +import { NuiDashboardsModule } from "@nova-ui/dashboards"; + +import { PersistenceHandlerSetupDocsComponent } from "./persistence-handler-setup-docs.component"; +import { PersistenceHandlerSetupComponent } from "./persistence-handler-setup.component"; +import { getDemoFiles } from "../../../../demo-files-factory"; + +const routes = [ + { + path: "", + component: PersistenceHandlerSetupDocsComponent, + data: { + srlc: { + hideIndicator: true, + }, + showThemeSwitcher: true, + }, + }, + { + path: "example", + component: PersistenceHandlerSetupComponent, + data: { + srlc: { + hideIndicator: true, + }, + }, + }, +]; + +@NgModule({ + imports: [ + CommonModule, + HttpClientModule, + NuiDashboardsModule, + NuiDocsModule, + NuiMessageModule, + NuiSwitchModule, + NuiToastModule, + NuiButtonModule, + RouterModule.forChild(routes), + ], + declarations: [ + PersistenceHandlerSetupDocsComponent, + PersistenceHandlerSetupComponent, + ], + providers: [ + { + provide: DEMO_PATH_TOKEN, + useValue: getDemoFiles("persistence-handler-setup"), + }, + ], +}) +export default class PersistenceHandlerSetupModule {} +\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\`, + "tutorials/tutorials.module.ts": \\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\`// © 2022 SolarWinds Worldwide, LLC. All rights reserved. +// +// Permission is hereby granted, free of charge, to any person obtaining a copy +// of this software and associated documentation files (the "Software"), to +// deal in the Software without restriction, including without limitation the +// rights to use, copy, modify, merge, publish, distribute, sublicense, and/or +// sell copies of the Software, and to permit persons to whom the Software is +// furnished to do so, subject to the following conditions: +// +// The above copyright notice and this permission notice shall be included in +// all copies or substantial portions of the Software. +// +// THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR +// IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, +// FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE +// AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER +// LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, +// OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN +// THE SOFTWARE. + +import { NgModule, Type } from "@angular/core"; +import { RouterModule, Routes } from "@angular/router"; + +import { ConfiguratorHeadingService } from "@nova-ui/dashboards"; + +export enum TutorialsModuleRoute { + HelloDashboards = "hello-dashboards", + DataSource = "data-source-setup", + WidgetEditor = "widget-editor-setup", + SubmitHandler = "persistence-handler-setup", + WidgetCreation = "widget-creation", + Customization = "customization", + WidgetErrorHandling = "widget-error-handling", + DynamicHeaderLinks = "dynamic-header-links", +} + +const routes: Routes = [ + { + path: TutorialsModuleRoute.HelloDashboards, + loadChildren: async () => + import( + "./hello-dashboards/hello-dashboards.module" + ) as object as Promise>, + }, + { + path: TutorialsModuleRoute.DataSource, + loadChildren: async () => + import( + "./data-source-setup/data-source-setup.module" + ) as object as Promise>, + }, + { + path: TutorialsModuleRoute.WidgetEditor, + loadChildren: async () => + import( + "./widget-editor-setup/widget-editor-setup.module" + ) as object as Promise>, + }, + { + path: TutorialsModuleRoute.SubmitHandler, + loadChildren: async () => + import( + "./persistence-handler-setup/persistence-handler-setup.module" + ) as object as Promise>, + }, + { + path: TutorialsModuleRoute.WidgetCreation, + loadChildren: async () => + import( + "./widget-creation/widget-creation.module" + ) as object as Promise>, + }, + { + path: TutorialsModuleRoute.Customization, + loadChildren: async () => + import("./customization/customization.module") as object as Promise< + Type + >, + }, + { + path: TutorialsModuleRoute.WidgetErrorHandling, + loadChildren: async () => + import( + "./widget-error-handling/widget-error-handling.module" + ) as object as Promise>, + }, + { + path: TutorialsModuleRoute.DynamicHeaderLinks, + loadChildren: async () => + import( + "./dynamic-header-links/dynamic-header-links-docs.module" + ) as object as Promise>, + }, +]; + +@NgModule({ + imports: [RouterModule.forChild(routes)], + providers: [ConfiguratorHeadingService], +}) +export default class TutorialsModule {} +\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\`, + "tutorials/widget-creation/widget-creation-docs.component.ts": \\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\`// © 2022 SolarWinds Worldwide, LLC. All rights reserved. +// +// Permission is hereby granted, free of charge, to any person obtaining a copy +// of this software and associated documentation files (the "Software"), to +// deal in the Software without restriction, including without limitation the +// rights to use, copy, modify, merge, publish, distribute, sublicense, and/or +// sell copies of the Software, and to permit persons to whom the Software is +// furnished to do so, subject to the following conditions: +// +// The above copyright notice and this permission notice shall be included in +// all copies or substantial portions of the Software. +// +// THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR +// IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, +// FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE +// AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER +// LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, +// OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN +// THE SOFTWARE. + +import { Component } from "@angular/core"; + +@Component({ + selector: "nui-dashboard-widget-creation-docs", + templateUrl: "./widget-creation-docs.component.html", + standalone: false, +}) +export class WidgetCreationDocsComponent {} +\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\`, + "tutorials/widget-creation/widget-creation.component.ts": \\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\`// © 2022 SolarWinds Worldwide, LLC. All rights reserved. +// +// Permission is hereby granted, free of charge, to any person obtaining a copy +// of this software and associated documentation files (the "Software"), to +// deal in the Software without restriction, including without limitation the +// rights to use, copy, modify, merge, publish, distribute, sublicense, and/or +// sell copies of the Software, and to permit persons to whom the Software is +// furnished to do so, subject to the following conditions: +// +// The above copyright notice and this permission notice shall be included in +// all copies or substantial portions of the Software. +// +// THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR +// IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, +// FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE +// AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER +// LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, +// OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN +// THE SOFTWARE. + +import { HttpClient, HttpErrorResponse } from "@angular/common/http"; +import { + Component, + EventEmitter, + Injectable, + OnDestroy, + OnInit, + Output, + ViewChild, +} from "@angular/core"; +import { GridsterConfig, GridsterItem } from "angular-gridster2"; +import { BehaviorSubject, Observable, Subject } from "rxjs"; +import { finalize, take, takeUntil } from "rxjs/operators"; + +import { + DataSourceService, + IFilteringOutputs, + ToastService, + uuid, +} from "@nova-ui/bits"; +import { + DashboardComponent, + DATA_SOURCE, + DEFAULT_PIZZAGNA_ROOT, + IDashboard, + IDashboardPersistenceHandler, + IDataSourceOutput, + IKpiData, + IProviderConfiguration, + IRefresherProperties, + IWidget, + IWidgets, + IWidgetSelector, + IWidgetTemplateSelector, + KpiComponent, + NOVA_KPI_DATASOURCE_ADAPTER, + PizzagnaLayer, + ProviderRegistryService, + WellKnownPathKey, + WellKnownProviders, + WidgetClonerService, + WidgetTypesService, +} from "@nova-ui/dashboards"; + +// Interface of a widget item +interface IWidgetItem { + name: string; + widget: IWidget; +} + +// This component acts as the first step, or page, in the wizard where the user selects a wizard type to create. +// It's recommended to have this component in a different file. For this tutorial, it's included in the same +// file for simplicity. +@Component({ + selector: "widget-template-selection", + styleUrls: ["./widget-creation.component.less"], + template: \\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\` +
+ + +
+ + +
+
{{ item.name }}
+
+
+ \\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\`, + standalone: false, +}) +export class WidgetTemplateSelectionComponent + implements IWidgetTemplateSelector, OnInit +{ + // This output will notify the wizard that a widget has been selected. + @Output() public widgetSelected = new EventEmitter(); + + public widgetItems: IWidgetItem[] = []; + public widgetSelection: IWidgetItem[]; + + constructor(private widgetTypesService: WidgetTypesService) {} + + public ngOnInit(): void { + // Here we combine the widget structure from the WidgetTypesService with the corresponding widget + // configuration to create an array of widget objects for the itemSource on the repeat component. + this.widgetItems = [ + { + name: "Fully Configured KPI Widget", + widget: this.widgetTypesService.mergeWithWidgetType( + fullKpiWidgetConfig + ), + }, + { + name: "Unconfigured Proportional Widget", + // Note that 'partialPropWidgetConfig' sets 'metadata.needsConfiguration' to true. + // When this widget is selected in the wizard, the 'Create Widget' button will be hidden + // to guide the user to the second step where they can complete the configuration. + widget: this.widgetTypesService.mergeWithWidgetType( + partialPropWidgetConfig + ), + }, + ]; + + // You can optionally auto-select a widget by doing the following + // this.onSelect([this.widgetItems[0]]); + } + + public onSelect(selectedItems: any[]): void { + // We emit the selected widget to communicate the selection to the configurator + this.widgetSelected.emit(selectedItems[0].widget); + this.widgetSelection = selectedItems; + } +} + +/** + * A simple persistence handler that is tied to the widget editor directive + */ +@Injectable() +// The realizer of IDashboardPersistenceHandler may implement a trySubmit and/or a tryRemove method. +export class PersistenceHandler implements IDashboardPersistenceHandler { + // This variable is just to show how to handle error handling. + private persistenceSucceeded: boolean = true; + + // The example uses the toast service to demonstrate the + // invocation of each of the persistence handler callbacks + constructor(private toastService: ToastService) { + // toastService options to let it sit on the page for 2 seconds. + this.toastService.setConfig({ + timeOut: 2000, + }); + } + + // This method will be invoked anytime the widget editor form gets submitted. + public trySubmit = (widget: IWidget): Observable => { + // Since we are working asynchronously, we'll return a subject. So, after the submit attempt + // succeeds or fails, we can let the subscriber know the result. + const subject = new Subject(); + + if (!widget.id) { + // Creates an id if the widget has no id. + // (This step will make more sense in the context of the widget cloning tutorial + // in which we handle the persistence of a newly created widget.) + widget.id = uuid(); + } + + // For this example, we're using a setTimeout to mock an asynchronous persistence request to a backend + setTimeout(() => { + if (this.persistenceSucceeded) { + // Passes along the new widget after one second. + subject.next(widget); + // Toast on the page on success. + this.toastService.success({ + title: $localize\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\`Submit succeeded.\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\`, + }); + } else { + const errorText = $localize\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\`Submit failed.\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\`; + // Toast on the page on failure. + this.toastService.error({ title: errorText }); + // Makes the subject say there is an error. + subject.error(errorText); + } + // Completes the subject so whoever subscribes to it knows its finished. + subject.complete(); + }, 1000); + + // Returns the subject as an observable. + return subject.asObservable(); + }; + + // This method will be invoked anytime there's a widget removal attempt. + public tryRemove = (widgetId: string): Observable => { + const subject = new Subject(); + + setTimeout(() => { + if (this.persistenceSucceeded) { + // Pass through the id of the widget that was removed. + subject.next(widgetId); + this.toastService.success({ + title: $localize\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\`Removal success\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\`, + }); + } else { + const errorText = $localize\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\`Removal failed.\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\`; + this.toastService.error({ title: errorText }); + subject.error(errorText); + } + subject.complete(); + }, 1000); + + return subject.asObservable(); + }; +} + +/** + * A component that instantiates the dashboard + */ +@Component({ + selector: "widget-creation", + templateUrl: "./widget-creation.component.html", + styleUrls: ["./widget-creation.component.less"], + // Here we provide our persistence handler at the component level; this can also be done in the module. + providers: [PersistenceHandler], + standalone: false, +}) +export class WidgetCreationComponent implements OnInit { + // The WidgetClonerService will need this for updating the dashboard + @ViewChild(DashboardComponent, { static: true }) + dashboardComponent: DashboardComponent; + // This variable will hold all the data needed to define the layout and behavior of the widgets. + // Pass this to the dashboard component's dashboard input in the template. + public dashboard: IDashboard; + + // Angular gridster requires a configuration object even if it's empty. + // Pass this to the dashboard component's gridsterConfig input in the template. + public gridsterConfig: GridsterConfig = { + // These values will be used to set the initial widget dimensions on creation. + // If not set, they each default to 6. + defaultItemCols: 3, + defaultItemRows: 5, + }; + + // Boolean the dashboard takes in as an input; if it's set to true + // the dashboard allows you to resize widgets and move them around. + public editMode: boolean = false; + + // Subject used for auto-unsubscribing from subscriptions on component destruction + private readonly destroy$ = new Subject(); + + constructor( + // WidgetTypesService provides the widget's necessary structure information + private widgetTypesService: WidgetTypesService, + + // In general, the ProviderRegistryService is used for making entities available for injection into dynamically loaded components. + private providerRegistry: ProviderRegistryService, + + // Injecting the PersistenceHandler we created and assigning it to a property we use in the template. + public persistenceHandler: PersistenceHandler, + + // Injecting the cloner service which is needed for opening up the cloner wizard. + private widgetClonerService: WidgetClonerService + ) {} + + public ngOnInit(): void { + // Grabbing the widget's default template which will be needed as a parameter for setNode + const kpiTemplate = this.widgetTypesService.getWidgetType("kpi", 1); + const proportionalTemplate = this.widgetTypesService.getWidgetType( + "proportional", + 1 + ); + + // Registering our data sources as dropdown options in the widget editor/configurator + // Note: This could also be done in the parent module's constructor so that + // multiple dashboards could have access to the same widget template modification. + this.widgetTypesService.setNode( + // This is the template we grabbed above with getWidgetType + proportionalTemplate, + // Setting the editor/configurator part of the widget template + "configurator", + // This indicates which node you are changing and we want to change + // the data source providers available for selection in the editor. + WellKnownPathKey.DataSourceProviders, + // Setting the data sources available for selection in the editor + [RandomCitiesProportionalDataSource.providerId] + ); + + // Same as above, but for the KPI data sources + this.widgetTypesService.setNode( + kpiTemplate, + "configurator", + WellKnownPathKey.DataSourceProviders, + [ + AverageRatingKpiDataSource.providerId, + RatingsCountKpiDataSource.providerId, + ] + ); + + // Registering the data sources available for injection into the KPI tiles and proportional widget. + // Note: Each tile of a KPI widget is assigned its own instance of a data source. + this.providerRegistry.setProviders({ + [AverageRatingKpiDataSource.providerId]: { + provide: DATA_SOURCE, + useClass: AverageRatingKpiDataSource, + // Any dependencies that need to be injected into the provider must be listed here + deps: [HttpClient], + }, + [RatingsCountKpiDataSource.providerId]: { + provide: DATA_SOURCE, + useClass: RatingsCountKpiDataSource, + deps: [HttpClient], + }, + [RandomCitiesProportionalDataSource.providerId]: { + provide: DATA_SOURCE, + useClass: RandomCitiesProportionalDataSource, + deps: [], + }, + }); + + this.initializeDashboard(); + } + + public onCreateWidget(): void { + const widgetSelector: IWidgetSelector = { + // Template ref of the dashboard component. + dashboardComponent: this.dashboardComponent, + // A trySubmit function; in this case, we use the trySubmit from the PersistenceHandler created in the previous tutorial. + trySubmit: this.persistenceHandler.trySubmit, + // WidgetTemplateSelectionComponent will act as step one of the wizard to allow the user to select which widget will be cloned. + widgetSelectionComponentType: WidgetTemplateSelectionComponent, + }; + this.widgetClonerService + .open(widgetSelector) + .pipe( + // Auto-unsubscribe after one emission or on component destruction + take(1), + takeUntil(this.destroy$) + ) + .subscribe(); + } + + public initializeDashboard(): void { + // We're using a static configuration object for this example (see widgetConfig at the bottom of the file), + // but this is where the widget's configuration could potentially be populated from a database + const kpiWidget = fullKpiWidgetConfig; + const widgetIndex: IWidgets = { + // Complete the KPI widget with information coming from its type definition + [kpiWidget.id]: + this.widgetTypesService.mergeWithWidgetType(kpiWidget), + }; + + // Setting the widget dimensions and position (this is for gridster) + // Note: If no position is given for a widget the 'defaultItemCols' and 'defaultItemRows' properties + // from the gridsterConfig will be used for the dimensions + const positions: Record = { + [kpiWidget.id]: { + cols: 3, + rows: 5, + y: 0, + x: 0, + }, + }; + + // Finally, assigning the variables we created above to the dashboard + this.dashboard = { + positions, + widgets: widgetIndex, + }; + } +} + +// Interface for each data point in a proportional widget. +interface IProportionalWidgetData { + id: string; + name: string; + data: number[]; + icon: string; + link: string; + value: string; +} + +@Injectable() +export class RandomCitiesProportionalDataSource implements OnDestroy { + public static providerId = "RandomCitiesProportionalDataSource"; + + public outputsSubject = new Subject< + IDataSourceOutput + >(); + + // Every time applyFilters gets ran we are changing the data source. + public applyFilters(): void { + setTimeout(() => { + this.outputsSubject.next({ + result: this.getRandomProportionalWidgetData(), + }); + }, 1000); + } + + public ngOnDestroy(): void { + this.outputsSubject.complete(); + } + + private getRandomProportionalWidgetData(): IProportionalWidgetData[] { + return [ + { + id: "Down", + name: "Down", + data: [Math.round(Math.random() * 100)], + icon: "status_down", + link: "https://en.wikipedia.org/wiki/Brno", + value: "Brno", + }, + { + id: "Critical", + name: "Critical", + data: [Math.round(Math.random() * 100)], + icon: "status_critical", + link: "https://en.wikipedia.org/wiki/Kyiv", + value: "Kyiv", + }, + { + id: "Warning", + name: "Warning", + data: [Math.round(Math.random() * 100)], + icon: "status_warning", + link: "https://en.wikipedia.org/wiki/Austin", + value: "Austin", + }, + { + id: "Unknown", + name: "Unknown", + data: [Math.round(Math.random() * 100)], + icon: "status_unknown", + link: "https://en.wikipedia.org/wiki/Lisbon", + value: "Lisbon", + }, + { + id: "Up", + name: "Up", + data: [Math.round(Math.random() * 100)], + icon: "status_up", + link: "https://en.wikipedia.org/wiki/Sydney", + value: "Sydney", + }, + { + id: "Unmanaged", + name: "Unmanaged", + data: [Math.round(Math.random() * 100)], + icon: "status_unmanaged", + link: "https://en.wikipedia.org/wiki/Nur-Sultan", + value: "Nur-Sultan", + }, + ]; + } +} + +/** + * A simple KPI data source to retrieve the average rating of Harry Potter and the Sorcerer's Stone (book) via googleapis + */ +@Injectable() +export class AverageRatingKpiDataSource + extends DataSourceService + implements OnDestroy +{ + // This is the ID we'll use to identify the provider + public static providerId = "AverageRatingKpiDataSource"; + + // Use this subject to communicate the data source's busy state + public busy = new BehaviorSubject(false); + + constructor(private http: HttpClient) { + super(); + } + + // In this example, getFilteredData is invoked every 10 minutes (Take a look at the refresher + // provider definition in the widget configuration below to see how the interval is set) + public async getFilteredData(): Promise { + this.busy.next(true); + return new Promise((resolve) => { + // *** Make a resource request to an external API (if needed) + this.http + .get("https://www.googleapis.com/books/v1/volumes/5MQFrgEACAAJ") + .pipe(finalize(() => this.busy.next(false))) + .subscribe({ + next: (data: any) => { + resolve({ + result: { + value: data.volumeInfo.averageRating, + }, + }); + }, + error: (error: HttpErrorResponse) => { + resolve({ + result: null, + error: { + type: error.status, + }, + }); + }, + }); + }); + } + + public ngOnDestroy(): void { + this.outputsSubject.complete(); + } +} + +/** + * A simple KPI data source to retrieve the ratings count of Harry Potter and the Sorcerer's Stone (book) via googleapis + */ +@Injectable() +export class RatingsCountKpiDataSource + extends DataSourceService + implements OnDestroy +{ + public static providerId = "RatingsCountKpiDataSource"; + + // Use this subject to communicate the data source's busy state + public busy = new BehaviorSubject(false); + + constructor(private http: HttpClient) { + super(); + } + + public async getFilteredData(): Promise { + this.busy.next(true); + return new Promise((resolve) => { + this.http + .get("https://www.googleapis.com/books/v1/volumes/5MQFrgEACAAJ") + .pipe(finalize(() => this.busy.next(false))) + .subscribe({ + next: (data: any) => { + resolve({ + result: { + value: data.volumeInfo.ratingsCount, + }, + }); + }, + error: (error: HttpErrorResponse) => { + resolve({ + result: null, + error: { + type: error.status, + }, + }); + }, + }); + }); + } + + public ngOnDestroy(): void { + this.outputsSubject.complete(); + } +} + +const fullKpiWidgetConfig: IWidget = { + id: "widget1", + type: "kpi", + pizzagna: { + [PizzagnaLayer.Configuration]: { + [DEFAULT_PIZZAGNA_ROOT]: { + providers: { + [WellKnownProviders.Refresher]: { + properties: { + // Configuring the refresher interval so that our data source is invoked every ten minutes + interval: 60 * 10, + enabled: true, + } as IRefresherProperties, + } as Partial, + }, + }, + header: { + properties: { + title: "Harry Potter and the Sorcerer's Stone", + subtitle: "By J. K. Rowling", + }, + }, + tiles: { + properties: { + nodes: ["kpi1"], + }, + }, + kpi1: { + id: "kpi1", + componentType: KpiComponent.lateLoadKey, + properties: { + widgetData: { + units: \\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\`out of 5 Stars\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\`, + label: \\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\`Average Rating\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\`, + }, + }, + providers: { + [WellKnownProviders.DataSource]: { + // Setting the data source providerId for the tile with id "kpi1" + providerId: AverageRatingKpiDataSource.providerId, + } as IProviderConfiguration, + [WellKnownProviders.Adapter]: { + providerId: NOVA_KPI_DATASOURCE_ADAPTER, + properties: { + componentId: "kpi1", + propertyPath: "widgetData", + }, + } as IProviderConfiguration, + }, + }, + }, + }, +}; + +const partialPropWidgetConfig: IWidget = { + id: "widget2", + type: "proportional", + metadata: { + // Set 'needsConfiguration' to true if the widget needs further configuration before it can be + // placed on the dashboard. The "Create Widget" button will be hidden in the wizard when this + // widget is selected. + needsConfiguration: true, + }, + pizzagna: { + [PizzagnaLayer.Configuration]: { + header: { + properties: { + title: "*New Proportional Widget*", + }, + }, + }, + }, +}; +\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\`, + "tutorials/widget-creation/widget-creation.module.ts": \\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\`// © 2022 SolarWinds Worldwide, LLC. All rights reserved. +// +// Permission is hereby granted, free of charge, to any person obtaining a copy +// of this software and associated documentation files (the "Software"), to +// deal in the Software without restriction, including without limitation the +// rights to use, copy, modify, merge, publish, distribute, sublicense, and/or +// sell copies of the Software, and to permit persons to whom the Software is +// furnished to do so, subject to the following conditions: +// +// The above copyright notice and this permission notice shall be included in +// all copies or substantial portions of the Software. +// +// THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR +// IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, +// FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE +// AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER +// LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, +// OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN +// THE SOFTWARE. + +import { CommonModule } from "@angular/common"; +import { HttpClientModule } from "@angular/common/http"; +import { NgModule } from "@angular/core"; +import { RouterModule } from "@angular/router"; + +import { + NuiButtonModule, + NuiDocsModule, + NuiImageModule, + NuiMessageModule, + NuiRepeatModule, + NuiSwitchModule, + NuiToastModule, + DEMO_PATH_TOKEN, +} from "@nova-ui/bits"; +import { NuiDashboardsModule } from "@nova-ui/dashboards"; + +import { WidgetCreationDocsComponent } from "./widget-creation-docs.component"; +import { + WidgetCreationComponent, + WidgetTemplateSelectionComponent, +} from "./widget-creation.component"; +import { getDemoFiles } from "../../../../demo-files-factory"; + +const routes = [ + { + path: "", + component: WidgetCreationDocsComponent, + data: { + srlc: { + hideIndicator: true, + }, + showThemeSwitcher: true, + }, + }, + { + path: "example", + component: WidgetCreationComponent, + data: { + srlc: { + hideIndicator: true, + }, + }, + }, +]; + +@NgModule({ + imports: [ + CommonModule, + HttpClientModule, + NuiDashboardsModule, + NuiDocsModule, + NuiMessageModule, + NuiSwitchModule, + NuiToastModule, + NuiButtonModule, + NuiRepeatModule, + NuiImageModule, + RouterModule.forChild(routes), + ], + declarations: [ + WidgetCreationDocsComponent, + WidgetCreationComponent, + WidgetTemplateSelectionComponent, + ], + providers: [ + { + provide: DEMO_PATH_TOKEN, + useValue: getDemoFiles("widget-creation"), + }, + ], +}) +export default class WidgetCreationModule {} +\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\`, + "tutorials/widget-editor-setup/widget-editor-setup-docs.component.ts": \\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\`// © 2022 SolarWinds Worldwide, LLC. All rights reserved. +// +// Permission is hereby granted, free of charge, to any person obtaining a copy +// of this software and associated documentation files (the "Software"), to +// deal in the Software without restriction, including without limitation the +// rights to use, copy, modify, merge, publish, distribute, sublicense, and/or +// sell copies of the Software, and to permit persons to whom the Software is +// furnished to do so, subject to the following conditions: +// +// The above copyright notice and this permission notice shall be included in +// all copies or substantial portions of the Software. +// +// THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR +// IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, +// FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE +// AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER +// LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, +// OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN +// THE SOFTWARE. + +import { Component } from "@angular/core"; + +@Component({ + selector: "nui-dashboard-widget-editor-docs", + templateUrl: "./widget-editor-setup-docs.component.html", + standalone: false, +}) +export class WidgetEditorDocsComponent {} +\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\`, + "tutorials/widget-editor-setup/widget-editor-setup.component.ts": \\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\`// © 2022 SolarWinds Worldwide, LLC. All rights reserved. +// +// Permission is hereby granted, free of charge, to any person obtaining a copy +// of this software and associated documentation files (the "Software"), to +// deal in the Software without restriction, including without limitation the +// rights to use, copy, modify, merge, publish, distribute, sublicense, and/or +// sell copies of the Software, and to permit persons to whom the Software is +// furnished to do so, subject to the following conditions: +// +// The above copyright notice and this permission notice shall be included in +// all copies or substantial portions of the Software. +// +// THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR +// IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, +// FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE +// AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER +// LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, +// OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN +// THE SOFTWARE. + +import { HttpClient, HttpErrorResponse } from "@angular/common/http"; +import { + ChangeDetectorRef, + Component, + Injectable, + OnDestroy, + OnInit, +} from "@angular/core"; +import { GridsterConfig, GridsterItem } from "angular-gridster2"; +import { BehaviorSubject } from "rxjs"; +import { finalize } from "rxjs/operators"; + +import { DataSourceService, IFilteringOutputs } from "@nova-ui/bits"; +import { + DATA_SOURCE, + DEFAULT_PIZZAGNA_ROOT, + IDashboard, + IKpiData, + IProviderConfiguration, + IRefresherProperties, + IWidget, + IWidgets, + KpiComponent, + NOVA_KPI_DATASOURCE_ADAPTER, + PizzagnaLayer, + ProviderRegistryService, + WellKnownPathKey, + WellKnownProviders, + WidgetTypesService, +} from "@nova-ui/dashboards"; + +/** + * A simple KPI data source to retrieve the average rating of Harry Potter and the Sorcerer's Stone (book) via googleapis + */ +@Injectable() +export class AverageRatingKpiDataSource + extends DataSourceService + implements OnDestroy +{ + // This is the ID we'll use to identify the provider + public static providerId = "AverageRatingKpiDataSource"; + + // Use this subject to communicate the data source's busy state + public busy = new BehaviorSubject(false); + + constructor(private http: HttpClient) { + super(); + } + + // In this example, getFilteredData is invoked every 10 minutes (Take a look at the refresher + // provider definition in the widget configuration below to see how the interval is set) + public async getFilteredData(): Promise { + this.busy.next(true); + return new Promise((resolve) => { + // *** Make a resource request to an external API (if needed) + this.http + .get("https://www.googleapis.com/books/v1/volumes/5MQFrgEACAAJ") + .pipe(finalize(() => this.busy.next(false))) + .subscribe({ + next: (data: any) => { + resolve({ + result: { + value: data.volumeInfo.averageRating, + }, + }); + }, + error: (error: HttpErrorResponse) => { + resolve({ + result: null, + error: { + type: error.status, + }, + }); + }, + }); + }); + } + + public ngOnDestroy(): void { + this.outputsSubject.complete(); + } +} + +/** + * A simple KPI data source to retrieve the ratings count of Harry Potter and the Sorcerer's Stone (book) via googleapis + */ +@Injectable() +export class RatingsCountKpiDataSource + extends DataSourceService + implements OnDestroy +{ + public static providerId = "RatingsCountKpiDataSource"; + + // Use this subject to communicate the data source's busy state + public busy = new BehaviorSubject(false); + + constructor(private http: HttpClient) { + super(); + } + + public async getFilteredData(): Promise { + this.busy.next(true); + return new Promise((resolve) => { + this.http + .get("https://www.googleapis.com/books/v1/volumes/5MQFrgEACAAJ") + .pipe(finalize(() => this.busy.next(false))) + .subscribe({ + next: (data: any) => { + resolve({ + result: { + value: data.volumeInfo.ratingsCount, + }, + }); + }, + error: (error: HttpErrorResponse) => { + resolve({ + result: null, + error: { + type: error.status, + }, + }); + }, + }); + }); + } + + public ngOnDestroy(): void { + this.outputsSubject.complete(); + } +} + +/** + * A component that instantiates the dashboard + */ +@Component({ + selector: "widget-editor-setup", + templateUrl: "./widget-editor-setup.component.html", + styleUrls: ["./widget-editor-setup.component.less"], + standalone: false, +}) +export class WidgetEditorSetupComponent implements OnInit { + // This variable will hold all the data needed to define the layout and behavior of the widgets. + // Pass this to the dashboard component's dashboard input in the template. + public dashboard: IDashboard | undefined; + + // Angular gridster requires a configuration object even if it's empty. + // Pass this to the dashboard component's gridsterConfig input in the template. + public gridsterConfig: GridsterConfig = {}; + + // Boolean which dashboard takes in as an input if its true it allows you to move widgets around. + public editMode: boolean = false; + + constructor( + // WidgetTypesService provides the widget's necessary structure information + private widgetTypesService: WidgetTypesService, + + // In general, the ProviderRegistryService is used for making entities available for injection into dynamically loaded components. + private providerRegistry: ProviderRegistryService, + private changeDetectorRef: ChangeDetectorRef + ) {} + + public ngOnInit(): void { + // Grabbing the widget's default template which will be needed as a parameter for setNode + const widgetTemplate = this.widgetTypesService.getWidgetType("kpi", 1); + // Registering our data sources as dropdown options in the widget editor/configurator + // Note: This could also be done in the parent module's constructor so that + // multiple dashboards could have access to the same widget template modification. + this.widgetTypesService.setNode( + // This is the template we grabbed above with getWidgetType + widgetTemplate, + // We are setting the editor/configurator part of the widget template + "configurator", + // This indicates which node you are changing and we want to change + // the data source providers available for selection in the editor. + WellKnownPathKey.DataSourceProviders, + // We are setting the data sources available for selection in the editor + [ + AverageRatingKpiDataSource.providerId, + RatingsCountKpiDataSource.providerId, + ] + ); + + // Registering the data sources available for injection into the KPI tiles. + // Note: Each tile of a KPI widget is assigned its own instance of a data source + this.providerRegistry.setProviders({ + [AverageRatingKpiDataSource.providerId]: { + provide: DATA_SOURCE, + useClass: AverageRatingKpiDataSource, + // Any dependencies that need to be injected into the provider must be listed here + deps: [HttpClient], + }, + [RatingsCountKpiDataSource.providerId]: { + provide: DATA_SOURCE, + useClass: RatingsCountKpiDataSource, + deps: [HttpClient], + }, + }); + + this.initializeDashboard(); + } + + /** Used for restoring widgets state */ + public reInitializeDashboard(): void { + // destroys the components and their providers so the dashboard can re init data + this.dashboard = undefined; + this.changeDetectorRef.detectChanges(); + + this.initializeDashboard(); + } + + public initializeDashboard(): void { + // We're using a static configuration object for this example (see widgetConfig at the bottom of the file), + // but this is where the widget's configuration could potentially be populated from a database + const kpiWidget = widgetConfig; + const widgetIndex: IWidgets = { + // Complete the KPI widget with information coming from its type definition + [kpiWidget.id]: + this.widgetTypesService.mergeWithWidgetType(kpiWidget), + }; + + // Setting the widget dimensions and position (this is for gridster) + const positions: Record = { + [kpiWidget.id]: { + cols: 4, + rows: 6, + y: 0, + x: 0, + }, + }; + + // Finally, assigning the variables we created above to the dashboard + this.dashboard = { + positions, + widgets: widgetIndex, + }; + } +} + +const widgetConfig: IWidget = { + id: "widget1", + type: "kpi", + pizzagna: { + [PizzagnaLayer.Configuration]: { + [DEFAULT_PIZZAGNA_ROOT]: { + providers: { + [WellKnownProviders.Refresher]: { + properties: { + // Configuring the refresher interval so that our data source is invoked every ten minutes + interval: 60 * 10, + enabled: true, + } as IRefresherProperties, + } as Partial, + }, + }, + header: { + properties: { + title: "Harry Potter and the Sorcerer's Stone", + subtitle: "By J. K. Rowling", + }, + }, + tiles: { + properties: { + nodes: ["kpi1"], + }, + }, + kpi1: { + id: "kpi1", + componentType: KpiComponent.lateLoadKey, + properties: { + widgetData: { + units: "out of 5 Stars", + label: "Average Rating", + }, + }, + providers: { + [WellKnownProviders.DataSource]: { + // Setting the data source providerId for the tile with id "kpi1" + providerId: AverageRatingKpiDataSource.providerId, + } as IProviderConfiguration, + [WellKnownProviders.Adapter]: { + providerId: NOVA_KPI_DATASOURCE_ADAPTER, + properties: { + componentId: "kpi1", + propertyPath: "widgetData", + }, + } as IProviderConfiguration, + }, + }, + }, + }, +}; +\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\`, + "tutorials/widget-editor-setup/widget-editor-setup.module.ts": \\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\`// © 2022 SolarWinds Worldwide, LLC. All rights reserved. +// +// Permission is hereby granted, free of charge, to any person obtaining a copy +// of this software and associated documentation files (the "Software"), to +// deal in the Software without restriction, including without limitation the +// rights to use, copy, modify, merge, publish, distribute, sublicense, and/or +// sell copies of the Software, and to permit persons to whom the Software is +// furnished to do so, subject to the following conditions: +// +// The above copyright notice and this permission notice shall be included in +// all copies or substantial portions of the Software. +// +// THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR +// IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, +// FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE +// AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER +// LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, +// OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN +// THE SOFTWARE. + +import { CommonModule } from "@angular/common"; +import { HttpClientModule } from "@angular/common/http"; +import { NgModule } from "@angular/core"; +import { RouterModule } from "@angular/router"; + +import { + NuiButtonModule, + NuiDocsModule, + NuiMessageModule, + NuiSwitchModule, + DEMO_PATH_TOKEN, +} from "@nova-ui/bits"; +import { NuiDashboardsModule } from "@nova-ui/dashboards"; + +import { WidgetEditorDocsComponent } from "./widget-editor-setup-docs.component"; +import { WidgetEditorSetupComponent } from "./widget-editor-setup.component"; +import { getDemoFiles } from "../../../../demo-files-factory"; + +const routes = [ + { + path: "", + component: WidgetEditorDocsComponent, + data: { + srlc: { + hideIndicator: true, + }, + showThemeSwitcher: true, + }, + }, + { + path: "example", + component: WidgetEditorSetupComponent, + data: { + srlc: { + hideIndicator: true, + }, + }, + }, +]; + +@NgModule({ + imports: [ + CommonModule, + HttpClientModule, + NuiDashboardsModule, + NuiDocsModule, + NuiMessageModule, + NuiSwitchModule, + NuiButtonModule, + RouterModule.forChild(routes), + ], + declarations: [WidgetEditorDocsComponent, WidgetEditorSetupComponent], + providers: [ + { + provide: DEMO_PATH_TOKEN, + useValue: getDemoFiles("widget-editor-setup"), + }, + ], +}) +export default class WidgetEditorSetupModule {} +\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\`, + "tutorials/widget-error-handling/widget-error-handling-docs.component.ts": \\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\`// © 2022 SolarWinds Worldwide, LLC. All rights reserved. +// +// Permission is hereby granted, free of charge, to any person obtaining a copy +// of this software and associated documentation files (the "Software"), to +// deal in the Software without restriction, including without limitation the +// rights to use, copy, modify, merge, publish, distribute, sublicense, and/or +// sell copies of the Software, and to permit persons to whom the Software is +// furnished to do so, subject to the following conditions: +// +// The above copyright notice and this permission notice shall be included in +// all copies or substantial portions of the Software. +// +// THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR +// IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, +// FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE +// AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER +// LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, +// OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN +// THE SOFTWARE. + +import { Component } from "@angular/core"; + +@Component({ + selector: "nui-widget-error-handling-docs", + templateUrl: "./widget-error-handling-docs.component.html", + standalone: false, +}) +export class WidgetErrorHandlingDocsComponent { + public fallbackAdapter = \\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\` +@Injectable() +export class StatusContentFallbackAdapter implements OnDestroy, IHasComponent { + + protected readonly destroy$ = new Subject(); + protected componentId: string; + + constructor(@Inject(PIZZAGNA_EVENT_BUS) protected eventBus: EventBus, + protected pizzagnaService: PizzagnaService) { + this.eventBus.getStream(DATA_SOURCE_OUTPUT) + .pipe(takeUntil(this.destroy$)).subscribe((event: IEvent>) => { + this.handleDataSourceOutput(event); + }); + } + + public ngOnDestroy(): void { + this.destroy$.next(); + this.destroy$.complete(); + } + + public setComponent(component: any, componentId: string) { + this.componentId = componentId; + } + + protected handleDataSourceOutput(event: IEvent>) { + this.pizzagnaService.setProperty({ + componentId: this.componentId, + propertyPath: ["fallbackKey"], + pizzagnaKey: PizzagnaLayer.Data, + }, typeof event.payload?.error?.type !== "undefined" ? event.payload?.error?.type.toString() : undefined); + } +}\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\`; + public errorsMap = \\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\` +export const ERROR_FALLBACK_MAP: Record = { + [HttpStatusCode.Unknown]: ErrorNodeKey.ErrorUnknown, + [HttpStatusCode.Forbidden]: ErrorNodeKey.ErrorForbidden, + [HttpStatusCode.NotFound]: ErrorNodeKey.ErrorNotFound, +}; +\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\`; + public errorNodes = \\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\` +export const ERROR_NODES: Record = { + [ErrorNodeKey.ErrorUnknown]: { + id: ErrorNodeKey.ErrorUnknown, + componentType: WidgetErrorComponent.lateLoadKey, + properties: { + image: "no-data-to-show", + title: $localize\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\`Whoops, something went wrong\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\`, + description: $localize\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\`There was an unexpected error.\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\`, + } as IWidgetErrorDisplayProperties, + }, + [ErrorNodeKey.ErrorForbidden]: { + id: ErrorNodeKey.ErrorForbidden, + componentType: WidgetErrorComponent.lateLoadKey, + properties: { + image: "no-data-to-show", + title: $localize\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\`403 - Forbidden\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\`, + description: $localize\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\`The requested action was forbidden.\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\`, + } as IWidgetErrorDisplayProperties, + }, + [ErrorNodeKey.ErrorNotFound]: { + id: ErrorNodeKey.ErrorNotFound, + componentType: WidgetErrorComponent.lateLoadKey, + properties: { + image: "no-data-to-show", + title: $localize\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\`404 - Not Found\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\`, + description: $localize\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\`The requested resource could not be found.\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\`, + } as IWidgetErrorDisplayProperties, + }, +};\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\`; + public widgetBodyContentNodesSignature = \\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\` +/** + * Retrieves an index of the basic widget body content nodes including fallback nodes + * + * @param mainContentNodeKey The key corresponding to the main body content node + * @param fallbackAdapterId The id for the adapter responsible for activating fallback content in case of an error + * @param fallbackMap A map of node keys to fallback content definitions + * @param fallbackNodes An index of fallback content definitions + * + * @returns An index of component configurations + */ +export function widgetBodyContentNodes( + mainContentNodeKey: string, + fallbackAdapterId = NOVA_STATUS_CONTENT_FALLBACK_ADAPTER, + fallbackMap: Record = ERROR_FALLBACK_MAP, + fallbackNodes: Record = ERROR_NODES +): Record { ... } +\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\`; +} +\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\`, + "tutorials/widget-error-handling/widget-error-handling.component.ts": \\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\`// © 2022 SolarWinds Worldwide, LLC. All rights reserved. +// +// Permission is hereby granted, free of charge, to any person obtaining a copy +// of this software and associated documentation files (the "Software"), to +// deal in the Software without restriction, including without limitation the +// rights to use, copy, modify, merge, publish, distribute, sublicense, and/or +// sell copies of the Software, and to permit persons to whom the Software is +// furnished to do so, subject to the following conditions: +// +// The above copyright notice and this permission notice shall be included in +// all copies or substantial portions of the Software. +// +// THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR +// IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, +// FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE +// AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER +// LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, +// OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN +// THE SOFTWARE. + +import { HttpClient, HttpErrorResponse } from "@angular/common/http"; +import { + ChangeDetectorRef, + Component, + Injectable, + OnDestroy, + OnInit, +} from "@angular/core"; +import { GridsterConfig, GridsterItem } from "angular-gridster2"; +import { BehaviorSubject } from "rxjs"; +import { finalize } from "rxjs/operators"; + +import { DataSourceService, IFilteringOutputs } from "@nova-ui/bits"; +import { + DATA_SOURCE, + DEFAULT_PIZZAGNA_ROOT, + HttpStatusCode, + IDashboard, + IKpiData, + IProviderConfiguration, + IRefresherProperties, + IWidget, + IWidgets, + KpiComponent, + NOVA_KPI_DATASOURCE_ADAPTER, + PizzagnaLayer, + ProviderRegistryService, + WellKnownPathKey, + WellKnownProviders, + WidgetTypesService, +} from "@nova-ui/dashboards"; + +/** + * A simple KPI data source to retrieve the average rating of Harry Potter and the Sorcerer's Stone (book) via googleapis + */ +@Injectable() +export class AverageRatingKpiDataSource + extends DataSourceService + implements OnDestroy +{ + // This is the ID we'll use to identify the provider + public static providerId = "AverageRatingKpiDataSource"; + + // Use this subject to communicate the data source's busy state + public busy = new BehaviorSubject(false); + + constructor(private http: HttpClient) { + super(); + } + + // In this example, getFilteredData is invoked every 10 minutes (Take a look at the refresher + // provider definition in the widget configuration below to see how the interval is set) + public async getFilteredData(): Promise { + this.busy.next(true); + return new Promise((resolve) => { + // *** Make a resource request to an external API (if needed) + this.http + .get("https://www.googleapis.com/books/v1/volumes/5MQFrgEACAAJ") + .pipe(finalize(() => this.busy.next(false))) + .subscribe({ + next: (data: any) => { + resolve({ + result: { + value: data.volumeInfo.averageRating, + }, + }); + }, + error: (error: HttpErrorResponse) => { + resolve({ + result: null, + error: { + type: error.status, + }, + }); + }, + }); + }); + } + + public ngOnDestroy(): void { + this.outputsSubject.complete(); + } +} + +/** + * A simple KPI data source to retrieve the average rating of Harry Potter and the Sorcerer's Stone (book) via googleapis + */ +@Injectable() +export class ErrorUnknownDataSource + extends DataSourceService + implements OnDestroy +{ + // This is the ID we'll use to identify the provider + public static providerId = "ErrorUnknownDataSource"; + + // Use this subject to communicate the data source's busy state + public busy = new BehaviorSubject(false); + + // In this example, getFilteredData is invoked every 10 minutes (Take a look at the refresher + // provider definition in the widget configuration below to see how the interval is set) + public async getFilteredData(): Promise { + this.busy.next(true); + const mockError = { + result: null, + error: { type: HttpStatusCode.Unknown }, + }; + this.busy.next(false); + return mockError; + } + + public ngOnDestroy(): void { + this.outputsSubject.complete(); + } +} + +/** + * A simple KPI data source to retrieve the ratings count of Harry Potter and the Sorcerer's Stone (book) via googleapis + */ +@Injectable() +export class ErrorForbiddenDataSource + extends DataSourceService + implements OnDestroy +{ + public static providerId = "ErrorForbiddenDataSource"; + + // Use this subject to communicate the data source's busy state + public busy = new BehaviorSubject(false); + + constructor(private http: HttpClient) { + super(); + } + + public async getFilteredData(): Promise { + this.busy.next(true); + // generate a 403 + return new Promise((resolve) => { + this.http + .get( + "http://www.mocky.io/v2/5ecc724a3200000f0023614a?mocky-delay=4000ms" + ) + .pipe(finalize(() => this.busy.next(false))) + .subscribe({ + error: (error: HttpErrorResponse) => { + resolve({ + result: null, + error: { + type: error.status, + }, + }); + }, + }); + }); + } + + public ngOnDestroy(): void { + this.outputsSubject.complete(); + } +} + +/** + * A simple KPI data source to retrieve the ratings count of Harry Potter and the Sorcerer's Stone (book) via googleapis + */ +@Injectable() +export class ErrorNotFoundDataSource + extends DataSourceService + implements OnDestroy +{ + public static providerId = "ErrorNotFoundDataSource"; + + // Use this subject to communicate the data source's busy state + public busy = new BehaviorSubject(false); + + constructor(private http: HttpClient) { + super(); + } + + public async getFilteredData(): Promise { + this.busy.next(true); + // generate a 404 + return new Promise((resolve) => { + this.http + .get( + "http://www.mocky.io/v2/5ec6bfd93200007800d75100?mocky-delay=1000ms" + ) + .pipe(finalize(() => this.busy.next(false))) + .subscribe({ + error: (error: HttpErrorResponse) => { + resolve({ + result: null, + error: { + type: error.status, + }, + }); + }, + }); + }); + } + + public ngOnDestroy(): void { + this.outputsSubject.complete(); + } +} + +/** + * A component that instantiates the dashboard + */ +@Component({ + selector: "widget-error-handling", + templateUrl: "./widget-error-handling.component.html", + styleUrls: ["./widget-error-handling.component.less"], + standalone: false, +}) +export class WidgetErrorHandlingComponent implements OnInit { + // This variable will hold all the data needed to define the layout and behavior of the widgets. + // Pass this to the dashboard component's dashboard input in the template. + public dashboard: IDashboard | undefined; + + // Angular gridster requires a configuration object even if it's empty. + // Pass this to the dashboard component's gridsterConfig input in the template. + public gridsterConfig: GridsterConfig = {}; + + // Boolean which dashboard takes in as an input if its true it allows you to move widgets around. + public editMode: boolean = false; + + constructor( + // WidgetTypesService provides the widget's necessary structure information + private widgetTypesService: WidgetTypesService, + + // In general, the ProviderRegistryService is used for making entities available for injection into dynamically loaded components. + private providerRegistry: ProviderRegistryService, + private changeDetectorRef: ChangeDetectorRef + ) {} + + public ngOnInit(): void { + // Grab the widget's default template which will be needed as a parameter for setNode. + const widgetTemplate = this.widgetTypesService.getWidgetType("kpi", 1); + // Register our data sources as dropdown options in the widget editor/configurator + // Note: This could also be done in the parent module's constructor so that + // multiple dashboards could have access to the same widget template modification. + this.widgetTypesService.setNode( + // This is the template we grabbed above with getWidgetType + widgetTemplate, + // We are setting the editor/configurator part of the widget template + "configurator", + // This indicates which node you are changing and we want to change + // the data source providers available for selection in the editor. + WellKnownPathKey.DataSourceProviders, + // We are setting the data sources available for selection in the editor + [ + ErrorUnknownDataSource.providerId, + ErrorForbiddenDataSource.providerId, + ErrorNotFoundDataSource.providerId, + AverageRatingKpiDataSource.providerId, + ] + ); + + // Register the data sources available for injection into the KPI tiles. + // Note: Each tile of a KPI widget is assigned its own instance of a data source + this.providerRegistry.setProviders({ + [ErrorUnknownDataSource.providerId]: { + provide: DATA_SOURCE, + useClass: ErrorUnknownDataSource, + // Any dependencies that need to be injected into the provider must be listed here + deps: [HttpClient], + }, + [ErrorForbiddenDataSource.providerId]: { + provide: DATA_SOURCE, + useClass: ErrorForbiddenDataSource, + deps: [HttpClient], + }, + [ErrorNotFoundDataSource.providerId]: { + provide: DATA_SOURCE, + useClass: ErrorNotFoundDataSource, + deps: [HttpClient], + }, + [AverageRatingKpiDataSource.providerId]: { + provide: DATA_SOURCE, + useClass: AverageRatingKpiDataSource, + // Any dependencies that need to be injected into the provider must be listed here + deps: [HttpClient], + }, + }); + + this.initializeDashboard(); + } + + /** Used for restoring widgets state */ + public reInitializeDashboard(): void { + // destroys the components and their providers so the dashboard can re init data + this.dashboard = undefined; + this.changeDetectorRef.detectChanges(); + + this.initializeDashboard(); + } + + public initializeDashboard(): void { + // We're using a static configuration object for this example (see widgetConfig at the bottom of the file), + // but this is where the widget's configuration could potentially be populated from a database + const kpiWidget = widgetConfig; + const widgetIndex: IWidgets = { + // Complete the KPI widget with information coming from its type definition + [kpiWidget.id]: + this.widgetTypesService.mergeWithWidgetType(kpiWidget), + }; + + // Setting the widget dimensions and position (this is for gridster) + const positions: Record = { + [kpiWidget.id]: { + cols: 4, + rows: 6, + y: 0, + x: 0, + }, + }; + + // Finally, assigning the variables we created above to the dashboard + this.dashboard = { + positions, + widgets: widgetIndex, + }; + } +} + +const widgetConfig: IWidget = { + id: "widget1", + type: "kpi", + pizzagna: { + [PizzagnaLayer.Configuration]: { + [DEFAULT_PIZZAGNA_ROOT]: { + providers: { + [WellKnownProviders.Refresher]: { + properties: { + // Configuring the refresher interval so that our data source is invoked every ten minutes + interval: 60 * 10, + enabled: true, + } as IRefresherProperties, + } as Partial, + }, + }, + header: { + properties: { + title: "Harry Potter and the Sorcerer's Stone", + subtitle: "By J. K. Rowling", + }, + }, + tiles: { + properties: { + nodes: ["kpi1"], + }, + }, + kpi1: { + id: "kpi1", + componentType: KpiComponent.lateLoadKey, + properties: { + widgetData: { + units: "out of 5 Stars", + label: "Average Rating", + }, + }, + providers: { + [WellKnownProviders.DataSource]: { + // Setting the data source providerId for the tile with id "kpi1" + providerId: ErrorUnknownDataSource.providerId, + } as IProviderConfiguration, + [WellKnownProviders.Adapter]: { + providerId: NOVA_KPI_DATASOURCE_ADAPTER, + properties: { + componentId: "kpi1", + propertyPath: "widgetData", + }, + } as IProviderConfiguration, + }, + }, + }, + }, +}; +\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\`, + "tutorials/widget-error-handling/widget-error-handling.module.ts": \\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\`// © 2022 SolarWinds Worldwide, LLC. All rights reserved. +// +// Permission is hereby granted, free of charge, to any person obtaining a copy +// of this software and associated documentation files (the "Software"), to +// deal in the Software without restriction, including without limitation the +// rights to use, copy, modify, merge, publish, distribute, sublicense, and/or +// sell copies of the Software, and to permit persons to whom the Software is +// furnished to do so, subject to the following conditions: +// +// The above copyright notice and this permission notice shall be included in +// all copies or substantial portions of the Software. +// +// THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR +// IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, +// FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE +// AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER +// LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, +// OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN +// THE SOFTWARE. + +import { CommonModule } from "@angular/common"; +import { HttpClientModule } from "@angular/common/http"; +import { NgModule } from "@angular/core"; +import { ReactiveFormsModule } from "@angular/forms"; +import { RouterModule } from "@angular/router"; + +import { + NuiButtonModule, + NuiDocsModule, + NuiFormFieldModule, + NuiIconModule, + NuiMessageModule, + NuiSwitchModule, + NuiTextboxModule, + DEMO_PATH_TOKEN, +} from "@nova-ui/bits"; +import { + NuiDashboardConfiguratorModule, + NuiDashboardsModule, +} from "@nova-ui/dashboards"; + +import { WidgetErrorHandlingDocsComponent } from "./widget-error-handling-docs.component"; +import { WidgetErrorHandlingComponent } from "./widget-error-handling.component"; +import { getDemoFiles } from "../../../../demo-files-factory"; + +const routes = [ + { + path: "", + component: WidgetErrorHandlingDocsComponent, + data: { + srlc: { + hideIndicator: true, + }, + showThemeSwitcher: true, + }, + }, + { + path: "example", + component: WidgetErrorHandlingComponent, + data: { + srlc: { + hideIndicator: true, + }, + }, + }, +]; + +@NgModule({ + imports: [ + CommonModule, + ReactiveFormsModule, + HttpClientModule, + NuiButtonModule, + NuiDashboardsModule, + NuiDashboardConfiguratorModule, + NuiDocsModule, + NuiFormFieldModule, + NuiIconModule, + NuiMessageModule, + NuiIconModule, + NuiTextboxModule, + NuiIconModule, + NuiSwitchModule, + RouterModule.forChild(routes), + ], + declarations: [ + WidgetErrorHandlingDocsComponent, + WidgetErrorHandlingComponent, + ], + providers: [ + { + provide: DEMO_PATH_TOKEN, + useValue: getDemoFiles("widget-error-handling"), + }, + ], +}) +export default class WidgetErrorHandlingModule {} +\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\`, + "types.ts": \\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\`// © 2022 SolarWinds Worldwide, LLC. All rights reserved. +// +// Permission is hereby granted, free of charge, to any person obtaining a copy +// of this software and associated documentation files (the "Software"), to +// deal in the Software without restriction, including without limitation the +// rights to use, copy, modify, merge, publish, distribute, sublicense, and/or +// sell copies of the Software, and to permit persons to whom the Software is +// furnished to do so, subject to the following conditions: +// +// The above copyright notice and this permission notice shall be included in +// all copies or substantial portions of the Software. +// +// THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR +// IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, +// FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE +// AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER +// LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, +// OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN +// THE SOFTWARE. + +export enum APOLLO_API_NAMESPACE { + COUNTRIES = "countries", +} +\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\`, + "widget-types/drilldown/drilldown-multi-request-widget/drilldown-multi-request-widget-example.component.ts": \\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\`// © 2022 SolarWinds Worldwide, LLC. All rights reserved. +// +// Permission is hereby granted, free of charge, to any person obtaining a copy +// of this software and associated documentation files (the "Software"), to +// deal in the Software without restriction, including without limitation the +// rights to use, copy, modify, merge, publish, distribute, sublicense, and/or +// sell copies of the Software, and to permit persons to whom the Software is +// furnished to do so, subject to the following conditions: +// +// The above copyright notice and this permission notice shall be included in +// all copies or substantial portions of the Software. +// +// THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR +// IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, +// FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE +// AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER +// LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, +// OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN +// THE SOFTWARE. + +import { HttpClient } from "@angular/common/http"; +import { + ChangeDetectorRef, + Component, + Injectable, + OnDestroy, + OnInit, +} from "@angular/core"; +import { GridsterConfig, GridsterItem } from "angular-gridster2"; +import { Apollo, gql } from "apollo-angular"; +import { BehaviorSubject, Observable, of, Subject } from "rxjs"; +// eslint-disable-next-line import/no-deprecated +import { finalize, map, switchMap, tap } from "rxjs/operators"; + +import { + DataSourceService, + IconStatus, + IDataField, + IFilters, + INovaFilters, +} from "@nova-ui/bits"; +import { + DATA_SOURCE, + DEFAULT_PIZZAGNA_ROOT, + IDashboard, + IDrilldownComponentsConfiguration, + IListWidgetConfiguration, + IProviderConfiguration, + IWidget, + IWidgets, + ListGroupItemComponent, + ListLeafItemComponent, + NOVA_DRILLDOWN_DATASOURCE_ADAPTER, + PizzagnaLayer, + ProviderRegistryService, + WellKnownPathKey, + WellKnownProviders, + WidgetTypesService, +} from "@nova-ui/dashboards"; + +import { APOLLO_API_NAMESPACE } from "../../../types"; + +/** + * A simple KPI data source to retrieve the average rating of Harry Potter and the Sorcerer's Stone (book) via googleapis + */ +@Injectable() +export class DrilldownDataSource + extends DataSourceService + implements OnDestroy +{ + // This is the ID we'll use to identify the provider + public static providerId = "DrilldownDataSource"; + + // Use this subject to communicate the data source's busy state + public busy = new BehaviorSubject(false); + + public dataFields: Partial[] = [ + { id: "Region", label: "Region name" }, + { id: "Subregion", label: "Subregion name" }, + ]; + + private drillState: string[] = []; + private groupBy: string[]; + private cache: any; + private lastDrillState: string[] = []; + private leafGroup: string = "country"; + private applyFilters$ = new Subject(); + + constructor(private http: HttpClient, private apollo: Apollo) { + super(); + + // TODO: remove Partial in vNext after marking dataType field as optional - NUI-5838 + ( + this.dataFieldsConfig.dataFields$ as BehaviorSubject< + Partial[] + > + ).next(this.dataFields); + + this.applyFilters$ + // eslint-disable-next-line import/no-deprecated + .pipe(switchMap((filters) => this.getData(filters))) + .subscribe(async (res) => { + this.outputsSubject.next(await this.getFilteredData(res)); + }); + } + + private groupedDataHistory: any[] = []; + + // In this example, getFilteredData is invoked every 10 minutes (Take a look at the refresher + // provider definition in the widget configuration below to see how the interval is set) + public async getFilteredData(data: any): Promise { + return of(data) + .pipe( + map((entries) => { + if (this.isDrillDown()) { + const activeDrillLvl = this.drillState.length; + const group = this.groupBy[activeDrillLvl]; + const lastGroupedValue = + this.getTransformedDataForGroup( + entries, + group, + getLast(this.drillState) + ); + + this.groupedDataHistory.push(lastGroupedValue); + + return lastGroupedValue; + } + + const mapIconsToEntries = entries.map((item: any) => ({ + ...item, + icon: "virtual-host", + icon_status: IconStatus.Up, + })); + this.groupedDataHistory.push(mapIconsToEntries); + const widgetInput = this.getOutput(entries); + + return widgetInput; + }) + ) + .toPromise(); + } + + public ngOnDestroy(): void { + this.outputsSubject.complete(); + } + + // redefine parent method + public async applyFilters(): Promise { + this.applyFilters$.next(this.getFilters()); + } + + private getQuery(key: string, value: string) { + const groupToRequestMap: Record = { + Region: \\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\`{ Region { name } }\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\`, + Subregion: \\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\`{ Subregion(filter: { region: { name: "\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\${value}" } } ) { name } }\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\`, + Country: \\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\`{ Country(filter: { subregion: { name: "\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\${value}" } } ) { name capital } }\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\`, + }; + + return gql\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\` + \\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\${groupToRequestMap[key]} + \\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\`; + } + + private getData(filters: INovaFilters): Observable { + this.drillState = filters.drillstate?.value; + this.groupBy = filters.group?.value; + const group = this.groupBy[this.drillState.length]; + const isDrillUp = this.drillState.length < this.lastDrillState.length; + + this.lastDrillState = [...this.drillState]; + + if (!this.drillState.length) { + this.groupedDataHistory.length = 0; + } + + this.busy.next(true); + + if (this.cache && (isDrillUp || this.isHome())) { + return of(this.cache).pipe( + map((data) => data.data[group]), + finalize(() => this.busy.next(false)) + ); + } else { + return this.apollo + .use(APOLLO_API_NAMESPACE.COUNTRIES) + .query({ + query: this.getQuery( + group || this.leafGroup, + getLast(this.drillState) + ), + }) + .pipe( + tap( + (data) => + (this.cache = { + data: { ...this.cache?.data, ...data?.data }, + }) + ), + map((data) => data.data[group || this.leafGroup]), + finalize(() => this.busy.next(false)) + ); + } + } + + private getTransformedDataForGroup( + data: any, + group: string, + drillStateValue: string + ) { + const fallback: string = \\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\`No \\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\${group} for \\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\${drillStateValue}\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\`; + const dataArr = Object.values(data).map((val: any) => ({ + id: val.name || fallback, + label: val.name || fallback, + statuses: [ + { key: "state_ok", value: val.name?.length }, + { + key: "status_unreachable", + value: generateNumberUpTo(100000), + }, + { key: "status_warning", value: generateNumberUpTo(10000) }, + { key: "status_unknown", value: generateNumberUpTo(1000) }, + ], + })); + + return dataArr; + } + + private isHome(): boolean { + return this.drillState.length === 0; + } + + private isDrillDown(): boolean { + return this.drillState.length !== this.groupBy.length; + } + + private getOutput(data: any) { + if (this.isHome()) { + this.groupedDataHistory.length = 0; + } + + const lastHistoryValue = getLast(this.groupedDataHistory); + + if (!lastHistoryValue) { + return data; + } + + return lastHistoryValue[getLast(this.drillState)] || lastHistoryValue; + } +} + +@Component({ + selector: "drilldown-multi-request-widget-example", + templateUrl: "./drilldown-multi-request-widget-example.component.html", + styleUrls: ["./drilldown-multi-request-widget-example.component.less"], + standalone: false, +}) +export class DrilldownMultiRequestWidgetExampleComponent implements OnInit { + // This variable will hold all the data needed to define the layout and behavior of the widgets. + // Pass this to the dashboard component's dashboard input in the template. + public dashboard: IDashboard | undefined; + + // Angular gridster requires a configuration object even if it's empty. + // Pass this to the dashboard component's gridsterConfig input in the template. + public gridsterConfig: GridsterConfig = {}; + + // Boolean passed as an input to the dashboard. When true, widgets can be moved, resized, removed, or edited + public editMode: boolean = false; + + constructor( + // WidgetTypesService provides the widget's necessary structure information + private widgetTypesService: WidgetTypesService, + private providerRegistry: ProviderRegistryService, + private changeDetectorRef: ChangeDetectorRef + ) {} + + public ngOnInit(): void { + // this.prepareNovaDashboards(); + this.initializeDashboard(); + const widgetTemplate = this.widgetTypesService.getWidgetType( + "drilldown", + 1 + ); + this.widgetTypesService.setNode( + widgetTemplate, + "configurator", + WellKnownPathKey.DataSourceProviders, + [DrilldownDataSource.providerId] + ); + + // Registering the data source for injection into the KPI tile. + // Note: Each tile of a KPI widget is assigned its own instance of the data source + this.providerRegistry.setProviders({ + [DrilldownDataSource.providerId]: { + provide: DATA_SOURCE, + useClass: DrilldownDataSource, + // Any dependencies that need to be injected into the provider must be listed here + deps: [HttpClient, Apollo], + }, + }); + } + + /** Used for restoring widgets state */ + public reInitializeDashboard(): void { + // destroys the components and their providers so the dashboard can re init data + this.dashboard = undefined; + this.changeDetectorRef.detectChanges(); + + this.initializeDashboard(); + } + + public initializeDashboard(): void { + // We're using a static configuration object for this example, but this is where + // the widget's configuration could potentially be populated from a database + const drilldownWidget = widgetConfig; + const widgets: IWidgets = { + // Complete the widget with information coming from its type definition + [drilldownWidget.id]: + this.widgetTypesService.mergeWithWidgetType(drilldownWidget), + }; + + // Setting the widget dimensions and position (this is for gridster) + const positions: Record = { + [drilldownWidget.id]: { + cols: 10, + rows: 10, + y: 0, + x: 0, + }, + }; + + // Finally, assigning the variables we created above to the dashboard + this.dashboard = { positions, widgets }; + } +} + +const widgetConfig: IWidget = { + id: "drilldown", + type: "drilldown", + pizzagna: { + [PizzagnaLayer.Configuration]: { + header: { + properties: { + title: "Drilldown Widget", + subtitle: "Countries BY continent THEN currency", + }, + }, + [DEFAULT_PIZZAGNA_ROOT]: { + providers: { + [WellKnownProviders.DataSource]: { + // Setting the data source providerId for the tile with id "kpi1" + providerId: DrilldownDataSource.providerId, + properties: {}, + } as IProviderConfiguration, + }, + }, + listWidget: { + providers: { + [WellKnownProviders.Adapter]: { + providerId: NOVA_DRILLDOWN_DATASOURCE_ADAPTER, + properties: { + // widget + navigationBarId: "navigationBar", + componentId: "listWidget", + dataPath: "data", + + // adapter props + drillstate: [], + groups: ["Region", "Subregion"], + groupBy: ["Region", "Subregion"], + + // components + componentsConfig: { + group: { + componentType: + ListGroupItemComponent.lateLoadKey, + properties: { + dataFieldIds: { + id: "id", + label: "label", + statuses: "statuses", + }, + }, + itemProperties: { + canNavigate: true, + }, + }, + leaf: { + componentType: + ListLeafItemComponent.lateLoadKey, + properties: { + dataFieldIds: { + icon: "icon", + status: "icon_status", + detailedUrl: "capital", + label: "name", + }, + }, + itemProperties: { + canNavigate: false, + }, + }, + } as IDrilldownComponentsConfiguration, + }, + }, + }, + properties: { + configuration: { + // FORMAT: + // componentType: ListLeafItemComponent.lateLoadKey, + // properties: { + // dataFieldIds: { + // icon: "", + // status: "code", + // detailedUrl: "capital", + // label: "name", + // }, + // }, + // + } as IListWidgetConfiguration, + }, + }, + }, + }, +}; + +const getLast = (arr: any[]) => arr[arr.length - 1]; + +const generateNumberUpTo = (upperLimit: number): number => + Math.floor(Math.random() * upperLimit + 1); +\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\`, + "widget-types/drilldown/drilldown-widget/data-mock.ts": \\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\`// © 2022 SolarWinds Worldwide, LLC. All rights reserved. +// +// Permission is hereby granted, free of charge, to any person obtaining a copy +// of this software and associated documentation files (the "Software"), to +// deal in the Software without restriction, including without limitation the +// rights to use, copy, modify, merge, publish, distribute, sublicense, and/or +// sell copies of the Software, and to permit persons to whom the Software is +// furnished to do so, subject to the following conditions: +// +// The above copyright notice and this permission notice shall be included in +// all copies or substantial portions of the Software. +// +// THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR +// IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, +// FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE +// AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER +// LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, +// OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN +// THE SOFTWARE. + +import { IconStatus } from "@nova-ui/bits"; + +export const GRAPH_DATA_MOCK = { + data: { + countries: [ + { + name: "Andorra", + code: "AD", + icon: "virtual-host", + icon_status: IconStatus.Up, + capital: "Andorra la Vella", + continent: { + name: "Europe", + }, + currency: "EUR", + languages: [ + { + name: "Catalan", + }, + ], + url: "https://en.wikipedia.org/wiki/Andorra", + }, + { + name: "United Arab Emirates", + code: "AE", + icon: "virtual-host", + icon_status: IconStatus.Up, + capital: "Abu Dhabi", + continent: { + name: "Asia", + }, + currency: "AED", + languages: [ + { + name: "Arabic", + }, + ], + url: "https://en.wikipedia.org/wiki/United_Arab_Emirates", + }, + { + name: "Afghanistan", + code: "AF", + icon: "virtual-host", + icon_status: IconStatus.Up, + capital: "Kabul", + continent: { + name: "Asia", + }, + currency: "AFN", + languages: [ + { + name: "Pashto", + }, + { + name: "Uzbek", + }, + { + name: "Turkmen", + }, + ], + url: "https://en.wikipedia.org/wiki/Afghanistan", + }, + { + name: "Antigua and Barbuda", + code: "AG", + icon: "virtual-host", + icon_status: IconStatus.Up, + capital: "Saint John's", + continent: { + name: "North America", + }, + currency: "XCD", + languages: [ + { + name: "English", + }, + ], + url: "https://en.wikipedia.org/wiki/Antigua_and_Barbuda", + }, + { + name: "Anguilla", + code: "AI", + icon: "virtual-host", + icon_status: IconStatus.Up, + capital: "The Valley", + continent: { + name: "North America", + }, + currency: "XCD", + languages: [ + { + name: "English", + }, + ], + url: "https://en.wikipedia.org/wiki/Anguilla", + }, + { + name: "Albania", + code: "AL", + icon: "virtual-host", + icon_status: IconStatus.Up, + capital: "Tirana", + continent: { + name: "Europe", + }, + currency: "ALL", + languages: [ + { + name: "Albanian", + }, + ], + url: "https://en.wikipedia.org/wiki/Albania", + }, + { + name: "Armenia", + code: "AM", + icon: "virtual-host", + icon_status: IconStatus.Up, + capital: "Yerevan", + continent: { + name: "Asia", + }, + currency: "AMD", + languages: [ + { + name: "Armenian", + }, + { + name: "Russian", + }, + ], + url: "https://en.wikipedia.org/wiki/Armenia", + }, + { + name: "Angola", + code: "AO", + icon: "virtual-host", + icon_status: IconStatus.Up, + capital: "Luanda", + continent: { + name: "Africa", + }, + currency: "AOA", + languages: [ + { + name: "Portuguese", + }, + ], + }, + { + name: "Antarctica", + code: "AQ", + icon: "virtual-host", + icon_status: IconStatus.Up, + capital: null, + continent: { + name: "Antarctica", + }, + currency: null, + languages: [], + }, + { + name: "Argentina", + code: "AR", + icon: "virtual-host", + icon_status: IconStatus.Up, + capital: "Buenos Aires", + continent: { + name: "South America", + }, + currency: "ARS", + languages: [ + { + name: "Spanish", + }, + { + name: "Guarani", + }, + ], + }, + { + name: "American Samoa", + code: "AS", + icon: "virtual-host", + icon_status: IconStatus.Up, + capital: "Pago Pago", + continent: { + name: "Oceania", + }, + currency: "USD", + languages: [ + { + name: "English", + }, + { + name: "Samoan", + }, + ], + }, + { + name: "Austria", + code: "AT", + icon: "virtual-host", + icon_status: IconStatus.Up, + capital: "Vienna", + continent: { + name: "Europe", + }, + currency: "EUR", + languages: [ + { + name: "German", + }, + ], + }, + { + name: "Australia", + code: "AU", + icon: "virtual-host", + icon_status: IconStatus.Up, + capital: "Canberra", + continent: { + name: "Oceania", + }, + currency: "AUD", + languages: [ + { + name: "English", + }, + ], + }, + { + name: "Aruba", + code: "AW", + icon: "virtual-host", + icon_status: IconStatus.Up, + capital: "Oranjestad", + continent: { + name: "North America", + }, + currency: "AWG", + languages: [ + { + name: "Dutch", + }, + { + name: "Panjabi / Punjabi", + }, + ], + }, + { + name: "Åland", + code: "AX", + icon: "virtual-host", + icon_status: IconStatus.Up, + capital: "Mariehamn", + continent: { + name: "Europe", + }, + currency: "EUR", + languages: [ + { + name: "Swedish", + }, + ], + }, + { + name: "Azerbaijan", + code: "AZ", + icon: "virtual-host", + icon_status: IconStatus.Up, + capital: "Baku", + continent: { + name: "Asia", + }, + currency: "AZN", + languages: [ + { + name: "Azerbaijani", + }, + ], + }, + { + name: "Bosnia and Herzegovina", + code: "BA", + icon: "virtual-host", + icon_status: IconStatus.Up, + capital: "Sarajevo", + continent: { + name: "Europe", + }, + currency: "BAM", + languages: [ + { + name: "Bosnian", + }, + { + name: "Croatian", + }, + { + name: "Serbian", + }, + ], + }, + { + name: "Barbados", + code: "BB", + icon: "virtual-host", + icon_status: IconStatus.Up, + capital: "Bridgetown", + continent: { + name: "North America", + }, + currency: "BBD", + languages: [ + { + name: "English", + }, + ], + }, + { + name: "Bangladesh", + code: "BD", + icon: "virtual-host", + icon_status: IconStatus.Up, + capital: "Dhaka", + continent: { + name: "Asia", + }, + currency: "BDT", + languages: [ + { + name: "Bengali", + }, + ], + }, + { + name: "Belgium", + code: "BE", + icon: "virtual-host", + icon_status: IconStatus.Up, + capital: "Brussels", + continent: { + name: "Europe", + }, + currency: "EUR", + languages: [ + { + name: "Dutch", + }, + { + name: "French", + }, + { + name: "German", + }, + ], + }, + { + name: "Burkina Faso", + code: "BF", + icon: "virtual-host", + icon_status: IconStatus.Up, + capital: "Ouagadougou", + continent: { + name: "Africa", + }, + currency: "XOF", + languages: [ + { + name: "French", + }, + { + name: "Peul", + }, + ], + }, + { + name: "Bulgaria", + code: "BG", + icon: "virtual-host", + icon_status: IconStatus.Up, + capital: "Sofia", + continent: { + name: "Europe", + }, + currency: "BGN", + languages: [ + { + name: "Bulgarian", + }, + ], + }, + { + name: "Bahrain", + code: "BH", + icon: "virtual-host", + icon_status: IconStatus.Up, + capital: "Manama", + continent: { + name: "Asia", + }, + currency: "BHD", + languages: [ + { + name: "Arabic", + }, + ], + }, + { + name: "Burundi", + code: "BI", + icon: "virtual-host", + icon_status: IconStatus.Up, + capital: "Bujumbura", + continent: { + name: "Africa", + }, + currency: "BIF", + languages: [ + { + name: "French", + }, + { + name: "Kirundi", + }, + ], + }, + { + name: "Benin", + code: "BJ", + icon: "virtual-host", + icon_status: IconStatus.Up, + capital: "Porto-Novo", + continent: { + name: "Africa", + }, + currency: "XOF", + languages: [ + { + name: "French", + }, + ], + }, + { + name: "Saint Barthélemy", + code: "BL", + icon: "virtual-host", + icon_status: IconStatus.Up, + capital: "Gustavia", + continent: { + name: "North America", + }, + currency: "EUR", + languages: [ + { + name: "French", + }, + ], + }, + { + name: "Bermuda", + code: "BM", + icon: "virtual-host", + icon_status: IconStatus.Up, + capital: "Hamilton", + continent: { + name: "North America", + }, + currency: "BMD", + languages: [ + { + name: "English", + }, + ], + }, + { + name: "Brunei", + code: "BN", + icon: "virtual-host", + icon_status: IconStatus.Up, + capital: "Bandar Seri Begawan", + continent: { + name: "Asia", + }, + currency: "BND", + languages: [ + { + name: "Malay", + }, + ], + }, + { + name: "Bolivia", + code: "BO", + icon: "virtual-host", + icon_status: IconStatus.Up, + capital: "Sucre", + continent: { + name: "South America", + }, + currency: "BOB,BOV", + languages: [ + { + name: "Spanish", + }, + { + name: "Aymara", + }, + { + name: "Quechua", + }, + ], + }, + { + name: "Bonaire", + code: "BQ", + icon: "virtual-host", + icon_status: IconStatus.Up, + capital: "Kralendijk", + continent: { + name: "North America", + }, + currency: "USD", + languages: [ + { + name: "Dutch", + }, + ], + }, + { + name: "Brazil", + code: "BR", + icon: "virtual-host", + icon_status: IconStatus.Up, + capital: "Brasília", + continent: { + name: "South America", + }, + currency: "BRL", + languages: [ + { + name: "Portuguese", + }, + ], + }, + { + name: "Bahamas", + code: "BS", + icon: "virtual-host", + icon_status: IconStatus.Up, + capital: "Nassau", + continent: { + name: "North America", + }, + currency: "BSD", + languages: [ + { + name: "English", + }, + ], + }, + { + name: "Bhutan", + code: "BT", + icon: "virtual-host", + icon_status: IconStatus.Up, + capital: "Thimphu", + continent: { + name: "Asia", + }, + currency: "BTN,INR", + languages: [ + { + name: "Dzongkha", + }, + ], + }, + { + name: "Bouvet Island", + code: "BV", + icon: "virtual-host", + icon_status: IconStatus.Up, + capital: null, + continent: { + name: "Antarctica", + }, + currency: "NOK", + languages: [ + { + name: "Norwegian", + }, + { + name: "Norwegian Bokmål", + }, + { + name: "Norwegian Nynorsk", + }, + ], + }, + { + name: "Botswana", + code: "BW", + icon: "virtual-host", + icon_status: IconStatus.Up, + capital: "Gaborone", + continent: { + name: "Africa", + }, + currency: "BWP", + languages: [ + { + name: "English", + }, + { + name: "Tswana", + }, + ], + }, + { + name: "Belarus", + code: "BY", + icon: "virtual-host", + icon_status: IconStatus.Up, + capital: "Minsk", + continent: { + name: "Europe", + }, + currency: "BYN", + languages: [ + { + name: "Belarusian", + }, + { + name: "Russian", + }, + ], + }, + { + name: "Belize", + code: "BZ", + icon: "virtual-host", + icon_status: IconStatus.Up, + capital: "Belmopan", + continent: { + name: "North America", + }, + currency: "BZD", + languages: [ + { + name: "English", + }, + { + name: "Spanish", + }, + ], + }, + { + name: "Canada", + code: "CA", + icon: "virtual-host", + icon_status: IconStatus.Up, + capital: "Ottawa", + continent: { + name: "North America", + }, + currency: "CAD", + languages: [ + { + name: "English", + }, + { + name: "French", + }, + ], + }, + { + name: "Cocos [Keeling] Islands", + code: "CC", + icon: "virtual-host", + icon_status: IconStatus.Up, + capital: "West Island", + continent: { + name: "Asia", + }, + currency: "AUD", + languages: [ + { + name: "English", + }, + ], + }, + { + name: "Democratic Republic of the Congo", + code: "CD", + icon: "virtual-host", + icon_status: IconStatus.Up, + capital: "Kinshasa", + continent: { + name: "Africa", + }, + currency: "CDF", + languages: [ + { + name: "French", + }, + { + name: "Lingala", + }, + { + name: "Kongo", + }, + { + name: "Swahili", + }, + { + name: "Luba-Katanga", + }, + ], + }, + { + name: "Central African Republic", + code: "CF", + icon: "virtual-host", + icon_status: IconStatus.Up, + capital: "Bangui", + continent: { + name: "Africa", + }, + currency: "XAF", + languages: [ + { + name: "French", + }, + { + name: "Sango", + }, + ], + }, + { + name: "Republic of the Congo", + code: "CG", + icon: "virtual-host", + icon_status: IconStatus.Up, + capital: "Brazzaville", + continent: { + name: "Africa", + }, + currency: "XAF", + languages: [ + { + name: "French", + }, + { + name: "Lingala", + }, + ], + }, + { + name: "Switzerland", + code: "CH", + icon: "virtual-host", + icon_status: IconStatus.Up, + capital: "Bern", + continent: { + name: "Europe", + }, + currency: "CHE,CHF,CHW", + languages: [ + { + name: "German", + }, + { + name: "French", + }, + { + name: "Italian", + }, + ], + }, + { + name: "Ivory Coast", + code: "CI", + icon: "virtual-host", + icon_status: IconStatus.Up, + capital: "Yamoussoukro", + continent: { + name: "Africa", + }, + currency: "XOF", + languages: [ + { + name: "French", + }, + ], + }, + { + name: "Cook Islands", + code: "CK", + icon: "virtual-host", + icon_status: IconStatus.Up, + capital: "Avarua", + continent: { + name: "Oceania", + }, + currency: "NZD", + languages: [ + { + name: "English", + }, + ], + }, + { + name: "Chile", + code: "CL", + icon: "virtual-host", + icon_status: IconStatus.Up, + capital: "Santiago", + continent: { + name: "South America", + }, + currency: "CLF,CLP", + languages: [ + { + name: "Spanish", + }, + ], + }, + { + name: "Cameroon", + code: "CM", + icon: "virtual-host", + icon_status: IconStatus.Up, + capital: "Yaoundé", + continent: { + name: "Africa", + }, + currency: "XAF", + languages: [ + { + name: "English", + }, + { + name: "French", + }, + ], + }, + { + name: "China", + code: "CN", + icon: "virtual-host", + icon_status: IconStatus.Up, + capital: "Beijing", + continent: { + name: "Asia", + }, + currency: "CNY", + languages: [ + { + name: "Chinese", + }, + ], + }, + { + name: "Colombia", + code: "CO", + icon: "virtual-host", + icon_status: IconStatus.Up, + capital: "Bogotá", + continent: { + name: "South America", + }, + currency: "COP", + languages: [ + { + name: "Spanish", + }, + ], + }, + { + name: "Costa Rica", + code: "CR", + icon: "virtual-host", + icon_status: IconStatus.Up, + capital: "San José", + continent: { + name: "North America", + }, + currency: "CRC", + languages: [ + { + name: "Spanish", + }, + ], + }, + { + name: "Cuba", + code: "CU", + icon: "virtual-host", + icon_status: IconStatus.Up, + capital: "Havana", + continent: { + name: "North America", + }, + currency: "CUC,CUP", + languages: [ + { + name: "Spanish", + }, + ], + }, + { + name: "Cape Verde", + code: "CV", + icon: "virtual-host", + icon_status: IconStatus.Up, + capital: "Praia", + continent: { + name: "Africa", + }, + currency: "CVE", + languages: [ + { + name: "Portuguese", + }, + ], + }, + { + name: "Curacao", + code: "CW", + icon: "virtual-host", + icon_status: IconStatus.Up, + capital: "Willemstad", + continent: { + name: "North America", + }, + currency: "ANG", + languages: [ + { + name: "Dutch", + }, + { + name: "Panjabi / Punjabi", + }, + { + name: "English", + }, + ], + }, + { + name: "Christmas Island", + code: "CX", + icon: "virtual-host", + icon_status: IconStatus.Up, + capital: "Flying Fish Cove", + continent: { + name: "Asia", + }, + currency: "AUD", + languages: [ + { + name: "English", + }, + ], + }, + { + name: "Cyprus", + code: "CY", + icon: "virtual-host", + icon_status: IconStatus.Up, + capital: "Nicosia", + continent: { + name: "Europe", + }, + currency: "EUR", + languages: [ + { + name: "Greek", + }, + { + name: "Turkish", + }, + { + name: "Armenian", + }, + ], + }, + { + name: "Czech Republic", + code: "CZ", + icon: "virtual-host", + icon_status: IconStatus.Up, + capital: "Prague", + continent: { + name: "Europe", + }, + currency: "CZK", + languages: [ + { + name: "Czech", + }, + { + name: "Slovak", + }, + ], + }, + { + name: "Germany", + code: "DE", + icon: "virtual-host", + icon_status: IconStatus.Up, + capital: "Berlin", + continent: { + name: "Europe", + }, + currency: "EUR", + languages: [ + { + name: "German", + }, + ], + }, + { + name: "Djibouti", + code: "DJ", + icon: "virtual-host", + icon_status: IconStatus.Up, + capital: "Djibouti", + continent: { + name: "Africa", + }, + currency: "DJF", + languages: [ + { + name: "French", + }, + { + name: "Arabic", + }, + ], + }, + { + name: "Denmark", + code: "DK", + icon: "virtual-host", + icon_status: IconStatus.Up, + capital: "Copenhagen", + continent: { + name: "Europe", + }, + currency: "DKK", + languages: [ + { + name: "Danish", + }, + ], + }, + { + name: "Dominica", + code: "DM", + icon: "virtual-host", + icon_status: IconStatus.Up, + capital: "Roseau", + continent: { + name: "North America", + }, + currency: "XCD", + languages: [ + { + name: "English", + }, + ], + }, + { + name: "Dominican Republic", + code: "DO", + icon: "virtual-host", + icon_status: IconStatus.Up, + capital: "Santo Domingo", + continent: { + name: "North America", + }, + currency: "DOP", + languages: [ + { + name: "Spanish", + }, + ], + }, + { + name: "Algeria", + code: "DZ", + icon: "virtual-host", + icon_status: IconStatus.Up, + capital: "Algiers", + continent: { + name: "Africa", + }, + currency: "DZD", + languages: [ + { + name: "Arabic", + }, + ], + }, + { + name: "Ecuador", + code: "EC", + icon: "virtual-host", + icon_status: IconStatus.Up, + capital: "Quito", + continent: { + name: "South America", + }, + currency: "USD", + languages: [ + { + name: "Spanish", + }, + ], + }, + { + name: "Estonia", + code: "EE", + icon: "virtual-host", + icon_status: IconStatus.Up, + capital: "Tallinn", + continent: { + name: "Europe", + }, + currency: "EUR", + languages: [ + { + name: "Estonian", + }, + ], + }, + { + name: "Egypt", + code: "EG", + icon: "virtual-host", + icon_status: IconStatus.Up, + capital: "Cairo", + continent: { + name: "Africa", + }, + currency: "EGP", + languages: [ + { + name: "Arabic", + }, + ], + }, + { + name: "Western Sahara", + code: "EH", + icon: "virtual-host", + icon_status: IconStatus.Up, + capital: "El Aaiún", + continent: { + name: "Africa", + }, + currency: "MAD,DZD,MRU", + languages: [ + { + name: "Spanish", + }, + ], + }, + { + name: "Eritrea", + code: "ER", + icon: "virtual-host", + icon_status: IconStatus.Up, + capital: "Asmara", + continent: { + name: "Africa", + }, + currency: "ERN", + languages: [ + { + name: "Tigrinya", + }, + { + name: "Arabic", + }, + { + name: "English", + }, + ], + }, + { + name: "Spain", + code: "ES", + icon: "virtual-host", + icon_status: IconStatus.Up, + capital: "Madrid", + continent: { + name: "Europe", + }, + currency: "EUR", + languages: [ + { + name: "Spanish", + }, + { + name: "Basque", + }, + { + name: "Catalan", + }, + { + name: "Galician", + }, + { + name: "Occitan", + }, + ], + }, + { + name: "Ethiopia", + code: "ET", + icon: "virtual-host", + icon_status: IconStatus.Up, + capital: "Addis Ababa", + continent: { + name: "Africa", + }, + currency: "ETB", + languages: [ + { + name: "Amharic", + }, + ], + }, + { + name: "Finland", + code: "FI", + icon: "virtual-host", + icon_status: IconStatus.Up, + capital: "Helsinki", + continent: { + name: "Europe", + }, + currency: "EUR", + languages: [ + { + name: "Finnish", + }, + { + name: "Swedish", + }, + ], + }, + { + name: "Fiji", + code: "FJ", + icon: "virtual-host", + icon_status: IconStatus.Up, + capital: "Suva", + continent: { + name: "Oceania", + }, + currency: "FJD", + languages: [ + { + name: "English", + }, + { + name: "Fijian", + }, + { + name: "Hindi", + }, + { + name: "Urdu", + }, + ], + }, + { + name: "Falkland Islands", + code: "FK", + icon: "virtual-host", + icon_status: IconStatus.Up, + capital: "Stanley", + continent: { + name: "South America", + }, + currency: "FKP", + languages: [ + { + name: "English", + }, + ], + }, + { + name: "Micronesia", + code: "FM", + icon: "virtual-host", + icon_status: IconStatus.Up, + capital: "Palikir", + continent: { + name: "Oceania", + }, + currency: "USD", + languages: [ + { + name: "English", + }, + ], + }, + { + name: "Faroe Islands", + code: "FO", + icon: "virtual-host", + icon_status: IconStatus.Up, + capital: "Tórshavn", + continent: { + name: "Europe", + }, + currency: "DKK", + languages: [ + { + name: "Faroese", + }, + ], + }, + { + name: "France", + code: "FR", + icon: "virtual-host", + icon_status: IconStatus.Up, + capital: "Paris", + continent: { + name: "Europe", + }, + currency: "EUR", + languages: [ + { + name: "French", + }, + ], + }, + { + name: "Gabon", + code: "GA", + icon: "virtual-host", + icon_status: IconStatus.Up, + capital: "Libreville", + continent: { + name: "Africa", + }, + currency: "XAF", + languages: [ + { + name: "French", + }, + ], + }, + { + name: "United Kingdom", + code: "GB", + icon: "virtual-host", + icon_status: IconStatus.Up, + capital: "London", + continent: { + name: "Europe", + }, + currency: "GBP", + languages: [ + { + name: "English", + }, + ], + }, + { + name: "Grenada", + code: "GD", + icon: "virtual-host", + icon_status: IconStatus.Up, + capital: "St. George's", + continent: { + name: "North America", + }, + currency: "XCD", + languages: [ + { + name: "English", + }, + ], + }, + { + name: "Georgia", + code: "GE", + icon: "virtual-host", + icon_status: IconStatus.Up, + capital: "Tbilisi", + continent: { + name: "Asia", + }, + currency: "GEL", + languages: [ + { + name: "Georgian", + }, + ], + }, + { + name: "French Guiana", + code: "GF", + icon: "virtual-host", + icon_status: IconStatus.Up, + capital: "Cayenne", + continent: { + name: "South America", + }, + currency: "EUR", + languages: [ + { + name: "French", + }, + ], + }, + { + name: "Guernsey", + code: "GG", + icon: "virtual-host", + icon_status: IconStatus.Up, + capital: "St. Peter Port", + continent: { + name: "Europe", + }, + currency: "GBP", + languages: [ + { + name: "English", + }, + { + name: "French", + }, + ], + }, + { + name: "Ghana", + code: "GH", + icon: "virtual-host", + icon_status: IconStatus.Up, + capital: "Accra", + continent: { + name: "Africa", + }, + currency: "GHS", + languages: [ + { + name: "English", + }, + ], + }, + { + name: "Gibraltar", + code: "GI", + icon: "virtual-host", + icon_status: IconStatus.Up, + capital: "Gibraltar", + continent: { + name: "Europe", + }, + currency: "GIP", + languages: [ + { + name: "English", + }, + ], + }, + { + name: "Greenland", + code: "GL", + icon: "virtual-host", + icon_status: IconStatus.Up, + capital: "Nuuk", + continent: { + name: "North America", + }, + currency: "DKK", + languages: [ + { + name: "Greenlandic", + }, + ], + }, + { + name: "Gambia", + code: "GM", + icon: "virtual-host", + icon_status: IconStatus.Up, + capital: "Banjul", + continent: { + name: "Africa", + }, + currency: "GMD", + languages: [ + { + name: "English", + }, + ], + }, + { + name: "Guinea", + code: "GN", + icon: "virtual-host", + icon_status: IconStatus.Up, + capital: "Conakry", + continent: { + name: "Africa", + }, + currency: "GNF", + languages: [ + { + name: "French", + }, + { + name: "Peul", + }, + ], + }, + { + name: "Guadeloupe", + code: "GP", + icon: "virtual-host", + icon_status: IconStatus.Up, + capital: "Basse-Terre", + continent: { + name: "North America", + }, + currency: "EUR", + languages: [ + { + name: "French", + }, + ], + }, + { + name: "Equatorial Guinea", + code: "GQ", + icon: "virtual-host", + icon_status: IconStatus.Up, + capital: "Malabo", + continent: { + name: "Africa", + }, + currency: "XAF", + languages: [ + { + name: "Spanish", + }, + { + name: "French", + }, + ], + }, + { + name: "Greece", + code: "GR", + icon: "virtual-host", + icon_status: IconStatus.Up, + capital: "Athens", + continent: { + name: "Europe", + }, + currency: "EUR", + languages: [ + { + name: "Greek", + }, + ], + }, + { + name: "South Georgia and the South Sandwich Islands", + code: "GS", + icon: "virtual-host", + icon_status: IconStatus.Up, + capital: "King Edward Point", + continent: { + name: "Antarctica", + }, + currency: "GBP", + languages: [ + { + name: "English", + }, + ], + }, + { + name: "Guatemala", + code: "GT", + icon: "virtual-host", + icon_status: IconStatus.Up, + capital: "Guatemala City", + continent: { + name: "North America", + }, + currency: "GTQ", + languages: [ + { + name: "Spanish", + }, + ], + }, + { + name: "Guam", + code: "GU", + icon: "virtual-host", + icon_status: IconStatus.Up, + capital: "Hagåtña", + continent: { + name: "Oceania", + }, + currency: "USD", + languages: [ + { + name: "English", + }, + { + name: "Chamorro", + }, + { + name: "Spanish", + }, + ], + }, + { + name: "Guinea-Bissau", + code: "GW", + icon: "virtual-host", + icon_status: IconStatus.Up, + capital: "Bissau", + continent: { + name: "Africa", + }, + currency: "XOF", + languages: [ + { + name: "Portuguese", + }, + ], + }, + { + name: "Guyana", + code: "GY", + icon: "virtual-host", + icon_status: IconStatus.Up, + capital: "Georgetown", + continent: { + name: "South America", + }, + currency: "GYD", + languages: [ + { + name: "English", + }, + ], + }, + { + name: "Hong Kong", + code: "HK", + icon: "virtual-host", + icon_status: IconStatus.Up, + capital: "City of Victoria", + continent: { + name: "Asia", + }, + currency: "HKD", + languages: [ + { + name: "Chinese", + }, + { + name: "English", + }, + ], + }, + { + name: "Heard Island and McDonald Islands", + code: "HM", + icon: "virtual-host", + icon_status: IconStatus.Up, + capital: null, + continent: { + name: "Antarctica", + }, + currency: "AUD", + languages: [ + { + name: "English", + }, + ], + }, + { + name: "Honduras", + code: "HN", + icon: "virtual-host", + icon_status: IconStatus.Up, + capital: "Tegucigalpa", + continent: { + name: "North America", + }, + currency: "HNL", + languages: [ + { + name: "Spanish", + }, + ], + }, + { + name: "Croatia", + code: "HR", + icon: "virtual-host", + icon_status: IconStatus.Up, + capital: "Zagreb", + continent: { + name: "Europe", + }, + currency: "HRK", + languages: [ + { + name: "Croatian", + }, + ], + }, + { + name: "Haiti", + code: "HT", + icon: "virtual-host", + icon_status: IconStatus.Up, + capital: "Port-au-Prince", + continent: { + name: "North America", + }, + currency: "HTG,USD", + languages: [ + { + name: "French", + }, + { + name: "Haitian", + }, + ], + }, + { + name: "Hungary", + code: "HU", + icon: "virtual-host", + icon_status: IconStatus.Up, + capital: "Budapest", + continent: { + name: "Europe", + }, + currency: "HUF", + languages: [ + { + name: "Hungarian", + }, + ], + }, + { + name: "Indonesia", + code: "ID", + icon: "virtual-host", + icon_status: IconStatus.Up, + capital: "Jakarta", + continent: { + name: "Asia", + }, + currency: "IDR", + languages: [ + { + name: "Indonesian", + }, + ], + }, + { + name: "Ireland", + code: "IE", + icon: "virtual-host", + icon_status: IconStatus.Up, + capital: "Dublin", + continent: { + name: "Europe", + }, + currency: "EUR", + languages: [ + { + name: "Irish", + }, + { + name: "English", + }, + ], + }, + { + name: "Israel", + code: "IL", + icon: "virtual-host", + icon_status: IconStatus.Up, + capital: "Jerusalem", + continent: { + name: "Asia", + }, + currency: "ILS", + languages: [ + { + name: "Hebrew", + }, + { + name: "Arabic", + }, + ], + }, + { + name: "Isle of Man", + code: "IM", + icon: "virtual-host", + icon_status: IconStatus.Up, + capital: "Douglas", + continent: { + name: "Europe", + }, + currency: "GBP", + languages: [ + { + name: "English", + }, + { + name: "Manx", + }, + ], + }, + { + name: "India", + code: "IN", + icon: "virtual-host", + icon_status: IconStatus.Up, + capital: "New Delhi", + continent: { + name: "Asia", + }, + currency: "INR", + languages: [ + { + name: "Hindi", + }, + { + name: "English", + }, + ], + }, + { + name: "British Indian Ocean Territory", + code: "IO", + icon: "virtual-host", + icon_status: IconStatus.Up, + capital: "Diego Garcia", + continent: { + name: "Asia", + }, + currency: "USD", + languages: [ + { + name: "English", + }, + ], + }, + { + name: "Iraq", + code: "IQ", + icon: "virtual-host", + icon_status: IconStatus.Up, + capital: "Baghdad", + continent: { + name: "Asia", + }, + currency: "IQD", + languages: [ + { + name: "Arabic", + }, + { + name: "Kurdish", + }, + ], + }, + { + name: "Iran", + code: "IR", + icon: "virtual-host", + icon_status: IconStatus.Up, + capital: "Tehran", + continent: { + name: "Asia", + }, + currency: "IRR", + languages: [ + { + name: "Persian", + }, + ], + }, + { + name: "Iceland", + code: "IS", + icon: "virtual-host", + icon_status: IconStatus.Up, + capital: "Reykjavik", + continent: { + name: "Europe", + }, + currency: "ISK", + languages: [ + { + name: "Icelandic", + }, + ], + }, + { + name: "Italy", + code: "IT", + icon: "virtual-host", + icon_status: IconStatus.Up, + capital: "Rome", + continent: { + name: "Europe", + }, + currency: "EUR", + languages: [ + { + name: "Italian", + }, + ], + }, + { + name: "Jersey", + code: "JE", + icon: "virtual-host", + icon_status: IconStatus.Up, + capital: "Saint Helier", + continent: { + name: "Europe", + }, + currency: "GBP", + languages: [ + { + name: "English", + }, + { + name: "French", + }, + ], + }, + { + name: "Jamaica", + code: "JM", + icon: "virtual-host", + icon_status: IconStatus.Up, + capital: "Kingston", + continent: { + name: "North America", + }, + currency: "JMD", + languages: [ + { + name: "English", + }, + ], + }, + { + name: "Jordan", + code: "JO", + icon: "virtual-host", + icon_status: IconStatus.Up, + capital: "Amman", + continent: { + name: "Asia", + }, + currency: "JOD", + languages: [ + { + name: "Arabic", + }, + ], + }, + { + name: "Japan", + code: "JP", + icon: "virtual-host", + icon_status: IconStatus.Up, + capital: "Tokyo", + continent: { + name: "Asia", + }, + currency: "JPY", + languages: [ + { + name: "Japanese", + }, + ], + }, + { + name: "Kenya", + code: "KE", + icon: "virtual-host", + icon_status: IconStatus.Up, + capital: "Nairobi", + continent: { + name: "Africa", + }, + currency: "KES", + languages: [ + { + name: "English", + }, + { + name: "Swahili", + }, + ], + }, + { + name: "Kyrgyzstan", + code: "KG", + icon: "virtual-host", + icon_status: IconStatus.Up, + capital: "Bishkek", + continent: { + name: "Asia", + }, + currency: "KGS", + languages: [ + { + name: "Kirghiz", + }, + { + name: "Russian", + }, + ], + }, + { + name: "Cambodia", + code: "KH", + icon: "virtual-host", + icon_status: IconStatus.Up, + capital: "Phnom Penh", + continent: { + name: "Asia", + }, + currency: "KHR", + languages: [ + { + name: "Cambodian", + }, + ], + }, + { + name: "Kiribati", + code: "KI", + icon: "virtual-host", + icon_status: IconStatus.Up, + capital: "South Tarawa", + continent: { + name: "Oceania", + }, + currency: "AUD", + languages: [ + { + name: "English", + }, + ], + }, + { + name: "Comoros", + code: "KM", + icon: "virtual-host", + icon_status: IconStatus.Up, + capital: "Moroni", + continent: { + name: "Africa", + }, + currency: "KMF", + languages: [ + { + name: "Arabic", + }, + { + name: "French", + }, + ], + }, + { + name: "Saint Kitts and Nevis", + code: "KN", + icon: "virtual-host", + icon_status: IconStatus.Up, + capital: "Basseterre", + continent: { + name: "North America", + }, + currency: "XCD", + languages: [ + { + name: "English", + }, + ], + }, + { + name: "North Korea", + code: "KP", + icon: "virtual-host", + icon_status: IconStatus.Up, + capital: "Pyongyang", + continent: { + name: "Asia", + }, + currency: "KPW", + languages: [ + { + name: "Korean", + }, + ], + }, + { + name: "South Korea", + code: "KR", + icon: "virtual-host", + icon_status: IconStatus.Up, + capital: "Seoul", + continent: { + name: "Asia", + }, + currency: "KRW", + languages: [ + { + name: "Korean", + }, + ], + }, + { + name: "Kuwait", + code: "KW", + icon: "virtual-host", + icon_status: IconStatus.Up, + capital: "Kuwait City", + continent: { + name: "Asia", + }, + currency: "KWD", + languages: [ + { + name: "Arabic", + }, + ], + }, + { + name: "Cayman Islands", + code: "KY", + icon: "virtual-host", + icon_status: IconStatus.Up, + capital: "George Town", + continent: { + name: "North America", + }, + currency: "KYD", + languages: [ + { + name: "English", + }, + ], + }, + { + name: "Kazakhstan", + code: "KZ", + icon: "virtual-host", + icon_status: IconStatus.Up, + capital: "Astana", + continent: { + name: "Asia", + }, + currency: "KZT", + languages: [ + { + name: "Kazakh", + }, + { + name: "Russian", + }, + ], + }, + { + name: "Laos", + code: "LA", + icon: "virtual-host", + icon_status: IconStatus.Up, + capital: "Vientiane", + continent: { + name: "Asia", + }, + currency: "LAK", + languages: [ + { + name: "Laotian", + }, + ], + }, + { + name: "Lebanon", + code: "LB", + icon: "virtual-host", + icon_status: IconStatus.Up, + capital: "Beirut", + continent: { + name: "Asia", + }, + currency: "LBP", + languages: [ + { + name: "Arabic", + }, + { + name: "French", + }, + ], + }, + { + name: "Saint Lucia", + code: "LC", + icon: "virtual-host", + icon_status: IconStatus.Up, + capital: "Castries", + continent: { + name: "North America", + }, + currency: "XCD", + languages: [ + { + name: "English", + }, + ], + }, + { + name: "Liechtenstein", + code: "LI", + icon: "virtual-host", + icon_status: IconStatus.Up, + capital: "Vaduz", + continent: { + name: "Europe", + }, + currency: "CHF", + languages: [ + { + name: "German", + }, + ], + }, + { + name: "Sri Lanka", + code: "LK", + icon: "virtual-host", + icon_status: IconStatus.Up, + capital: "Colombo", + continent: { + name: "Asia", + }, + currency: "LKR", + languages: [ + { + name: "Sinhalese", + }, + { + name: "Tamil", + }, + ], + }, + { + name: "Liberia", + code: "LR", + icon: "virtual-host", + icon_status: IconStatus.Up, + capital: "Monrovia", + continent: { + name: "Africa", + }, + currency: "LRD", + languages: [ + { + name: "English", + }, + ], + }, + { + name: "Lesotho", + code: "LS", + icon: "virtual-host", + icon_status: IconStatus.Up, + capital: "Maseru", + continent: { + name: "Africa", + }, + currency: "LSL,ZAR", + languages: [ + { + name: "English", + }, + { + name: "Southern Sotho", + }, + ], + }, + { + name: "Lithuania", + code: "LT", + icon: "virtual-host", + icon_status: IconStatus.Up, + capital: "Vilnius", + continent: { + name: "Europe", + }, + currency: "EUR", + languages: [ + { + name: "Lithuanian", + }, + ], + }, + { + name: "Luxembourg", + code: "LU", + icon: "virtual-host", + icon_status: IconStatus.Up, + capital: "Luxembourg", + continent: { + name: "Europe", + }, + currency: "EUR", + languages: [ + { + name: "French", + }, + { + name: "German", + }, + { + name: "Luxembourgish", + }, + ], + }, + { + name: "Latvia", + code: "LV", + icon: "virtual-host", + icon_status: IconStatus.Up, + capital: "Riga", + continent: { + name: "Europe", + }, + currency: "EUR", + languages: [ + { + name: "Latvian", + }, + ], + }, + { + name: "Libya", + code: "LY", + icon: "virtual-host", + icon_status: IconStatus.Up, + capital: "Tripoli", + continent: { + name: "Africa", + }, + currency: "LYD", + languages: [ + { + name: "Arabic", + }, + ], + }, + { + name: "Morocco", + code: "MA", + icon: "virtual-host", + icon_status: IconStatus.Up, + capital: "Rabat", + continent: { + name: "Africa", + }, + currency: "MAD", + languages: [ + { + name: "Arabic", + }, + ], + }, + { + name: "Monaco", + code: "MC", + icon: "virtual-host", + icon_status: IconStatus.Up, + capital: "Monaco", + continent: { + name: "Europe", + }, + currency: "EUR", + languages: [ + { + name: "French", + }, + ], + }, + { + name: "Moldova", + code: "MD", + icon: "virtual-host", + icon_status: IconStatus.Up, + capital: "Chișinău", + continent: { + name: "Europe", + }, + currency: "MDL", + languages: [ + { + name: "Romanian", + }, + ], + }, + { + name: "Montenegro", + code: "ME", + icon: "virtual-host", + icon_status: IconStatus.Up, + capital: "Podgorica", + continent: { + name: "Europe", + }, + currency: "EUR", + languages: [ + { + name: "Serbian", + }, + { + name: "Bosnian", + }, + { + name: "Albanian", + }, + { + name: "Croatian", + }, + ], + }, + { + name: "Saint Martin", + code: "MF", + icon: "virtual-host", + icon_status: IconStatus.Up, + capital: "Marigot", + continent: { + name: "North America", + }, + currency: "EUR", + languages: [ + { + name: "English", + }, + { + name: "French", + }, + { + name: "Dutch", + }, + ], + }, + { + name: "Madagascar", + code: "MG", + icon: "virtual-host", + icon_status: IconStatus.Up, + capital: "Antananarivo", + continent: { + name: "Africa", + }, + currency: "MGA", + languages: [ + { + name: "French", + }, + { + name: "Malagasy", + }, + ], + }, + { + name: "Marshall Islands", + code: "MH", + icon: "virtual-host", + icon_status: IconStatus.Up, + capital: "Majuro", + continent: { + name: "Oceania", + }, + currency: "USD", + languages: [ + { + name: "English", + }, + { + name: "Marshallese", + }, + ], + }, + { + name: "North Macedonia", + code: "MK", + icon: "virtual-host", + icon_status: IconStatus.Up, + capital: "Skopje", + continent: { + name: "Europe", + }, + currency: "MKD", + languages: [ + { + name: "Macedonian", + }, + ], + }, + { + name: "Mali", + code: "ML", + icon: "virtual-host", + icon_status: IconStatus.Up, + capital: "Bamako", + continent: { + name: "Africa", + }, + currency: "XOF", + languages: [ + { + name: "French", + }, + ], + }, + { + name: "Myanmar [Burma]", + code: "MM", + icon: "virtual-host", + icon_status: IconStatus.Up, + capital: "Naypyidaw", + continent: { + name: "Asia", + }, + currency: "MMK", + languages: [ + { + name: "Burmese", + }, + ], + }, + { + name: "Mongolia", + code: "MN", + icon: "virtual-host", + icon_status: IconStatus.Up, + capital: "Ulan Bator", + continent: { + name: "Asia", + }, + currency: "MNT", + languages: [ + { + name: "Mongolian", + }, + ], + }, + { + name: "Macao", + code: "MO", + icon: "virtual-host", + icon_status: IconStatus.Up, + capital: null, + continent: { + name: "Asia", + }, + currency: "MOP", + languages: [ + { + name: "Chinese", + }, + { + name: "Portuguese", + }, + ], + }, + { + name: "Northern Mariana Islands", + code: "MP", + icon: "virtual-host", + icon_status: IconStatus.Up, + capital: "Saipan", + continent: { + name: "Oceania", + }, + currency: "USD", + languages: [ + { + name: "English", + }, + { + name: "Chamorro", + }, + ], + }, + { + name: "Martinique", + code: "MQ", + icon: "virtual-host", + icon_status: IconStatus.Up, + capital: "Fort-de-France", + continent: { + name: "North America", + }, + currency: "EUR", + languages: [ + { + name: "French", + }, + ], + }, + { + name: "Mauritania", + code: "MR", + icon: "virtual-host", + icon_status: IconStatus.Up, + capital: "Nouakchott", + continent: { + name: "Africa", + }, + currency: "MRU", + languages: [ + { + name: "Arabic", + }, + ], + }, + { + name: "Montserrat", + code: "MS", + icon: "virtual-host", + icon_status: IconStatus.Up, + capital: "Plymouth", + continent: { + name: "North America", + }, + currency: "XCD", + languages: [ + { + name: "English", + }, + ], + }, + { + name: "Malta", + code: "MT", + icon: "virtual-host", + icon_status: IconStatus.Up, + capital: "Valletta", + continent: { + name: "Europe", + }, + currency: "EUR", + languages: [ + { + name: "Maltese", + }, + { + name: "English", + }, + ], + }, + { + name: "Mauritius", + code: "MU", + icon: "virtual-host", + icon_status: IconStatus.Up, + capital: "Port Louis", + continent: { + name: "Africa", + }, + currency: "MUR", + languages: [ + { + name: "English", + }, + ], + }, + { + name: "Maldives", + code: "MV", + icon: "virtual-host", + icon_status: IconStatus.Up, + capital: "Malé", + continent: { + name: "Asia", + }, + currency: "MVR", + languages: [ + { + name: "Divehi", + }, + ], + }, + { + name: "Malawi", + code: "MW", + icon: "virtual-host", + icon_status: IconStatus.Up, + capital: "Lilongwe", + continent: { + name: "Africa", + }, + currency: "MWK", + languages: [ + { + name: "English", + }, + { + name: "Chichewa", + }, + ], + }, + { + name: "Mexico", + code: "MX", + icon: "virtual-host", + icon_status: IconStatus.Up, + capital: "Mexico City", + continent: { + name: "North America", + }, + currency: "MXN", + languages: [ + { + name: "Spanish", + }, + ], + }, + { + name: "Malaysia", + code: "MY", + icon: "virtual-host", + icon_status: IconStatus.Up, + capital: "Kuala Lumpur", + continent: { + name: "Asia", + }, + currency: "MYR", + languages: [ + { + name: "Malay", + }, + ], + }, + { + name: "Mozambique", + code: "MZ", + icon: "virtual-host", + icon_status: IconStatus.Up, + capital: "Maputo", + continent: { + name: "Africa", + }, + currency: "MZN", + languages: [ + { + name: "Portuguese", + }, + ], + }, + { + name: "Namibia", + code: "NA", + icon: "virtual-host", + icon_status: IconStatus.Up, + capital: "Windhoek", + continent: { + name: "Africa", + }, + currency: "NAD,ZAR", + languages: [ + { + name: "English", + }, + { + name: "Afrikaans", + }, + ], + }, + { + name: "New Caledonia", + code: "NC", + icon: "virtual-host", + icon_status: IconStatus.Up, + capital: "Nouméa", + continent: { + name: "Oceania", + }, + currency: "XPF", + languages: [ + { + name: "French", + }, + ], + }, + { + name: "Niger", + code: "NE", + icon: "virtual-host", + icon_status: IconStatus.Up, + capital: "Niamey", + continent: { + name: "Africa", + }, + currency: "XOF", + languages: [ + { + name: "French", + }, + ], + }, + { + name: "Norfolk Island", + code: "NF", + icon: "virtual-host", + icon_status: IconStatus.Up, + capital: "Kingston", + continent: { + name: "Oceania", + }, + currency: "AUD", + languages: [ + { + name: "English", + }, + ], + }, + { + name: "Nigeria", + code: "NG", + icon: "virtual-host", + icon_status: IconStatus.Up, + capital: "Abuja", + continent: { + name: "Africa", + }, + currency: "NGN", + languages: [ + { + name: "English", + }, + ], + }, + { + name: "Nicaragua", + code: "NI", + icon: "virtual-host", + icon_status: IconStatus.Up, + capital: "Managua", + continent: { + name: "North America", + }, + currency: "NIO", + languages: [ + { + name: "Spanish", + }, + ], + }, + { + name: "Netherlands", + code: "NL", + icon: "virtual-host", + icon_status: IconStatus.Up, + capital: "Amsterdam", + continent: { + name: "Europe", + }, + currency: "EUR", + languages: [ + { + name: "Dutch", + }, + ], + }, + { + name: "Norway", + code: "NO", + icon: "virtual-host", + icon_status: IconStatus.Up, + capital: "Oslo", + continent: { + name: "Europe", + }, + currency: "NOK", + languages: [ + { + name: "Norwegian", + }, + { + name: "Norwegian Bokmål", + }, + { + name: "Norwegian Nynorsk", + }, + ], + }, + { + name: "Nepal", + code: "NP", + icon: "virtual-host", + icon_status: IconStatus.Up, + capital: "Kathmandu", + continent: { + name: "Asia", + }, + currency: "NPR", + languages: [ + { + name: "Nepali", + }, + ], + }, + { + name: "Nauru", + code: "NR", + icon: "virtual-host", + icon_status: IconStatus.Up, + capital: "Yaren", + continent: { + name: "Oceania", + }, + currency: "AUD", + languages: [ + { + name: "English", + }, + { + name: "Nauruan", + }, + ], + }, + { + name: "Niue", + code: "NU", + icon: "virtual-host", + icon_status: IconStatus.Up, + capital: "Alofi", + continent: { + name: "Oceania", + }, + currency: "NZD", + languages: [ + { + name: "English", + }, + ], + }, + { + name: "New Zealand", + code: "NZ", + icon: "virtual-host", + icon_status: IconStatus.Up, + capital: "Wellington", + continent: { + name: "Oceania", + }, + currency: "NZD", + languages: [ + { + name: "English", + }, + { + name: "Maori", + }, + ], + }, + { + name: "Oman", + code: "OM", + icon: "virtual-host", + icon_status: IconStatus.Up, + capital: "Muscat", + continent: { + name: "Asia", + }, + currency: "OMR", + languages: [ + { + name: "Arabic", + }, + ], + }, + { + name: "Panama", + code: "PA", + icon: "virtual-host", + icon_status: IconStatus.Up, + capital: "Panama City", + continent: { + name: "North America", + }, + currency: "PAB,USD", + languages: [ + { + name: "Spanish", + }, + ], + }, + { + name: "Peru", + code: "PE", + icon: "virtual-host", + icon_status: IconStatus.Up, + capital: "Lima", + continent: { + name: "South America", + }, + currency: "PEN", + languages: [ + { + name: "Spanish", + }, + ], + }, + { + name: "French Polynesia", + code: "PF", + icon: "virtual-host", + icon_status: IconStatus.Up, + capital: "Papeetē", + continent: { + name: "Oceania", + }, + currency: "XPF", + languages: [ + { + name: "French", + }, + ], + }, + { + name: "Papua New Guinea", + code: "PG", + icon: "virtual-host", + icon_status: IconStatus.Up, + capital: "Port Moresby", + continent: { + name: "Oceania", + }, + currency: "PGK", + languages: [ + { + name: "English", + }, + ], + }, + { + name: "Philippines", + code: "PH", + icon: "virtual-host", + icon_status: IconStatus.Up, + capital: "Manila", + continent: { + name: "Asia", + }, + currency: "PHP", + languages: [ + { + name: "English", + }, + ], + }, + { + name: "Pakistan", + code: "PK", + icon: "virtual-host", + icon_status: IconStatus.Up, + capital: "Islamabad", + continent: { + name: "Asia", + }, + currency: "PKR", + languages: [ + { + name: "English", + }, + { + name: "Urdu", + }, + ], + }, + { + name: "Poland", + code: "PL", + icon: "virtual-host", + icon_status: IconStatus.Up, + capital: "Warsaw", + continent: { + name: "Europe", + }, + currency: "PLN", + languages: [ + { + name: "Polish", + }, + ], + }, + { + name: "Saint Pierre and Miquelon", + code: "PM", + icon: "virtual-host", + icon_status: IconStatus.Up, + capital: "Saint-Pierre", + continent: { + name: "North America", + }, + currency: "EUR", + languages: [ + { + name: "French", + }, + ], + }, + { + name: "Pitcairn Islands", + code: "PN", + icon: "virtual-host", + icon_status: IconStatus.Up, + capital: "Adamstown", + continent: { + name: "Oceania", + }, + currency: "NZD", + languages: [ + { + name: "English", + }, + ], + }, + { + name: "Puerto Rico", + code: "PR", + icon: "virtual-host", + icon_status: IconStatus.Up, + capital: "San Juan", + continent: { + name: "North America", + }, + currency: "USD", + languages: [ + { + name: "Spanish", + }, + { + name: "English", + }, + ], + }, + { + name: "Palestine", + code: "PS", + icon: "virtual-host", + icon_status: IconStatus.Up, + capital: "Ramallah", + continent: { + name: "Asia", + }, + currency: "ILS", + languages: [ + { + name: "Arabic", + }, + ], + }, + { + name: "Portugal", + code: "PT", + icon: "virtual-host", + icon_status: IconStatus.Up, + capital: "Lisbon", + continent: { + name: "Europe", + }, + currency: "EUR", + languages: [ + { + name: "Portuguese", + }, + ], + }, + { + name: "Palau", + code: "PW", + icon: "virtual-host", + icon_status: IconStatus.Up, + capital: "Ngerulmud", + continent: { + name: "Oceania", + }, + currency: "USD", + languages: [ + { + name: "English", + }, + ], + }, + { + name: "Paraguay", + code: "PY", + icon: "virtual-host", + icon_status: IconStatus.Up, + capital: "Asunción", + continent: { + name: "South America", + }, + currency: "PYG", + languages: [ + { + name: "Spanish", + }, + { + name: "Guarani", + }, + ], + }, + { + name: "Qatar", + code: "QA", + icon: "virtual-host", + icon_status: IconStatus.Up, + capital: "Doha", + continent: { + name: "Asia", + }, + currency: "QAR", + languages: [ + { + name: "Arabic", + }, + ], + }, + { + name: "Réunion", + code: "RE", + icon: "virtual-host", + icon_status: IconStatus.Up, + capital: "Saint-Denis", + continent: { + name: "Africa", + }, + currency: "EUR", + languages: [ + { + name: "French", + }, + ], + }, + { + name: "Romania", + code: "RO", + icon: "virtual-host", + icon_status: IconStatus.Up, + capital: "Bucharest", + continent: { + name: "Europe", + }, + currency: "RON", + languages: [ + { + name: "Romanian", + }, + ], + }, + { + name: "Serbia", + code: "RS", + icon: "virtual-host", + icon_status: IconStatus.Up, + capital: "Belgrade", + continent: { + name: "Europe", + }, + currency: "RSD", + languages: [ + { + name: "Serbian", + }, + ], + }, + { + name: "Russia", + code: "RU", + icon: "virtual-host", + icon_status: IconStatus.Up, + capital: "Moscow", + continent: { + name: "Europe", + }, + currency: "RUB", + languages: [ + { + name: "Russian", + }, + ], + }, + { + name: "Rwanda", + code: "RW", + icon: "virtual-host", + icon_status: IconStatus.Up, + capital: "Kigali", + continent: { + name: "Africa", + }, + currency: "RWF", + languages: [ + { + name: "Rwandi", + }, + { + name: "English", + }, + { + name: "French", + }, + ], + }, + { + name: "Saudi Arabia", + code: "SA", + icon: "virtual-host", + icon_status: IconStatus.Up, + capital: "Riyadh", + continent: { + name: "Asia", + }, + currency: "SAR", + languages: [ + { + name: "Arabic", + }, + ], + }, + { + name: "Solomon Islands", + code: "SB", + icon: "virtual-host", + icon_status: IconStatus.Up, + capital: "Honiara", + continent: { + name: "Oceania", + }, + currency: "SBD", + languages: [ + { + name: "English", + }, + ], + }, + { + name: "Seychelles", + code: "SC", + icon: "virtual-host", + icon_status: IconStatus.Up, + capital: "Victoria", + continent: { + name: "Africa", + }, + currency: "SCR", + languages: [ + { + name: "French", + }, + { + name: "English", + }, + ], + }, + { + name: "Sudan", + code: "SD", + icon: "virtual-host", + icon_status: IconStatus.Up, + capital: "Khartoum", + continent: { + name: "Africa", + }, + currency: "SDG", + languages: [ + { + name: "Arabic", + }, + { + name: "English", + }, + ], + }, + { + name: "Sweden", + code: "SE", + icon: "virtual-host", + icon_status: IconStatus.Up, + capital: "Stockholm", + continent: { + name: "Europe", + }, + currency: "SEK", + languages: [ + { + name: "Swedish", + }, + ], + }, + { + name: "Singapore", + code: "SG", + icon: "virtual-host", + icon_status: IconStatus.Up, + capital: "Singapore", + continent: { + name: "Asia", + }, + currency: "SGD", + languages: [ + { + name: "English", + }, + { + name: "Malay", + }, + { + name: "Tamil", + }, + { + name: "Chinese", + }, + ], + }, + { + name: "Saint Helena", + code: "SH", + icon: "virtual-host", + icon_status: IconStatus.Up, + capital: "Jamestown", + continent: { + name: "Africa", + }, + currency: "SHP", + languages: [ + { + name: "English", + }, + ], + }, + { + name: "Slovenia", + code: "SI", + icon: "virtual-host", + icon_status: IconStatus.Up, + capital: "Ljubljana", + continent: { + name: "Europe", + }, + currency: "EUR", + languages: [ + { + name: "Slovenian", + }, + ], + }, + { + name: "Svalbard and Jan Mayen", + code: "SJ", + icon: "virtual-host", + icon_status: IconStatus.Up, + capital: "Longyearbyen", + continent: { + name: "Europe", + }, + currency: "NOK", + languages: [ + { + name: "Norwegian", + }, + ], + }, + { + name: "Slovakia", + code: "SK", + icon: "virtual-host", + icon_status: IconStatus.Up, + capital: "Bratislava", + continent: { + name: "Europe", + }, + currency: "EUR", + languages: [ + { + name: "Slovak", + }, + ], + }, + { + name: "Sierra Leone", + code: "SL", + icon: "virtual-host", + icon_status: IconStatus.Up, + capital: "Freetown", + continent: { + name: "Africa", + }, + currency: "SLL", + languages: [ + { + name: "English", + }, + ], + }, + { + name: "San Marino", + code: "SM", + icon: "virtual-host", + icon_status: IconStatus.Up, + capital: "City of San Marino", + continent: { + name: "Europe", + }, + currency: "EUR", + languages: [ + { + name: "Italian", + }, + ], + }, + { + name: "Senegal", + code: "SN", + icon: "virtual-host", + icon_status: IconStatus.Up, + capital: "Dakar", + continent: { + name: "Africa", + }, + currency: "XOF", + languages: [ + { + name: "French", + }, + ], + }, + { + name: "Somalia", + code: "SO", + icon: "virtual-host", + icon_status: IconStatus.Up, + capital: "Mogadishu", + continent: { + name: "Africa", + }, + currency: "SOS", + languages: [ + { + name: "Somalia", + }, + { + name: "Arabic", + }, + ], + }, + { + name: "Suriname", + code: "SR", + icon: "virtual-host", + icon_status: IconStatus.Up, + capital: "Paramaribo", + continent: { + name: "South America", + }, + currency: "SRD", + languages: [ + { + name: "Dutch", + }, + ], + }, + { + name: "South Sudan", + code: "SS", + icon: "virtual-host", + icon_status: IconStatus.Up, + capital: "Juba", + continent: { + name: "Africa", + }, + currency: "SSP", + languages: [ + { + name: "English", + }, + ], + }, + { + name: "São Tomé and Príncipe", + code: "ST", + icon: "virtual-host", + icon_status: IconStatus.Up, + capital: "São Tomé", + continent: { + name: "Africa", + }, + currency: "STN", + languages: [ + { + name: "Portuguese", + }, + ], + }, + { + name: "El Salvador", + code: "SV", + icon: "virtual-host", + icon_status: IconStatus.Up, + capital: "San Salvador", + continent: { + name: "North America", + }, + currency: "SVC,USD", + languages: [ + { + name: "Spanish", + }, + ], + }, + { + name: "Sint Maarten", + code: "SX", + icon: "virtual-host", + icon_status: IconStatus.Up, + capital: "Philipsburg", + continent: { + name: "North America", + }, + currency: "ANG", + languages: [ + { + name: "Dutch", + }, + { + name: "English", + }, + ], + }, + { + name: "Syria", + code: "SY", + icon: "virtual-host", + icon_status: IconStatus.Up, + capital: "Damascus", + continent: { + name: "Asia", + }, + currency: "SYP", + languages: [ + { + name: "Arabic", + }, + ], + }, + { + name: "Swaziland", + code: "SZ", + icon: "virtual-host", + icon_status: IconStatus.Up, + capital: "Lobamba", + continent: { + name: "Africa", + }, + currency: "SZL", + languages: [ + { + name: "English", + }, + { + name: "Swati", + }, + ], + }, + { + name: "Turks and Caicos Islands", + code: "TC", + icon: "virtual-host", + icon_status: IconStatus.Up, + capital: "Cockburn Town", + continent: { + name: "North America", + }, + currency: "USD", + languages: [ + { + name: "English", + }, + ], + }, + { + name: "Chad", + code: "TD", + icon: "virtual-host", + icon_status: IconStatus.Up, + capital: "N'Djamena", + continent: { + name: "Africa", + }, + currency: "XAF", + languages: [ + { + name: "French", + }, + { + name: "Arabic", + }, + ], + }, + { + name: "French Southern Territories", + code: "TF", + icon: "virtual-host", + icon_status: IconStatus.Up, + capital: "Port-aux-Français", + continent: { + name: "Antarctica", + }, + currency: "EUR", + languages: [ + { + name: "French", + }, + ], + }, + { + name: "Togo", + code: "TG", + icon: "virtual-host", + icon_status: IconStatus.Up, + capital: "Lomé", + continent: { + name: "Africa", + }, + currency: "XOF", + languages: [ + { + name: "French", + }, + ], + }, + { + name: "Thailand", + code: "TH", + icon: "virtual-host", + icon_status: IconStatus.Up, + capital: "Bangkok", + continent: { + name: "Asia", + }, + currency: "THB", + languages: [ + { + name: "Thai", + }, + ], + }, + { + name: "Tajikistan", + code: "TJ", + icon: "virtual-host", + icon_status: IconStatus.Up, + capital: "Dushanbe", + continent: { + name: "Asia", + }, + currency: "TJS", + languages: [ + { + name: "Tajik", + }, + { + name: "Russian", + }, + ], + }, + { + name: "Tokelau", + code: "TK", + icon: "virtual-host", + icon_status: IconStatus.Up, + capital: "Fakaofo", + continent: { + name: "Oceania", + }, + currency: "NZD", + languages: [ + { + name: "English", + }, + ], + }, + { + name: "East Timor", + code: "TL", + icon: "virtual-host", + icon_status: IconStatus.Up, + capital: "Dili", + continent: { + name: "Oceania", + }, + currency: "USD", + languages: [ + { + name: "Portuguese", + }, + ], + }, + { + name: "Turkmenistan", + code: "TM", + icon: "virtual-host", + icon_status: IconStatus.Up, + capital: "Ashgabat", + continent: { + name: "Asia", + }, + currency: "TMT", + languages: [ + { + name: "Turkmen", + }, + { + name: "Russian", + }, + ], + }, + { + name: "Tunisia", + code: "TN", + icon: "virtual-host", + icon_status: IconStatus.Up, + capital: "Tunis", + continent: { + name: "Africa", + }, + currency: "TND", + languages: [ + { + name: "Arabic", + }, + ], + }, + { + name: "Tonga", + code: "TO", + icon: "virtual-host", + icon_status: IconStatus.Up, + capital: "Nuku'alofa", + continent: { + name: "Oceania", + }, + currency: "TOP", + languages: [ + { + name: "English", + }, + { + name: "Tonga", + }, + ], + }, + { + name: "Turkey", + code: "TR", + icon: "virtual-host", + icon_status: IconStatus.Up, + capital: "Ankara", + continent: { + name: "Asia", + }, + currency: "TRY", + languages: [ + { + name: "Turkish", + }, + ], + }, + { + name: "Trinidad and Tobago", + code: "TT", + icon: "virtual-host", + icon_status: IconStatus.Up, + capital: "Port of Spain", + continent: { + name: "North America", + }, + currency: "TTD", + languages: [ + { + name: "English", + }, + ], + }, + { + name: "Tuvalu", + code: "TV", + icon: "virtual-host", + icon_status: IconStatus.Up, + capital: "Funafuti", + continent: { + name: "Oceania", + }, + currency: "AUD", + languages: [ + { + name: "English", + }, + ], + }, + { + name: "Taiwan", + code: "TW", + icon: "virtual-host", + icon_status: IconStatus.Up, + capital: "Taipei", + continent: { + name: "Asia", + }, + currency: "TWD", + languages: [ + { + name: "Chinese", + }, + ], + }, + { + name: "Tanzania", + code: "TZ", + icon: "virtual-host", + icon_status: IconStatus.Up, + capital: "Dodoma", + continent: { + name: "Africa", + }, + currency: "TZS", + languages: [ + { + name: "Swahili", + }, + { + name: "English", + }, + ], + }, + { + name: "Ukraine", + code: "UA", + icon: "virtual-host", + icon_status: IconStatus.Up, + capital: "Kyiv", + continent: { + name: "Europe", + }, + currency: "UAH", + languages: [ + { + name: "Ukrainian", + }, + ], + }, + { + name: "Uganda", + code: "UG", + icon: "virtual-host", + icon_status: IconStatus.Up, + capital: "Kampala", + continent: { + name: "Africa", + }, + currency: "UGX", + languages: [ + { + name: "English", + }, + { + name: "Swahili", + }, + ], + }, + { + name: "U.S. Minor Outlying Islands", + code: "UM", + icon: "virtual-host", + icon_status: IconStatus.Up, + capital: null, + continent: { + name: "Oceania", + }, + currency: "USD", + languages: [ + { + name: "English", + }, + ], + }, + { + name: "United States", + code: "US", + icon: "virtual-host", + icon_status: IconStatus.Up, + capital: "Washington D.C.", + continent: { + name: "North America", + }, + currency: "USD,USN,USS", + languages: [ + { + name: "English", + }, + ], + }, + { + name: "Uruguay", + code: "UY", + icon: "virtual-host", + icon_status: IconStatus.Up, + capital: "Montevideo", + continent: { + name: "South America", + }, + currency: "UYI,UYU", + languages: [ + { + name: "Spanish", + }, + ], + }, + { + name: "Uzbekistan", + code: "UZ", + icon: "virtual-host", + icon_status: IconStatus.Up, + capital: "Tashkent", + continent: { + name: "Asia", + }, + currency: "UZS", + languages: [ + { + name: "Uzbek", + }, + { + name: "Russian", + }, + ], + }, + { + name: "Vatican City", + code: "VA", + icon: "virtual-host", + icon_status: IconStatus.Up, + capital: "Vatican City", + continent: { + name: "Europe", + }, + currency: "EUR", + languages: [ + { + name: "Italian", + }, + { + name: "Latin", + }, + ], + }, + { + name: "Saint Vincent and the Grenadines", + code: "VC", + icon: "virtual-host", + icon_status: IconStatus.Up, + capital: "Kingstown", + continent: { + name: "North America", + }, + currency: "XCD", + languages: [ + { + name: "English", + }, + ], + }, + { + name: "Venezuela", + code: "VE", + icon: "virtual-host", + icon_status: IconStatus.Up, + capital: "Caracas", + continent: { + name: "South America", + }, + currency: "VES", + languages: [ + { + name: "Spanish", + }, + ], + }, + { + name: "British Virgin Islands", + code: "VG", + icon: "virtual-host", + icon_status: IconStatus.Up, + capital: "Road Town", + continent: { + name: "North America", + }, + currency: "USD", + languages: [ + { + name: "English", + }, + ], + }, + { + name: "U.S. Virgin Islands", + code: "VI", + icon: "virtual-host", + icon_status: IconStatus.Up, + capital: "Charlotte Amalie", + continent: { + name: "North America", + }, + currency: "USD", + languages: [ + { + name: "English", + }, + ], + }, + { + name: "Vietnam", + code: "VN", + icon: "virtual-host", + icon_status: IconStatus.Up, + capital: "Hanoi", + continent: { + name: "Asia", + }, + currency: "VND", + languages: [ + { + name: "Vietnamese", + }, + ], + }, + { + name: "Vanuatu", + code: "VU", + icon: "virtual-host", + icon_status: IconStatus.Up, + capital: "Port Vila", + continent: { + name: "Oceania", + }, + currency: "VUV", + languages: [ + { + name: "Bislama", + }, + { + name: "English", + }, + { + name: "French", + }, + ], + }, + { + name: "Wallis and Futuna", + code: "WF", + icon: "virtual-host", + icon_status: IconStatus.Up, + capital: "Mata-Utu", + continent: { + name: "Oceania", + }, + currency: "XPF", + languages: [ + { + name: "French", + }, + ], + }, + { + name: "Samoa", + code: "WS", + icon: "virtual-host", + icon_status: IconStatus.Up, + capital: "Apia", + continent: { + name: "Oceania", + }, + currency: "WST", + languages: [ + { + name: "Samoan", + }, + { + name: "English", + }, + ], + }, + { + name: "Kosovo", + code: "XK", + icon: "virtual-host", + icon_status: IconStatus.Up, + capital: "Pristina", + continent: { + name: "Europe", + }, + currency: "EUR", + languages: [ + { + name: "Albanian", + }, + { + name: "Serbian", + }, + ], + }, + { + name: "Yemen", + code: "YE", + icon: "virtual-host", + icon_status: IconStatus.Up, + capital: "Sana'a", + continent: { + name: "Asia", + }, + currency: "YER", + languages: [ + { + name: "Arabic", + }, + ], + }, + { + name: "Mayotte", + code: "YT", + icon: "virtual-host", + icon_status: IconStatus.Up, + capital: "Mamoudzou", + continent: { + name: "Africa", + }, + currency: "EUR", + languages: [ + { + name: "French", + }, + ], + }, + { + name: "South Africa", + code: "ZA", + icon: "virtual-host", + icon_status: IconStatus.Up, + capital: "Pretoria", + continent: { + name: "Africa", + }, + currency: "ZAR", + languages: [ + { + name: "Afrikaans", + }, + { + name: "English", + }, + { + name: "South Ndebele", + }, + { + name: "Southern Sotho", + }, + { + name: "Swati", + }, + { + name: "Tswana", + }, + { + name: "Tsonga", + }, + { + name: "Venda", + }, + { + name: "Xhosa", + }, + { + name: "Zulu", + }, + ], + }, + { + name: "Zambia", + code: "ZM", + icon: "virtual-host", + icon_status: IconStatus.Up, + capital: "Lusaka", + continent: { + name: "Africa", + }, + currency: "ZMW", + languages: [ + { + name: "English", + }, + ], + }, + { + name: "Zimbabwe", + code: "ZW", + icon: "virtual-host", + icon_status: IconStatus.Up, + capital: "Harare", + continent: { + name: "Africa", + }, + currency: "USD,ZAR,BWP,GBP,AUD,CNY,INR,JPY", + languages: [ + { + name: "English", + }, + { + name: "Shona", + }, + { + name: "North Ndebele", + }, + ], + }, + ], + }, +}; +\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\`, + "widget-types/drilldown/drilldown-widget/drilldown-widget-example.component.ts": \\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\`// © 2022 SolarWinds Worldwide, LLC. All rights reserved. +// +// Permission is hereby granted, free of charge, to any person obtaining a copy +// of this software and associated documentation files (the "Software"), to +// deal in the Software without restriction, including without limitation the +// rights to use, copy, modify, merge, publish, distribute, sublicense, and/or +// sell copies of the Software, and to permit persons to whom the Software is +// furnished to do so, subject to the following conditions: +// +// The above copyright notice and this permission notice shall be included in +// all copies or substantial portions of the Software. +// +// THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR +// IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, +// FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE +// AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER +// LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, +// OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN +// THE SOFTWARE. + +import { HttpClient } from "@angular/common/http"; +import { + ChangeDetectorRef, + Component, + Injectable, + OnDestroy, + OnInit, +} from "@angular/core"; +import { GridsterConfig, GridsterItem } from "angular-gridster2"; +import { Apollo, gql } from "apollo-angular"; +import groupBy from "lodash/groupBy"; +import { BehaviorSubject, Observable, of } from "rxjs"; +import { catchError, delay, filter, map } from "rxjs/operators"; + +import { + DataSourceFeatures, + IconStatus, + IDataField, + IDataSource, + IDataSourceFeatures, + IDataSourceFeaturesConfiguration, + INovaFilters, + LoggerService, + ServerSideDataSource, + IFilters, +} from "@nova-ui/bits"; +import { + DATA_SOURCE, + DEFAULT_PIZZAGNA_ROOT, + IDashboard, + IDrilldownComponentsConfiguration, + IListWidgetConfiguration, + IProviderConfiguration, + IWidget, + IWidgets, + ListGroupItemComponent, + ListLeafItemComponent, + NOVA_DRILLDOWN_DATASOURCE_ADAPTER, + PizzagnaLayer, + ProviderRegistryService, + WellKnownPathKey, + WellKnownProviders, + WidgetTypesService, +} from "@nova-ui/dashboards"; + +import { DrilldownDataSource } from "./mock-data-source"; + +/** + * A simple KPI data source to retrieve the average rating of Harry Potter and the Sorcerer's Stone (book) via googleapis + */ +@Injectable() +export class DrilldownDataSourceRealApi + extends ServerSideDataSource + implements OnDestroy, IDataSource +{ + // This is the ID we'll use to identify the provider + public static providerId = "DrilldownDataSourceRealApi"; + + // Use this subject to communicate the data source's busy state + public busy = new BehaviorSubject(false); + public dataFields: Partial[] = [ + { id: "regionName", label: "Region name" }, + { id: "subregionName", label: "Subregion name" }, + ]; + + public features: IDataSourceFeaturesConfiguration; + private supportedFeatures: IDataSourceFeatures = { + search: { enabled: true }, + }; + + private drillState: string[] = []; + private groupBy: string[]; + + constructor( + private logger: LoggerService, + private http: HttpClient, + private apollo: Apollo + ) { + super(); + this.features = new DataSourceFeatures(this.supportedFeatures); + // TODO: remove Partial in vNext after marking dataType field as optional - NUI-5838 + ( + this.dataFieldsConfig.dataFields$ as BehaviorSubject< + Partial[] + > + ).next(this.dataFields); + } + + private groupedDataHistory: Array> = []; + + // In this example, getFilteredData is invoked every 10 minutes (Take a look at the refresher + // provider definition in the widget configuration below to see how the interval is set) + public async getFilteredData(data: IFilters): Promise { + return of(data) + .pipe( + filter(() => !!this.drillState), + map((countries) => { + const lastHistory = () => getLast(this.groupedDataHistory); + + if (!this.drillState.length && !this.groupBy.length) { + return countries; + } + + // adding "ROOT" as a root level for drilling + const fullDrillState = ["ROOT", ...this.drillState]; + const activeDrillLvl = fullDrillState.length; + const historyLvl = this.groupedDataHistory.length; + + // checking how many lvls we have to group for drilling, in case some are missed + const drillLvlDiff = activeDrillLvl - historyLvl; + + if (!drillLvlDiff) { + return lastHistory() || countries; + } + + const drillToGroup = fullDrillState.slice( + fullDrillState.length - drillLvlDiff + ); + + for (const drill of drillToGroup) { + const drillIdx = fullDrillState.findIndex( + (v) => v === drill + ); + const group = this.groupBy[drillIdx]; + + if (group) { + const dataToGroup = lastHistory() + ? lastHistory()[drill] + : countries; + const lastGroupedValue = groupBy( + dataToGroup, + group + ); + + this.groupedDataHistory.push(lastGroupedValue); + } + } + + // take last if we have all data grouped + if (this.groupBy.length === this.drillState.length) { + return lastHistory()[getLast(this.drillState)]; + } + + // get groping and transform to raw data format + return this.getGroupsWidgetData(lastHistory()); + }) + ) + .toPromise(); + } + + public ngOnDestroy(): void { + this.outputsSubject.complete(); + } + + // This method is expected to return all data needed for repeat/paginator/filterGroups in order to work. + // In case of custom filtering participants feel free to extend INovaFilteringOutputs. + protected getBackendData(filters: INovaFilters): Observable { + const mainRequest = this.apollo.watchQuery<{ countries: any }>({ + query: this.generateQuery(filters), + }); + + return mainRequest.valueChanges.pipe( + // mock delay + delay(300), + // data mapping, !DS specific! + map((res) => res.data.countries), + // adds mock icons to be displayed on leaf nodes !DS specific! + map((res: any[]) => + res.map((v) => ({ + ...v, + icon: "virtual-host", + icon_status: IconStatus.Up, + subregionName: + v.subregion?.name || "No Subregion Specified", + regionName: + v.subregion?.region?.name || "No Region Specified", + })) + ), + catchError((e) => { + this.logger.error(e); + return of({} as any); + }) + ); + } + + private generateQuery(filters: INovaFilters) { + const { search } = filters; + const searchValue = search?.value ? \\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\`^[\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\${search.value}]*\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\` : ""; + + const queryString = \\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\` + query { + countries(filter: {name: {regex: "\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\${searchValue}"} }) { + name + native + capital + languages { + name + } + currencies + subdivisions { + name + } + } + } + \\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\`; + + return gql\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\` + \\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\${queryString} + \\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\`; + } + + // Overrides default ServerSideDataSource.beforeApplyFilters implementation + // to save some filters that are used internally + // -- !DS specific + protected beforeApplyFilters(filters: INovaFilters): void { + this.busy.next(true); + + this.drillState = filters.drillstate?.value; + this.groupBy = filters.group?.value; + + if (this.isHome()) { + this.groupedDataHistory.length = 0; + } + + if (this.isBack()) { + this.groupedDataHistory.length = this.groupedDataHistory.length - 1; + } + + if (this.getFilters()["search"] && this.filterChanged("search")) { + this.groupedDataHistory.length = 0; + } + } + + private getGroupsWidgetData(groupByObj: Record) { + return Object.keys(groupByObj).map((property) => ({ + id: property, + label: property, + // statuses that will be displayed on group item + statuses: [ + { key: "virtual-host", value: groupByObj[property].length }, + { + key: "acknowledge", + value: this.getPopulation(groupByObj[property]), + }, + ], + })); + } + + private isHome(): boolean { + return this.drillState?.length === 0; + } + + private isBack(): boolean { + return ( + this.groupedDataHistory?.length > this.drillState?.length && + !this.isHome() + ); + } + + /** + * Gets population for the country(ies) + */ + private getPopulation(countries: any[]) { + const totalPopulation = countries.reduce( + (acc, next) => (acc += next.population), + 0 + ); + return \\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\`\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\${totalPopulation * Math.pow(10, -3)} k\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\`; + } +} + +@Component({ + selector: "drilldown-widget-example", + templateUrl: "./drilldown-widget-example.component.html", + styleUrls: ["./drilldown-widget-example.component.less"], + standalone: false, +}) +export class DrilldownWidgetExampleComponent implements OnInit { + // This variable will hold all the data needed to define the layout and behavior of the widgets. + // Pass this to the dashboard component's dashboard input in the template. + public dashboard: IDashboard | undefined; + + // Angular gridster requires a configuration object even if it's empty. + // Pass this to the dashboard component's gridsterConfig input in the template. + public gridsterConfig: GridsterConfig = {}; + + // Boolean passed as an input to the dashboard. When true, widgets can be moved, resized, removed, or edited + public editMode: boolean = false; + + constructor( + // WidgetTypesService provides the widget's necessary structure information + private widgetTypesService: WidgetTypesService, + private providerRegistry: ProviderRegistryService, + private changeDetectorRef: ChangeDetectorRef + ) {} + + public ngOnInit(): void { + // Registering the data source for injection into the KPI tile. + // Note: Each tile of a KPI widget is assigned its own instance of the data source + this.providerRegistry.setProviders({ + [DrilldownDataSource.providerId]: { + provide: DATA_SOURCE, + useClass: DrilldownDataSource, + // Any dependencies that need to be injected into the provider must be listed here + deps: [HttpClient], + }, + [DrilldownDataSourceRealApi.providerId]: { + provide: DATA_SOURCE, + useClass: DrilldownDataSourceRealApi, + // Any dependencies that need to be injected into the provider must be listed here + deps: [LoggerService, HttpClient, Apollo], + }, + }); + + this.initializeDashboard(); + const widgetTemplate = this.widgetTypesService.getWidgetType( + "drilldown", + 1 + ); + this.widgetTypesService.setNode( + widgetTemplate, + "configurator", + WellKnownPathKey.DataSourceProviders, + [ + DrilldownDataSourceRealApi.providerId, + DrilldownDataSource.providerId, + ] + ); + } + + public initializeDashboard(): void { + // We're using a static configuration object for this example, but this is where + // the widget's configuration could potentially be populated from a database + const drilldownWidget = widgetConfig; + const widgets: IWidgets = { + // Complete the widget with information coming from its type definition + [drilldownWidget.id]: + this.widgetTypesService.mergeWithWidgetType(drilldownWidget), + }; + + // Setting the widget dimensions and position (this is for gridster) + const positions: Record = { + [drilldownWidget.id]: { + cols: 10, + rows: 10, + y: 0, + x: 0, + }, + }; + + // Finally, assigning the variables we created above to the dashboard + this.dashboard = { positions, widgets }; + } + + public reInitializeDashboard(): void { + // destroys the components and their providers so the dashboard can re init data + this.dashboard = undefined; + this.changeDetectorRef.detectChanges(); + + const adapterProperties = + widgetConfig.pizzagna[PizzagnaLayer.Configuration].listWidget + .providers?.adapter?.properties; + + if (adapterProperties) { + adapterProperties.drillstate = []; + } + + this.initializeDashboard(); + } +} + +const widgetConfig: IWidget = { + id: "drilldown", + type: "drilldown", + pizzagna: { + [PizzagnaLayer.Configuration]: { + [DEFAULT_PIZZAGNA_ROOT]: { + providers: { + [WellKnownProviders.DataSource]: { + // Setting the data source providerId for the tile with id "kpi1" + providerId: DrilldownDataSourceRealApi.providerId, + properties: {}, + } as IProviderConfiguration, + }, + }, + header: { + properties: { + title: "Drilldown Widget", + subtitle: "Search is case sensitive!", + }, + }, + listWidget: { + providers: { + [WellKnownProviders.Adapter]: { + providerId: NOVA_DRILLDOWN_DATASOURCE_ADAPTER, + properties: { + // widget + navigationBarId: "navigationBar", + componentId: "listWidget", + dataPath: "data", + + // adapter props + drillstate: [], + groupBy: ["regionName", "subregionName"], + groups: ["regionName", "subregionName"], + + // components + componentsConfig: { + group: { + componentType: + ListGroupItemComponent.lateLoadKey, + properties: { + dataFieldIds: { + id: "id", + label: "label", + statuses: "statuses", + }, + }, + itemProperties: { + canNavigate: true, + }, + }, + leaf: { + componentType: + ListLeafItemComponent.lateLoadKey, + properties: { + dataFieldIds: { + icon: "icon", + status: "icon_status", + detailedUrl: "capital", + label: "name", + }, + }, + itemProperties: { + canNavigate: false, + }, + }, + } as IDrilldownComponentsConfiguration, + }, + }, + }, + properties: { + configuration: { + // FORMAT: + // componentType: ListLeafItemComponent.lateLoadKey, + // properties: { + // dataFieldIds: { + // icon: "", + // status: "code", + // detailedUrl: "capital", + // label: "name", + // }, + // }, + // + } as IListWidgetConfiguration, + }, + }, + }, + }, +}; + +const getLast = (arr: any[]) => arr[arr.length - 1]; +\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\`, + "widget-types/drilldown/drilldown-widget/mock-data-source.ts": \\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\`// © 2022 SolarWinds Worldwide, LLC. All rights reserved. +// +// Permission is hereby granted, free of charge, to any person obtaining a copy +// of this software and associated documentation files (the "Software"), to +// deal in the Software without restriction, including without limitation the +// rights to use, copy, modify, merge, publish, distribute, sublicense, and/or +// sell copies of the Software, and to permit persons to whom the Software is +// furnished to do so, subject to the following conditions: +// +// The above copyright notice and this permission notice shall be included in +// all copies or substantial portions of the Software. +// +// THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR +// IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, +// FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE +// AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER +// LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, +// OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN +// THE SOFTWARE. + +import { Injectable, OnDestroy } from "@angular/core"; +import groupBy from "lodash/groupBy"; +import { BehaviorSubject, Observable, of, Subject } from "rxjs"; +import { + catchError, + delay, + finalize, + map, + // eslint-disable-next-line import/no-deprecated + switchMap, + tap, +} from "rxjs/operators"; + +import { + DataSourceService, + IDataField, + IDataSource, + IFilters, + INovaFilters, +} from "@nova-ui/bits"; + +import { GRAPH_DATA_MOCK } from "./data-mock"; + +/** + * A simple KPI data source to retrieve the average rating of Harry Potter and the Sorcerer's Stone (book) via googleapis + */ +@Injectable() +export class DrilldownDataSource + extends DataSourceService + implements IDataSource, OnDestroy +{ + // This is the ID we'll use to identify the provider + public static providerId = "DrilldownDataSource"; + + // Use this subject to communicate the data source's busy state + public busy = new BehaviorSubject(false); + public dataFields: Partial[] = [ + { id: "continent.name", label: "Continent name" }, + { id: "currency", label: "Currency" }, + ]; + + private drillState: string[] = []; + private groupBy: string[]; + private cache: any; + private applyFilters$ = new Subject(); + + constructor() { + super(); + + // TODO: remove Partial in vNext after marking dataType field as optional - NUI-5838 + ( + this.dataFieldsConfig.dataFields$ as BehaviorSubject< + Partial[] + > + ).next(this.dataFields); + + this.applyFilters$ + // eslint-disable-next-line import/no-deprecated + .pipe(switchMap((filters) => this.getData(filters))) + .subscribe(async (res) => { + this.outputsSubject.next(await this.getFilteredData(res)); + }); + } + + private groupedDataHistory: any[] = []; + + // In this example, getFilteredData is invoked every 10 minutes (Take a look at the refresher + // provider definition in the widget configuration below to see how the interval is set) + public async getFilteredData(data: any): Promise { + return of(data) + .pipe( + map((countries) => { + const widgetInput = this.getOutput(countries); + + if (this.isDrillDown()) { + const activeDrillLvl = this.drillState.length; + const group = this.groupBy[activeDrillLvl]; + const [lastGroupedValue, groupedData] = + this.getTransformedDataForGroup(widgetInput, group); + + this.groupedDataHistory.push(lastGroupedValue); + + return groupedData; + } + + return widgetInput; + }) + ) + .toPromise(); + } + + public ngOnDestroy(): void { + this.outputsSubject.complete(); + } + + // redefine parent method + public async applyFilters(): Promise { + this.applyFilters$.next(this.getFilters()); + } + + private getData(filters: INovaFilters): Observable { + this.drillState = filters.drillstate?.value; + this.groupBy = filters.group?.value; + + this.busy.next(true); + + return of(this.cache || GRAPH_DATA_MOCK).pipe( + delay(1000), + tap((data) => (this.cache = data)), + map((data) => data.data.countries), + catchError((e) => of([])), + finalize(() => this.busy.next(false)) + ); + } + + private getTransformedDataForGroup(data: any, groupName: string) { + const groupedDict = groupBy(data, groupName); + const dataArr = Object.keys(groupedDict).map((property) => ({ + id: property, + label: property, + // TODO: apply groups mapping here + statuses: [ + { key: "state_ok", value: groupedDict[property].length }, + { + key: "status_unreachable", + value: generateNumberUpTo(100000), + }, + { key: "status_warning", value: generateNumberUpTo(10000) }, + { key: "status_unknown", value: generateNumberUpTo(1000) }, + ], + })); + + return [groupedDict, dataArr]; + } + + private isHome(): boolean { + return !this.drillState || this.drillState.length === 0; + } + + private isBack(): boolean { + return ( + this.groupedDataHistory.length > this.drillState?.length && + !this.isHome() + ); + } + + private isDrillDown(): boolean { + return this.drillState?.length !== this.groupBy?.length; + } + + private getOutput(data: any) { + if (this.isHome()) { + this.groupedDataHistory.length = 0; + } + + if (this.isBack()) { + this.groupedDataHistory.length = this.groupedDataHistory.length - 1; + } + + const lastHistoryValue = getLast(this.groupedDataHistory); + + if (!lastHistoryValue) { + return data; + } + + return lastHistoryValue[getLast(this.drillState)] || lastHistoryValue; + } +} + +const getLast = (arr: any[]) => arr[arr.length - 1]; +const generateNumberUpTo = (upperLimit: number): number => + Math.floor(Math.random() * upperLimit + 1); +\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\`, + "widget-types/drilldown/drilldown-widget-docs.component.ts": \\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\`// © 2022 SolarWinds Worldwide, LLC. All rights reserved. +// +// Permission is hereby granted, free of charge, to any person obtaining a copy +// of this software and associated documentation files (the "Software"), to +// deal in the Software without restriction, including without limitation the +// rights to use, copy, modify, merge, publish, distribute, sublicense, and/or +// sell copies of the Software, and to permit persons to whom the Software is +// furnished to do so, subject to the following conditions: +// +// The above copyright notice and this permission notice shall be included in +// all copies or substantial portions of the Software. +// +// THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR +// IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, +// FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE +// AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER +// LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, +// OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN +// THE SOFTWARE. + +import { Component, OnInit } from "@angular/core"; + +import { mapContentFile } from "../../../../demo-files-factory"; + +@Component({ + selector: "nui-drilldown-docs", + templateUrl: "./drilldown-widget-docs.component.html", + standalone: false, +}) +export class DrilldownDocsComponent implements OnInit { + public widgetFileText = ""; + public configuratorFileText = ""; + + public predefinedGroping = \\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\` +listWidget: { + providers: { + [WellKnownProviders.Adapter]: { + providerId: NOVA_DRILLDOWN_DATASOURCE_ADAPTER, + properties: { + ... + // adapter props + drillstate: [], + groupBy: ["regionName", "subregionName"], + groups: ["regionName", "subregionName"], + ... + }, + }, + }, +}, +\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\`; + public featuredDeclaredText = \\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\` + private supportedFeatures: IDataSourceFeatures = { + search: { enabled: true }, + };\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\`; + public featuresUsedText = \\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\` + this.features = new DataSourceFeatures(this.supportedFeatures); + \\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\`; + + public async ngOnInit(): Promise { + this.widgetFileText = await import( + "./../../../../../../src/lib/widget-types/drilldown/drilldown-widget" + ).then(mapContentFile); + this.configuratorFileText = await import( + "./../../../../../../src/lib/widget-types/drilldown/drilldown-configurator" + ).then(mapContentFile); + } +} +\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\`, + "widget-types/drilldown/drilldown-widget-docs.module.ts": \\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\`// © 2022 SolarWinds Worldwide, LLC. All rights reserved. +// +// Permission is hereby granted, free of charge, to any person obtaining a copy +// of this software and associated documentation files (the "Software"), to +// deal in the Software without restriction, including without limitation the +// rights to use, copy, modify, merge, publish, distribute, sublicense, and/or +// sell copies of the Software, and to permit persons to whom the Software is +// furnished to do so, subject to the following conditions: +// +// The above copyright notice and this permission notice shall be included in +// all copies or substantial portions of the Software. +// +// THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR +// IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, +// FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE +// AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER +// LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, +// OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN +// THE SOFTWARE. + +import { NgModule } from "@angular/core"; +import { RouterModule, Routes } from "@angular/router"; + +// eslint-disable-next-line max-len +import { + NuiButtonModule, + NuiDocsModule, + NuiMessageModule, + NuiSwitchModule, + DEMO_PATH_TOKEN, +} from "@nova-ui/bits"; +import { NuiDashboardsModule } from "@nova-ui/dashboards"; + +import { DrilldownMultiRequestWidgetExampleComponent } from "./drilldown-multi-request-widget/drilldown-multi-request-widget-example.component"; +import { DrilldownWidgetExampleComponent } from "./drilldown-widget/drilldown-widget-example.component"; +import { DrilldownDocsComponent } from "./drilldown-widget-docs.component"; +import { getDemoFiles } from "../../../../demo-files-factory"; + +const routes: Routes = [ + { + path: "", + component: DrilldownDocsComponent, + data: { + srlc: { + hideIndicator: true, + }, + }, + }, + { + path: "example", + component: DrilldownWidgetExampleComponent, + data: { + srlc: { + hideIndicator: true, + }, + }, + }, + { + path: "multiple-requests", + component: DrilldownMultiRequestWidgetExampleComponent, + data: { + srlc: { + hideIndicator: true, + }, + }, + }, +]; + +@NgModule({ + imports: [ + RouterModule.forChild(routes), + NuiButtonModule, + NuiDocsModule, + NuiMessageModule, + NuiDashboardsModule, + NuiSwitchModule, + ], + declarations: [ + DrilldownDocsComponent, + DrilldownWidgetExampleComponent, + DrilldownMultiRequestWidgetExampleComponent, + ], + providers: [ + { + provide: DEMO_PATH_TOKEN, + useValue: getDemoFiles("drilldown"), + }, + ], +}) +export default class DrilldownDocsModule {} +\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\`, + "widget-types/embedded-content/embedded-content-docs.component.ts": \\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\`// © 2022 SolarWinds Worldwide, LLC. All rights reserved. +// +// Permission is hereby granted, free of charge, to any person obtaining a copy +// of this software and associated documentation files (the "Software"), to +// deal in the Software without restriction, including without limitation the +// rights to use, copy, modify, merge, publish, distribute, sublicense, and/or +// sell copies of the Software, and to permit persons to whom the Software is +// furnished to do so, subject to the following conditions: +// +// The above copyright notice and this permission notice shall be included in +// all copies or substantial portions of the Software. +// +// THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR +// IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, +// FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE +// AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER +// LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, +// OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN +// THE SOFTWARE. + +import { Component, OnInit } from "@angular/core"; + +import { mapContentFile } from "../../../../demo-files-factory"; + +@Component({ + selector: "nui-embedded-content-docs", + templateUrl: "./embedded-content-docs.component.html", + standalone: false, +}) +export class EmbeddedContentDocsComponent implements OnInit { + public embeddedContentWidgetFileText = ""; + public embeddedContentConfiguratorFileText = ""; + + public async ngOnInit(): Promise { + this.embeddedContentWidgetFileText = await import( + "./../../../../../../src/lib/widget-types/embedded-content/embedded-content-widget" + ).then(mapContentFile); + this.embeddedContentWidgetFileText = await import( + "./../../../../../../src/lib/widget-types/embedded-content/embedded-content-configurator" + ).then(mapContentFile); + } +} +\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\`, + "widget-types/embedded-content/embedded-content-docs.module.ts": \\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\`// © 2022 SolarWinds Worldwide, LLC. All rights reserved. +// +// Permission is hereby granted, free of charge, to any person obtaining a copy +// of this software and associated documentation files (the "Software"), to +// deal in the Software without restriction, including without limitation the +// rights to use, copy, modify, merge, publish, distribute, sublicense, and/or +// sell copies of the Software, and to permit persons to whom the Software is +// furnished to do so, subject to the following conditions: +// +// The above copyright notice and this permission notice shall be included in +// all copies or substantial portions of the Software. +// +// THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR +// IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, +// FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE +// AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER +// LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, +// OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN +// THE SOFTWARE. + +import { NgModule } from "@angular/core"; +import { RouterModule, Routes } from "@angular/router"; + +// eslint-disable-next-line max-len +import { + NuiButtonModule, + NuiDocsModule, + NuiMessageModule, + NuiSwitchModule, + DEMO_PATH_TOKEN, +} from "@nova-ui/bits"; +import { NuiDashboardsModule } from "@nova-ui/dashboards"; + +import { EmbeddedContentDocsComponent } from "./embedded-content-docs.component"; +import { EmbeddedContentWidgetExampleComponent } from "./embedded-content-widget-example/embedded-content-widget-example.component"; +import { getDemoFiles } from "../../../../demo-files-factory"; + +const routes: Routes = [ + { + path: "", + component: EmbeddedContentDocsComponent, + data: { + srlc: { + hideIndicator: true, + }, + }, + }, + { + path: "example", + component: EmbeddedContentWidgetExampleComponent, + data: { + srlc: { + hideIndicator: true, + }, + }, + }, +]; + +@NgModule({ + imports: [ + RouterModule.forChild(routes), + NuiButtonModule, + NuiDocsModule, + NuiMessageModule, + NuiDashboardsModule, + NuiSwitchModule, + ], + declarations: [ + EmbeddedContentDocsComponent, + EmbeddedContentWidgetExampleComponent, + ], + providers: [ + { + provide: DEMO_PATH_TOKEN, + useValue: getDemoFiles("embedded-content"), + }, + ], +}) +export default class EmbeddedContentDocsModule {} +\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\`, + "widget-types/embedded-content/embedded-content-widget-example/embedded-content-widget-example.component.ts": \\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\`// © 2022 SolarWinds Worldwide, LLC. All rights reserved. +// +// Permission is hereby granted, free of charge, to any person obtaining a copy +// of this software and associated documentation files (the "Software"), to +// deal in the Software without restriction, including without limitation the +// rights to use, copy, modify, merge, publish, distribute, sublicense, and/or +// sell copies of the Software, and to permit persons to whom the Software is +// furnished to do so, subject to the following conditions: +// +// The above copyright notice and this permission notice shall be included in +// all copies or substantial portions of the Software. +// +// THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR +// IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, +// FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE +// AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER +// LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, +// OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN +// THE SOFTWARE. + +import { ChangeDetectorRef, Component, OnInit } from "@angular/core"; +import { GridsterConfig, GridsterItem } from "angular-gridster2"; + +import { + ComponentRegistryService, + EmbeddedContentComponent, + EmbeddedContentConfigurationComponent, + EmbeddedContentMode, + IDashboard, + IWidget, + IWidgets, + PizzagnaLayer, + WidgetTypesService, +} from "@nova-ui/dashboards"; + +@Component({ + selector: "embedded-content-widget-example", + templateUrl: "./embedded-content-widget-example.component.html", + styleUrls: ["./embedded-content-widget-example.component.less"], + standalone: false, +}) +export class EmbeddedContentWidgetExampleComponent implements OnInit { + // This variable will hold all the data needed to define the layout and behavior of the widgets. + // Pass this to the dashboard component's dashboard input in the template. + public dashboard: IDashboard | undefined; + + // Angular gridster requires a configuration object even if it's empty. + // Pass this to the dashboard component's gridsterConfig input in the template. + public gridsterConfig: GridsterConfig = {}; + + // Boolean passed as an input to the dashboard. When true, widgets can be moved, resized, removed, or edited + public editMode: boolean = false; + + constructor( + // WidgetTypesService provides the widget's necessary structure information + private widgetTypesService: WidgetTypesService, + + private componentRegistry: ComponentRegistryService, + private changeDetectorRef: ChangeDetectorRef + ) {} + + public ngOnInit(): void { + this.prepareNovaDashboards(); + this.initializeDashboard(); + } + + /** Used for restoring widgets state */ + public reInitializeDashboard(): void { + // destroys the components and their providers so the dashboard can re init data + this.dashboard = undefined; + this.changeDetectorRef.detectChanges(); + + this.initializeDashboard(); + } + + public initializeDashboard(): void { + // We're using a static configuration object for this example, but this is where + // the widget's configuration could potentially be populated from a database + const embeddedContentWidget = widgetConfig; + const widgets: IWidgets = { + // Complete the widget with information coming from its type definition + [embeddedContentWidget.id]: + this.widgetTypesService.mergeWithWidgetType( + embeddedContentWidget + ), + }; + + // Setting the widget dimensions and position (this is for gridster) + const positions: Record = { + [embeddedContentWidget.id]: { + cols: 10, + rows: 10, + y: 0, + x: 0, + }, + }; + + // Finally, assigning the variables we created above to the dashboard + this.dashboard = { positions, widgets }; + } + + private prepareNovaDashboards() { + this.componentRegistry.registerByLateLoadKey(EmbeddedContentComponent); + this.componentRegistry.registerByLateLoadKey( + EmbeddedContentConfigurationComponent + ); + } +} + +const widgetConfig: IWidget = { + id: "embeddedContentWidgetId", + type: "embedded-content", + pizzagna: { + [PizzagnaLayer.Configuration]: { + header: { + properties: { + title: "Embedded Content Widget", + subtitle: "", + }, + }, + mainContent: { + properties: { + sanitized: true, + mode: EmbeddedContentMode.URL, + customEmbeddedContent: "https://www.ventusky.com/", + }, + }, + }, + }, +}; +\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\`, + "widget-types/kpi/kpi-docs.component.ts": \\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\`// © 2022 SolarWinds Worldwide, LLC. All rights reserved. +// +// Permission is hereby granted, free of charge, to any person obtaining a copy +// of this software and associated documentation files (the "Software"), to +// deal in the Software without restriction, including without limitation the +// rights to use, copy, modify, merge, publish, distribute, sublicense, and/or +// sell copies of the Software, and to permit persons to whom the Software is +// furnished to do so, subject to the following conditions: +// +// The above copyright notice and this permission notice shall be included in +// all copies or substantial portions of the Software. +// +// THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR +// IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, +// FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE +// AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER +// LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, +// OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN +// THE SOFTWARE. + +import { Component, OnInit } from "@angular/core"; + +import { mapContentFile } from "../../../../demo-files-factory"; + +@Component({ + selector: "nui-kpi-docs", + templateUrl: "./kpi-docs.component.html", + standalone: false, +}) +export class KpiDocsComponent implements OnInit { + public kpiWidgetFileText = ""; + public kpiConfiguratorFileText = ""; + + public async ngOnInit(): Promise { + this.kpiWidgetFileText = await import( + "./../../../../../../src/lib/widget-types/kpi/kpi-widget" + ).then(mapContentFile); + this.kpiConfiguratorFileText = await import( + "./../../../../../../src/lib/widget-types/kpi/kpi-configurator" + ).then(mapContentFile); + } +} +\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\`, + "widget-types/kpi/kpi-docs.module.ts": \\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\`// © 2022 SolarWinds Worldwide, LLC. All rights reserved. +// +// Permission is hereby granted, free of charge, to any person obtaining a copy +// of this software and associated documentation files (the "Software"), to +// deal in the Software without restriction, including without limitation the +// rights to use, copy, modify, merge, publish, distribute, sublicense, and/or +// sell copies of the Software, and to permit persons to whom the Software is +// furnished to do so, subject to the following conditions: +// +// The above copyright notice and this permission notice shall be included in +// all copies or substantial portions of the Software. +// +// THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR +// IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, +// FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE +// AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER +// LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, +// OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN +// THE SOFTWARE. + +import { NgModule } from "@angular/core"; +import { RouterModule, Routes } from "@angular/router"; + +import { + NuiButtonModule, + NuiDocsModule, + NuiMessageModule, + NuiSwitchModule, + DEMO_PATH_TOKEN, +} from "@nova-ui/bits"; +import { + KpiColorComparatorsRegistryService, + NuiDashboardsModule, +} from "@nova-ui/dashboards"; + +import { KpiDocsComponent } from "./kpi-docs.component"; +import { KpiSyncBrokerExampleComponent } from "./kpi-sync-broker/kpi-sync-broker-example.component"; +import { KpiSyncBrokerDocsComponent } from "./kpi-sync-broker-docs.component"; +import { KpiSyncBrokerForAllTilesExampleComponent } from "./kpi-sync-broker-for-all-tiles/kpi-sync-broker-for-all-tiles-example.component"; +import { KpiWidgetExampleComponent } from "./kpi-widget/kpi-widget-example.component"; +import { KpiWidgetBackgroundColorExampleComponent } from "./kpi-widget-background-color/kpi-widget-background-color-example.component"; +import { KpiWidgetBackgroundColorDocsComponent } from "./kpi-widget-background-color-docs.component"; +import { KpiWidgetInteractiveExampleComponent } from "./kpi-widget-interactive/kpi-widget-interactive-example.component"; +import { getDemoFiles } from "../../../../demo-files-factory"; + +const routes: Routes = [ + { + path: "", + component: KpiDocsComponent, + data: { + srlc: { + hideIndicator: true, + }, + showThemeSwitcher: true, + }, + }, + { + path: "example", + component: KpiWidgetExampleComponent, + data: { + srlc: { + hideIndicator: true, + }, + }, + }, + { + path: "background-color", + component: KpiWidgetBackgroundColorDocsComponent, + data: { + srlc: { + hideIndicator: true, + }, + }, + }, + { + path: "sync-broker", + component: KpiSyncBrokerDocsComponent, + data: { + srlc: { + hideIndicator: true, + }, + }, + }, +]; + +@NgModule({ + imports: [ + RouterModule.forChild(routes), + NuiButtonModule, + NuiDocsModule, + NuiMessageModule, + NuiDashboardsModule, + NuiSwitchModule, + ], + declarations: [ + KpiDocsComponent, + KpiWidgetExampleComponent, + KpiWidgetInteractiveExampleComponent, + KpiWidgetBackgroundColorDocsComponent, + KpiWidgetBackgroundColorExampleComponent, + KpiSyncBrokerDocsComponent, + KpiSyncBrokerExampleComponent, + KpiSyncBrokerForAllTilesExampleComponent, + ], + providers: [ + KpiColorComparatorsRegistryService, + { + provide: DEMO_PATH_TOKEN, + useValue: getDemoFiles("kpi"), + }, + ], +}) +export default class KpiDocsModule { + constructor( + private comparatorsRegistry: KpiColorComparatorsRegistryService + ) { + this.backgroundColorDocsSetup(); + } + + private backgroundColorDocsSetup() { + this.comparatorsRegistry.registerComparators({ + "!=": { + comparatorFn: (actual: any, reference: any) => + // eslint-disable-next-line eqeqeq + actual != reference, + label: "Not equal", + }, + }); + } +} +\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\`, + "widget-types/kpi/kpi-sync-broker/kpi-sync-broker-example.component.ts": \\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\`// © 2022 SolarWinds Worldwide, LLC. All rights reserved. +// +// Permission is hereby granted, free of charge, to any person obtaining a copy +// of this software and associated documentation files (the "Software"), to +// deal in the Software without restriction, including without limitation the +// rights to use, copy, modify, merge, publish, distribute, sublicense, and/or +// sell copies of the Software, and to permit persons to whom the Software is +// furnished to do so, subject to the following conditions: +// +// The above copyright notice and this permission notice shall be included in +// all copies or substantial portions of the Software. +// +// THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR +// IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, +// FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE +// AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER +// LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, +// OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN +// THE SOFTWARE. + +import { HttpClient, HttpErrorResponse } from "@angular/common/http"; +import { + ChangeDetectorRef, + Component, + Injectable, + OnDestroy, + OnInit, +} from "@angular/core"; +import { GridsterConfig, GridsterItem } from "angular-gridster2"; +import keyBy from "lodash/keyBy"; +import { BehaviorSubject, of } from "rxjs"; +import { delay, finalize, take } from "rxjs/operators"; + +import { DataSourceService, IFilteringOutputs } from "@nova-ui/bits"; +import { + DATA_SOURCE, + IDashboard, + IKpiData, + IProviderConfiguration, + IWidget, + KpiComponent, + NOVA_KPI_DATASOURCE_ADAPTER, + NOVA_KPI_SCALE_SYNC_BROKER, + PizzagnaLayer, + ProviderRegistryService, + WellKnownPathKey, + WellKnownProviders, + WidgetTypesService, +} from "@nova-ui/dashboards"; + +/** + * A simple KPI data source to retrieve the average rating of Harry Potter and the Sorcerer's Stone (book) via googleapis + */ +@Injectable() +export class AverageRatingKpiDataSource + extends DataSourceService + implements OnDestroy +{ + public static providerId = "AverageRatingKpiDataSource"; + public busy = new BehaviorSubject(false); + + constructor(private http: HttpClient) { + super(); + } + + public async getFilteredData(): Promise { + this.busy.next(true); + return new Promise((resolve) => { + // *** Make a resource request to an external API (if needed) + this.http + .get("https://www.googleapis.com/books/v1/volumes/5MQFrgEACAAJ") + .pipe(finalize(() => this.busy.next(false))) + .subscribe({ + next: (data: any) => { + resolve({ + result: { + value: data.volumeInfo.averageRating, + }, + }); + }, + error: (error: HttpErrorResponse) => { + resolve({ + result: null, + error: { + type: error.status, + }, + }); + }, + }); + }); + } + + public ngOnDestroy(): void { + this.outputsSubject.complete(); + } +} + +/** + * A simple KPI data source to retrieve the ratings count of Harry Potter and the Sorcerer's Stone (book) via googleapis + */ +@Injectable() +export class RatingsCountKpiDataSource + extends DataSourceService + implements OnDestroy +{ + public static providerId = "RatingsCountKpiDataSource"; + + // Use this subject to communicate the data source's busy state + public busy = new BehaviorSubject(false); + + constructor(private http: HttpClient) { + super(); + } + + public async getFilteredData(): Promise { + this.busy.next(true); + return new Promise((resolve) => { + this.http + .get("https://www.googleapis.com/books/v1/volumes/5MQFrgEACAAJ") + .pipe( + delay(2000), + finalize(() => this.busy.next(false)) + ) + .subscribe({ + next: (data: any) => { + resolve({ + result: { + value: data.volumeInfo.ratingsCount, + }, + }); + }, + error: (error: HttpErrorResponse) => { + resolve({ + result: null, + error: { + type: error.status, + }, + }); + }, + }); + }); + } + + public ngOnDestroy(): void { + this.outputsSubject.complete(); + } +} +/** + * A simple KPI data source to retrieve the ratings count of Harry Potter and the Sorcerer's Stone (book) via googleapis + */ +@Injectable() +export class MockKpiDataSource + extends DataSourceService + implements OnDestroy +{ + public static providerId = "MockKpiDataSource"; + + // Use this subject to communicate the data source's busy state + public busy = new BehaviorSubject(false); + + constructor() { + super(); + } + + public async getFilteredData(): Promise { + this.busy.next(true); + return new Promise((resolve) => { + of(3381342) + .pipe( + delay(5000), + take(1), + finalize(() => this.busy.next(false)) + ) + .subscribe({ + next: (data: any) => { + resolve({ + result: { + value: data, + }, + }); + }, + }); + }); + } + + public ngOnDestroy(): void { + this.outputsSubject.complete(); + } +} + +/** + * A component that instantiates the dashboard + */ +@Component({ + selector: "kpi-sync-broker-example", + templateUrl: "./kpi-sync-broker-example.component.html", + styleUrls: ["./kpi-sync-broker-example.component.less"], + standalone: false, +}) +export class KpiSyncBrokerExampleComponent implements OnInit { + public dashboard: IDashboard | undefined; + public gridsterConfig: GridsterConfig = {}; + public editMode: boolean = false; + + constructor( + private widgetTypesService: WidgetTypesService, + private providerRegistry: ProviderRegistryService, + private changeDetectorRef: ChangeDetectorRef + ) {} + + public ngOnInit(): void { + this.setupDashboard(); + + this.initializeDashboard(); + } + + private setupDashboard() { + const widgetTemplate = this.widgetTypesService.getWidgetType("kpi", 1); + + this.widgetTypesService.setNode( + widgetTemplate, + "configurator", + WellKnownPathKey.DataSourceProviders, + [ + AverageRatingKpiDataSource.providerId, + RatingsCountKpiDataSource.providerId, + MockKpiDataSource.providerId, + ] + ); + + this.providerRegistry.setProviders({ + [AverageRatingKpiDataSource.providerId]: { + provide: DATA_SOURCE, + useClass: AverageRatingKpiDataSource, + deps: [HttpClient], + }, + [RatingsCountKpiDataSource.providerId]: { + provide: DATA_SOURCE, + useClass: RatingsCountKpiDataSource, + deps: [HttpClient], + }, + [MockKpiDataSource.providerId]: { + provide: DATA_SOURCE, + useClass: MockKpiDataSource, + deps: [], + }, + }); + } + + /** Used for restoring widgets state */ + public reInitializeDashboard(): void { + // destroys the components and their providers so the dashboard can re init data + this.dashboard = undefined; + this.changeDetectorRef.detectChanges(); + + this.initializeDashboard(); + } + + private initializeDashboard(): void { + const widgetsWithStructure = widgetsConfig.map((w) => + this.widgetTypesService.mergeWithWidgetType(w) + ); + const widgetsIndex = keyBy(widgetsWithStructure, (w: IWidget) => w.id); + + const positions: Record = { + kpiWidgetId: { + cols: 3, + rows: 6, + y: 0, + x: 0, + }, + kpiWidgetId2: { + cols: 3, + rows: 6, + y: 0, + x: 0, + }, + }; + + this.dashboard = { + positions, + widgets: widgetsIndex, + }; + } +} + +const widgetsConfig: IWidget[] = [ + { + id: "kpiWidgetId", + type: "kpi", + pizzagna: { + [PizzagnaLayer.Configuration]: { + header: { + properties: { + title: "NO Sync Broker", + subtitle: "Values sizes are being not synced", + }, + }, + tiles: { + properties: { + nodes: ["kpi1", "kpi2", "kpi3"], + }, + }, + kpi1: { + id: "kpi1", + componentType: KpiComponent.lateLoadKey, + properties: { + widgetData: { + units: \\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\`out of 5 Stars\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\`, + label: \\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\`Average Rating\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\`, + backgroundColor: "lightpink", + }, + }, + providers: { + [WellKnownProviders.DataSource]: { + providerId: AverageRatingKpiDataSource.providerId, + } as IProviderConfiguration, + [WellKnownProviders.Adapter]: { + providerId: NOVA_KPI_DATASOURCE_ADAPTER, + properties: { + componentId: "kpi1", + propertyPath: "widgetData", + }, + } as IProviderConfiguration, + }, + }, + kpi2: { + id: "kpi2", + componentType: KpiComponent.lateLoadKey, + properties: { + widgetData: { + label: \\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\`Another label which might be a pretty long one\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\`, + units: \\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\`Which comes from somewhere\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\`, + backgroundColor: "skyblue", + }, + }, + providers: { + [WellKnownProviders.DataSource]: { + providerId: RatingsCountKpiDataSource.providerId, + } as IProviderConfiguration, + [WellKnownProviders.Adapter]: { + providerId: NOVA_KPI_DATASOURCE_ADAPTER, + properties: { + componentId: "kpi2", + propertyPath: "widgetData", + }, + } as IProviderConfiguration, + }, + }, + kpi3: { + id: "kpi3", + componentType: KpiComponent.lateLoadKey, + properties: { + widgetData: { + label: \\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\`Random\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\`, + units: \\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\`Data\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\`, + }, + }, + providers: { + [WellKnownProviders.DataSource]: { + providerId: MockKpiDataSource.providerId, + } as IProviderConfiguration, + [WellKnownProviders.Adapter]: { + providerId: NOVA_KPI_DATASOURCE_ADAPTER, + properties: { + componentId: "kpi3", + propertyPath: "widgetData", + }, + } as IProviderConfiguration, + }, + }, + }, + }, + }, + { + id: "kpiWidgetId2", + type: "kpi", + pizzagna: { + [PizzagnaLayer.Configuration]: { + header: { + properties: { + title: "WITH Sync Broker", + subtitle: + "Now the values of label, units, and value are being synced", + }, + }, + tiles: { + properties: { + nodes: ["kpi4", "kpi5", "kpi6"], + }, + providers: { + // This is where and how you set the sync broker provider + kpiScaleSyncBroker: { + providerId: NOVA_KPI_SCALE_SYNC_BROKER, + properties: { + scaleSyncConfig: [ + // You can decide which values to keep in sync. For instance, you can leave only 'label' id in the array below + { id: "value" }, + { id: "label" }, + { id: "units" }, + ], + }, + }, + }, + }, + kpi4: { + id: "kpi4", + componentType: KpiComponent.lateLoadKey, + properties: { + widgetData: { + units: \\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\`out of 5 Stars\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\`, + label: \\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\`Average Rating\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\`, + backgroundColor: "lightpink", + }, + }, + providers: { + [WellKnownProviders.DataSource]: { + providerId: AverageRatingKpiDataSource.providerId, + } as IProviderConfiguration, + [WellKnownProviders.Adapter]: { + providerId: NOVA_KPI_DATASOURCE_ADAPTER, + properties: { + componentId: "kpi4", + propertyPath: "widgetData", + }, + } as IProviderConfiguration, + }, + }, + kpi5: { + id: "kpi5", + componentType: KpiComponent.lateLoadKey, + properties: { + widgetData: { + label: \\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\`Another label which might be a pretty long one\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\`, + units: \\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\`Which comes from somewhere\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\`, + backgroundColor: "skyblue", + }, + }, + providers: { + [WellKnownProviders.DataSource]: { + providerId: RatingsCountKpiDataSource.providerId, + } as IProviderConfiguration, + [WellKnownProviders.Adapter]: { + providerId: NOVA_KPI_DATASOURCE_ADAPTER, + properties: { + componentId: "kpi5", + propertyPath: "widgetData", + }, + } as IProviderConfiguration, + }, + }, + kpi6: { + id: "kpi6", + componentType: KpiComponent.lateLoadKey, + properties: { + widgetData: { + label: \\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\`Random\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\`, + units: \\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\`Data\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\`, + }, + }, + providers: { + [WellKnownProviders.DataSource]: { + providerId: MockKpiDataSource.providerId, + } as IProviderConfiguration, + [WellKnownProviders.Adapter]: { + providerId: NOVA_KPI_DATASOURCE_ADAPTER, + properties: { + componentId: "kpi6", + propertyPath: "widgetData", + }, + } as IProviderConfiguration, + }, + }, + }, + }, + }, +]; +\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\`, + "widget-types/kpi/kpi-sync-broker-docs.component.ts": \\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\`// © 2022 SolarWinds Worldwide, LLC. All rights reserved. +// +// Permission is hereby granted, free of charge, to any person obtaining a copy +// of this software and associated documentation files (the "Software"), to +// deal in the Software without restriction, including without limitation the +// rights to use, copy, modify, merge, publish, distribute, sublicense, and/or +// sell copies of the Software, and to permit persons to whom the Software is +// furnished to do so, subject to the following conditions: +// +// The above copyright notice and this permission notice shall be included in +// all copies or substantial portions of the Software. +// +// THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR +// IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, +// FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE +// AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER +// LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, +// OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN +// THE SOFTWARE. + +import { Component } from "@angular/core"; + +@Component({ + selector: "kpi-sync-broker-docs", + templateUrl: "./kpi-sync-broker-docs.component.html", + standalone: false, +}) +export class KpiSyncBrokerDocsComponent { + public kpiScaleSyncBroker = \\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\` +"tiles": { + "providers": { + kpiScaleSyncBroker: { + providerId: NOVA_KPI_SCALE_SYNC_BROKER, + properties: { + scaleSyncConfig: [ + { id: "value" }, + { id: "label" }, + { id: "units" }, + ], + }, + }, + }, +}, +\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\`; + + public defineScaleBrokerOnDashboardSetup = \\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\` +// To add the sync broker globally to all the kpi tiles you may start with setting up the broker config +// Here you define which values to keep in sync +const brokerConfig = { + providerId: NOVA_KPI_SCALE_SYNC_BROKER, + properties: { + scaleSyncConfig: [ + { id: "value" }, + { id: "label" }, + { id: "units" }, + ], + }, + }; + +// And here is how you set the sync broker for every KPI widget in the dashboard. +// Later, you will be able to override this setting for each separate KPI widget in the configuration (just like it is shown in the third +// width of the example with the 'kpiWidgetId3') +this.widgetTypesService.setNode( + widgetTemplate, + "widget", + "tiles.providers.kpiScaleSyncBroker", + brokerConfig +); +\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\`; +} +\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\`, + "widget-types/kpi/kpi-sync-broker-for-all-tiles/kpi-sync-broker-for-all-tiles-example.component.ts": \\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\`// © 2022 SolarWinds Worldwide, LLC. All rights reserved. +// +// Permission is hereby granted, free of charge, to any person obtaining a copy +// of this software and associated documentation files (the "Software"), to +// deal in the Software without restriction, including without limitation the +// rights to use, copy, modify, merge, publish, distribute, sublicense, and/or +// sell copies of the Software, and to permit persons to whom the Software is +// furnished to do so, subject to the following conditions: +// +// The above copyright notice and this permission notice shall be included in +// all copies or substantial portions of the Software. +// +// THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR +// IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, +// FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE +// AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER +// LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, +// OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN +// THE SOFTWARE. + +import { HttpClient, HttpErrorResponse } from "@angular/common/http"; +import { + ChangeDetectorRef, + Component, + Injectable, + OnDestroy, + OnInit, +} from "@angular/core"; +import { GridsterConfig, GridsterItem } from "angular-gridster2"; +import keyBy from "lodash/keyBy"; +import { BehaviorSubject, of } from "rxjs"; +import { delay, finalize, take } from "rxjs/operators"; + +import { DataSourceService, IFilteringOutputs } from "@nova-ui/bits"; +import { + DATA_SOURCE, + IDashboard, + IKpiData, + IProviderConfiguration, + IWidget, + KpiComponent, + NOVA_KPI_DATASOURCE_ADAPTER, + NOVA_KPI_SCALE_SYNC_BROKER, + PizzagnaLayer, + ProviderRegistryService, + WellKnownPathKey, + WellKnownProviders, + WidgetTypesService, +} from "@nova-ui/dashboards"; + +/** + * A simple KPI data source to retrieve the average rating of Harry Potter and the Sorcerer's Stone (book) via googleapis + */ +@Injectable() +export class AverageRatingKpiDataSource + extends DataSourceService + implements OnDestroy +{ + public static providerId = "AverageRatingKpiDataSource"; + public busy = new BehaviorSubject(false); + + constructor(private http: HttpClient) { + super(); + } + + public async getFilteredData(): Promise { + this.busy.next(true); + return new Promise((resolve) => { + // *** Make a resource request to an external API (if needed) + this.http + .get("https://www.googleapis.com/books/v1/volumes/5MQFrgEACAAJ") + .pipe(finalize(() => this.busy.next(false))) + .subscribe({ + next: (data: any) => { + resolve({ + result: { + value: data.volumeInfo.averageRating, + }, + }); + }, + error: (error: HttpErrorResponse) => { + resolve({ + result: null, + error: { + type: error.status, + }, + }); + }, + }); + }); + } + + public ngOnDestroy(): void { + this.outputsSubject.complete(); + } +} + +/** + * A simple KPI data source to retrieve the ratings count of Harry Potter and the Sorcerer's Stone (book) via googleapis + */ +@Injectable() +export class RatingsCountKpiDataSource + extends DataSourceService + implements OnDestroy +{ + public static providerId = "RatingsCountKpiDataSource"; + + // Use this subject to communicate the data source's busy state + public busy = new BehaviorSubject(false); + + constructor(private http: HttpClient) { + super(); + } + + public async getFilteredData(): Promise { + this.busy.next(true); + return new Promise((resolve) => { + this.http + .get("https://www.googleapis.com/books/v1/volumes/5MQFrgEACAAJ") + .pipe( + delay(2000), + finalize(() => this.busy.next(false)) + ) + .subscribe({ + next: (data: any) => { + resolve({ + result: { + value: data.volumeInfo.ratingsCount, + }, + }); + }, + error: (error: HttpErrorResponse) => { + resolve({ + result: null, + error: { + type: error.status, + }, + }); + }, + }); + }); + } + + public ngOnDestroy(): void { + this.outputsSubject.complete(); + } +} +/** + * A simple KPI data source to retrieve the ratings count of Harry Potter and the Sorcerer's Stone (book) via googleapis + */ +@Injectable() +export class MockKpiDataSource + extends DataSourceService + implements OnDestroy +{ + public static providerId = "MockKpiDataSource"; + + // Use this subject to communicate the data source's busy state + public busy = new BehaviorSubject(false); + public value: number = 3381342; + + constructor() { + super(); + } + + public async getFilteredData(): Promise { + this.busy.next(true); + return new Promise((resolve) => { + of(this.value) + .pipe( + delay(5000), + take(1), + finalize(() => this.busy.next(false)) + ) + .subscribe({ + next: (data: any) => { + resolve({ + result: { + value: data, + }, + }); + }, + }); + }); + } + + public ngOnDestroy(): void { + this.outputsSubject.complete(); + } +} + +/** + * A component that instantiates the dashboard + */ +@Component({ + selector: "kpi-sync-broker-for-all-tiles-example", + templateUrl: "./kpi-sync-broker-for-all-tiles-example.component.html", + styleUrls: ["./kpi-sync-broker-for-all-tiles-example.component.less"], + standalone: false, +}) +export class KpiSyncBrokerForAllTilesExampleComponent implements OnInit { + public dashboard: IDashboard | undefined; + public gridsterConfig: GridsterConfig = {}; + public editMode: boolean = false; + + constructor( + private widgetTypesService: WidgetTypesService, + private providerRegistry: ProviderRegistryService, + private changeDetectorRef: ChangeDetectorRef + ) {} + + public ngOnInit(): void { + this.setupDashboard(); + + this.initializeDashboard(); + } + + /** Used for restoring widgets state */ + public reInitializeDashboard(): void { + // destroys the components and their providers so the dashboard can re init data + this.dashboard = undefined; + this.changeDetectorRef.detectChanges(); + + this.initializeDashboard(); + } + + private setupDashboard() { + // To add the sync broker globally to all the kpi tiles you may start with setting up the broker config + // Here you define which values to keep in sync + const brokerConfig = { + providerId: NOVA_KPI_SCALE_SYNC_BROKER, + properties: { + scaleSyncConfig: [ + { id: "value" }, + { id: "label" }, + { id: "units" }, + ], + }, + }; + const widgetTemplate = this.widgetTypesService.getWidgetType("kpi", 1); + + this.widgetTypesService.setNode( + widgetTemplate, + "configurator", + WellKnownPathKey.DataSourceProviders, + [ + AverageRatingKpiDataSource.providerId, + RatingsCountKpiDataSource.providerId, + MockKpiDataSource.providerId, + ] + ); + + // And here is how you set the sync broker for every KPI widget in the dashboard. + // Later, you will be able to override this setting for each separate KPI widget in the configuration (just like it is shown in the third + // width of the example with the 'kpiWidgetId3') + this.widgetTypesService.setNode( + widgetTemplate, + "widget", + "tiles.providers.kpiScaleSyncBroker", + brokerConfig + ); + + this.providerRegistry.setProviders({ + [AverageRatingKpiDataSource.providerId]: { + provide: DATA_SOURCE, + useClass: AverageRatingKpiDataSource, + deps: [HttpClient], + }, + [RatingsCountKpiDataSource.providerId]: { + provide: DATA_SOURCE, + useClass: RatingsCountKpiDataSource, + deps: [HttpClient], + }, + [MockKpiDataSource.providerId]: { + provide: DATA_SOURCE, + useClass: MockKpiDataSource, + deps: [], + }, + }); + } + + private initializeDashboard(): void { + const widgetsWithStructure = widgetsConfig.map((w) => + this.widgetTypesService.mergeWithWidgetType(w) + ); + const widgetsIndex = keyBy(widgetsWithStructure, (w: IWidget) => w.id); + + const positions: Record = { + kpiWidgetId: { + cols: 3, + rows: 6, + y: 0, + x: 0, + }, + kpiWidgetId2: { + cols: 3, + rows: 6, + y: 0, + x: 3, + }, + kpiWidgetId3: { + cols: 3, + rows: 6, + y: 0, + x: 6, + }, + }; + + this.dashboard = { + positions, + widgets: widgetsIndex, + }; + } +} + +const widgetsConfig: IWidget[] = [ + { + id: "kpiWidgetId", + type: "kpi", + pizzagna: { + [PizzagnaLayer.Configuration]: { + header: { + properties: { + title: "Sync Broker Applied for ALL Widgets", + subtitle: "Values are being synced", + }, + }, + tiles: { + properties: { + nodes: ["kpi1", "kpi2", "kpi3"], + }, + }, + kpi1: { + id: "kpi1", + componentType: KpiComponent.lateLoadKey, + properties: { + widgetData: { + units: \\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\`out of 5 Stars\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\`, + label: \\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\`Average Rating\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\`, + backgroundColor: "lightpink", + }, + }, + providers: { + [WellKnownProviders.DataSource]: { + providerId: AverageRatingKpiDataSource.providerId, + } as IProviderConfiguration, + [WellKnownProviders.Adapter]: { + providerId: NOVA_KPI_DATASOURCE_ADAPTER, + properties: { + componentId: "kpi1", + propertyPath: "widgetData", + }, + } as IProviderConfiguration, + }, + }, + kpi2: { + id: "kpi2", + componentType: KpiComponent.lateLoadKey, + properties: { + widgetData: { + label: \\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\`Another label which might be a pretty long one\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\`, + units: \\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\`Which comes from somewhere\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\`, + backgroundColor: "skyblue", + }, + }, + providers: { + [WellKnownProviders.DataSource]: { + providerId: RatingsCountKpiDataSource.providerId, + } as IProviderConfiguration, + [WellKnownProviders.Adapter]: { + providerId: NOVA_KPI_DATASOURCE_ADAPTER, + properties: { + componentId: "kpi2", + propertyPath: "widgetData", + }, + } as IProviderConfiguration, + }, + }, + kpi3: { + id: "kpi3", + componentType: KpiComponent.lateLoadKey, + properties: { + widgetData: { + label: \\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\`Random\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\`, + units: \\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\`Data\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\`, + }, + }, + providers: { + [WellKnownProviders.DataSource]: { + providerId: MockKpiDataSource.providerId, + } as IProviderConfiguration, + [WellKnownProviders.Adapter]: { + providerId: NOVA_KPI_DATASOURCE_ADAPTER, + properties: { + componentId: "kpi3", + propertyPath: "widgetData", + }, + } as IProviderConfiguration, + }, + }, + }, + }, + }, + { + id: "kpiWidgetId2", + type: "kpi", + pizzagna: { + [PizzagnaLayer.Configuration]: { + header: { + properties: { + title: "Sync Broker Applied for ALL Widgets", + subtitle: + "Now the values of label, units, and value are being synced", + }, + }, + tiles: { + properties: { + nodes: ["kpi1", "kpi2", "kpi3"], + }, + }, + kpi1: { + id: "kpi1", + componentType: KpiComponent.lateLoadKey, + properties: { + widgetData: { + units: \\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\`out of 5 Stars\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\`, + label: \\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\`Average Rating\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\`, + backgroundColor: "lightpink", + }, + }, + providers: { + [WellKnownProviders.DataSource]: { + providerId: AverageRatingKpiDataSource.providerId, + } as IProviderConfiguration, + [WellKnownProviders.Adapter]: { + providerId: NOVA_KPI_DATASOURCE_ADAPTER, + properties: { + componentId: "kpi1", + propertyPath: "widgetData", + }, + } as IProviderConfiguration, + }, + }, + kpi2: { + id: "kpi2", + componentType: KpiComponent.lateLoadKey, + properties: { + widgetData: { + label: \\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\`Another label which might be a pretty long one\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\`, + units: \\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\`Which comes from somewhere\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\`, + backgroundColor: "skyblue", + }, + }, + providers: { + [WellKnownProviders.DataSource]: { + providerId: RatingsCountKpiDataSource.providerId, + } as IProviderConfiguration, + [WellKnownProviders.Adapter]: { + providerId: NOVA_KPI_DATASOURCE_ADAPTER, + properties: { + componentId: "kpi2", + propertyPath: "widgetData", + }, + } as IProviderConfiguration, + }, + }, + kpi3: { + id: "kpi3", + componentType: KpiComponent.lateLoadKey, + properties: { + widgetData: { + label: \\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\`Random\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\`, + units: \\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\`Data\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\`, + }, + }, + providers: { + [WellKnownProviders.DataSource]: { + providerId: MockKpiDataSource.providerId, + } as IProviderConfiguration, + [WellKnownProviders.Adapter]: { + providerId: NOVA_KPI_DATASOURCE_ADAPTER, + properties: { + componentId: "kpi3", + propertyPath: "widgetData", + }, + } as IProviderConfiguration, + }, + }, + }, + }, + }, + { + id: "kpiWidgetId3", + type: "kpi", + pizzagna: { + [PizzagnaLayer.Configuration]: { + header: { + properties: { + title: "Here We Sync Only Labels and Units", + subtitle: + "Now only the label, and units are being synced", + }, + }, + tiles: { + properties: { + nodes: ["kpi1", "kpi2", "kpi3"], + }, + providers: { + // This is where and how you can override the globally set broker config + kpiScaleSyncBroker: { + providerId: NOVA_KPI_SCALE_SYNC_BROKER, + properties: { + scaleSyncConfig: [ + { id: "label" }, + { id: "units" }, + ], + }, + }, + }, + }, + kpi1: { + id: "kpi1", + componentType: KpiComponent.lateLoadKey, + properties: { + widgetData: { + units: \\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\`out of 5 Stars\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\`, + label: \\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\`Average Rating\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\`, + backgroundColor: "lightpink", + }, + }, + providers: { + [WellKnownProviders.DataSource]: { + providerId: AverageRatingKpiDataSource.providerId, + } as IProviderConfiguration, + [WellKnownProviders.Adapter]: { + providerId: NOVA_KPI_DATASOURCE_ADAPTER, + properties: { + componentId: "kpi1", + propertyPath: "widgetData", + }, + } as IProviderConfiguration, + }, + }, + kpi2: { + id: "kpi2", + componentType: KpiComponent.lateLoadKey, + properties: { + widgetData: { + label: \\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\`Another label which might be a pretty long one\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\`, + units: \\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\`Which comes from somewhere\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\`, + backgroundColor: "skyblue", + }, + }, + providers: { + [WellKnownProviders.DataSource]: { + providerId: RatingsCountKpiDataSource.providerId, + } as IProviderConfiguration, + [WellKnownProviders.Adapter]: { + providerId: NOVA_KPI_DATASOURCE_ADAPTER, + properties: { + componentId: "kpi2", + propertyPath: "widgetData", + }, + } as IProviderConfiguration, + }, + }, + kpi3: { + id: "kpi3", + componentType: KpiComponent.lateLoadKey, + properties: { + widgetData: { + label: \\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\`Random\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\`, + units: \\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\`Data\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\`, + }, + }, + providers: { + [WellKnownProviders.DataSource]: { + providerId: MockKpiDataSource.providerId, + } as IProviderConfiguration, + [WellKnownProviders.Adapter]: { + providerId: NOVA_KPI_DATASOURCE_ADAPTER, + properties: { + componentId: "kpi3", + propertyPath: "widgetData", + }, + } as IProviderConfiguration, + }, + }, + }, + }, + }, +]; +\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\`, + "widget-types/kpi/kpi-widget/kpi-widget-example.component.ts": \\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\`// © 2022 SolarWinds Worldwide, LLC. All rights reserved. +// +// Permission is hereby granted, free of charge, to any person obtaining a copy +// of this software and associated documentation files (the "Software"), to +// deal in the Software without restriction, including without limitation the +// rights to use, copy, modify, merge, publish, distribute, sublicense, and/or +// sell copies of the Software, and to permit persons to whom the Software is +// furnished to do so, subject to the following conditions: +// +// The above copyright notice and this permission notice shall be included in +// all copies or substantial portions of the Software. +// +// THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR +// IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, +// FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE +// AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER +// LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, +// OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN +// THE SOFTWARE. + +import { HttpClient, HttpErrorResponse } from "@angular/common/http"; +import { Component, Injectable, OnDestroy, OnInit } from "@angular/core"; +import { GridsterConfig, GridsterItem } from "angular-gridster2"; +import { BehaviorSubject } from "rxjs"; +import { finalize } from "rxjs/operators"; + +import { DataSourceService, IFilteringOutputs } from "@nova-ui/bits"; +import { + DATA_SOURCE, + DEFAULT_PIZZAGNA_ROOT, + IDashboard, + IKpiData, + IProviderConfiguration, + IRefresherProperties, + IWidget, + IWidgets, + KpiComponent, + NOVA_KPI_DATASOURCE_ADAPTER, + PizzagnaLayer, + ProviderRegistryService, + WellKnownPathKey, + WellKnownProviders, + WidgetTypesService, +} from "@nova-ui/dashboards"; + +/** + * A simple KPI data source to retrieve the average rating of Harry Potter and the Sorcerer's Stone (book) via googleapis + */ +@Injectable() +export class AverageRatingKpiDataSource + extends DataSourceService + implements OnDestroy +{ + // This is the ID we'll use to identify the provider + public static providerId = "AverageRatingKpiDataSource"; + + // Use this subject to communicate the data source's busy state + public busy = new BehaviorSubject(false); + + constructor(private http: HttpClient) { + super(); + } + + // In this example, getFilteredData is invoked every 10 minutes (Take a look at the refresher + // provider definition in the widget configuration below to see how the interval is set) + public async getFilteredData(): Promise { + this.busy.next(true); + return new Promise((resolve) => { + // *** Make a resource request to an external API (if needed) + this.http + .get("https://www.googleapis.com/books/v1/volumes/5MQFrgEACAAJ") + .pipe(finalize(() => this.busy.next(false))) + .subscribe({ + next: (data: any) => { + resolve({ + result: { + value: data.volumeInfo.averageRating, + }, + }); + }, + error: (error: HttpErrorResponse) => { + resolve({ + result: null, + error: { + type: error.status, + }, + }); + }, + }); + }); + } + + public ngOnDestroy(): void { + this.outputsSubject.complete(); + } +} + +/** + * A component that instantiates the dashboard + */ +@Component({ + selector: "kpi-widget-example", + templateUrl: "./kpi-widget-example.component.html", + styleUrls: ["./kpi-widget-example.component.less"], + standalone: false, +}) +export class KpiWidgetExampleComponent implements OnInit { + // This variable will hold all the data needed to define the layout and behavior of the widgets. + // Pass this to the dashboard component's dashboard input in the template. + public dashboard: IDashboard; + + // Angular gridster requires a configuration object even if it's empty. + // Pass this to the dashboard component's gridsterConfig input in the template. + public gridsterConfig: GridsterConfig = {}; + + // Boolean passed as an input to the dashboard. When true, widgets can be moved, resized, removed, or edited + public editMode: boolean = false; + + constructor( + // WidgetTypesService provides the widget's necessary structure information + private widgetTypesService: WidgetTypesService, + + // In general, the ProviderRegistryService is used for making entities available for injection into dynamically loaded components. + private providerRegistry: ProviderRegistryService + ) {} + + public ngOnInit(): void { + // Grabbing the widget's default template which will be needed as a parameter for setNode + const widgetTemplate = this.widgetTypesService.getWidgetType("kpi", 1); + // Registering our data sources as dropdown options in the widget editor/configurator + // Note: This could also be done in the parent module's constructor so that + // multiple dashboards could have access to the same widget template modification. + this.widgetTypesService.setNode( + // This is the template we grabbed above with getWidgetType + widgetTemplate, + // We are setting the editor/configurator part of the widget template + "configurator", + // This indicates which node you are changing and we want to change + // the data source providers available for selection in the editor. + WellKnownPathKey.DataSourceProviders, + // We are setting the data sources available for selection in the editor + [AverageRatingKpiDataSource.providerId] + ); + + // Registering the data source for injection into the KPI tile. + // Note: Each tile of a KPI widget is assigned its own instance of the data source + this.providerRegistry.setProviders({ + [AverageRatingKpiDataSource.providerId]: { + provide: DATA_SOURCE, + useClass: AverageRatingKpiDataSource, + // Any dependencies that need to be injected into the provider must be listed here + deps: [HttpClient], + }, + }); + + this.initializeDashboard(); + } + + public initializeDashboard(): void { + // We're using a static configuration object for this example, but this is where + // the widget's configuration could potentially be populated from a database + const kpiWidget = widgetConfig; + const widgetIndex: IWidgets = { + // Complete the KPI widget with information coming from its type definition + [kpiWidget.id]: + this.widgetTypesService.mergeWithWidgetType(kpiWidget), + }; + + // Setting the widget dimensions and position (this is for gridster) + const positions: Record = { + [kpiWidget.id]: { + cols: 4, + rows: 6, + y: 0, + x: 0, + }, + }; + + // Finally, assigning the variables we created above to the dashboard + this.dashboard = { + positions, + widgets: widgetIndex, + }; + } +} + +const widgetConfig: IWidget = { + id: "kpiWidgetId", + type: "kpi", + pizzagna: { + [PizzagnaLayer.Configuration]: { + [DEFAULT_PIZZAGNA_ROOT]: { + providers: { + [WellKnownProviders.Refresher]: { + properties: { + // Configuring the refresher interval so that our data source is invoked every ten minutes + interval: 60 * 10, + enabled: true, + } as IRefresherProperties, + } as Partial, + }, + }, + header: { + properties: { + title: "Harry Potter and the Sorcerer's Stone", + subtitle: "By J. K. Rowling", + }, + }, + tiles: { + properties: { + nodes: ["kpi1"], + }, + }, + kpi1: { + id: "kpi1", + componentType: KpiComponent.lateLoadKey, + properties: { + widgetData: { + units: \\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\`out of 5 Stars\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\`, + label: \\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\`Average Rating\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\`, + }, + }, + providers: { + [WellKnownProviders.DataSource]: { + // Setting the data source providerId for the tile with id "kpi1" + providerId: AverageRatingKpiDataSource.providerId, + } as IProviderConfiguration, + [WellKnownProviders.Adapter]: { + providerId: NOVA_KPI_DATASOURCE_ADAPTER, + properties: { + componentId: "kpi1", + propertyPath: "widgetData", + }, + } as IProviderConfiguration, + }, + }, + }, + }, +}; +\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\`, + "widget-types/kpi/kpi-widget-background-color/kpi-widget-background-color-example.component.ts": \\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\`// © 2022 SolarWinds Worldwide, LLC. All rights reserved. +// +// Permission is hereby granted, free of charge, to any person obtaining a copy +// of this software and associated documentation files (the "Software"), to +// deal in the Software without restriction, including without limitation the +// rights to use, copy, modify, merge, publish, distribute, sublicense, and/or +// sell copies of the Software, and to permit persons to whom the Software is +// furnished to do so, subject to the following conditions: +// +// The above copyright notice and this permission notice shall be included in +// all copies or substantial portions of the Software. +// +// THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR +// IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, +// FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE +// AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER +// LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, +// OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN +// THE SOFTWARE. + +import { HttpClient, HttpErrorResponse } from "@angular/common/http"; +import { + ChangeDetectorRef, + Component, + Injectable, + OnDestroy, + OnInit, +} from "@angular/core"; +import { GridsterConfig, GridsterItem } from "angular-gridster2"; +import { BehaviorSubject } from "rxjs"; +import { finalize } from "rxjs/operators"; + +import { DataSourceService, IFilteringOutputs } from "@nova-ui/bits"; +import { + DATA_SOURCE, + DEFAULT_KPI_BACKGROUND_COLORS, + IDashboard, + IKpiColorRules, + IKpiData, + IProviderConfiguration, + IWidget, + IWidgets, + KpiComponent, + NOVA_KPI_COLOR_PRIORITIZER, + NOVA_KPI_DATASOURCE_ADAPTER, + PizzagnaLayer, + ProviderRegistryService, + WellKnownPathKey, + WellKnownProviders, + WidgetTypesService, +} from "@nova-ui/dashboards"; + +/** + * A simple KPI data source to retrieve the average rating of Harry Potter and the Sorcerer's Stone (book) via googleapis + */ +@Injectable() +export class AverageRatingKpiDataSource + extends DataSourceService + implements OnDestroy +{ + public static providerId = "AverageRatingKpiDataSource"; + public busy = new BehaviorSubject(false); + + constructor(private http: HttpClient) { + super(); + } + + public async getFilteredData(): Promise { + this.busy.next(true); + return new Promise((resolve) => { + // *** Make a resource request to an external API (if needed) + this.http + .get("https://www.googleapis.com/books/v1/volumes/5MQFrgEACAAJ") + .pipe(finalize(() => this.busy.next(false))) + .subscribe({ + next: (data: any) => { + resolve({ + result: { + value: data.volumeInfo.averageRating, + // setting the color on the dataSource "Sea Green", + // uncomment to get the background color update from the "Data" layer + // backgroundColor: "var(--nui-color-chart-three)", + }, + }); + }, + error: (error: HttpErrorResponse) => { + resolve({ + result: null, + error: { + type: error.status, + }, + }); + }, + }); + }); + } + + public ngOnDestroy(): void { + this.outputsSubject.complete(); + } +} + +/** + * A component that instantiates the dashboard + */ +@Component({ + selector: "kpi-widget-background-color-example", + templateUrl: "./kpi-widget-background-color-example.component.html", + styleUrls: ["./kpi-widget-background-color-example.component.less"], + standalone: false, +}) +export class KpiWidgetBackgroundColorExampleComponent implements OnInit { + public dashboard: IDashboard | undefined; + public gridsterConfig: GridsterConfig = {}; + public editMode: boolean = false; + + constructor( + private widgetTypesService: WidgetTypesService, + private providerRegistry: ProviderRegistryService, + private changeDetectorRef: ChangeDetectorRef + ) {} + + public ngOnInit(): void { + this.setupDashboard(); + + // KPI tile default color setup + this.setupDefaultColorStructure(); + + // Sets the custom pallette to the 'Description' section + this.setupCustomPalletteDescription(); + + // Sets the custom pallette to the 'Background color rules' section + this.setupCustomPalletteRules(); + + this.initializeDashboard(); + } + + /** Used for restoring widgets state */ + public reInitializeDashboard(): void { + // destroys the components and their providers so the dashboard can re init data + this.dashboard = undefined; + this.changeDetectorRef.detectChanges(); + + this.initializeDashboard(); + } + + private setupCustomPalletteDescription() { + const kpiWidgetTemplate = this.widgetTypesService.getWidgetType( + "kpi", + 1 + ); + this.widgetTypesService.setNode( + kpiWidgetTemplate, + "configurator", + WellKnownPathKey.TileDescriptionBackgroundColors, + [ + { color: "var(--nui-color-chart-one)", label: "Blue" }, + { + color: "var(--nui-color-chart-one-light)", + label: "Blue Light", + }, + { + color: "var(--nui-color-chart-one-dark)", + label: "Blue Dark", + }, + ] + ); + } + + private setupCustomPalletteRules() { + const kpiWidgetTemplate = this.widgetTypesService.getWidgetType( + "kpi", + 1 + ); + this.widgetTypesService.setNode( + kpiWidgetTemplate, + "configurator", + WellKnownPathKey.TileBackgroundColorRulesBackgroundColors, + [ + { color: "red", label: "Native Red" }, + ...DEFAULT_KPI_BACKGROUND_COLORS, + ] + ); + } + + private setupDefaultColorStructure() { + const widgetTemplate = this.widgetTypesService.getWidgetType("kpi", 1); + this.widgetTypesService.setNode( + widgetTemplate, + "widget", + "tiles.properties.template.properties.widgetData.backgroundColor", + "red" + ); + } + + private setupDashboard() { + const widgetTemplate = this.widgetTypesService.getWidgetType("kpi", 1); + this.widgetTypesService.setNode( + widgetTemplate, + "configurator", + WellKnownPathKey.DataSourceProviders, + [AverageRatingKpiDataSource.providerId] + ); + + this.providerRegistry.setProviders({ + [AverageRatingKpiDataSource.providerId]: { + provide: DATA_SOURCE, + useClass: AverageRatingKpiDataSource, + deps: [HttpClient], + }, + }); + } + + private initializeDashboard(): void { + const kpiWidget = widgetConfig; + const widgetIndex: IWidgets = { + [kpiWidget.id]: + this.widgetTypesService.mergeWithWidgetType(kpiWidget), + }; + + const positions: Record = { + [kpiWidget.id]: { + cols: 4, + rows: 6, + y: 0, + x: 0, + }, + }; + + this.dashboard = { + positions, + widgets: widgetIndex, + }; + } +} + +const widgetConfig: IWidget = { + id: "kpiWidgetId", + type: "kpi", + pizzagna: { + [PizzagnaLayer.Configuration]: { + header: { + properties: { + title: "Harry Potter and the Sorcerer's Stone", + subtitle: "By J. K. Rowling", + }, + }, + tiles: { + properties: { + nodes: ["kpi1"], + }, + }, + kpi1: { + id: "kpi1", + componentType: KpiComponent.lateLoadKey, + properties: { + widgetData: { + units: \\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\`out of 5 Stars\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\`, + label: \\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\`Average Rating\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\`, + // Configuration color "Blue" + backgroundColor: "var(--nui-color-chart-one)", + }, + }, + providers: { + [WellKnownProviders.DataSource]: { + providerId: AverageRatingKpiDataSource.providerId, + } as IProviderConfiguration, + [WellKnownProviders.Adapter]: { + providerId: NOVA_KPI_DATASOURCE_ADAPTER, + properties: { + componentId: "kpi1", + propertyPath: "widgetData", + }, + } as IProviderConfiguration, + [WellKnownProviders.KpiColorPrioritizer]: { + providerId: NOVA_KPI_COLOR_PRIORITIZER, + properties: { + // Color Prioritizer Rules + // settings rules - if the value is more than "2" display "Violet" color + rules: [ + { + comparisonType: ">", + value: 2, + color: "var(--nui-color-chart-four)", + }, + ] as IKpiColorRules[], + }, + } as IProviderConfiguration, + }, + }, + }, + }, +}; +\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\`, + "widget-types/kpi/kpi-widget-background-color-docs.component.ts": \\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\`// © 2022 SolarWinds Worldwide, LLC. All rights reserved. +// +// Permission is hereby granted, free of charge, to any person obtaining a copy +// of this software and associated documentation files (the "Software"), to +// deal in the Software without restriction, including without limitation the +// rights to use, copy, modify, merge, publish, distribute, sublicense, and/or +// sell copies of the Software, and to permit persons to whom the Software is +// furnished to do so, subject to the following conditions: +// +// The above copyright notice and this permission notice shall be included in +// all copies or substantial portions of the Software. +// +// THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR +// IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, +// FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE +// AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER +// LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, +// OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN +// THE SOFTWARE. + +import { Component } from "@angular/core"; + +@Component({ + selector: "nui-kpi-background-color-docs", + templateUrl: "./kpi-widget-background-color-docs.component.html", + standalone: false, +}) +export class KpiWidgetBackgroundColorDocsComponent { + public comparatorsRegistryCode = \\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\` + this.comparatorsRegistry.registerComparators({ + "!=": { + comparatorFn: (actual: any, reference: any) => actual != reference, + label: "Not equal", + }, + }); + \\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\`; +} +\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\`, + "widget-types/kpi/kpi-widget-interactive/kpi-widget-interactive-example.component.ts": \\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\`// © 2022 SolarWinds Worldwide, LLC. All rights reserved. +// +// Permission is hereby granted, free of charge, to any person obtaining a copy +// of this software and associated documentation files (the "Software"), to +// deal in the Software without restriction, including without limitation the +// rights to use, copy, modify, merge, publish, distribute, sublicense, and/or +// sell copies of the Software, and to permit persons to whom the Software is +// furnished to do so, subject to the following conditions: +// +// The above copyright notice and this permission notice shall be included in +// all copies or substantial portions of the Software. +// +// THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR +// IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, +// FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE +// AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER +// LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, +// OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN +// THE SOFTWARE. + +import { HttpClient, HttpErrorResponse } from "@angular/common/http"; +import { Component, Injectable, OnDestroy, OnInit } from "@angular/core"; +import { GridsterConfig, GridsterItem } from "angular-gridster2"; +import { BehaviorSubject } from "rxjs"; +import { finalize } from "rxjs/operators"; + +import { DataSourceService, IFilteringOutputs } from "@nova-ui/bits"; +import { + DATA_SOURCE, + IDashboard, + IKpiData, + IProviderConfiguration, + IWidget, + IWidgets, + KpiComponent, + NOVA_KPI_DATASOURCE_ADAPTER, + NOVA_URL_INTERACTION_HANDLER, + PizzagnaLayer, + ProviderRegistryService, + WellKnownPathKey, + WellKnownProviders, + WidgetTypesService, +} from "@nova-ui/dashboards"; + +/** + * A simple KPI data source to retrieve the average rating of Harry Potter and the Sorcerer's Stone (book) via googleapis + */ +@Injectable() +export class BookRatingDataSource + extends DataSourceService + implements OnDestroy +{ + // This is the ID we'll use to identify the provider + public static providerId = "BookRatingDataSource"; + + // Use this subject to communicate the data source's busy state + public busy = new BehaviorSubject(false); + + constructor(private http: HttpClient) { + super(); + } + + // In this example, getFilteredData is invoked every 10 minutes (Take a look at the refresher + // provider definition in the widget configuration below to see how the interval is set) + public async getFilteredData(): Promise { + this.busy.next(true); + return new Promise((resolve) => { + // *** Make a resource request to an external API (if needed) + this.http + .get("https://www.googleapis.com/books/v1/volumes/zpvysRGsBlwC") + .pipe(finalize(() => this.busy.next(false))) + .subscribe({ + next: (data: any) => { + resolve({ + result: { + value: data.volumeInfo.averageRating, + link: data.volumeInfo.infoLink, + }, + }); + }, + error: (error: HttpErrorResponse) => { + resolve({ + result: null, + error: { + type: error.status, + }, + }); + }, + }); + }); + } + + public ngOnDestroy(): void { + this.outputsSubject.complete(); + } +} + +/** + * A component that instantiates the dashboard + */ +@Component({ + selector: "kpi-widget-interactive-example", + templateUrl: "./kpi-widget-interactive-example.component.html", + styleUrls: ["./kpi-widget-interactive-example.component.less"], + standalone: false, +}) +export class KpiWidgetInteractiveExampleComponent implements OnInit { + // This variable will hold all the data needed to define the layout and behavior of the widgets. + // Pass this to the dashboard component's dashboard input in the template. + public dashboard: IDashboard; + + // Angular gridster requires a configuration object even if it's empty. + // Pass this to the dashboard component's gridsterConfig input in the template. + public gridsterConfig: GridsterConfig = {}; + + // Boolean passed as an input to the dashboard. When true, widgets can be moved, resized, removed, or edited + public editMode: boolean = false; + + constructor( + // WidgetTypesService provides the widget's necessary structure information + private widgetTypesService: WidgetTypesService, + + // In general, the ProviderRegistryService is used for making entities available for injection into dynamically loaded components. + private providerRegistry: ProviderRegistryService + ) {} + + public ngOnInit(): void { + // Grabbing the widget's default template which will be needed as a parameter for setNode + const widgetTemplate = this.widgetTypesService.getWidgetType("kpi", 1); + // Registering our data sources as dropdown options in the widget editor/configurator + // Note: This could also be done in the parent module's constructor so that + // multiple dashboards could have access to the same widget template modification. + this.widgetTypesService.setNode( + // This is the template we grabbed above with getWidgetType + widgetTemplate, + // We are setting the editor/configurator part of the widget template + "configurator", + // This indicates which node you are changing and we want to change + // the data source providers available for selection in the editor. + WellKnownPathKey.DataSourceProviders, + // We are setting the data sources available for selection in the editor + [BookRatingDataSource.providerId] + ); + + // Registering the data source for injection into the KPI tile. + // Note: Each tile of a KPI widget is assigned its own instance of the data source + this.providerRegistry.setProviders({ + [BookRatingDataSource.providerId]: { + provide: DATA_SOURCE, + useClass: BookRatingDataSource, + // Any dependencies that need to be injected into the provider must be listed here + deps: [HttpClient], + }, + }); + + this.initializeDashboard(); + } + + public initializeDashboard(): void { + // We're using a static configuration object for this example, but this is where + // the widget's configuration could potentially be populated from a database + const kpiWidget = widgetConfig; + const widgetIndex: IWidgets = { + // Complete the KPI widget with information coming from its type definition + [kpiWidget.id]: + this.widgetTypesService.mergeWithWidgetType(kpiWidget), + }; + + // Setting the widget dimensions and position (this is for gridster) + const positions: Record = { + [kpiWidget.id]: { + cols: 4, + rows: 6, + y: 0, + x: 0, + }, + }; + + // Finally, assigning the variables we created above to the dashboard + this.dashboard = { + positions, + widgets: widgetIndex, + }; + } +} + +const widgetConfig: IWidget = { + id: "kpiWidgetId", + type: "kpi", + pizzagna: { + [PizzagnaLayer.Configuration]: { + header: { + properties: { + title: "Harry Potter and the Order of the Phoenix", + subtitle: "By: J. K. Rowling", + }, + }, + tiles: { + providers: { + interaction: { + // Configuring the UrlInteractionHandler for interactions on the tiles + providerId: NOVA_URL_INTERACTION_HANDLER, + properties: { + // the 'url' property tells the handler what link to use when interaction occurs on the series + url: "\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\${data.link}", + }, + }, + }, + properties: { + nodes: ["kpi1"], + }, + }, + kpi1: { + id: "kpi1", + componentType: KpiComponent.lateLoadKey, + properties: { + widgetData: { + units: \\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\`out of 5 stars\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\`, + label: \\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\`Average Rating\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\`, + value: 0, + // the link property that is passed to the UrlInteractionHandler when the title is clicked + // this will be updated in BookRatingDataSource's 'getFilteredData' call. + link: "http://www.google.com", + }, + }, + providers: { + [WellKnownProviders.DataSource]: { + // Setting the data source providerId for the tile with id "kpi1" + providerId: BookRatingDataSource.providerId, + } as IProviderConfiguration, + [WellKnownProviders.Adapter]: { + providerId: NOVA_KPI_DATASOURCE_ADAPTER, + properties: { + componentId: "kpi1", + propertyPath: "widgetData", + }, + } as IProviderConfiguration, + }, + }, + }, + }, +}; +\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\`, + "widget-types/proportional/models.ts": \\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\`export interface IMockBeerReview { + id: string; + name: string; + data: number[]; + icon: string; + link?: string; + value: string; + color?: string; +} +\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\`, + "widget-types/proportional/proportional-docs.component.ts": \\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\`// © 2022 SolarWinds Worldwide, LLC. All rights reserved. +// +// Permission is hereby granted, free of charge, to any person obtaining a copy +// of this software and associated documentation files (the "Software"), to +// deal in the Software without restriction, including without limitation the +// rights to use, copy, modify, merge, publish, distribute, sublicense, and/or +// sell copies of the Software, and to permit persons to whom the Software is +// furnished to do so, subject to the following conditions: +// +// The above copyright notice and this permission notice shall be included in +// all copies or substantial portions of the Software. +// +// THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR +// IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, +// FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE +// AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER +// LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, +// OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN +// THE SOFTWARE. + +import { Component, OnInit } from "@angular/core"; + +import { mapContentFile } from "../../../../demo-files-factory"; + +@Component({ + selector: "nui-proportional-docs", + templateUrl: "./proportional-docs.component.html", + standalone: false, +}) +export class ProportionalDocsComponent implements OnInit { + public proportionalWidgetFileText = ""; + public proportionalConfiguratorFileText = ""; + + public async ngOnInit(): Promise { + this.proportionalWidgetFileText = await import( + "./../../../../../../src/lib/widget-types/proportional/proportional-widget" + ).then(mapContentFile); + this.proportionalConfiguratorFileText = await import( + "./../../../../../../src/lib/widget-types/proportional/proportional-configurator" + ).then(mapContentFile); + } +} +\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\`, + "widget-types/proportional/proportional-docs.module.ts": \\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\`// © 2022 SolarWinds Worldwide, LLC. All rights reserved. +// +// Permission is hereby granted, free of charge, to any person obtaining a copy +// of this software and associated documentation files (the "Software"), to +// deal in the Software without restriction, including without limitation the +// rights to use, copy, modify, merge, publish, distribute, sublicense, and/or +// sell copies of the Software, and to permit persons to whom the Software is +// furnished to do so, subject to the following conditions: +// +// The above copyright notice and this permission notice shall be included in +// all copies or substantial portions of the Software. +// +// THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR +// IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, +// FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE +// AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER +// LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, +// OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN +// THE SOFTWARE. + +import { NgModule } from "@angular/core"; +import { RouterModule, Routes } from "@angular/router"; + +import { + NuiButtonModule, + NuiDocsModule, + NuiMessageModule, + NuiSwitchModule, + DEMO_PATH_TOKEN, +} from "@nova-ui/bits"; +import { NuiDashboardsModule } from "@nova-ui/dashboards"; + +import { ProportionalDocsComponent } from "./proportional-docs.component"; +import { ProportionalDonutContentDocsComponent } from "./proportional-donut-content-docs.component"; +import { ProportionalWidgetDonutContentFormattersExampleComponent } from "./proportional-donut-content-formatters/proportional-donut-content-formatters-example.component"; +import { ProportionalWidgetExampleComponent } from "./proportional-widget/proportional-widget-example.component"; +import { ProportionalWidgetInteractiveExampleComponent } from "./proportional-widget-interactive/proportional-widget-interactive-example.component"; +import { getDemoFiles } from "../../../../demo-files-factory"; + +const routes: Routes = [ + { + path: "", + component: ProportionalDocsComponent, + data: { + srlc: { + hideIndicator: true, + }, + showThemeSwitcher: true, + }, + }, + { + path: "example", + component: ProportionalWidgetExampleComponent, + data: { + srlc: { + hideIndicator: true, + }, + }, + }, + { + path: "donut-content-formatters", + component: ProportionalDonutContentDocsComponent, + data: { + srlc: { + hideIndicator: true, + }, + }, + }, + { + path: "donut-content-formatters-example", + component: ProportionalWidgetDonutContentFormattersExampleComponent, + data: { + srlc: { + hideIndicator: true, + }, + }, + }, + { + path: "proportional-widget-interactive-example", + component: ProportionalWidgetInteractiveExampleComponent, + data: { + srlc: { + hideIndicator: true, + }, + }, + }, +]; + +@NgModule({ + imports: [ + RouterModule.forChild(routes), + NuiButtonModule, + NuiDocsModule, + NuiDashboardsModule, + NuiMessageModule, + NuiSwitchModule, + ], + declarations: [ + ProportionalDocsComponent, + ProportionalWidgetExampleComponent, + ProportionalWidgetInteractiveExampleComponent, + ProportionalWidgetDonutContentFormattersExampleComponent, + ProportionalDonutContentDocsComponent, + ], + providers: [ + { + provide: DEMO_PATH_TOKEN, + useValue: getDemoFiles("proportional"), + }, + ], +}) +export default class ProportionalDocsModule {} +\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\`, + "widget-types/proportional/proportional-donut-content-docs.component.ts": \\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\`// © 2022 SolarWinds Worldwide, LLC. All rights reserved. +// +// Permission is hereby granted, free of charge, to any person obtaining a copy +// of this software and associated documentation files (the "Software"), to +// deal in the Software without restriction, including without limitation the +// rights to use, copy, modify, merge, publish, distribute, sublicense, and/or +// sell copies of the Software, and to permit persons to whom the Software is +// furnished to do so, subject to the following conditions: +// +// The above copyright notice and this permission notice shall be included in +// all copies or substantial portions of the Software. +// +// THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR +// IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, +// FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE +// AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER +// LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, +// OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN +// THE SOFTWARE. + +import { Component } from "@angular/core"; + +@Component({ + selector: "nui-proportional-donut-content-docs", + templateUrl: "./proportional-donut-content-docs.component.html", + standalone: false, +}) +export class ProportionalDonutContentDocsComponent { + public dataSourceDataFieldsConfig = \\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\` +public dataFieldsConfig: IProportionalDataFieldsConfig = { + dataFields$: new BehaviorSubject(this.dataFields), + chartSeriesDataFields$: new BehaviorSubject(this.chartSeriesDataFields), +}; + \\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\`; + + public widgetConfigSlice = \\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\` +"properties": { + "configuration": { + "chartOptions": { + donutContentConfig: { + formatter: { + componentType: SiUnitsFormatterComponent.lateLoadKey, + }, + aggregator: { + aggregatorType: sumAggregator.aggregatorType, + }, + }, + } + } +} + + \\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\`; +} +\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\`, + "widget-types/proportional/proportional-donut-content-formatters/proportional-donut-content-formatters-example.component.ts": \\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\`// © 2022 SolarWinds Worldwide, LLC. All rights reserved. +// +// Permission is hereby granted, free of charge, to any person obtaining a copy +// of this software and associated documentation files (the "Software"), to +// deal in the Software without restriction, including without limitation the +// rights to use, copy, modify, merge, publish, distribute, sublicense, and/or +// sell copies of the Software, and to permit persons to whom the Software is +// furnished to do so, subject to the following conditions: +// +// The above copyright notice and this permission notice shall be included in +// all copies or substantial portions of the Software. +// +// THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR +// IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, +// FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE +// AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER +// LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, +// OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN +// THE SOFTWARE. + +import { + ChangeDetectorRef, + Component, + Injectable, + OnDestroy, + OnInit, +} from "@angular/core"; +import { GridsterConfig, GridsterItem } from "angular-gridster2"; +import { BehaviorSubject } from "rxjs"; + +import { + DataSourceService, + IDataField, + IDataSource, + IFilteringOutputs, +} from "@nova-ui/bits"; +import { IAccessors, IChartAssistSeries } from "@nova-ui/charts"; +import { + DATA_SOURCE, + DEFAULT_LEGEND_FORMATTERS, + DEFAULT_PIZZAGNA_ROOT, + DEFAULT_PROPORTIONAL_CONTENT_AGGREGATORS, + DEFAULT_PROPORTIONAL_CONTENT_FORMATTERS, + DONUT_CONTENT_CONFIGURATION_SLICE, + IDashboard, + IDonutContentConfig, + IProportionalDataFieldsConfig, + IProportionalWidgetChartOptions, + IProportionalWidgetConfig, + IProviderConfiguration, + IWidget, + IWidgets, + LegendPlacement, + PizzagnaLayer, + ProportionalContentAggregatorsRegistryService, + ProportionalDonutContentFormattersRegistryService, + ProportionalLegendFormattersRegistryService, + ProportionalWidgetChartTypes, + ProviderRegistryService, + SiUnitsFormatterComponent, + sumAggregator, + WellKnownPathKey, + WellKnownProviders, + WidgetTypesService, +} from "@nova-ui/dashboards"; + +import { IMockBeerReview } from "../models"; + +/** + * A simple proportional data source to retrieve beer review counts by city + */ +@Injectable() +export class BeerReviewCountsByCityMockDataSource + extends DataSourceService> + implements IDataSource>, OnDestroy +{ + public static providerId = "BeerReviewCountsByCityMockDataSource"; + public busy = new BehaviorSubject(false); + + protected dataFields: IDataField[] = [ + { + id: "Brno", + label: "Brno", + // @ts-ignore + dataType: null, + }, + { + id: "kyiv", + label: "Kyiv", + // @ts-ignore + dataType: null, + }, + { + id: "austin", + label: "Austin", + // @ts-ignore + dataType: null, + }, + { + id: "lisbon", + label: "Lisbon", + // @ts-ignore + dataType: null, + }, + { + id: "sydney", + label: "Sydney", + // @ts-ignore + dataType: null, + }, + { + id: "nur-sultan", + label: "Nur-Sultan", + // @ts-ignore + dataType: null, + }, + ]; + protected chartSeriesDataFields: IDataField[] = [ + // default field in the chart series that is used for the aggregation + { + id: "data[0]", + label: "data", + // @ts-ignore + dataType: null, + }, + // any custom field in the chart series that is used for the aggregation + { + id: "customDonutContent", + label: "Custom Donut Content", + // @ts-ignore + dataType: null, + }, + ]; + + /** + * DataSource needs to implement the "IDataFieldsConfig" for this scenario. + * + * It's necessary to provide the "chartSeriesDataFields", + * that's why proportional widget dataSource has it's own interface for that - IProportionalDataFieldsConfig. + * + * dataFields$ - stands for possible series fields + * chartSeriesDataFields$ - stands for the fields IN the series + * + * see declaration of "dataFields" and "chartSeriesDataFields" for the example. + */ + public dataFieldsConfig: IProportionalDataFieldsConfig = { + dataFields$: new BehaviorSubject(this.dataFields), + chartSeriesDataFields$: new BehaviorSubject( + this.chartSeriesDataFields + ), + }; + + public async getFilteredData(): Promise { + this.busy.next(true); + return new Promise((resolve) => { + setTimeout(() => { + this.outputsSubject.next({ + result: getMockBeerReviewCountsByCity(), + }); + this.busy.next(false); + }, 300); + }); + } + + public ngOnDestroy(): void { + this.outputsSubject.complete(); + } +} + +/** + * A component that instantiates the dashboard + */ +@Component({ + selector: "proportional-widget-donut-content-formatters-example", + templateUrl: "./proportional-donut-content-formatters-example.component.html", + styleUrls: [ + "./proportional-donut-content-formatters-example.component.less", + ], + standalone: false, +}) +export class ProportionalWidgetDonutContentFormattersExampleComponent + implements OnInit +{ + public dashboard: IDashboard | undefined; + + // Angular gridster requires a configuration object even if it's empty. + // Pass this to the dashboard component's gridsterConfig input in the template. + public gridsterConfig: GridsterConfig = {}; + + // Boolean passed as an input to the dashboard. When true, widgets can be moved, resized, removed, or edited + public editMode: boolean = false; + + constructor( + // WidgetTypesService provides the widget's necessary structure information + private widgetTypesService: WidgetTypesService, + // In general, the ProviderRegistryService is used for making entities available for injection into dynamically loaded components. + private providerRegistry: ProviderRegistryService, + // registry for adding the formatter for donut content + contentFormattersRegistry: ProportionalDonutContentFormattersRegistryService, + // registry for adding the formatter for proportional legend + legendFormattersRegistry: ProportionalLegendFormattersRegistryService, + // registry for adding the aggregators for donut content + aggregatorRegistry: ProportionalContentAggregatorsRegistryService, + private changeDetectorRef: ChangeDetectorRef + ) { + // on the dashboard startup, it's necessary to add possible content formatters, legend formatters and content aggregators to the registry. + // using registry is a way for setting the available formatters. + legendFormattersRegistry.addItems(DEFAULT_LEGEND_FORMATTERS); + contentFormattersRegistry.addItems( + DEFAULT_PROPORTIONAL_CONTENT_FORMATTERS + ); + aggregatorRegistry.addItems(DEFAULT_PROPORTIONAL_CONTENT_AGGREGATORS); + } + + public ngOnInit(): void { + // Grabbing the widget's default template which will be needed as a parameter for setNode + const widgetTemplate = this.widgetTypesService.getWidgetType( + "proportional", + 1 + ); + + // Registering our data sources as dropdown options in the widget editor/configurator + // Note: This could also be done in the parent module's constructor so that + // multiple dashboards could have access to the same widget template modification. + this.widgetTypesService.setNode( + // This is the template we grabbed above with getWidgetType + widgetTemplate, + // We are setting the editor/configurator part of the widget template + "configurator", + // This indicates which node you are changing and we want to change + // the data source providers available for selection in the editor. + WellKnownPathKey.DataSourceProviders, + // We are setting the data sources available for selection in the editor + [BeerReviewCountsByCityMockDataSource.providerId] + ); + + // Setup of the configurator is done here + this.setupConfigurator(); + + // Registering the data source for injection into the Proportional widget. + this.providerRegistry.setProviders({ + [BeerReviewCountsByCityMockDataSource.providerId]: { + provide: DATA_SOURCE, + useClass: BeerReviewCountsByCityMockDataSource, + deps: [], + }, + }); + + this.initializeDashboard(); + } + + /** Used for restoring widgets state */ + public reInitializeDashboard(): void { + // destroys the components and their providers so the dashboard can re init data + this.dashboard = undefined; + this.changeDetectorRef.detectChanges(); + + this.initializeDashboard(); + } + + private initializeDashboard(): void { + // We're using a static configuration object for this example, but this is where + // the widget's configuration could potentially be populated from a database + const widgetIndex: IWidgets = { + // Complete the proportional widget with information coming from its type definition + [widgetConfig.id]: + this.widgetTypesService.mergeWithWidgetType(widgetConfig), + }; + + // Setting the widget dimensions and position (this is for gridster) + const positions: Record = { + [widgetConfig.id]: { + cols: 6, + rows: 6, + y: 0, + x: 0, + }, + }; + + // Finally, assigning the variables we created above to the dashboard + this.dashboard = { + positions, + widgets: widgetIndex, + }; + } + + /** + * Sets up the configurator sections for proportional donut + */ + private setupConfigurator() { + const widgetTemplate = this.widgetTypesService.getWidgetType( + "proportional", + 1 + ); + + // remove old "presentation", "chartOptionsEditor" and "donutContentConfiguration" sections from the configurator + delete widgetTemplate.configurator?.structure?.presentation; + delete widgetTemplate.configurator?.structure?.chartOptionsEditor; + delete widgetTemplate.configurator?.structure + ?.donutContentConfiguration; + + // add new "presentation" section + this.widgetTypesService.setNode( + widgetTemplate, + "configurator", + "presentation", + DONUT_CONTENT_CONFIGURATION_SLICE.presentation + ); + // add new "chartOptionsEditor" section + this.widgetTypesService.setNode( + widgetTemplate, + "configurator", + "chartOptionsEditor", + DONUT_CONTENT_CONFIGURATION_SLICE.chartOptionsEditor + ); + // add new "donutContentConfiguration" section + this.widgetTypesService.setNode( + widgetTemplate, + "configurator", + "donutContentConfiguration", + DONUT_CONTENT_CONFIGURATION_SLICE.donutContentConfiguration + ); + } +} + +const widgetConfig: IWidget = { + id: "proportionalWidgetId", + type: "proportional", + pizzagna: { + [PizzagnaLayer.Configuration]: { + [DEFAULT_PIZZAGNA_ROOT]: { + providers: {}, + }, + header: { + properties: { + title: "Beer Review Tally by City", + subtitle: "These People Love Beer", + }, + }, + chart: { + providers: { + [WellKnownProviders.DataSource]: { + // Setting the data source providerId for the chart + providerId: + BeerReviewCountsByCityMockDataSource.providerId, + } as IProviderConfiguration, + }, + properties: { + configuration: { + chartOptions: { + type: ProportionalWidgetChartTypes.DonutChart, + legendPlacement: LegendPlacement.Right, + // old configuration looks like this + // contentFormatter: { + // componentType: DonutContentSumFormatterComponent.lateLoadKey, + // }, + + // NEW configuration looks like this + donutContentConfig: { + formatter: { + componentType: + SiUnitsFormatterComponent.lateLoadKey, + }, + aggregator: { + aggregatorType: + sumAggregator.aggregatorType, + properties: { + // example of a default metric to be used for the percentage calculation + // activeMetricId: "austin", + }, + }, + } as IDonutContentConfig, + } as IProportionalWidgetChartOptions, + } as IProportionalWidgetConfig, + }, + }, + }, + }, +}; + +export function getMockBeerReviewCountsByCity(): IMockBeerReview[] { + return [ + { + id: "Brno", + name: "Brno", + data: [Math.round(Math.random() * 1000000)], + icon: "status_down", + link: "https://en.wikipedia.org/wiki/Brno", + value: "Brno", + customDonutContent: "Custom Brno", + }, + { + id: "kyiv", + name: "Kyiv", + data: [Math.round(Math.random() * 1000000)], + icon: "status_critical", + link: "https://en.wikipedia.org/wiki/Kyiv", + value: "Kyiv", + customDonutContent: "Custom Kyiv", + }, + { + id: "austin", + name: "Austin", + data: [Math.round(Math.random() * 1000000)], + icon: "status_warning", + link: "https://en.wikipedia.org/wiki/Austin", + value: "Austin", + customDonutContent: "Custom Austin", + }, + { + id: "lisbon", + name: "Lisbon", + data: [Math.round(Math.random() * 1000000)], + icon: "status_unknown", + link: "https://en.wikipedia.org/wiki/Lisbon", + value: "Lisbon", + customDonutContent: "Custom Lisbon", + }, + { + id: "sydney", + name: "Sydney", + data: [Math.round(Math.random() * 1000000)], + icon: "status_up", + link: "https://en.wikipedia.org/wiki/Sydney", + value: "Sydney", + customDonutContent: "Custom Sydney", + }, + { + id: "nur-sultan", + name: "Nur-Sultan", + data: [Math.round(Math.random() * 1000000)], + icon: "status_unmanaged", + link: "https://en.wikipedia.org/wiki/Nur-Sultan", + value: "Nur-Sultan", + customDonutContent: "Custom Nur-Sultan", + }, + ].sort((a, b) => a.data[0] - b.data[0]); +} +\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\`, + "widget-types/proportional/proportional-widget/proportional-widget-example.component.ts": \\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\`// © 2022 SolarWinds Worldwide, LLC. All rights reserved. +// +// Permission is hereby granted, free of charge, to any person obtaining a copy +// of this software and associated documentation files (the "Software"), to +// deal in the Software without restriction, including without limitation the +// rights to use, copy, modify, merge, publish, distribute, sublicense, and/or +// sell copies of the Software, and to permit persons to whom the Software is +// furnished to do so, subject to the following conditions: +// +// The above copyright notice and this permission notice shall be included in +// all copies or substantial portions of the Software. +// +// THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR +// IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, +// FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE +// AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER +// LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, +// OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN +// THE SOFTWARE. + +import { + ChangeDetectorRef, + Component, + Injectable, + OnDestroy, + OnInit, +} from "@angular/core"; +import { GridsterConfig, GridsterItem } from "angular-gridster2"; +import { BehaviorSubject } from "rxjs"; + +import { + DataSourceService, + IDataSource, + IFilteringOutputs, +} from "@nova-ui/bits"; +import { + DATA_SOURCE, + DEFAULT_PIZZAGNA_ROOT, + IDashboard, + IProportionalWidgetChartOptions, + IProportionalWidgetConfig, + IProportionalWidgetData, + IProviderConfiguration, + IRefresherProperties, + IWidget, + IWidgets, + LegendPlacement, + PizzagnaLayer, + ProportionalWidgetChartTypes, + ProviderRegistryService, + WellKnownPathKey, + WellKnownProviders, + WidgetTypesService, +} from "@nova-ui/dashboards"; + +import { IMockBeerReview } from "../models"; + +/** + * A simple proportional data source to retrieve beer review counts by city + */ +@Injectable() +export class BeerReviewCountsByCityMockDataSource + extends DataSourceService + implements IDataSource, OnDestroy +{ + // This is the ID we'll use to identify the provider + public static providerId = "BeerReviewCountsByCityMockDataSource"; + public busy = new BehaviorSubject(false); + + public async getFilteredData(): Promise { + this.busy.next(true); + return new Promise((resolve) => { + setTimeout(() => { + this.outputsSubject.next({ + result: getMockBeerReviewCountsByCity(), + }); + this.busy.next(false); + }, 300); + }); + } + + public ngOnDestroy(): void { + this.outputsSubject.complete(); + } +} + +/** + * A component that instantiates the dashboard + */ +@Component({ + selector: "proportional-widget-example", + templateUrl: "./proportional-widget-example.component.html", + styleUrls: ["./proportional-widget-example.component.less"], + standalone: false, +}) +export class ProportionalWidgetExampleComponent implements OnInit { + // This variable will hold all the data needed to define the layout and behavior of the widgets. + // Pass this to the dashboard component's dashboard input in the template. + public dashboard: IDashboard | undefined; + + // Angular gridster requires a configuration object even if it's empty. + // Pass this to the dashboard component's gridsterConfig input in the template. + public gridsterConfig: GridsterConfig = {}; + + // Boolean passed as an input to the dashboard. When true, widgets can be moved, resized, removed, or edited + public editMode: boolean = false; + + constructor( + // WidgetTypesService provides the widget's necessary structure information + private widgetTypesService: WidgetTypesService, + // In general, the ProviderRegistryService is used for making entities available for injection into dynamically loaded components. + private providerRegistry: ProviderRegistryService, + private changeDetectorRef: ChangeDetectorRef + ) {} + + public ngOnInit(): void { + // Grabbing the widget's default template which will be needed as a parameter for setNode + const widgetTemplate = this.widgetTypesService.getWidgetType( + "proportional", + 1 + ); + + // Registering our data sources as dropdown options in the widget editor/configurator + // Note: This could also be done in the parent module's constructor so that + // multiple dashboards could have access to the same widget template modification. + this.widgetTypesService.setNode( + // This is the template we grabbed above with getWidgetType + widgetTemplate, + // We are setting the editor/configurator part of the widget template + "configurator", + // This indicates which node you are changing and we want to change + // the data source providers available for selection in the editor. + WellKnownPathKey.DataSourceProviders, + // We are setting the data sources available for selection in the editor + [BeerReviewCountsByCityMockDataSource.providerId] + ); + + // Registering the data source for injection into the Proportional widget. + this.providerRegistry.setProviders({ + [BeerReviewCountsByCityMockDataSource.providerId]: { + provide: DATA_SOURCE, + useClass: BeerReviewCountsByCityMockDataSource, + deps: [], + }, + }); + + this.initializeDashboard(); + } + + /** Used for restoring widgets state */ + public reInitializeDashboard(): void { + // destroys the components and their providers so the dashboard can re init data + this.dashboard = undefined; + this.changeDetectorRef.detectChanges(); + + this.initializeDashboard(); + } + + public initializeDashboard(): void { + // We're using a static configuration object for this example, but this is where + // the widget's configuration could potentially be populated from a database + const widgetIndex: IWidgets = { + // Complete the proportional widget with information coming from its type definition + [widgetConfig.id]: + this.widgetTypesService.mergeWithWidgetType(widgetConfig), + }; + + // Setting the widget dimensions and position (this is for gridster) + const positions: Record = { + [widgetConfig.id]: { + cols: 5, + rows: 6, + y: 0, + x: 0, + }, + }; + + // Finally, assigning the variables we created above to the dashboard + this.dashboard = { + positions, + widgets: widgetIndex, + }; + } +} + +const widgetConfig: IWidget = { + id: "proportionalWidgetId", + type: "proportional", + pizzagna: { + [PizzagnaLayer.Configuration]: { + [DEFAULT_PIZZAGNA_ROOT]: { + providers: { + [WellKnownProviders.Refresher]: { + properties: { + // Configuring the refresher interval so that our data source is invoked every ten minutes + interval: 60 * 10, + enabled: true, + } as IRefresherProperties, + } as Partial, + }, + }, + header: { + properties: { + title: "Beer Review Tally by City", + subtitle: "These People Love Beer", + }, + }, + chart: { + providers: { + [WellKnownProviders.DataSource]: { + // Setting the data source providerId for the chart + providerId: + BeerReviewCountsByCityMockDataSource.providerId, + } as IProviderConfiguration, + }, + properties: { + configuration: { + chartOptions: { + type: ProportionalWidgetChartTypes.DonutChart, + legendPlacement: LegendPlacement.Right, + } as IProportionalWidgetChartOptions, + // You can optionally define custom colors for the chart by setting the 'chartColors' configuration property + // "chartColors": [ + // "var(--nui-color-chart-five)", + // "var(--nui-color-chart-six)", + // "var(--nui-color-chart-seven)", + // "var(--nui-color-chart-eight)", + // "var(--nui-color-chart-nine)", + // "var(--nui-color-chart-ten)", + // ], + // or use-mapped structure + chartColors: { + Brno: "var(--nui-color-chart-five)", + kyiv: "var(--nui-color-chart-six)", + austin: "var(--nui-color-chart-seven)", + lisbon: "var(--nui-color-chart-eight)", + sydney: "var(--nui-color-chart-nine)", + "nur-sultan": "var(--nui-color-chart-ten)", + }, + prioritizeWidgetColors: false, + } as IProportionalWidgetConfig, + }, + }, + }, + }, +}; + +export function getMockBeerReviewCountsByCity(): IMockBeerReview[] { + return [ + { + id: "Brno", + name: "Brno", + data: [Math.round(Math.random() * 100000)], + icon: "status_down", + link: "https://en.wikipedia.org/wiki/Brno", + value: "Brno", + color: "var(--nui-color-chart-one)", + }, + { + id: "kyiv", + name: "Kyiv", + data: [Math.round(Math.random() * 100000)], + icon: "status_critical", + link: "https://en.wikipedia.org/wiki/Kyiv", + value: "Kyiv", + color: "var(--nui-color-chart-two)", + }, + { + id: "austin", + name: "Austin", + data: [Math.round(Math.random() * 100000)], + icon: "status_warning", + link: "https://en.wikipedia.org/wiki/Austin", + value: "Austin", + color: "var(--nui-color-chart-three)", + }, + { + id: "lisbon", + name: "Lisbon", + data: [Math.round(Math.random() * 100000)], + icon: "status_unknown", + link: "https://en.wikipedia.org/wiki/Lisbon", + value: "Lisbon", + color: "var(--nui-color-chart-four)", + }, + { + id: "sydney", + name: "Sydney", + data: [Math.round(Math.random() * 100000)], + icon: "status_up", + link: "https://en.wikipedia.org/wiki/Sydney", + value: "Sydney", + color: "var(--nui-color-chart-five)", + }, + { + id: "nur-sultan", + name: "Nur-Sultan", + data: [Math.round(Math.random() * 100000)], + icon: "status_unmanaged", + link: "https://en.wikipedia.org/wiki/Nur-Sultan", + value: "Nur-Sultan", + color: "var(--nui-color-chart-six)", + }, + ].sort((a, b) => a.data[0] - b.data[0]); +} +\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\`, + "widget-types/proportional/proportional-widget-interactive/proportional-widget-interactive-example.component.ts": \\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\`// © 2022 SolarWinds Worldwide, LLC. All rights reserved. +// +// Permission is hereby granted, free of charge, to any person obtaining a copy +// of this software and associated documentation files (the "Software"), to +// deal in the Software without restriction, including without limitation the +// rights to use, copy, modify, merge, publish, distribute, sublicense, and/or +// sell copies of the Software, and to permit persons to whom the Software is +// furnished to do so, subject to the following conditions: +// +// The above copyright notice and this permission notice shall be included in +// all copies or substantial portions of the Software. +// +// THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR +// IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, +// FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE +// AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER +// LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, +// OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN +// THE SOFTWARE. + +import { + ChangeDetectorRef, + Component, + Injectable, + OnDestroy, + OnInit, +} from "@angular/core"; +import { GridsterConfig, GridsterItem } from "angular-gridster2"; +import keyBy from "lodash/keyBy"; +import { BehaviorSubject } from "rxjs"; + +import { + DataSourceService, + IDataSource, + IFilteringOutputs, +} from "@nova-ui/bits"; +import { + DATA_SOURCE, + DEFAULT_PIZZAGNA_ROOT, + IDashboard, + IProportionalWidgetChartOptions, + IProportionalWidgetConfig, + IProportionalWidgetData, + IProviderConfiguration, + IWidget, + LegendPlacement, + NOVA_URL_INTERACTION_HANDLER, + PizzagnaLayer, + ProportionalWidgetChartTypes, + ProviderRegistryService, + WellKnownPathKey, + WellKnownProviders, + WidgetTypesService, +} from "@nova-ui/dashboards"; + +import { IMockBeerReview } from "../models"; + +/** + * A simple proportional data source to retrieve beer review counts by city + */ +@Injectable() +export class ReviewCountsByCityMockDataSource + extends DataSourceService + implements IDataSource, OnDestroy +{ + // This is the ID we'll use to identify the provider + public static providerId = "ReviewCountsByCityMockDataSource"; + public busy = new BehaviorSubject(false); + + public async getFilteredData(): Promise { + this.busy.next(true); + return new Promise((resolve) => { + setTimeout(() => { + this.outputsSubject.next({ + result: getMockBeerReviewCountsByCity(), + }); + this.busy.next(false); + }, 300); + }); + } + + public ngOnDestroy(): void { + this.outputsSubject.complete(); + } +} + +/** + * A component that instantiates the dashboard + */ +@Component({ + selector: "proportional-widget-interactive-example", + templateUrl: "./proportional-widget-interactive-example.component.html", + styleUrls: ["./proportional-widget-interactive-example.component.less"], + standalone: false, +}) +export class ProportionalWidgetInteractiveExampleComponent implements OnInit { + // This variable will hold all the data needed to define the layout and behavior of the widgets. + // Pass this to the dashboard component's dashboard input in the template. + public dashboard: IDashboard | undefined; + + // Angular gridster requires a configuration object even if it's empty. + // Pass this to the dashboard component's gridsterConfig input in the template. + public gridsterConfig: GridsterConfig = {}; + + // Boolean passed as an input to the dashboard. When true, widgets can be moved, resized, removed, or edited + public editMode: boolean = false; + + constructor( + // WidgetTypesService provides the widget's necessary structure information + private widgetTypesService: WidgetTypesService, + // In general, the ProviderRegistryService is used for making entities available for injection into dynamically loaded components. + private providerRegistry: ProviderRegistryService, + private changeDetectorRef: ChangeDetectorRef + ) {} + + public ngOnInit(): void { + // Grabbing the widget's default template which will be needed as a parameter for setNode + const widgetTemplate = this.widgetTypesService.getWidgetType( + "proportional", + 1 + ); + + // Registering our data sources as dropdown options in the widget editor/configurator + // Note: This could also be done in the parent module's constructor so that + // multiple dashboards could have access to the same widget template modification. + this.widgetTypesService.setNode( + // This is the template we grabbed above with getWidgetType + widgetTemplate, + // We are setting the editor/configurator part of the widget template + "configurator", + // This indicates which node you are changing and we want to change + // the data source providers available for selection in the editor. + WellKnownPathKey.DataSourceProviders, + // We are setting the data sources available for selection in the editor + [ReviewCountsByCityMockDataSource.providerId] + ); + + // Registering the data source for injection into the Proportional widget. + this.providerRegistry.setProviders({ + [ReviewCountsByCityMockDataSource.providerId]: { + provide: DATA_SOURCE, + useClass: ReviewCountsByCityMockDataSource, + deps: [], + }, + }); + + this.initializeDashboard(); + } + + /** Used for restoring widgets state */ + public reInitializeDashboard(): void { + // destroys the components and their providers so the dashboard can re init data + this.dashboard = undefined; + this.changeDetectorRef.detectChanges(); + + this.initializeDashboard(); + } + + public initializeDashboard(): void { + // We're using a static configuration object for this example, but this is where + // the widget's configuration could potentially be populated from a database + const widgetsWithStructure = widgetConfigs.map((w) => + this.widgetTypesService.mergeWithWidgetType(w) + ); + const widgetsIndex = keyBy(widgetsWithStructure, (w: IWidget) => w.id); + + // Setting the widget dimensions and position (this is for gridster) + const positions: Record = { + [widgetConfigs[0].id]: { + cols: 6, + rows: 6, + y: 0, + x: 0, + }, + [widgetConfigs[1].id]: { + cols: 6, + rows: 6, + y: 0, + x: 6, + }, + }; + + // Finally, assigning the variables we created above to the dashboard + this.dashboard = { + positions, + widgets: widgetsIndex, + }; + } +} + +const widgetConfigs: IWidget[] = [ + { + id: "widget1", + type: "proportional", + pizzagna: { + [PizzagnaLayer.Configuration]: { + [DEFAULT_PIZZAGNA_ROOT]: { + providers: { + // Configuring the UrlInteractionHandler to handle interactions + [WellKnownProviders.InteractionHandler]: { + providerId: NOVA_URL_INTERACTION_HANDLER, + properties: { + // the 'url' property tells the handler what link to use when interaction occurs on the series + // if the series does not have a link we are passing one to the handler + url: "\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\${data.link || 'https://en.wikipedia.org/wiki/'+data.id}", + // by default the link is opened in the current window, set 'newWindow' to true to open in a new tab instead + // newWindow: true, + }, + }, + }, + }, + header: { + properties: { + title: "Proportional Widget", + subtitle: "With interaction handler", + }, + }, + chart: { + providers: { + [WellKnownProviders.DataSource]: { + // Setting the data source providerId for the chart + providerId: + ReviewCountsByCityMockDataSource.providerId, + } as IProviderConfiguration, + }, + properties: { + configuration: { + // Setting the interactive to true + interactive: true, + chartOptions: { + type: ProportionalWidgetChartTypes.VerticalBarChart, + legendPlacement: LegendPlacement.Bottom, + } as IProportionalWidgetChartOptions, + prioritizeWidgetColors: false, + } as IProportionalWidgetConfig, + }, + }, + }, + }, + }, + { + id: "widget2", + type: "proportional", + pizzagna: { + [PizzagnaLayer.Configuration]: { + header: { + properties: { + title: "Proportional Widget", + subtitle: "Without interaction handler", + }, + }, + chart: { + providers: { + [WellKnownProviders.DataSource]: { + // Setting the data source providerId for the chart + providerId: + ReviewCountsByCityMockDataSource.providerId, + } as IProviderConfiguration, + }, + properties: { + configuration: { + // interactive set to false so series without links are not styled like a link + interactive: false, + chartOptions: { + type: ProportionalWidgetChartTypes.HorizontalBarChart, + legendPlacement: LegendPlacement.Bottom, + } as IProportionalWidgetChartOptions, + prioritizeWidgetColors: false, + } as IProportionalWidgetConfig, + }, + }, + }, + }, + }, +]; + +export function getMockBeerReviewCountsByCity(): IMockBeerReview[] { + return [ + { + id: "Brno", + name: "Brno", + data: [Math.round(Math.random() * 100000)], + icon: "status_down", + link: "https://en.wikipedia.org/wiki/Brno", + value: "Brno", + color: "var(--nui-color-chart-one)", + }, + { + id: "kyiv", + name: "Kyiv", + data: [Math.round(Math.random() * 100000)], + icon: "status_critical", + link: "https://en.wikipedia.org/wiki/Kyiv", + value: "Kyiv", + color: "var(--nui-color-chart-two)", + }, + { + id: "austin", + name: "Austin", + data: [Math.round(Math.random() * 100000)], + icon: "status_warning", + value: "Austin", + color: "var(--nui-color-chart-three)", + }, + { + id: "lisbon", + name: "Lisbon", + data: [Math.round(Math.random() * 100000)], + icon: "status_unknown", + link: "https://en.wikipedia.org/wiki/Lisbon", + value: "Lisbon", + color: "var(--nui-color-chart-four)", + }, + { + id: "sydney", + name: "Sydney", + data: [Math.round(Math.random() * 100000)], + icon: "status_up", + value: "Sydney", + color: "var(--nui-color-chart-five)", + }, + { + id: "nur-sultan", + name: "Nur-Sultan", + data: [Math.round(Math.random() * 100000)], + icon: "status_unmanaged", + value: "Nur-Sultan", + color: "var(--nui-color-chart-six)", + }, + ].sort((a, b) => a.data[0] - b.data[0]); +} +\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\`, + "widget-types/risk-score/risk-score-docs.component.ts": \\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\`// © 2023 SolarWinds Worldwide, LLC. All rights reserved. +// +// Permission is hereby granted, free of charge, to any person obtaining a copy +// of this software and associated documentation files (the "Software"), to +// deal in the Software without restriction, including without limitation the +// rights to use, copy, modify, merge, publish, distribute, sublicense, and/or +// sell copies of the Software, and to permit persons to whom the Software is +// furnished to do so, subject to the following conditions: +// +// The above copyright notice and this permission notice shall be included in +// all copies or substantial portions of the Software. +// +// THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR +// IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, +// FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE +// AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER +// LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, +// OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN +// THE SOFTWARE. + +import { Component, OnInit } from "@angular/core"; + +import { mapContentFile } from "../../../../demo-files-factory"; + +@Component({ + selector: "nui-risk-score-docs", + templateUrl: "./risk-score-docs.component.html", + standalone: false, +}) +export class RiskScoreDocsComponent implements OnInit { + public riskScoreWidgetFileText = ""; + public riskScoreConfiguratorFileText = ""; + + public async ngOnInit(): Promise { + this.riskScoreWidgetFileText = await import( + "./../../../../../../src/lib/widget-types/risk-score/risk-score-widget" + ).then(mapContentFile); + this.riskScoreConfiguratorFileText = await import( + "./../../../../../../src/lib/widget-types/risk-score/risk-score-configurator" + ).then(mapContentFile); + } +} +\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\`, + "widget-types/risk-score/risk-score-docs.module.ts": \\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\`// © 2023 SolarWinds Worldwide, LLC. All rights reserved. +// +// Permission is hereby granted, free of charge, to any person obtaining a copy +// of this software and associated documentation files (the "Software"), to +// deal in the Software without restriction, including without limitation the +// rights to use, copy, modify, merge, publish, distribute, sublicense, and/or +// sell copies of the Software, and to permit persons to whom the Software is +// furnished to do so, subject to the following conditions: +// +// The above copyright notice and this permission notice shall be included in +// all copies or substantial portions of the Software. +// +// THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR +// IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, +// FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE +// AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER +// LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, +// OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN +// THE SOFTWARE. + +import { NgModule } from "@angular/core"; +import { RouterModule, Routes } from "@angular/router"; + +import { + DEMO_PATH_TOKEN, + NuiButtonModule, + NuiDocsModule, + NuiMessageModule, + NuiSwitchModule, +} from "@nova-ui/bits"; +import { NuiDashboardsModule } from "@nova-ui/dashboards"; + +import { RiskScoreDocsComponent } from "./risk-score-docs.component"; +import { RiskScoreWidgetExampleComponent } from "./risk-score-widget-example/risk-score-widget-example.component"; +import { getDemoFiles } from "../../../../demo-files-factory"; + +const routes: Routes = [ + { + path: "", + component: RiskScoreDocsComponent, + data: { + srlc: { + hideIndicator: true, + }, + showThemeSwitcher: true, + }, + }, + { + path: "example", + component: RiskScoreWidgetExampleComponent, + data: { + srlc: { + hideIndicator: true, + }, + }, + }, +]; + +@NgModule({ + imports: [ + RouterModule.forChild(routes), + NuiButtonModule, + NuiDocsModule, + NuiMessageModule, + NuiDashboardsModule, + NuiSwitchModule, + ], + declarations: [RiskScoreDocsComponent, RiskScoreWidgetExampleComponent], + providers: [ + { + provide: DEMO_PATH_TOKEN, + useValue: getDemoFiles("risk-score"), + }, + ], +}) +export default class RiskScoreDocsModule {} +\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\`, + "widget-types/risk-score/risk-score-widget-example/risk-score-widget-example.component.ts": \\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\`// © 2023 SolarWinds Worldwide, LLC. All rights reserved. +// +// Permission is hereby granted, free of charge, to any person obtaining a copy +// of this software and associated documentation files (the "Software"), to +// deal in the Software without restriction, including without limitation the +// rights to use, copy, modify, merge, publish, distribute, sublicense, and/or +// sell copies of the Software, and to permit persons to whom the Software is +// furnished to do so, subject to the following conditions: +// +// The above copyright notice and this permission notice shall be included in +// all copies or substantial portions of the Software. +// +// THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR +// IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, +// FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE +// AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER +// LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, +// OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN +// THE SOFTWARE. + +import { HttpClient, HttpErrorResponse } from "@angular/common/http"; +import { Component, Injectable, OnDestroy, OnInit } from "@angular/core"; +import { GridsterConfig, GridsterItem } from "angular-gridster2"; +import { BehaviorSubject } from "rxjs"; +import { finalize } from "rxjs/operators"; + +import { DataSourceService, IFilteringOutputs } from "@nova-ui/bits"; +import { + DATA_SOURCE, + DEFAULT_PIZZAGNA_ROOT, + IDashboard, + IRiskScoreData, + IProviderConfiguration, + IRefresherProperties, + IWidget, + IWidgets, + RiskScoreTileComponent, + NOVA_KPI_DATASOURCE_ADAPTER, + PizzagnaLayer, + ProviderRegistryService, + WellKnownPathKey, + WellKnownProviders, + WidgetTypesService, +} from "@nova-ui/dashboards"; + +/** + * A simple KPI data source to retrieve the average rating of Harry Potter and the Sorcerer's Stone (book) via googleapis + */ +@Injectable() +export class AverageRatingRiskScoreDataSource + extends DataSourceService + implements OnDestroy +{ + // This is the ID we'll use to identify the provider + public static providerId = "AverageRatingRiskScoreDataSource"; + + // Use this subject to communicate the data source's busy state + public busy = new BehaviorSubject(false); + + constructor(private http: HttpClient) { + super(); + } + + // In this example, getFilteredData is invoked every 10 minutes (Take a look at the refresher + // provider definition in the widget configuration below to see how the interval is set) + public async getFilteredData(): Promise { + this.busy.next(true); + return new Promise((resolve) => { + // *** Make a resource request to an external API (if needed) + this.http + .get("https://www.googleapis.com/books/v1/volumes/5MQFrgEACAAJ") + .pipe(finalize(() => this.busy.next(false))) + .subscribe({ + next: (data: any) => { + resolve({ + result: { + value: data.volumeInfo.averageRating, + }, + }); + }, + error: (error: HttpErrorResponse) => { + resolve({ + result: null, + error: { + type: error.status, + }, + }); + }, + }); + }); + } + + public ngOnDestroy(): void { + this.outputsSubject.complete(); + } +} + +/** + * A component that instantiates the dashboard + */ +@Component({ + selector: "risk-score-widget-example", + templateUrl: "./risk-score-widget-example.component.html", + styleUrls: ["./risk-score-widget-example.component.less"], + standalone: false, +}) +export class RiskScoreWidgetExampleComponent implements OnInit { + // This variable will hold all the data needed to define the layout and behavior of the widgets. + // Pass this to the dashboard component's dashboard input in the template. + public dashboard: IDashboard; + + // Angular gridster requires a configuration object even if it's empty. + // Pass this to the dashboard component's gridsterConfig input in the template. + public gridsterConfig: GridsterConfig = {}; + + // Boolean passed as an input to the dashboard. When true, widgets can be moved, resized, removed, or edited + public editMode: boolean = false; + + constructor( + // WidgetTypesService provides the widget's necessary structure information + private widgetTypesService: WidgetTypesService, + + // In general, the ProviderRegistryService is used for making entities available for injection into dynamically loaded components. + private providerRegistry: ProviderRegistryService + ) {} + + public ngOnInit(): void { + // Grabbing the widget's default template which will be needed as a parameter for setNode + const widgetTemplate = this.widgetTypesService.getWidgetType( + "risk-score", + 1 + ); + // Registering our data sources as dropdown options in the widget editor/configurator + // Note: This could also be done in the parent module's constructor so that + // multiple dashboards could have access to the same widget template modification. + this.widgetTypesService.setNode( + // This is the template we grabbed above with getWidgetType + widgetTemplate, + // We are setting the editor/configurator part of the widget template + "configurator", + // This indicates which node you are changing and we want to change + // the data source providers available for selection in the editor. + WellKnownPathKey.DataSourceProviders, + // We are setting the data sources available for selection in the editor + [AverageRatingRiskScoreDataSource.providerId] + ); + + // Registering the data source for injection into the KPI tile. + // Note: Each tile of a KPI widget is assigned its own instance of the data source + this.providerRegistry.setProviders({ + [AverageRatingRiskScoreDataSource.providerId]: { + provide: DATA_SOURCE, + useClass: AverageRatingRiskScoreDataSource, + // Any dependencies that need to be injected into the provider must be listed here + deps: [HttpClient], + }, + }); + + this.initializeDashboard(); + } + + public initializeDashboard(): void { + // We're using a static configuration object for this example, but this is where + // the widget's configuration could potentially be populated from a database + const riskScoreWidget = widgetConfig; + const widgetIndex: IWidgets = { + // Complete the KPI widget with information coming from its type definition + [riskScoreWidget.id]: + this.widgetTypesService.mergeWithWidgetType(riskScoreWidget), + }; + + // Setting the widget dimensions and position (this is for gridster) + const positions: Record = { + [riskScoreWidget.id]: { + cols: 4, + rows: 6, + y: 0, + x: 0, + }, + }; + + // Finally, assigning the variables we created above to the dashboard + this.dashboard = { + positions, + widgets: widgetIndex, + }; + } +} + +const widgetConfig: IWidget = { + id: "riskScoreWidgetId", + type: "risk-score", + pizzagna: { + [PizzagnaLayer.Configuration]: { + [DEFAULT_PIZZAGNA_ROOT]: { + providers: { + [WellKnownProviders.Refresher]: { + properties: { + // Configuring the refresher interval so that our data source is invoked every ten minutes + interval: 60 * 10, + enabled: true, + } as IRefresherProperties, + } as Partial, + }, + }, + header: { + properties: { + title: "Harry Potter and the Sorcerer's Stone", + subtitle: "By J. K. Rowling", + }, + }, + tiles: { + properties: { + nodes: ["riskScore1"], + }, + }, + riskScore1: { + id: "riskScore1", + componentType: RiskScoreTileComponent.lateLoadKey, + properties: { + widgetData: { + minValue: 0, + maxValue: 5, + useStaticLabel: false, + staticLabel: undefined, + label: \\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\`Average Rating\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\`, + description: \\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\`Harry Potter and the Sorcerer's Stone By J. K. Rowling Average Rating Risk Score\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\`, + }, + }, + providers: { + [WellKnownProviders.DataSource]: { + // Setting the data source providerId for the tile with id "kpi1" + providerId: AverageRatingRiskScoreDataSource.providerId, + } as IProviderConfiguration, + [WellKnownProviders.Adapter]: { + providerId: NOVA_KPI_DATASOURCE_ADAPTER, + properties: { + componentId: "riskScore1", + propertyPath: "widgetData", + }, + } as IProviderConfiguration, + }, + }, + }, + }, +}; +\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\`, + "widget-types/table/table-docs.component.ts": \\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\`// © 2022 SolarWinds Worldwide, LLC. All rights reserved. +// +// Permission is hereby granted, free of charge, to any person obtaining a copy +// of this software and associated documentation files (the "Software"), to +// deal in the Software without restriction, including without limitation the +// rights to use, copy, modify, merge, publish, distribute, sublicense, and/or +// sell copies of the Software, and to permit persons to whom the Software is +// furnished to do so, subject to the following conditions: +// +// The above copyright notice and this permission notice shall be included in +// all copies or substantial portions of the Software. +// +// THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR +// IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, +// FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE +// AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER +// LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, +// OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN +// THE SOFTWARE. + +import { Component, OnInit } from "@angular/core"; + +import { mapContentFile } from "../../../../demo-files-factory"; + +@Component({ + selector: "nui-table-docs", + templateUrl: "./table-docs.component.html", + standalone: false, +}) +export class TableDocsComponent implements OnInit { + public widgetFileText = ""; + public configuratorFileText = ""; + + public async ngOnInit(): Promise { + this.widgetFileText = await import( + "./../../../../../../src/lib/widget-types/table/table-widget" + ).then(mapContentFile); + this.configuratorFileText = await import( + "./../../../../../../src/lib/widget-types/table/table-configurator" + ).then(mapContentFile); + } +} +\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\`, + "widget-types/table/table-docs.module.ts": \\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\`// © 2022 SolarWinds Worldwide, LLC. All rights reserved. +// +// Permission is hereby granted, free of charge, to any person obtaining a copy +// of this software and associated documentation files (the "Software"), to +// deal in the Software without restriction, including without limitation the +// rights to use, copy, modify, merge, publish, distribute, sublicense, and/or +// sell copies of the Software, and to permit persons to whom the Software is +// furnished to do so, subject to the following conditions: +// +// The above copyright notice and this permission notice shall be included in +// all copies or substantial portions of the Software. +// +// THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR +// IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, +// FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE +// AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER +// LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, +// OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN +// THE SOFTWARE. + +import { NgModule } from "@angular/core"; +import { RouterModule, Routes } from "@angular/router"; + +import { DEMO_PATH_TOKEN } from "@nova-ui/bits"; +import { + NuiButtonModule, + NuiDocsModule, + NuiMessageModule, + NuiSwitchModule, +} from "@nova-ui/bits"; +import { + NuiDashboardsModule, + TableFormatterRegistryService, +} from "@nova-ui/dashboards"; + +import { TableDocsComponent } from "./table-docs.component"; +import { TablePaginatorDocsComponent } from "./table-paginator-docs.component"; +import { TableSelectableDocsComponent } from "./table-selectable-docs.component"; +import { TableWidgetExampleComponent } from "./table-widget/table-widget-example.component"; +import { TableWidgetInteractiveExampleComponent } from "./table-widget-interactive/table-widget-interactive-example.component"; +import { TableWidgetPaginatorExampleComponent } from "./table-widget-paginator/table-widget-paginator-example.component"; +import { TableWidgetSearchExampleComponent } from "./table-widget-search/table-widget-search-example.component"; +import { TableSearchDocsComponent } from "./table-widget-search-docs.component"; +import { TableWidgetSelectableMultiExampleComponent } from "./table-widget-selectable/table-widget-selectable-multi/table-widget-selectable-multi.example.component"; +import { TableWidgetSelectableRadioExampleComponent } from "./table-widget-selectable/table-widget-selectable-radio/table-widget-selectable-radio.example.component"; +import { TableWidgetSelectableSingleExampleComponent } from "./table-widget-selectable/table-widget-selectable-single/table-widget-selectable-single.example.component"; +import { TableWidgetSelectableExampleComponent } from "./table-widget-selectable/table-widget-selectable.example.component"; +import { DEFAULT_TABLE_FORMATTERS } from "../../../../../../src/lib/widget-types/table/default-table-formatters"; +import { getDemoFiles } from "../../../../demo-files-factory"; + +const routes: Routes = [ + { + path: "", + component: TableDocsComponent, + data: { + srlc: { + hideIndicator: true, + }, + showThemeSwitcher: true, + }, + }, + { + path: "example", + component: TableWidgetExampleComponent, + data: { + srlc: { + hideIndicator: true, + }, + }, + }, + { + path: "table-search", + component: TableSearchDocsComponent, + data: { + srlc: { + hideIndicator: true, + }, + showThemeSwitcher: true, + }, + }, + { + path: "table-paginator", + component: TablePaginatorDocsComponent, + data: { + srlc: { + hideIndicator: true, + }, + showThemeSwitcher: true, + }, + }, + { + path: "table-select", + component: TableSelectableDocsComponent, + data: { + srlc: { + hideIndicator: true, + }, + showThemeSwitcher: true, + }, + }, +]; + +@NgModule({ + imports: [ + RouterModule.forChild(routes), + NuiButtonModule, + NuiDocsModule, + NuiMessageModule, + NuiSwitchModule, + NuiDashboardsModule, + ], + declarations: [ + TableDocsComponent, + TableSearchDocsComponent, + TablePaginatorDocsComponent, + TableWidgetPaginatorExampleComponent, + TableSelectableDocsComponent, + TableWidgetInteractiveExampleComponent, + TableWidgetExampleComponent, + TableWidgetSearchExampleComponent, + TableWidgetSelectableExampleComponent, + TableWidgetSelectableMultiExampleComponent, + TableWidgetSelectableSingleExampleComponent, + TableWidgetSelectableRadioExampleComponent, + ], + providers: [ + { + provide: DEMO_PATH_TOKEN, + useValue: getDemoFiles("table"), + }, + ], +}) +export default class TableDocsModule { + constructor(tableFormattersRegistryService: TableFormatterRegistryService) { + tableFormattersRegistryService.addItems(DEFAULT_TABLE_FORMATTERS); + } +} +\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\`, + "widget-types/table/table-paginator-docs.component.ts": \\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\`// © 2022 SolarWinds Worldwide, LLC. All rights reserved. +// +// Permission is hereby granted, free of charge, to any person obtaining a copy +// of this software and associated documentation files (the "Software"), to +// deal in the Software without restriction, including without limitation the +// rights to use, copy, modify, merge, publish, distribute, sublicense, and/or +// sell copies of the Software, and to permit persons to whom the Software is +// furnished to do so, subject to the following conditions: +// +// The above copyright notice and this permission notice shall be included in +// all copies or substantial portions of the Software. +// +// THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR +// IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, +// FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE +// AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER +// LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, +// OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN +// THE SOFTWARE. + +import { Component } from "@angular/core"; + +@Component({ + selector: "nui-table-paginator-docs", + templateUrl: "./table-paginator-docs.component.html", + standalone: false, +}) +export class TablePaginatorDocsComponent { + public tableConfigurationText = \\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\` + "table": { + ... + properties: { + configuration: { + // define paginator configuration here + scrollType: ScrollType.paginator, + paginatorConfiguration: { + pageSize: 10, // Value have to be one of pageSizeSet values + pageSizeSet: [10, 20, 30], + }, + // If not specified, default is set to + // pageSize: 10, + // pageSizeSet: [10, 20, 50], + hasVirtualScroll: false, // Has to be speciefied because of backward compatibility + } as ITableWidgetConfig, + }, + }, + \\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\`; +} +\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\`, + "widget-types/table/table-selectable-docs.component.ts": \\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\`// © 2022 SolarWinds Worldwide, LLC. All rights reserved. +// +// Permission is hereby granted, free of charge, to any person obtaining a copy +// of this software and associated documentation files (the "Software"), to +// deal in the Software without restriction, including without limitation the +// rights to use, copy, modify, merge, publish, distribute, sublicense, and/or +// sell copies of the Software, and to permit persons to whom the Software is +// furnished to do so, subject to the following conditions: +// +// The above copyright notice and this permission notice shall be included in +// all copies or substantial portions of the Software. +// +// THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR +// IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, +// FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE +// AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER +// LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, +// OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN +// THE SOFTWARE. + +import { Component } from "@angular/core"; + +@Component({ + selector: "nui-table-selectable-docs", + templateUrl: "./table-selectable-docs.component.html", + standalone: false, +}) +export class TableSelectableDocsComponent { + public tableConfigurationText = \\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\` + "table": { + ... + properties: { + // enabling selection here + selectionConfiguration: { + // whether the selection is enabled or disabled + enabled: true, + // can be Multi | Radio | Single + selectionMode: TableSelectionMode.Multi, + // property that uniquely identifies row in a table + trackByProperty: "id", + // whether clicking on row should select it + clickableRow: true, + }, + }, + }, + \\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\`; + + public eventSubscriptionText = \\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\` +... +constructor(Inject(PIZZAGNA_EVENT_BUS) eventBus: EventBus) { + eventBus + .getStream(SELECTION) + // don't forget to unsubscribe! + .subscribe((selection: ISelection) => ...) +} +... + \\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\`; +} +\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\`, + "widget-types/table/table-widget/table-widget-example.component.ts": \\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\`// © 2022 SolarWinds Worldwide, LLC. All rights reserved. +// +// Permission is hereby granted, free of charge, to any person obtaining a copy +// of this software and associated documentation files (the "Software"), to +// deal in the Software without restriction, including without limitation the +// rights to use, copy, modify, merge, publish, distribute, sublicense, and/or +// sell copies of the Software, and to permit persons to whom the Software is +// furnished to do so, subject to the following conditions: +// +// The above copyright notice and this permission notice shall be included in +// all copies or substantial portions of the Software. +// +// THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR +// IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, +// FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE +// AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER +// LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, +// OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN +// THE SOFTWARE. + +import { ChangeDetectorRef, Component, OnInit } from "@angular/core"; +import { GridsterConfig, GridsterItem } from "angular-gridster2"; +import orderBy from "lodash/orderBy"; +import { BehaviorSubject, firstValueFrom, from } from "rxjs"; +import { map, tap } from "rxjs/operators"; + +import { + DataSourceService, + IDataField, + INovaFilteringOutputs, + INovaFilters, + nameof, +} from "@nova-ui/bits"; +import { + DATA_SOURCE, + IDashboard, + ITableWidgetColumnConfig, + IWidget, + IWidgets, + ProviderRegistryService, + WellKnownPathKey, + WellKnownProviders, + WidgetTypesService, +} from "@nova-ui/dashboards"; + +export const BREW_API_URL = "https://api.punkapi.com/v2/beers"; + +export interface IBrewInfo { + id: string; + name: string; + tagline: string; + first_brewed: string; + description: string; + brewers_tips: string; +} + +export interface IBrewDatasourceResponse { + brewInfo: IBrewInfo[]; + total: number; +} + +export class BeerDataSource extends DataSourceService { + public static providerId = "BeerDataSource"; + + private cache: IBrewInfo[] = []; + + public busy = new BehaviorSubject(false); + + public dataFields: Array = [ + { + id: nameof("id"), + label: "No", + dataType: "number", + sortable: true, + }, + // To indicate that a column should not be sortable, set the optional IDataField 'sortable' property to false + { + id: nameof("name"), + label: "Name", + dataType: "string", + sortable: true, + }, + { + id: nameof("tagline"), + label: "Tagline", + dataType: "string", + sortable: true, + }, + { + id: nameof("first_brewed"), + label: "First Brewed", + dataType: "string", + sortable: true, + }, + { + id: nameof("description"), + label: "Description", + dataType: "string", + sortable: false, + }, + { + id: nameof("brewers_tips"), + label: "Brewer's Tips", + dataType: "string", + sortable: false, + }, + ]; + + public async getFilteredData( + filters: INovaFilters + ): Promise { + const start = filters.virtualScroll?.value?.start ?? 0; + const end = filters.virtualScroll?.value?.end ?? 0; + + // Resetting cache on first page request + if (start === 0) { + this.cache = []; + } + + // extract sorter settings to send to the backend + // filters.sorterValue.sortBy; filters.sorterValue.direction + return firstValueFrom( + from(this.fetch(start, end)).pipe( + tap((response: IBrewDatasourceResponse | undefined) => { + if (!response) { + return; + } + this.cache = this.sortData( + this.cache.concat(response.brewInfo), + filters + ); + this.dataSubject.next(this.cache); + }), + map(() => ({ + repeat: { itemsSource: this.cache }, + dataFields: this.dataFields, + })) + ) + ); + } + + public async fetch( + start: number, + end: number + ): Promise { + const delta: number = end - start; + const currentPage: number = end / delta || 0; + const response: object | Array = await ( + await fetch( + \\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\`\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\${BREW_API_URL}/?page=\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\${currentPage}&per_page=\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\${delta}\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\` + ) + ).json(); + + // Note: In case request fails we should not proceed with mapping + if (!Array.isArray(response)) { + return undefined; + } + + return { + brewInfo: response.map((result: IBrewInfo) => ({ + id: result.id, + name: result.name, + tagline: result.tagline, + first_brewed: result.first_brewed, + description: result.description, + brewers_tips: result.brewers_tips, + })), + total: response.length, + }; + } + + private sortData(data: IBrewInfo[], filters: INovaFilters): IBrewInfo[] { + return orderBy( + data, + filters.sorter?.value?.sortBy, + filters.sorter?.value?.direction as "desc" | "asc" + ); + } +} + +/** + * A component that instantiates the dashboard + */ +@Component({ + selector: "table-widget-example", + templateUrl: "./table-widget-example.component.html", + styleUrls: ["./table-widget-example.component.less"], + standalone: false, +}) +export class TableWidgetExampleComponent implements OnInit { + // This variable will hold all the data needed to define the layout and behavior of the widgets. + // Pass this to the dashboard component's dashboard input in the template. + public dashboard: IDashboard | undefined; + + // Angular gridster requires a configuration object even if it's empty. + // Pass this to the dashboard component's gridsterConfig input in the template. + public gridsterConfig: GridsterConfig = {}; + + // Boolean passed as an input to the dashboard. When true, widgets can be moved, resized, removed, or edited + public editMode: boolean = false; + + constructor( + // WidgetTypesService provides the widget's necessary structure information + private widgetTypesService: WidgetTypesService, + // In general, the ProviderRegistryService is used for making entities available for injection into dynamically loaded components. + private providerRegistry: ProviderRegistryService, + private changeDetectorRef: ChangeDetectorRef + ) {} + + public ngOnInit(): void { + // Grabbing the widget's default template which will be needed as a parameter for setNode + const widgetTemplate = this.widgetTypesService.getWidgetType( + "table", + 1 + ); + + // Registering our data sources as dropdown options in the widget editor/configurator + // Note: This could also be done in the parent module's constructor so that + // multiple dashboards could have access to the same widget template modification. + this.widgetTypesService.setNode( + // This is the template we grabbed above with getWidgetType + widgetTemplate, + // We are setting the editor/configurator part of the widget template + "configurator", + // This indicates which node you are changing and we want to change + // the data source providers available for selection in the editor. + WellKnownPathKey.DataSourceProviders, + // We are setting the data sources available for selection in the editor + [BeerDataSource.providerId] + ); + + // Registering the data source for injection into the widget. + this.providerRegistry.setProviders({ + [BeerDataSource.providerId]: { + provide: DATA_SOURCE, + useClass: BeerDataSource, + // Any dependencies that need to be injected into the provider must be listed here + deps: [], + }, + }); + + this.initializeDashboard(); + } + + /** Used for restoring widgets state */ + public reInitializeDashboard(): void { + // destroys the components and their providers so the dashboard can re init data + this.dashboard = undefined; + this.changeDetectorRef.detectChanges(); + + this.initializeDashboard(); + } + + public initializeDashboard(): void { + // We're using a static configuration object for this example, but this is where + // the widget's configuration could potentially be populated from a database + const tableWidget = widgetConfig; + const widgetIndex: IWidgets = { + // Enhance the widget with information coming from it's type definition + [tableWidget.id]: + this.widgetTypesService.mergeWithWidgetType(tableWidget), + }; + + // Setting the widget dimensions and position (this is for gridster) + const positions: Record = { + [tableWidget.id]: { + cols: 12, + rows: 6, + y: 0, + x: 0, + }, + }; + + // Finally, assigning the variables we created above to the dashboard + this.dashboard = { + positions, + widgets: widgetIndex, + }; + } +} + +const TABLE_COLUMNS: ITableWidgetColumnConfig[] = [ + { + id: "column1", + label: $localize\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\`Beer Name\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\`, + isActive: true, + width: 185, + formatter: { + componentType: "RawFormatterComponent", + properties: { + dataFieldIds: { + value: "name", + }, + }, + }, + }, + { + id: "column2", + label: $localize\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\`Tagline\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\`, + isActive: true, + width: 250, + formatter: { + componentType: "RawFormatterComponent", + properties: { + dataFieldIds: { + value: "tagline", + }, + }, + }, + }, + { + id: "column3", + label: $localize\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\`First Brewed\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\`, + isActive: true, + width: 100, + formatter: { + componentType: "RawFormatterComponent", + properties: { + dataFieldIds: { + value: "first_brewed", + }, + }, + }, + }, + { + id: "column4", + label: $localize\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\`Description\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\`, + isActive: true, + formatter: { + componentType: "RawFormatterComponent", + properties: { + dataFieldIds: { + value: "description", + }, + }, + }, + }, +]; + +export const widgetConfig: IWidget = { + id: "tableWidgetId", + type: "table", + pizzagna: { + configuration: { + header: { + properties: { + title: "Stupendous Suds", + subtitle: "Try These Brilliant Brews", + }, + }, + table: { + providers: { + [WellKnownProviders.DataSource]: { + providerId: BeerDataSource.providerId, + }, + }, + properties: { + configuration: { + columns: TABLE_COLUMNS, + sortable: true, + sorterConfiguration: { + descendantSorting: false, + sortBy: "", + }, + hasVirtualScroll: true, + }, + }, + }, + }, + }, +}; +\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\`, + "widget-types/table/table-widget-interactive/table-widget-interactive-example.component.ts": \\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\`// © 2022 SolarWinds Worldwide, LLC. All rights reserved. +// +// Permission is hereby granted, free of charge, to any person obtaining a copy +// of this software and associated documentation files (the "Software"), to +// deal in the Software without restriction, including without limitation the +// rights to use, copy, modify, merge, publish, distribute, sublicense, and/or +// sell copies of the Software, and to permit persons to whom the Software is +// furnished to do so, subject to the following conditions: +// +// The above copyright notice and this permission notice shall be included in +// all copies or substantial portions of the Software. +// +// THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR +// IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, +// FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE +// AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER +// LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, +// OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN +// THE SOFTWARE. + +import { ChangeDetectorRef, Component, OnInit } from "@angular/core"; +import { GridsterConfig, GridsterItem } from "angular-gridster2"; +import orderBy from "lodash/orderBy"; +import { BehaviorSubject, firstValueFrom, from } from "rxjs"; +import { map, tap } from "rxjs/operators"; + +import { + DataSourceService, + IDataField, + INovaFilteringOutputs, + INovaFilters, + nameof, +} from "@nova-ui/bits"; +import { + DATA_SOURCE, + DEFAULT_PIZZAGNA_ROOT, + IDashboard, + ITableWidgetColumnConfig, + IWidget, + IWidgets, + NOVA_URL_INTERACTION_HANDLER, + ProviderRegistryService, + WellKnownPathKey, + WellKnownProviders, + WidgetTypesService, +} from "@nova-ui/dashboards"; + +export const BREW_API_URL = "https://api.punkapi.com/v2/beers"; + +export interface IBrewInfo { + id: string; + name: string; + tagline: string; + first_brewed: string; + description: string; + brewers_tips: string; +} + +export interface IBrewDatasourceResponse { + brewInfo: IBrewInfo[]; + total: number; +} + +export class MockBeerDataSource extends DataSourceService { + public static providerId = "MockBeerDataSource"; + + private cache: IBrewInfo[] = []; + + public busy = new BehaviorSubject(false); + + public dataFields: Array = [ + { + id: nameof("id"), + label: "No", + dataType: "number", + sortable: true, + }, + // To indicate that a column should not be sortable, set the optional IDataField 'sortable' property to false + { + id: nameof("name"), + label: "Name", + dataType: "string", + sortable: true, + }, + { + id: nameof("tagline"), + label: "Tagline", + dataType: "string", + sortable: true, + }, + { + id: nameof("first_brewed"), + label: "First Brewed", + dataType: "string", + sortable: true, + }, + { + id: nameof("description"), + label: "Description", + dataType: "string", + sortable: false, + }, + { + id: nameof("brewers_tips"), + label: "Brewer's Tips", + dataType: "string", + sortable: false, + }, + ]; + + public async getFilteredData( + filters: INovaFilters + ): Promise { + const start = filters.virtualScroll?.value?.start ?? 0; + const end = filters.virtualScroll?.value?.end ?? 0; + + // Resetting cache on first page request + if (start === 0) { + this.cache = []; + } + + // extract sorter settings to send to the backend + // filters.sorterValue.sortBy; filters.sorterValue.direction + return firstValueFrom( + from(this.fetch(start, end)).pipe( + tap((response) => { + if (!response) { + return; + } + this.cache = this.sortData( + this.cache.concat(response.brewInfo), + filters + ); + this.dataSubject.next(this.cache); + }), + map(() => ({ + repeat: { itemsSource: this.cache }, + dataFields: this.dataFields, + })) + ) + ); + } + + public async fetch( + start: number, + end: number + ): Promise { + const delta: number = end - start; + const currentPage: number = end / delta || 0; + const response: object | Array = await ( + await fetch( + \\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\`\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\${BREW_API_URL}/?page=\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\${currentPage}&per_page=\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\${delta}\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\` + ) + ).json(); + console.log( + "📘 table-widget-interactive-example.component: 85# -> response:", + response + ); + + // Note: In case request fails we should not proceed with mapping + if (!Array.isArray(response)) { + return undefined; + } + + return { + brewInfo: response.map((result: IBrewInfo) => ({ + id: result.id, + name: result.name, + tagline: result.tagline, + first_brewed: result.first_brewed, + description: result.description, + brewers_tips: result.brewers_tips, + })), + total: response.length, + }; + } + + private sortData(data: IBrewInfo[], filters: INovaFilters): IBrewInfo[] { + return orderBy( + data, + filters.sorter?.value?.sortBy, + filters.sorter?.value?.direction as "desc" | "asc" + ); + } +} + +/** + * A component that instantiates the dashboard + */ +@Component({ + selector: "table-widget-interactive-example", + templateUrl: "./table-widget-interactive-example.component.html", + styleUrls: ["./table-widget-interactive-example.component.less"], + standalone: false, +}) +export class TableWidgetInteractiveExampleComponent implements OnInit { + // This variable will hold all the data needed to define the layout and behavior of the widgets. + // Pass this to the dashboard component's dashboard input in the template. + public dashboard: IDashboard | undefined; + + // Angular gridster requires a configuration object even if it's empty. + // Pass this to the dashboard component's gridsterConfig input in the template. + public gridsterConfig: GridsterConfig = {}; + + // Boolean passed as an input to the dashboard. When true, widgets can be moved, resized, removed, or edited + public editMode: boolean = false; + + constructor( + // WidgetTypesService provides the widget's necessary structure information + private widgetTypesService: WidgetTypesService, + // In general, the ProviderRegistryService is used for making entities available for injection into dynamically loaded components. + private providerRegistry: ProviderRegistryService, + private changeDetectorRef: ChangeDetectorRef + ) {} + + public ngOnInit(): void { + // Grabbing the widget's default template which will be needed as a parameter for setNode + const widgetTemplate = this.widgetTypesService.getWidgetType( + "table", + 1 + ); + + // Registering our data sources as dropdown options in the widget editor/configurator + // Note: This could also be done in the parent module's constructor so that + // multiple dashboards could have access to the same widget template modification. + this.widgetTypesService.setNode( + // This is the template we grabbed above with getWidgetType + widgetTemplate, + // We are setting the editor/configurator part of the widget template + "configurator", + // This indicates which node you are changing and we want to change + // the data source providers available for selection in the editor. + WellKnownPathKey.DataSourceProviders, + // We are setting the data sources available for selection in the editor + [MockBeerDataSource.providerId] + ); + + // Registering the data source for injection into the widget. + this.providerRegistry.setProviders({ + [MockBeerDataSource.providerId]: { + provide: DATA_SOURCE, + useClass: MockBeerDataSource, + // Any dependencies that need to be injected into the provider must be listed here + deps: [], + }, + }); + + this.initializeDashboard(); + } + + /** Used for restoring widgets state */ + public reInitializeDashboard(): void { + // destroys the components and their providers so the dashboard can re init data + this.dashboard = undefined; + this.changeDetectorRef.detectChanges(); + + this.initializeDashboard(); + } + + public initializeDashboard(): void { + // We're using a static configuration object for this example, but this is where + // the widget's configuration could potentially be populated from a database + const tableWidget = widgetConfig; + const widgetIndex: IWidgets = { + // Enhance the widget with information coming from it's type definition + [tableWidget.id]: + this.widgetTypesService.mergeWithWidgetType(tableWidget), + }; + + // Setting the widget dimensions and position (this is for gridster) + const positions: Record = { + [tableWidget.id]: { + cols: 12, + rows: 6, + y: 0, + x: 0, + }, + }; + + // Finally, assigning the variables we created above to the dashboard + this.dashboard = { + positions, + widgets: widgetIndex, + }; + } +} + +const TABLE_COLUMNS: ITableWidgetColumnConfig[] = [ + { + id: "column1", + label: $localize\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\`Beer Name\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\`, + isActive: true, + width: 185, + formatter: { + componentType: "RawFormatterComponent", + properties: { + dataFieldIds: { + value: "name", + }, + }, + }, + }, + { + id: "column2", + label: $localize\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\`Tagline\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\`, + isActive: true, + width: 250, + formatter: { + componentType: "RawFormatterComponent", + properties: { + dataFieldIds: { + value: "tagline", + }, + }, + }, + }, + { + id: "column3", + label: $localize\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\`First Brewed\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\`, + isActive: true, + width: 100, + formatter: { + componentType: "RawFormatterComponent", + properties: { + dataFieldIds: { + value: "first_brewed", + }, + }, + }, + }, + { + id: "column4", + label: $localize\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\`Description\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\`, + isActive: true, + formatter: { + componentType: "RawFormatterComponent", + properties: { + dataFieldIds: { + value: "description", + }, + }, + }, + }, +]; + +export const widgetConfig: IWidget = { + id: "tableWidgetId", + type: "table", + pizzagna: { + configuration: { + [DEFAULT_PIZZAGNA_ROOT]: { + providers: { + [WellKnownProviders.InteractionHandler]: { + // Configuring the UrlInteractionHandler to handle interactions + providerId: NOVA_URL_INTERACTION_HANDLER, + properties: { + // the 'url' property tells the handler what link to use when interaction occurs on the series + url: "\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\${'https://untappd.com/search?q='+data.name}", + // by default the link is opened in the current window, set 'newWindow' to true to open in a new tab instead + newWindow: true, + }, + }, + }, + }, + header: { + properties: { + title: "Stupendous Suds", + subtitle: "Try These Brilliant Brews", + }, + }, + table: { + providers: { + [WellKnownProviders.DataSource]: { + providerId: MockBeerDataSource.providerId, + }, + }, + properties: { + configuration: { + // set interactions to true on the table + interactive: true, + columns: TABLE_COLUMNS, + sortable: true, + sorterConfiguration: { + descendantSorting: false, + sortBy: "", + }, + hasVirtualScroll: true, + }, + }, + }, + }, + }, +}; +\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\`, + "widget-types/table/table-widget-paginator/table-widget-paginator-example.component.ts": \\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\`// © 2022 SolarWinds Worldwide, LLC. All rights reserved. +// +// Permission is hereby granted, free of charge, to any person obtaining a copy +// of this software and associated documentation files (the "Software"), to +// deal in the Software without restriction, including without limitation the +// rights to use, copy, modify, merge, publish, distribute, sublicense, and/or +// sell copies of the Software, and to permit persons to whom the Software is +// furnished to do so, subject to the following conditions: +// +// The above copyright notice and this permission notice shall be included in +// all copies or substantial portions of the Software. +// +// THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR +// IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, +// FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE +// AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER +// LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, +// OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN +// THE SOFTWARE. + +import { HttpClient } from "@angular/common/http"; +import { ChangeDetectorRef, Component, OnInit } from "@angular/core"; +import { GridsterConfig, GridsterItem } from "angular-gridster2"; + +import { LoggerService } from "@nova-ui/bits"; +import { + DATA_SOURCE, + DEFAULT_PIZZAGNA_ROOT, + IDashboard, + IProviderConfiguration, + ITableWidgetConfig, + IWidget, + IWidgets, + NOVA_URL_INTERACTION_HANDLER, + PizzagnaLayer, + ProviderRegistryService, + RawFormatterComponent, + ScrollType, + WellKnownPathKey, + WellKnownProviders, + WidgetTypesService, +} from "@nova-ui/dashboards"; + +import { AcmeTableMockDataSource } from "../../../../prototypes/data/table/acme-table-mock-data-source.service"; + +/** + * A component that instantiates the dashboard + */ +@Component({ + selector: "table-widget-paginator-example", + templateUrl: "./table-widget-paginator-example.component.html", + styleUrls: ["./table-widget-paginator-example.component.less"], + standalone: false, +}) +export class TableWidgetPaginatorExampleComponent implements OnInit { + public dashboard: IDashboard | undefined; + public gridsterConfig: GridsterConfig = {}; + public editMode: boolean = false; + + constructor( + private widgetTypesService: WidgetTypesService, + private providerRegistry: ProviderRegistryService, + private changeDetectorRef: ChangeDetectorRef + ) {} + + public ngOnInit(): void { + const widgetTemplate = this.widgetTypesService.getWidgetType( + "table", + 1 + ); + this.widgetTypesService.setNode( + widgetTemplate, + "configurator", + WellKnownPathKey.DataSourceProviders, + [AcmeTableMockDataSource.providerId] + ); + + this.providerRegistry.setProviders({ + [AcmeTableMockDataSource.providerId]: { + provide: DATA_SOURCE, + useClass: AcmeTableMockDataSource, + deps: [LoggerService, HttpClient], + }, + }); + + this.initializeDashboard(); + } + + /** Used for restoring widgets state */ + public reInitializeDashboard(): void { + // destroys the components and their providers so the dashboard can re init data + this.dashboard = undefined; + this.changeDetectorRef.detectChanges(); + + this.initializeDashboard(); + } + + public initializeDashboard(): void { + const tableWithPaginator = tableWidgetWithPaginator; + const tableWithVirtualScroll = tableWidgetWithVirtualScroll; + + const widgetIndex: IWidgets = { + [tableWithPaginator.id]: + this.widgetTypesService.mergeWithWidgetType(tableWithPaginator), + [tableWithVirtualScroll.id]: + this.widgetTypesService.mergeWithWidgetType( + tableWithVirtualScroll + ), + }; + + const positions: Record = { + [tableWithPaginator.id]: { + cols: 6, + rows: 6, + y: 0, + x: 0, + }, + [tableWithVirtualScroll.id]: { + cols: 6, + rows: 6, + y: 0, + x: 0, + }, + }; + + this.dashboard = { + positions, + widgets: widgetIndex, + }; + } +} + +export const tableWidgetWithPaginator: IWidget = { + id: "widget1", + type: "table", + pizzagna: { + [PizzagnaLayer.Configuration]: { + [DEFAULT_PIZZAGNA_ROOT]: { + providers: { + [WellKnownProviders.InteractionHandler]: { + providerId: NOVA_URL_INTERACTION_HANDLER, + }, + }, + }, + header: { + properties: { + title: "Table Widget with paginator!", + subtitle: "Basic table widget", + collapsible: true, + }, + }, + table: { + providers: { + [WellKnownProviders.DataSource]: { + providerId: AcmeTableMockDataSource.providerId, + } as IProviderConfiguration, + }, + properties: { + configuration: { + interactive: true, + columns: [ + { + id: "column1", + label: "No.", + isActive: true, + formatter: { + componentType: + RawFormatterComponent.lateLoadKey, + properties: { + dataFieldIds: { + value: "position", + }, + }, + }, + }, + { + id: "column2", + label: "Name", + isActive: true, + formatter: { + componentType: + RawFormatterComponent.lateLoadKey, + properties: { + dataFieldIds: { + value: "name", + }, + }, + }, + }, + { + id: "column3", + label: "Status", + isActive: true, + formatter: { + componentType: + RawFormatterComponent.lateLoadKey, + properties: { + dataFieldIds: { + value: "status", + }, + }, + }, + }, + ], + sorterConfiguration: { + descendantSorting: false, + sortBy: "column1", + }, + scrollType: ScrollType.paginator, + paginatorConfiguration: { + pageSize: 5, + pageSizeSet: [5, 10, 20, 30], + }, + hasVirtualScroll: false, + searchConfiguration: { + enabled: true, + }, + } as ITableWidgetConfig, + }, + }, + }, + }, +}; + +export const tableWidgetWithVirtualScroll: IWidget = { + id: "widget2", + type: "table", + pizzagna: { + [PizzagnaLayer.Configuration]: { + [DEFAULT_PIZZAGNA_ROOT]: { + providers: { + [WellKnownProviders.InteractionHandler]: { + providerId: NOVA_URL_INTERACTION_HANDLER, + }, + }, + }, + header: { + properties: { + title: "Table Widget with virtual scroll!", + subtitle: "Basic table widget", + collapsible: true, + }, + }, + table: { + providers: { + [WellKnownProviders.DataSource]: { + providerId: AcmeTableMockDataSource.providerId, + } as IProviderConfiguration, + }, + properties: { + configuration: { + interactive: true, + columns: [ + { + id: "column1", + label: "No.", + isActive: true, + formatter: { + componentType: + RawFormatterComponent.lateLoadKey, + properties: { + dataFieldIds: { + value: "position", + }, + }, + }, + }, + { + id: "column2", + label: "Name", + isActive: true, + formatter: { + componentType: + RawFormatterComponent.lateLoadKey, + properties: { + dataFieldIds: { + value: "name", + }, + }, + }, + }, + { + id: "column3", + label: "Status", + isActive: true, + formatter: { + componentType: + RawFormatterComponent.lateLoadKey, + properties: { + dataFieldIds: { + value: "status", + }, + }, + }, + }, + ], + sorterConfiguration: { + descendantSorting: false, + sortBy: "column1", + }, + hasVirtualScroll: true, + searchConfiguration: { + enabled: true, + }, + } as ITableWidgetConfig, + }, + }, + }, + }, +}; +\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\`, + "widget-types/table/table-widget-search/table-widget-search-example.component.ts": \\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\`// © 2022 SolarWinds Worldwide, LLC. All rights reserved. +// +// Permission is hereby granted, free of charge, to any person obtaining a copy +// of this software and associated documentation files (the "Software"), to +// deal in the Software without restriction, including without limitation the +// rights to use, copy, modify, merge, publish, distribute, sublicense, and/or +// sell copies of the Software, and to permit persons to whom the Software is +// furnished to do so, subject to the following conditions: +// +// The above copyright notice and this permission notice shall be included in +// all copies or substantial portions of the Software. +// +// THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR +// IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, +// FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE +// AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER +// LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, +// OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN +// THE SOFTWARE. + +import { HttpClient } from "@angular/common/http"; +import { + ChangeDetectorRef, + Component, + Injectable, + OnInit, +} from "@angular/core"; +import { GridsterConfig, GridsterItem } from "angular-gridster2"; +import isEqual from "lodash/isEqual"; +import isNil from "lodash/isNil"; +import { BehaviorSubject, firstValueFrom, Observable, of, Subject } from "rxjs"; +import { + catchError, + delay, + finalize, + map, + // eslint-disable-next-line import/no-deprecated + switchMap, + tap, +} from "rxjs/operators"; + +import { + DataSourceFeatures, + DataSourceService, + IDataField, + IDataSource, + IDataSourceFeatures, + IDataSourceFeaturesConfiguration, + IDataSourceOutput, + IFilter, + IFilters, + INovaFilteringOutputs, + INovaFilters, + LoggerService, +} from "@nova-ui/bits"; +import { + DATA_SOURCE, + IDashboard, + ITableWidgetConfig, + IWidget, + IWidgets, + ProviderRegistryService, + WellKnownPathKey, + WellKnownProviders, + WidgetTypesService, +} from "@nova-ui/dashboards"; + +import { GBOOKS_API_URL } from "../../../../prototypes/data/table/constants"; + +interface IGBooksApiResponse { + kind: string; + totalItems: number; + items: IGBooksItemModel[]; + [key: string]: any; +} + +interface IGBooksItemModel { + id: string; + volumeInfo: { + title: string; + subtitle: string; + authors: string[]; + [key: string]: any; + }; + accessInfo: { [key: string]: any }; + saleInfo: { [key: string]: any }; +} + +interface IGBooksData { + books: IGBooksVolume[]; + totalItems: number; +} + +interface IGBooksVolume { + title: string; + authors: string; +} + +type searchableColumnType = "title" | "authors"; + +@Injectable() +export class AcmeTableGBooksDataSource + extends DataSourceService + implements IDataSource +{ + public static providerId = "AcmeTableGBooksDataSource"; + public static mockError = false; + + public searchableColumn: searchableColumnType = "title"; + + public page: number = 1; + public busy = new BehaviorSubject(false); + public features: IDataSourceFeaturesConfiguration; + + private cache = Array.from({ length: 0 }); + private previousFilters: INovaFilters; + // DataSource Features declared + private supportedFeatures: IDataSourceFeatures = { + search: { enabled: true }, + pagination: { enabled: true }, + }; + private columnToQueryParamMap: { [k in searchableColumnType]: string } = { + title: "intitle", + authors: "inauthor", + }; + + private applyFilters$ = new Subject(); + + public dataFields: Array = [ + { + id: "title", + label: $localize\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\`Title\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\`, + dataType: "string", + sortable: false, + }, + { + id: "authors", + label: $localize\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\`Authors\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\`, + dataType: "string", + sortable: false, + }, + ]; + + constructor(private logger: LoggerService, private http: HttpClient) { + super(); + // Using Nova DataSourceFeatures implementation for the features + this.features = new DataSourceFeatures(this.supportedFeatures); + + this.applyFilters$ + // eslint-disable-next-line import/no-deprecated + .pipe(switchMap((filters) => this.getData(filters))) + .subscribe(async (res) => { + this.outputsSubject.next(await this.getFilteredData(res)); + }); + } + + public async getFilteredData( + booksData: IGBooksData + ): Promise> { + return firstValueFrom( + of(booksData).pipe( + tap((response) => { + this.cache = this.cache.concat(response.books); + }), + map((response) => ({ + result: { + repeat: { itemsSource: this.cache }, + paginator: { total: response.totalItems }, + dataFields: this.dataFields, + }, + })) + ) + ); + } + + private getData(filters: INovaFilters): Observable { + if ( + this.isNewSearchTerm(filters.search) && + filters.virtualScroll?.value.start === 0 + ) { + this.cache = []; + } + + return this.http + .get(this.getComposedUrl(filters)) + .pipe( + tap(() => this.busy.next(true)), + delay(300), // mock + map((response) => ({ + books: + response.items?.map((volume) => ({ + title: volume.volumeInfo.title, + authors: + volume.volumeInfo.authors?.join(", ") || "", + })) || [], + totalItems: response.totalItems, + })), + catchError((e) => { + this.logger.error(e); + return of({ + books: [], + totalItems: 0, + }); + }), + finalize(() => { + this.busy.next(false); + this.previousFilters = filters; + }) + ); + } + + private getComposedUrl(filters: INovaFilters) { + const initialUrl = \\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\`\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\${GBOOKS_API_URL}?q=\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\`; + const maxResults = \\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\`maxResults=\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\${ + (filters.virtualScroll?.value.end || 0) - + (filters.virtualScroll?.value.start || 0) + }\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\`; + + const virtualScrollPart = filters.virtualScroll + ? \\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\`startIndex=\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\${filters.virtualScroll.value.start}\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\` + : ""; + + const searchQueryParam = + this.columnToQueryParamMap[this.searchableColumn]; + const searchPart = filters.search + ? \\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\`\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\${searchQueryParam}:\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\${filters.search.value}\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\` + : "_"; // google books api requires some criteria to do the search + + return \\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\`\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\${initialUrl}\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\${searchPart}&\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\${maxResults}&\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\${virtualScrollPart}&filter=full\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\`; + } + + private isNewSearchTerm(search: IFilter | undefined) { + return ( + !isNil(search?.value) && + !isEqual(search?.value, this.previousFilters?.search?.value) + ); + } + + // redefine parent method + public async applyFilters(): Promise { + this.applyFilters$.next(this.getFilters()); + } +} + +/** + * A component that instantiates the dashboard + */ +@Component({ + selector: "table-widget-search-example", + templateUrl: "./table-widget-search-example.component.html", + styleUrls: ["./table-widget-search-example.component.less"], + standalone: false, +}) +export class TableWidgetSearchExampleComponent implements OnInit { + public dashboard: IDashboard | undefined; + public gridsterConfig: GridsterConfig = {}; + public editMode: boolean = false; + + constructor( + private widgetTypesService: WidgetTypesService, + private providerRegistry: ProviderRegistryService, + private changeDetectorRef: ChangeDetectorRef + ) {} + + public ngOnInit(): void { + const widgetTemplate = this.widgetTypesService.getWidgetType( + "table", + 1 + ); + this.widgetTypesService.setNode( + widgetTemplate, + "configurator", + WellKnownPathKey.DataSourceProviders, + [AcmeTableGBooksDataSource.providerId] + ); + + this.providerRegistry.setProviders({ + [AcmeTableGBooksDataSource.providerId]: { + provide: DATA_SOURCE, + useClass: AcmeTableGBooksDataSource, + deps: [LoggerService, HttpClient], + }, + }); + + this.initializeDashboard(); + } + + /** Used for restoring widgets state */ + public reInitializeDashboard(): void { + // destroys the components and their providers so the dashboard can re init data + this.dashboard = undefined; + this.changeDetectorRef.detectChanges(); + + this.initializeDashboard(); + } + + public initializeDashboard(): void { + const tableWidget = widgetConfig; + const widgetIndex: IWidgets = { + [tableWidget.id]: + this.widgetTypesService.mergeWithWidgetType(tableWidget), + }; + + const positions: Record = { + [tableWidget.id]: { + cols: 12, + rows: 6, + y: 0, + x: 0, + }, + }; + + this.dashboard = { + positions, + widgets: widgetIndex, + }; + } +} + +export const widgetConfig: IWidget = { + id: "tableWidgetId", + type: "table", + pizzagna: { + configuration: { + header: { + properties: { + title: "Google Books", + }, + }, + table: { + providers: { + [WellKnownProviders.DataSource]: { + providerId: AcmeTableGBooksDataSource.providerId, + }, + }, + properties: { + configuration: { + columns: [ + { + id: "column1", + label: $localize\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\`Title\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\`, + isActive: true, + formatter: { + componentType: "RawFormatterComponent", + properties: { + dataFieldIds: { + value: "title", + }, + }, + }, + }, + { + id: "column2", + label: $localize\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\`Author\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\`, + isActive: true, + formatter: { + componentType: "RawFormatterComponent", + properties: { + dataFieldIds: { + value: "authors", + }, + }, + }, + }, + ], + sortable: false, + // define search configuration here + searchConfiguration: { + enabled: true, + // following properties below can be configured as well + // searchTerm: "search criteria here", + // searchDebounce: 300, + }, + hasVirtualScroll: true, + } as ITableWidgetConfig, + }, + }, + }, + }, +}; +\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\`, + "widget-types/table/table-widget-search-docs.component.ts": \\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\`// © 2022 SolarWinds Worldwide, LLC. All rights reserved. +// +// Permission is hereby granted, free of charge, to any person obtaining a copy +// of this software and associated documentation files (the "Software"), to +// deal in the Software without restriction, including without limitation the +// rights to use, copy, modify, merge, publish, distribute, sublicense, and/or +// sell copies of the Software, and to permit persons to whom the Software is +// furnished to do so, subject to the following conditions: +// +// The above copyright notice and this permission notice shall be included in +// all copies or substantial portions of the Software. +// +// THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR +// IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, +// FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE +// AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER +// LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, +// OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN +// THE SOFTWARE. + +import { Component } from "@angular/core"; + +@Component({ + selector: "nui-table-search-docs", + templateUrl: "./table-widget-search-docs.component.html", + standalone: false, +}) +export class TableSearchDocsComponent { + public featuredDeclaredText = \\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\` + private supportedFeatures: IDataSourceFeatures = { + search: { enabled: true }, + pagination: { enabled: true }, + };\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\`; + public featuresUsedText = \\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\` + this.features = new DataSourceFeatures(this.supportedFeatures); + \\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\`; + public tableConfigurationText = \\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\` + "table": { + ... + properties: { + configuration: { + // define search configuration here + searchConfiguration: { + enabled: true, + // following optional properties below can be configured as well + // searchTerm: "search criteria here", + // searchDebounce: 300, + }, + } as ITableWidgetConfig, + }, + }, + \\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\`; +} +\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\`, + "widget-types/table/table-widget-selectable/table-widget-selectable-multi/table-widget-selectable-multi.example.component.ts": \\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\`// © 2022 SolarWinds Worldwide, LLC. All rights reserved. +// +// Permission is hereby granted, free of charge, to any person obtaining a copy +// of this software and associated documentation files (the "Software"), to +// deal in the Software without restriction, including without limitation the +// rights to use, copy, modify, merge, publish, distribute, sublicense, and/or +// sell copies of the Software, and to permit persons to whom the Software is +// furnished to do so, subject to the following conditions: +// +// The above copyright notice and this permission notice shall be included in +// all copies or substantial portions of the Software. +// +// THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR +// IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, +// FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE +// AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER +// LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, +// OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN +// THE SOFTWARE. + +import { Component } from "@angular/core"; + +import { TableSelectionMode } from "@nova-ui/bits"; +import { TableWidgetSelectionConfig } from "@nova-ui/dashboards"; + +/** + * A component that instantiates the dashboard + */ +@Component({ + selector: "table-widget-selectable-multi-example", + templateUrl: "./table-widget-selectable-multi.example.component.html", + styleUrls: ["./table-widget-selectable-multi.example.component.less"], + standalone: false, +}) +export class TableWidgetSelectableMultiExampleComponent { + public selectionConfiguration: TableWidgetSelectionConfig = { + enabled: true, + selectionMode: TableSelectionMode.Multi, + trackByProperty: "id", + clickableRow: true, + }; +} +\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\`, + "widget-types/table/table-widget-selectable/table-widget-selectable-radio/table-widget-selectable-radio.example.component.ts": \\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\`// © 2022 SolarWinds Worldwide, LLC. All rights reserved. +// +// Permission is hereby granted, free of charge, to any person obtaining a copy +// of this software and associated documentation files (the "Software"), to +// deal in the Software without restriction, including without limitation the +// rights to use, copy, modify, merge, publish, distribute, sublicense, and/or +// sell copies of the Software, and to permit persons to whom the Software is +// furnished to do so, subject to the following conditions: +// +// The above copyright notice and this permission notice shall be included in +// all copies or substantial portions of the Software. +// +// THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR +// IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, +// FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE +// AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER +// LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, +// OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN +// THE SOFTWARE. + +import { Component } from "@angular/core"; + +import { TableSelectionMode } from "@nova-ui/bits"; +import { TableWidgetSelectionConfig } from "@nova-ui/dashboards"; + +/** + * A component that instantiates the dashboard + */ +@Component({ + selector: "table-widget-selectable-radio-example", + templateUrl: "./table-widget-selectable-radio.example.component.html", + styleUrls: ["./table-widget-selectable-radio.example.component.less"], + standalone: false, +}) +export class TableWidgetSelectableRadioExampleComponent { + public selectionConfiguration: TableWidgetSelectionConfig = { + enabled: true, + selectionMode: TableSelectionMode.Radio, + trackByProperty: "id", + clickableRow: true, + }; +} +\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\`, + "widget-types/table/table-widget-selectable/table-widget-selectable-single/table-widget-selectable-single.example.component.ts": \\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\`// © 2022 SolarWinds Worldwide, LLC. All rights reserved. +// +// Permission is hereby granted, free of charge, to any person obtaining a copy +// of this software and associated documentation files (the "Software"), to +// deal in the Software without restriction, including without limitation the +// rights to use, copy, modify, merge, publish, distribute, sublicense, and/or +// sell copies of the Software, and to permit persons to whom the Software is +// furnished to do so, subject to the following conditions: +// +// The above copyright notice and this permission notice shall be included in +// all copies or substantial portions of the Software. +// +// THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR +// IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, +// FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE +// AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER +// LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, +// OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN +// THE SOFTWARE. + +import { Component } from "@angular/core"; + +import { TableSelectionMode } from "@nova-ui/bits"; +import { TableWidgetSelectionConfig } from "@nova-ui/dashboards"; + +/** + * A component that instantiates the dashboard + */ +@Component({ + selector: "table-widget-selectable-single-example", + templateUrl: "./table-widget-selectable-single.example.component.html", + styleUrls: ["./table-widget-selectable-single.example.component.less"], + standalone: false, +}) +export class TableWidgetSelectableSingleExampleComponent { + public selectionConfiguration: TableWidgetSelectionConfig = { + enabled: true, + selectionMode: TableSelectionMode.Single, + trackByProperty: "id", + }; +} +\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\`, + "widget-types/table/table-widget-selectable/table-widget-selectable.example.component.ts": \\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\`// © 2022 SolarWinds Worldwide, LLC. All rights reserved. +// +// Permission is hereby granted, free of charge, to any person obtaining a copy +// of this software and associated documentation files (the "Software"), to +// deal in the Software without restriction, including without limitation the +// rights to use, copy, modify, merge, publish, distribute, sublicense, and/or +// sell copies of the Software, and to permit persons to whom the Software is +// furnished to do so, subject to the following conditions: +// +// The above copyright notice and this permission notice shall be included in +// all copies or substantial portions of the Software. +// +// THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR +// IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, +// FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE +// AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER +// LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, +// OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN +// THE SOFTWARE. + +import { HttpClient } from "@angular/common/http"; +import { ChangeDetectorRef, Component, Input, OnInit } from "@angular/core"; +import { GridsterConfig, GridsterItem } from "angular-gridster2"; + +import { LoggerService, TableSelectionMode } from "@nova-ui/bits"; +import { + DATA_SOURCE, + DEFAULT_PIZZAGNA_ROOT, + IDashboard, + IProviderConfiguration, + ITableWidgetConfig, + IWidget, + IWidgets, + NOVA_URL_INTERACTION_HANDLER, + PizzagnaLayer, + ProviderRegistryService, + RawFormatterComponent, + TableWidgetSelectionConfig, + WellKnownPathKey, + WellKnownProviders, + WidgetTypesService, +} from "@nova-ui/dashboards"; + +import { AcmeTableMockDataSource } from "../../../../prototypes/data/table/acme-table-mock-data-source.service"; + +/** + * A component that instantiates the dashboard + */ +@Component({ + selector: "table-widget-selectable-example", + templateUrl: "./table-widget-selectable.example.component.html", + styleUrls: ["./table-widget-selectable.example.component.less"], + standalone: false, +}) +export class TableWidgetSelectableExampleComponent implements OnInit { + public dashboard: IDashboard | undefined; + public gridsterConfig: GridsterConfig = {}; + public editMode: boolean = false; + + @Input() public selectionConfiguration: TableWidgetSelectionConfig = { + enabled: false, + selectionMode: TableSelectionMode.None, + }; + + constructor( + private widgetTypesService: WidgetTypesService, + private providerRegistry: ProviderRegistryService, + private changeDetectorRef: ChangeDetectorRef + ) {} + + public ngOnInit(): void { + const widgetTemplate = this.widgetTypesService.getWidgetType( + "table", + 1 + ); + this.widgetTypesService.setNode( + widgetTemplate, + "configurator", + WellKnownPathKey.DataSourceProviders, + [AcmeTableMockDataSource.providerId] + ); + + this.providerRegistry.setProviders({ + [AcmeTableMockDataSource.providerId]: { + provide: DATA_SOURCE, + useClass: AcmeTableMockDataSource, + deps: [LoggerService, HttpClient], + }, + }); + + this.initializeDashboard(); + } + + /** Used for restoring widgets state */ + public reInitializeDashboard(): void { + // destroys the components and their providers so the dashboard can re init data + this.dashboard = undefined; + this.changeDetectorRef.detectChanges(); + + this.initializeDashboard(); + } + + public initializeDashboard(): void { + const tableWidget = this.widgetConfig; + const widgetIndex: IWidgets = { + [tableWidget.id]: + this.widgetTypesService.mergeWithWidgetType(tableWidget), + }; + + const positions: Record = { + [tableWidget.id]: { + cols: 12, + rows: 6, + y: 0, + x: 0, + }, + }; + + this.dashboard = { + positions, + widgets: widgetIndex, + }; + } + + private get widgetConfig(): IWidget { + return { + id: "widget1", + type: "table", + pizzagna: { + [PizzagnaLayer.Configuration]: { + [DEFAULT_PIZZAGNA_ROOT]: { + providers: { + [WellKnownProviders.InteractionHandler]: { + providerId: NOVA_URL_INTERACTION_HANDLER, + }, + }, + }, + header: { + properties: { + title: "Table Widget with Selection!", + subtitle: "Basic table widget", + collapsible: true, + }, + }, + table: { + providers: { + [WellKnownProviders.DataSource]: { + providerId: AcmeTableMockDataSource.providerId, + } as IProviderConfiguration, + }, + properties: { + configuration: { + // enabling selection here + selectionConfiguration: + this.selectionConfiguration, + columns: [ + { + id: "column1", + label: "No.", + isActive: true, + formatter: { + componentType: + RawFormatterComponent.lateLoadKey, + properties: { + dataFieldIds: { + value: "position", + }, + }, + }, + }, + { + id: "column2", + label: "Name", + isActive: true, + formatter: { + componentType: + RawFormatterComponent.lateLoadKey, + properties: { + dataFieldIds: { + value: "name", + }, + }, + }, + }, + { + id: "column3", + label: "Status", + isActive: true, + formatter: { + componentType: + RawFormatterComponent.lateLoadKey, + properties: { + dataFieldIds: { + value: "status", + }, + }, + }, + }, + ], + } as ITableWidgetConfig, + }, + }, + }, + }, + }; + } +} +\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\`, + "widget-types/timeseries/timeseries-docs.component.ts": \\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\`// © 2022 SolarWinds Worldwide, LLC. All rights reserved. +// +// Permission is hereby granted, free of charge, to any person obtaining a copy +// of this software and associated documentation files (the "Software"), to +// deal in the Software without restriction, including without limitation the +// rights to use, copy, modify, merge, publish, distribute, sublicense, and/or +// sell copies of the Software, and to permit persons to whom the Software is +// furnished to do so, subject to the following conditions: +// +// The above copyright notice and this permission notice shall be included in +// all copies or substantial portions of the Software. +// +// THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR +// IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, +// FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE +// AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER +// LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, +// OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN +// THE SOFTWARE. + +import { Component, OnInit } from "@angular/core"; + +import { mapContentFile } from "../../../../demo-files-factory"; + +@Component({ + selector: "nui-timeseries-docs", + templateUrl: "./timeseries-docs.component.html", + standalone: false, +}) +export class TimeseriesDocsComponent implements OnInit { + public timeseriesWidgetFileText = ""; + public timeseriesConfiguratorFileText = ""; + + async ngOnInit(): Promise { + this.timeseriesWidgetFileText = await import( + "./../../../../../../src/lib/widget-types/timeseries/timeseries-widget" + ).then(mapContentFile); + this.timeseriesConfiguratorFileText = await import( + "./../../../../../../src/lib/widget-types/timeseries/timeseries-configurator" + ).then(mapContentFile); + } +} +\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\`, + "widget-types/timeseries/timeseries-docs.module.ts": \\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\`// © 2022 SolarWinds Worldwide, LLC. All rights reserved. +// +// Permission is hereby granted, free of charge, to any person obtaining a copy +// of this software and associated documentation files (the "Software"), to +// deal in the Software without restriction, including without limitation the +// rights to use, copy, modify, merge, publish, distribute, sublicense, and/or +// sell copies of the Software, and to permit persons to whom the Software is +// furnished to do so, subject to the following conditions: +// +// The above copyright notice and this permission notice shall be included in +// all copies or substantial portions of the Software. +// +// THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR +// IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, +// FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE +// AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER +// LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, +// OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN +// THE SOFTWARE. + +import { NgModule } from "@angular/core"; +import { RouterModule, Routes } from "@angular/router"; + +import { DEMO_PATH_TOKEN } from "@nova-ui/bits"; +import { + NuiButtonModule, + NuiDocsModule, + NuiMessageModule, + NuiSwitchModule, +} from "@nova-ui/bits"; +import { NuiDashboardsModule } from "@nova-ui/dashboards"; + +import { TimeseriesDocsComponent } from "./timeseries-docs.component"; +import { TimeseriesWidgetExampleComponent } from "./timeseries-widget-example/timeseries-widget-example.component"; +import { TimeseriesWidgetInteractiveExampleComponent } from "./timeseries-widget-interactive-example/timeseries-widget-interactive-example.component"; +import { TimeseriesWidgetStatusBarExampleComponent } from "./timeseries-widget-status-bar-example/timeseries-widget-status-bar-example.component"; +import { getDemoFiles } from "../../../../demo-files-factory"; + +const routes: Routes = [ + { + path: "", + component: TimeseriesDocsComponent, + data: { + srlc: { + hideIndicator: true, + }, + showThemeSwitcher: true, + }, + }, + { + path: "example", + component: TimeseriesWidgetExampleComponent, + data: { + srlc: { + hideIndicator: true, + }, + }, + }, +]; + +@NgModule({ + imports: [ + RouterModule.forChild(routes), + NuiButtonModule, + NuiDocsModule, + NuiMessageModule, + NuiSwitchModule, + NuiDashboardsModule, + ], + declarations: [ + TimeseriesDocsComponent, + TimeseriesWidgetExampleComponent, + TimeseriesWidgetInteractiveExampleComponent, + TimeseriesWidgetStatusBarExampleComponent, + ], + providers: [ + { + provide: DEMO_PATH_TOKEN, + useValue: getDemoFiles("timeseries"), + }, + ], +}) +export default class TimeseriesDocsModule {} +\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\`, + "widget-types/timeseries/timeseries-widget-example/timeseries-widget-example.component.ts": \\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\`// © 2022 SolarWinds Worldwide, LLC. All rights reserved. +// +// Permission is hereby granted, free of charge, to any person obtaining a copy +// of this software and associated documentation files (the "Software"), to +// deal in the Software without restriction, including without limitation the +// rights to use, copy, modify, merge, publish, distribute, sublicense, and/or +// sell copies of the Software, and to permit persons to whom the Software is +// furnished to do so, subject to the following conditions: +// +// The above copyright notice and this permission notice shall be included in +// all copies or substantial portions of the Software. +// +// THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR +// IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, +// FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE +// AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER +// LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, +// OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN +// THE SOFTWARE. + +import { + ChangeDetectorRef, + Component, + Injectable, + OnInit, +} from "@angular/core"; +import { GridsterConfig, GridsterItem } from "angular-gridster2"; +import cloneDeep from "lodash/cloneDeep"; +import keyBy from "lodash/keyBy"; +import moment, { Moment } from "moment/moment"; +import { BehaviorSubject } from "rxjs"; + +import { + DataSourceService, + IDataSource, + INovaFilters, + ITimeframe, +} from "@nova-ui/bits"; +import { + DATA_SOURCE, + DEFAULT_PIZZAGNA_ROOT, + IDashboard, + IDataSourceOutput, + IProviderConfiguration, + ISerializableTimeframe, + ITimeseriesItemConfiguration, + ITimeseriesOutput, + ITimeseriesScaleConfig, + ITimeseriesWidgetConfig, + ITimeseriesWidgetData, + ITimeseriesWidgetSeriesData, + IWidget, + LegendPlacement, + PizzagnaLayer, + ProviderRegistryService, + TimeseriesChartPreset, + TimeseriesScaleType, + WellKnownPathKey, + WellKnownProviders, + WidgetTypesService, +} from "@nova-ui/dashboards"; + +/** + * A simple Timeseries data source implementation + */ +@Injectable() +export class BeerVsReadingMockDataSource + extends DataSourceService + implements IDataSource +{ + public static providerId = "BeerVsReadingMockDataSource"; + + public busy = new BehaviorSubject(false); + + public async getFilteredData( + filters: INovaFilters + ): Promise> { + // In this example we're using some static mock data located at the bottom of this file. In a real-world + // scenario, the data for the chart would likely be retrieved via an asynchronous backend call. + let filteredData = getData(); + + this.busy.next(true); + + // Filtering using the filter registered by the TimeFrameBar + const timeframeFilter = filters.timeframe?.value as ITimeframe; + if (timeframeFilter) { + filteredData = filteredData.map((item: ITimeseriesWidgetData) => ({ + id: item.id, + name: item.name, + description: item.description, + data: item.data.filter( + (seriesData: ITimeseriesWidgetSeriesData) => + filterDates( + seriesData.x, + timeframeFilter.startDatetime, + timeframeFilter.endDatetime + ) + ), + })); + } + + this.busy.next(false); + + return { result: { series: filteredData } }; + } +} + +function filterDates(dateToCheck: Date, startDate: Moment, endDate: Moment) { + const mom = moment(dateToCheck); + return ( + mom.isBetween(startDate, endDate) || + mom.isSame(startDate) || + mom.isSame(endDate) + ); +} + +/** + * A component that instantiates the dashboard + */ +@Component({ + selector: "timeseries-widget-example", + templateUrl: "./timeseries-widget-example.component.html", + styleUrls: ["./timeseries-widget-example.component.less"], + standalone: false, +}) +export class TimeseriesWidgetExampleComponent implements OnInit { + // This variable will hold all the data needed to define the layout and behavior of the widgets. + // Pass this to the dashboard component's dashboard input in the template. + public dashboard: IDashboard | undefined; + + // Angular gridster requires a configuration object even if it's empty. + // Pass this to the dashboard component's gridsterConfig input in the template. + public gridsterConfig: GridsterConfig = {}; + + // Boolean passed as an input to the dashboard. When true, widgets can be moved, resized, removed, or edited + public editMode: boolean = false; + + constructor( + // WidgetTypesService provides the widget's necessary structure information + private widgetTypesService: WidgetTypesService, + + // In general, the ProviderRegistryService is used for making entities available for injection into dynamically loaded components. + private providerRegistry: ProviderRegistryService, + + // Angular's ChangeDetectorRef + private changeDetectorRef: ChangeDetectorRef + ) {} + + public ngOnInit(): void { + // Grabbing the widget's default template which will be needed as a parameter for setNode + const widgetTemplate = this.widgetTypesService.getWidgetType( + "timeseries", + 1 + ); + // Registering our data sources as dropdown options in the widget editor/configurator + // Note: This could also be done in the parent module's constructor so that + // multiple dashboards could have access to the same widget template modification. + this.widgetTypesService.setNode( + // This is the template we grabbed above with getWidgetType + widgetTemplate, + // We are setting the editor/configurator part of the widget template + "configurator", + // This indicates which node you are changing and we want to change + // the data source providers available for selection in the editor. + WellKnownPathKey.DataSourceProviders, + // We are setting the data sources available for selection in the editor + [BeerVsReadingMockDataSource.providerId] + ); + + // Registering the data source for injection into the widget. + this.providerRegistry.setProviders({ + [BeerVsReadingMockDataSource.providerId]: { + provide: DATA_SOURCE, + useClass: BeerVsReadingMockDataSource, + deps: [], + }, + }); + + this.initializeDashboard(); + } + + public initializeDashboard(): void { + // We're using a static configuration object for this example, but this is where + // the widget's configuration could potentially be populated from a database + const widgetsWithStructure = widgetConfigs.map((w) => + this.widgetTypesService.mergeWithWidgetType(w) + ); + const widgetsIndex = keyBy(widgetsWithStructure, (w: IWidget) => w.id); + + // Finally, assigning the variables we created above to the dashboard + this.dashboard = { + positions: cloneDeep(positions), + widgets: widgetsIndex, + }; + } + + /** Used for restoring widgets state */ + public reInitializeDashboard(): void { + // destroys the components and their providers so the dashboard can re init data + this.dashboard = undefined; + this.changeDetectorRef.detectChanges(); + + this.initializeDashboard(); + } +} + +const widgetConfigs: IWidget[] = [ + { + id: "lineWidgetId", + type: "timeseries", + pizzagna: { + [PizzagnaLayer.Configuration]: { + [DEFAULT_PIZZAGNA_ROOT]: { + providers: { + [WellKnownProviders.DataSource]: { + // Setting the initially selected data source providerId + providerId: BeerVsReadingMockDataSource.providerId, + } as IProviderConfiguration, + }, + }, + header: { + properties: { + title: "Line Chart", + subtitle: "Survey of 1000 Solarians", + }, + }, + chart: { + providers: { + [WellKnownProviders.Adapter]: { + properties: { + // Setting the series and corresponding labels to initially display on the chart + series: [ + { + id: "series-1", + label: "Beer Tasting", + selectedSeriesId: "series-1", + }, + { + id: "series-2", + label: "Reading", + selectedSeriesId: "series-2", + }, + ] as ITimeseriesItemConfiguration[], + }, + } as Partial, + }, + properties: { + // Setting the general chart configuration + configuration: { + legendPlacement: LegendPlacement.Right, + enableZoom: true, + leftAxisLabel: "Solarians (%)", + // You can optionally define custom colors for the chart by setting the 'chartColors' configuration property + // "chartColors": [ + // "var(--nui-color-chart-eight)", + // "var(--nui-color-chart-nine)", + // "var(--nui-color-chart-ten)", + // ], + } as ITimeseriesWidgetConfig, + }, + }, + timeframeSelection: { + properties: { + // Setting the initial timeframe selected in the timeframe bar + timeframe: { + selectedPresetId: "last7Days", + } as ISerializableTimeframe, + minDate: moment().subtract(60, "days").format(), + maxDate: moment().format(), + }, + }, + }, + }, + }, + { + id: "stackedAreaWidgetId", + type: "timeseries", + pizzagna: { + [PizzagnaLayer.Configuration]: { + [DEFAULT_PIZZAGNA_ROOT]: { + providers: { + [WellKnownProviders.DataSource]: { + // Setting the initially selected data source providerId + providerId: BeerVsReadingMockDataSource.providerId, + } as IProviderConfiguration, + }, + }, + header: { + properties: { + title: "Stacked Area Chart", + subtitle: "Survey of 1000 Solarians", + }, + }, + chart: { + providers: { + [WellKnownProviders.Adapter]: { + properties: { + // Setting the series and corresponding labels to initially display on the chart + series: [ + { + id: "series-1", + label: "Beer Tasting", + selectedSeriesId: "series-1", + }, + { + id: "series-2", + label: "Reading", + selectedSeriesId: "series-2", + }, + ] as ITimeseriesItemConfiguration[], + }, + } as Partial, + }, + properties: { + // Setting the general chart configuration + configuration: { + legendPlacement: LegendPlacement.Right, + enableZoom: true, + // Setting the preset to stacked area + preset: TimeseriesChartPreset.StackedArea, + leftAxisLabel: "Solarians (%)", + // You can optionally define custom colors for the chart by setting the 'chartColors' configuration property + // "chartColors": [ + // "var(--nui-color-chart-eight)", + // "var(--nui-color-chart-nine)", + // "var(--nui-color-chart-ten)", + // ], + } as ITimeseriesWidgetConfig, + }, + }, + timeframeSelection: { + properties: { + // Setting the initial timeframe selected in the timeframe bar + timeframe: { + selectedPresetId: "last7Days", + } as ISerializableTimeframe, + minDate: moment().subtract(60, "days").format(), + maxDate: moment().format(), + }, + }, + }, + }, + }, + { + id: "stackedPercentageAreaWidgetId", + type: "timeseries", + pizzagna: { + [PizzagnaLayer.Configuration]: { + [DEFAULT_PIZZAGNA_ROOT]: { + providers: { + [WellKnownProviders.DataSource]: { + // Setting the initially selected data source providerId + providerId: BeerVsReadingMockDataSource.providerId, + } as IProviderConfiguration, + }, + }, + header: { + properties: { + title: "Stacked Percentage Area Chart", + subtitle: "Survey of 1000 Solarians", + }, + }, + chart: { + providers: { + [WellKnownProviders.Adapter]: { + properties: { + // Setting the series and corresponding labels to initially display on the chart + series: [ + { + id: "series-1", + label: "Beer Tasting", + selectedSeriesId: "series-1", + }, + { + id: "series-2", + label: "Reading", + selectedSeriesId: "series-2", + }, + ] as ITimeseriesItemConfiguration[], + }, + } as Partial, + }, + properties: { + // Setting the general chart configuration + configuration: { + legendPlacement: LegendPlacement.Right, + enableZoom: true, + // Setting the preset to stacked percentage area + preset: TimeseriesChartPreset.StackedPercentageArea, + leftAxisLabel: "Solarians (%)", + // You can optionally define custom colors for the chart by setting the 'chartColors' configuration property + // "chartColors": [ + // "var(--nui-color-chart-eight)", + // "var(--nui-color-chart-nine)", + // "var(--nui-color-chart-ten)", + // ], + } as ITimeseriesWidgetConfig, + }, + }, + timeframeSelection: { + properties: { + // Setting the initial timeframe selected in the timeframe bar + timeframe: { + selectedPresetId: "last7Days", + } as ISerializableTimeframe, + minDate: moment().subtract(60, "days").format(), + maxDate: moment().format(), + }, + }, + }, + }, + }, + { + id: "stackedBarWidgetId", + type: "timeseries", + pizzagna: { + [PizzagnaLayer.Configuration]: { + [DEFAULT_PIZZAGNA_ROOT]: { + providers: { + [WellKnownProviders.DataSource]: { + // Setting the initially selected data source providerId + providerId: BeerVsReadingMockDataSource.providerId, + } as IProviderConfiguration, + }, + }, + header: { + properties: { + title: "Stacked Bar Chart", + subtitle: "Survey of 1000 Solarians", + }, + }, + chart: { + providers: { + [WellKnownProviders.Adapter]: { + properties: { + // Setting the series and corresponding labels to initially display on the chart + series: [ + { + id: "series-1", + label: "Beer Tasting", + selectedSeriesId: "series-1", + }, + { + id: "series-2", + label: "Reading", + selectedSeriesId: "series-2", + }, + ] as ITimeseriesItemConfiguration[], + }, + } as Partial, + }, + properties: { + configuration: { + legendPlacement: LegendPlacement.Right, + enableZoom: true, + leftAxisLabel: "Solarians (%)", + // Setting the preset to stacked bar + preset: TimeseriesChartPreset.StackedBar, + scales: { + x: { + type: TimeseriesScaleType.TimeInterval, + properties: { + interval: 24 * 60 * 60, + }, + } as ITimeseriesScaleConfig, + }, + } as ITimeseriesWidgetConfig, + }, + }, + timeframeSelection: { + properties: { + // Setting the initial timeframe selected in the timeframe bar + timeframe: { + selectedPresetId: "last7Days", + } as ISerializableTimeframe, + minDate: moment().subtract(60, "days").format(), + maxDate: moment().format(), + }, + }, + }, + }, + }, +]; + +// using startOf("day") so that each band for the bar chart corresponds to a calendar day +const now = moment().startOf("day"); + +export const getData = (): ITimeseriesWidgetData[] => [ + { + id: "series-1", + name: "Beer Tasting", + description: "Havin' some suds", + data: [ + { x: now.clone().subtract(20, "day").toDate(), y: 30 }, + { x: now.clone().subtract(19, "day").toDate(), y: 35 }, + { x: now.clone().subtract(18, "day").toDate(), y: 33 }, + { x: now.clone().subtract(17, "day").toDate(), y: 40 }, + { x: now.clone().subtract(16, "day").toDate(), y: 35 }, + { x: now.clone().subtract(15, "day").toDate(), y: 30 }, + { x: now.clone().subtract(14, "day").toDate(), y: 35 }, + { x: now.clone().subtract(13, "day").toDate(), y: 15 }, + { x: now.clone().subtract(12, "day").toDate(), y: 30 }, + { x: now.clone().subtract(11, "day").toDate(), y: 45 }, + { x: now.clone().subtract(10, "day").toDate(), y: 60 }, + { x: now.clone().subtract(9, "day").toDate(), y: 54 }, + { x: now.clone().subtract(8, "day").toDate(), y: 42 }, + { x: now.clone().subtract(7, "day").toDate(), y: 44 }, + { x: now.clone().subtract(6, "day").toDate(), y: 54 }, + { x: now.clone().subtract(5, "day").toDate(), y: 43 }, + { x: now.clone().subtract(4, "day").toDate(), y: 76 }, + { x: now.clone().subtract(3, "day").toDate(), y: 54 }, + { x: now.clone().subtract(2, "day").toDate(), y: 42 }, + { x: now.clone().subtract(1, "day").toDate(), y: 34 }, + ], + }, + { + id: "series-2", + name: "Reading", + description: "Hittin' the books", + data: [ + { x: now.clone().subtract(20, "day").toDate(), y: 60 }, + { x: now.clone().subtract(19, "day").toDate(), y: 64 }, + { x: now.clone().subtract(18, "day").toDate(), y: 70 }, + { x: now.clone().subtract(17, "day").toDate(), y: 55 }, + { x: now.clone().subtract(16, "day").toDate(), y: 55 }, + { x: now.clone().subtract(15, "day").toDate(), y: 45 }, + { x: now.clone().subtract(14, "day").toDate(), y: 60 }, + { x: now.clone().subtract(13, "day").toDate(), y: 65 }, + { x: now.clone().subtract(12, "day").toDate(), y: 63 }, + { x: now.clone().subtract(11, "day").toDate(), y: 60 }, + { x: now.clone().subtract(10, "day").toDate(), y: 61 }, + { x: now.clone().subtract(9, "day").toDate(), y: 65 }, + { x: now.clone().subtract(8, "day").toDate(), y: 63 }, + { x: now.clone().subtract(7, "day").toDate(), y: 58 }, + { x: now.clone().subtract(6, "day").toDate(), y: 64 }, + { x: now.clone().subtract(5, "day").toDate(), y: 63 }, + { x: now.clone().subtract(4, "day").toDate(), y: 60 }, + { x: now.clone().subtract(3, "day").toDate(), y: 62 }, + { x: now.clone().subtract(2, "day").toDate(), y: 61 }, + { x: now.clone().subtract(1, "day").toDate(), y: 62 }, + ], + }, +]; + +// Setting the widget dimensions and position (this is for gridster) +const positions: Record = { + [widgetConfigs[0].id]: { + cols: 6, + rows: 6, + y: 0, + x: 0, + }, + [widgetConfigs[1].id]: { + cols: 6, + rows: 6, + y: 0, + x: 6, + }, + [widgetConfigs[3].id]: { + cols: 6, + rows: 6, + y: 6, + x: 0, + }, + [widgetConfigs[2].id]: { + cols: 6, + rows: 6, + y: 6, + x: 6, + }, +}; +\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\`, + "widget-types/timeseries/timeseries-widget-interactive-example/timeseries-widget-interactive-example.component.ts": \\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\`// © 2022 SolarWinds Worldwide, LLC. All rights reserved. +// +// Permission is hereby granted, free of charge, to any person obtaining a copy +// of this software and associated documentation files (the "Software"), to +// deal in the Software without restriction, including without limitation the +// rights to use, copy, modify, merge, publish, distribute, sublicense, and/or +// sell copies of the Software, and to permit persons to whom the Software is +// furnished to do so, subject to the following conditions: +// +// The above copyright notice and this permission notice shall be included in +// all copies or substantial portions of the Software. +// +// THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR +// IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, +// FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE +// AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER +// LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, +// OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN +// THE SOFTWARE. + +import { + ChangeDetectorRef, + Component, + Injectable, + OnInit, +} from "@angular/core"; +import { GridsterConfig, GridsterItem } from "angular-gridster2"; +import cloneDeep from "lodash/cloneDeep"; +import keyBy from "lodash/keyBy"; +import moment, { Moment } from "moment/moment"; +import { BehaviorSubject } from "rxjs"; + +import { + DataSourceService, + IDataSource, + INovaFilters, + ITimeframe, +} from "@nova-ui/bits"; +import { + DATA_SOURCE, + DEFAULT_PIZZAGNA_ROOT, + IDashboard, + IDataSourceOutput, + IProviderConfiguration, + ISerializableTimeframe, + ITimeseriesItemConfiguration, + ITimeseriesOutput, + ITimeseriesScaleConfig, + ITimeseriesWidgetConfig, + ITimeseriesWidgetData, + ITimeseriesWidgetSeriesData, + IWidget, + NOVA_URL_INTERACTION_HANDLER, + LegendPlacement, + PizzagnaLayer, + ProviderRegistryService, + TimeseriesChartPreset, + TimeseriesScaleType, + WellKnownPathKey, + WellKnownProviders, + WidgetTypesService, +} from "@nova-ui/dashboards"; + +/** + * A simple Timeseries data source implementation + */ +@Injectable() +export class TimeseriesMockDataSource + extends DataSourceService + implements IDataSource +{ + public static providerId = "TimeseriesMockDataSource"; + + public busy = new BehaviorSubject(false); + + public async getFilteredData( + filters: INovaFilters + ): Promise> { + // In this example we're using some static mock data located at the bottom of this file. In a real-world + // scenario, the data for the chart would likely be retrieved via an asynchronous backend call. + let filteredData = getData(); + + this.busy.next(true); + + // Filtering using the filter registered by the TimeFrameBar + const timeframeFilter = filters.timeframe?.value as ITimeframe; + if (timeframeFilter) { + filteredData = filteredData.map((item: ITimeseriesWidgetData) => ({ + id: item.id, + name: item.name, + description: item.description, + // the filtered data should return the provided links if they are set. + link: item?.link, + secondaryLink: item?.secondaryLink, + data: item.data.filter( + (seriesData: ITimeseriesWidgetSeriesData) => + filterDates( + seriesData.x, + timeframeFilter.startDatetime, + timeframeFilter.endDatetime + ) + ), + })); + } + + this.busy.next(false); + + return { result: { series: filteredData } }; + } +} + +function filterDates(dateToCheck: Date, startDate: Moment, endDate: Moment) { + const mom = moment(dateToCheck); + return ( + mom.isBetween(startDate, endDate) || + mom.isSame(startDate) || + mom.isSame(endDate) + ); +} + +/** + * A component that instantiates the dashboard + */ +@Component({ + selector: "timeseries-widget-interactive-example", + templateUrl: "./timeseries-widget-interactive-example.component.html", + styleUrls: ["./timeseries-widget-interactive-example.component.less"], + standalone: false, +}) +export class TimeseriesWidgetInteractiveExampleComponent implements OnInit { + // This variable will hold all the data needed to define the layout and behavior of the widgets. + // Pass this to the dashboard component's dashboard input in the template. + public dashboard: IDashboard | undefined; + + // Angular gridster requires a configuration object even if it's empty. + // Pass this to the dashboard component's gridsterConfig input in the template. + public gridsterConfig: GridsterConfig = {}; + + // Boolean passed as an input to the dashboard. When true, widgets can be moved, resized, removed, or edited + public editMode: boolean = false; + + constructor( + // WidgetTypesService provides the widget's necessary structure information + private widgetTypesService: WidgetTypesService, + + // In general, the ProviderRegistryService is used for making entities available for injection into dynamically loaded components. + private providerRegistry: ProviderRegistryService, + + // Angular's ChangeDetectorRef + private changeDetectorRef: ChangeDetectorRef + ) {} + + public ngOnInit(): void { + // Grabbing the widget's default template which will be needed as a parameter for setNode + const widgetTemplate = this.widgetTypesService.getWidgetType( + "timeseries", + 1 + ); + // Registering our data sources as dropdown options in the widget editor/configurator + // Note: This could also be done in the parent module's constructor so that + // multiple dashboards could have access to the same widget template modification. + this.widgetTypesService.setNode( + // This is the template we grabbed above with getWidgetType + widgetTemplate, + // We are setting the editor/configurator part of the widget template + "configurator", + // This indicates which node you are changing and we want to change + // the data source providers available for selection in the editor. + WellKnownPathKey.DataSourceProviders, + // We are setting the data sources available for selection in the editor + [TimeseriesMockDataSource.providerId] + ); + + // Registering the data source for injection into the widget. + this.providerRegistry.setProviders({ + [TimeseriesMockDataSource.providerId]: { + provide: DATA_SOURCE, + useClass: TimeseriesMockDataSource, + deps: [], + }, + }); + + this.initializeDashboard(); + } + + public initializeDashboard(): void { + // We're using a static configuration object for this example, but this is where + // the widget's configuration could potentially be populated from a database + const widgetsWithStructure = widgetConfigs.map((w) => + this.widgetTypesService.mergeWithWidgetType(w) + ); + const widgetsIndex = keyBy(widgetsWithStructure, (w: IWidget) => w.id); + + // Finally, assigning the variables we created above to the dashboard + this.dashboard = { + positions: cloneDeep(positions), + widgets: widgetsIndex, + }; + } + + /** Used for restoring widgets state */ + public reInitializeDashboard(): void { + // destroys the components and their providers so the dashboard can re init data + this.dashboard = undefined; + this.changeDetectorRef.detectChanges(); + + this.initializeDashboard(); + } +} + +const widgetConfigs: IWidget[] = [ + { + id: "lineWidgetId", + type: "timeseries", + pizzagna: { + [PizzagnaLayer.Configuration]: { + [DEFAULT_PIZZAGNA_ROOT]: { + providers: { + [WellKnownProviders.DataSource]: { + // Setting the initially selected data source providerId + providerId: TimeseriesMockDataSource.providerId, + } as IProviderConfiguration, + [WellKnownProviders.InteractionHandler]: { + // Setting the UrlInteractionHandler as an interactionHandler + providerId: NOVA_URL_INTERACTION_HANDLER, + properties: { + // the 'url' property tells the handler what link to use when interaction occurs on the series + url: "\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\${data.link || 'https://en.wikipedia.org/wiki/'+data.legendDescriptionPrimary}", + // by default the link is opened in the current window, set 'newWindow' to true to open in a new tab instead + // newWindow: true, + }, + }, + }, + }, + header: { + properties: { + title: "Line Chart", + subtitle: "Basic Timeseries with Interaction", + }, + }, + chart: { + providers: { + [WellKnownProviders.Adapter]: { + properties: { + // Setting the series and corresponding labels to initially display on the chart + series: [ + { + id: "series-1", + label: "Nur-Sultan", + selectedSeriesId: "series-1", + }, + { + id: "series-2", + label: "Brno", + selectedSeriesId: "series-2", + }, + { + id: "series-3", + label: "Lisbon", + selectedSeriesId: "series-3", + }, + { + id: "series-4", + label: "Austin", + selectedSeriesId: "series-4", + }, + ] as ITimeseriesItemConfiguration[], + }, + } as Partial, + }, + properties: { + // Setting the general chart configuration + configuration: { + // setting interaction to 'series' will make all series in the chart interactable + interaction: "series", + legendPlacement: LegendPlacement.Right, + enableZoom: true, + } as ITimeseriesWidgetConfig, + }, + }, + timeframeSelection: { + properties: { + timeframe: { + selectedPresetId: "last7Days", + } as ISerializableTimeframe, + minDate: moment().subtract(60, "days").format(), + maxDate: moment().format(), + }, + }, + }, + }, + }, + { + id: "stackedBarWidgetId", + type: "timeseries", + pizzagna: { + [PizzagnaLayer.Configuration]: { + [DEFAULT_PIZZAGNA_ROOT]: { + providers: { + [WellKnownProviders.DataSource]: { + // Setting the initially selected data source providerId + providerId: TimeseriesMockDataSource.providerId, + } as IProviderConfiguration, + }, + }, + header: { + properties: { + title: "Stacked Bar Chart", + subtitle: + "Basic Timeseries without Interaction Handler", + }, + }, + chart: { + providers: { + [WellKnownProviders.Adapter]: { + properties: { + // Setting the series and corresponding labels to initially display on the chart + series: [ + { + id: "series-1", + label: "Nur-Sultan", + selectedSeriesId: "series-1", + }, + { + id: "series-2", + label: "Brno", + selectedSeriesId: "series-2", + }, + { + id: "series-3", + label: "Lisbon", + selectedSeriesId: "series-3", + }, + { + id: "series-4", + label: "Austin", + selectedSeriesId: "series-4", + }, + ] as ITimeseriesItemConfiguration[], + }, + } as Partial, + }, + properties: { + // Setting the general chart configuration + configuration: { + legendPlacement: LegendPlacement.Right, + enableZoom: true, + // Setting the preset to stacked bar + preset: TimeseriesChartPreset.StackedBar, + scales: { + x: { + type: TimeseriesScaleType.TimeInterval, + properties: { + interval: 24 * 60 * 60, + }, + } as ITimeseriesScaleConfig, + }, + } as ITimeseriesWidgetConfig, + }, + }, + timeframeSelection: { + properties: { + // Setting the initial timeframe selected in the timeframe bar + timeframe: { + selectedPresetId: "last7Days", + } as ISerializableTimeframe, + minDate: moment().subtract(60, "days").format(), + maxDate: moment().format(), + }, + }, + }, + }, + }, +]; + +// using startOf("day") so that each band for the bar chart corresponds to a calendar day +const startOfToday = moment().startOf("day").toDate(); + +export const getData = (): ITimeseriesWidgetData[] => [ + { + id: "series-1", + name: "Nur-Sultan", + description: "'link' only", + link: "https://en.wikipedia.org/wiki/Nur-Sultan", + data: [ + { x: moment(startOfToday).subtract(59, "day").toDate(), y: 35 }, + { x: moment(startOfToday).subtract(58, "day").toDate(), y: 33 }, + { x: moment(startOfToday).subtract(57, "day").toDate(), y: 40 }, + { x: moment(startOfToday).subtract(56, "day").toDate(), y: 35 }, + { x: moment(startOfToday).subtract(55, "day").toDate(), y: 30 }, + { x: moment(startOfToday).subtract(54, "day").toDate(), y: 35 }, + { x: moment(startOfToday).subtract(53, "day").toDate(), y: 15 }, + { x: moment(startOfToday).subtract(52, "day").toDate(), y: 30 }, + { x: moment(startOfToday).subtract(51, "day").toDate(), y: 35 }, + { x: moment(startOfToday).subtract(50, "day").toDate(), y: 34 }, + { x: moment(startOfToday).subtract(49, "day").toDate(), y: 35 }, + { x: moment(startOfToday).subtract(48, "day").toDate(), y: 33 }, + { x: moment(startOfToday).subtract(47, "day").toDate(), y: 40 }, + { x: moment(startOfToday).subtract(46, "day").toDate(), y: 35 }, + { x: moment(startOfToday).subtract(45, "day").toDate(), y: 30 }, + { x: moment(startOfToday).subtract(44, "day").toDate(), y: 35 }, + { x: moment(startOfToday).subtract(43, "day").toDate(), y: 15 }, + { x: moment(startOfToday).subtract(42, "day").toDate(), y: 30 }, + { x: moment(startOfToday).subtract(41, "day").toDate(), y: 35 }, + { x: moment(startOfToday).subtract(40, "day").toDate(), y: 34 }, + { x: moment(startOfToday).subtract(39, "day").toDate(), y: 35 }, + { x: moment(startOfToday).subtract(38, "day").toDate(), y: 33 }, + { x: moment(startOfToday).subtract(37, "day").toDate(), y: 40 }, + { x: moment(startOfToday).subtract(36, "day").toDate(), y: 35 }, + { x: moment(startOfToday).subtract(35, "day").toDate(), y: 30 }, + { x: moment(startOfToday).subtract(34, "day").toDate(), y: 35 }, + { x: moment(startOfToday).subtract(33, "day").toDate(), y: 15 }, + { x: moment(startOfToday).subtract(32, "day").toDate(), y: 30 }, + { x: moment(startOfToday).subtract(31, "day").toDate(), y: 35 }, + { x: moment(startOfToday).subtract(30, "day").toDate(), y: 34 }, + { x: moment(startOfToday).subtract(29, "day").toDate(), y: 35 }, + { x: moment(startOfToday).subtract(28, "day").toDate(), y: 33 }, + { x: moment(startOfToday).subtract(27, "day").toDate(), y: 40 }, + { x: moment(startOfToday).subtract(26, "day").toDate(), y: 35 }, + { x: moment(startOfToday).subtract(25, "day").toDate(), y: 30 }, + { x: moment(startOfToday).subtract(24, "day").toDate(), y: 35 }, + { x: moment(startOfToday).subtract(23, "day").toDate(), y: 15 }, + { x: moment(startOfToday).subtract(22, "day").toDate(), y: 30 }, + { x: moment(startOfToday).subtract(21, "day").toDate(), y: 35 }, + { x: moment(startOfToday).subtract(20, "day").toDate(), y: 34 }, + { x: moment(startOfToday).subtract(19, "day").toDate(), y: 35 }, + { x: moment(startOfToday).subtract(18, "day").toDate(), y: 33 }, + { x: moment(startOfToday).subtract(17, "day").toDate(), y: 40 }, + { x: moment(startOfToday).subtract(16, "day").toDate(), y: 35 }, + { x: moment(startOfToday).subtract(15, "day").toDate(), y: 30 }, + { x: moment(startOfToday).subtract(14, "day").toDate(), y: 35 }, + { x: moment(startOfToday).subtract(13, "day").toDate(), y: 15 }, + { x: moment(startOfToday).subtract(12, "day").toDate(), y: 30 }, + { x: moment(startOfToday).subtract(11, "day").toDate(), y: 35 }, + { x: moment(startOfToday).subtract(10, "day").toDate(), y: 34 }, + { x: moment(startOfToday).subtract(9, "day").toDate(), y: 33 }, + { x: moment(startOfToday).subtract(8, "day").toDate(), y: 35 }, + { x: moment(startOfToday).subtract(7, "day").toDate(), y: 36 }, + { x: moment(startOfToday).subtract(6, "day").toDate(), y: 34 }, + { x: moment(startOfToday).subtract(5, "day").toDate(), y: 33 }, + { x: moment(startOfToday).subtract(4, "day").toDate(), y: 30 }, + { x: moment(startOfToday).subtract(3, "day").toDate(), y: 32 }, + { x: moment(startOfToday).subtract(2, "day").toDate(), y: 31 }, + { x: moment(startOfToday).subtract(1, "day").toDate(), y: 34 }, + { x: moment(startOfToday).toDate(), y: 25 }, + ], + }, + { + id: "series-2", + name: "Brno", + description: "'link' and 'secondaryLink'", + link: "https://en.wikipedia.org/wiki/Brno", + secondaryLink: "https://en.wikipedia.org/wiki/Europe", + data: [ + { x: moment(startOfToday).subtract(59, "day").toDate(), y: 35 }, + { x: moment(startOfToday).subtract(58, "day").toDate(), y: 33 }, + { x: moment(startOfToday).subtract(57, "day").toDate(), y: 40 }, + { x: moment(startOfToday).subtract(56, "day").toDate(), y: 35 }, + { x: moment(startOfToday).subtract(55, "day").toDate(), y: 30 }, + { x: moment(startOfToday).subtract(54, "day").toDate(), y: 35 }, + { x: moment(startOfToday).subtract(53, "day").toDate(), y: 15 }, + { x: moment(startOfToday).subtract(52, "day").toDate(), y: 30 }, + { x: moment(startOfToday).subtract(51, "day").toDate(), y: 35 }, + { x: moment(startOfToday).subtract(50, "day").toDate(), y: 34 }, + { x: moment(startOfToday).subtract(49, "day").toDate(), y: 35 }, + { x: moment(startOfToday).subtract(48, "day").toDate(), y: 33 }, + { x: moment(startOfToday).subtract(47, "day").toDate(), y: 40 }, + { x: moment(startOfToday).subtract(46, "day").toDate(), y: 35 }, + { x: moment(startOfToday).subtract(45, "day").toDate(), y: 30 }, + { x: moment(startOfToday).subtract(44, "day").toDate(), y: 35 }, + { x: moment(startOfToday).subtract(43, "day").toDate(), y: 15 }, + { x: moment(startOfToday).subtract(42, "day").toDate(), y: 30 }, + { x: moment(startOfToday).subtract(41, "day").toDate(), y: 35 }, + { x: moment(startOfToday).subtract(40, "day").toDate(), y: 34 }, + { x: moment(startOfToday).subtract(39, "day").toDate(), y: 35 }, + { x: moment(startOfToday).subtract(38, "day").toDate(), y: 33 }, + { x: moment(startOfToday).subtract(37, "day").toDate(), y: 40 }, + { x: moment(startOfToday).subtract(36, "day").toDate(), y: 35 }, + { x: moment(startOfToday).subtract(35, "day").toDate(), y: 30 }, + { x: moment(startOfToday).subtract(34, "day").toDate(), y: 35 }, + { x: moment(startOfToday).subtract(33, "day").toDate(), y: 15 }, + { x: moment(startOfToday).subtract(32, "day").toDate(), y: 30 }, + { x: moment(startOfToday).subtract(31, "day").toDate(), y: 35 }, + { x: moment(startOfToday).subtract(30, "day").toDate(), y: 34 }, + { x: moment(startOfToday).subtract(29, "day").toDate(), y: 35 }, + { x: moment(startOfToday).subtract(28, "day").toDate(), y: 33 }, + { x: moment(startOfToday).subtract(27, "day").toDate(), y: 40 }, + { x: moment(startOfToday).subtract(26, "day").toDate(), y: 35 }, + { x: moment(startOfToday).subtract(25, "day").toDate(), y: 30 }, + { x: moment(startOfToday).subtract(24, "day").toDate(), y: 35 }, + { x: moment(startOfToday).subtract(23, "day").toDate(), y: 15 }, + { x: moment(startOfToday).subtract(22, "day").toDate(), y: 30 }, + { x: moment(startOfToday).subtract(21, "day").toDate(), y: 35 }, + { x: moment(startOfToday).subtract(20, "day").toDate(), y: 34 }, + { x: moment(startOfToday).subtract(19, "day").toDate(), y: 64 }, + { x: moment(startOfToday).subtract(18, "day").toDate(), y: 70 }, + { x: moment(startOfToday).subtract(17, "day").toDate(), y: 55 }, + { x: moment(startOfToday).subtract(16, "day").toDate(), y: 55 }, + { x: moment(startOfToday).subtract(15, "day").toDate(), y: 45 }, + { x: moment(startOfToday).subtract(14, "day").toDate(), y: 10 }, + { x: moment(startOfToday).subtract(13, "day").toDate(), y: 65 }, + { x: moment(startOfToday).subtract(12, "day").toDate(), y: 35 }, + { x: moment(startOfToday).subtract(11, "day").toDate(), y: 60 }, + { x: moment(startOfToday).subtract(10, "day").toDate(), y: 61 }, + { x: moment(startOfToday).subtract(9, "day").toDate(), y: 65 }, + { x: moment(startOfToday).subtract(8, "day").toDate(), y: 63 }, + { x: moment(startOfToday).subtract(7, "day").toDate(), y: 58 }, + { x: moment(startOfToday).subtract(6, "day").toDate(), y: 64 }, + { x: moment(startOfToday).subtract(5, "day").toDate(), y: 63 }, + { x: moment(startOfToday).subtract(4, "day").toDate(), y: 60 }, + { x: moment(startOfToday).subtract(3, "day").toDate(), y: 62 }, + { x: moment(startOfToday).subtract(2, "day").toDate(), y: 61 }, + { x: moment(startOfToday).subtract(1, "day").toDate(), y: 62 }, + { x: moment(startOfToday).toDate(), y: 55 }, + ], + }, + { + id: "series-3", + name: "Lisbon", + description: "'secondaryLink' only", + secondaryLink: "https://en.wikipedia.org/wiki/Lisbon", + data: [ + { x: moment(startOfToday).subtract(59, "day").toDate(), y: 35 }, + { x: moment(startOfToday).subtract(58, "day").toDate(), y: 33 }, + { x: moment(startOfToday).subtract(57, "day").toDate(), y: 40 }, + { x: moment(startOfToday).subtract(56, "day").toDate(), y: 35 }, + { x: moment(startOfToday).subtract(55, "day").toDate(), y: 30 }, + { x: moment(startOfToday).subtract(54, "day").toDate(), y: 35 }, + { x: moment(startOfToday).subtract(53, "day").toDate(), y: 15 }, + { x: moment(startOfToday).subtract(52, "day").toDate(), y: 30 }, + { x: moment(startOfToday).subtract(51, "day").toDate(), y: 35 }, + { x: moment(startOfToday).subtract(50, "day").toDate(), y: 34 }, + { x: moment(startOfToday).subtract(49, "day").toDate(), y: 35 }, + { x: moment(startOfToday).subtract(48, "day").toDate(), y: 33 }, + { x: moment(startOfToday).subtract(47, "day").toDate(), y: 40 }, + { x: moment(startOfToday).subtract(46, "day").toDate(), y: 35 }, + { x: moment(startOfToday).subtract(45, "day").toDate(), y: 30 }, + { x: moment(startOfToday).subtract(44, "day").toDate(), y: 35 }, + { x: moment(startOfToday).subtract(43, "day").toDate(), y: 15 }, + { x: moment(startOfToday).subtract(42, "day").toDate(), y: 30 }, + { x: moment(startOfToday).subtract(41, "day").toDate(), y: 35 }, + { x: moment(startOfToday).subtract(40, "day").toDate(), y: 34 }, + { x: moment(startOfToday).subtract(39, "day").toDate(), y: 35 }, + { x: moment(startOfToday).subtract(38, "day").toDate(), y: 33 }, + { x: moment(startOfToday).subtract(37, "day").toDate(), y: 40 }, + { x: moment(startOfToday).subtract(36, "day").toDate(), y: 35 }, + { x: moment(startOfToday).subtract(35, "day").toDate(), y: 30 }, + { x: moment(startOfToday).subtract(34, "day").toDate(), y: 35 }, + { x: moment(startOfToday).subtract(33, "day").toDate(), y: 15 }, + { x: moment(startOfToday).subtract(32, "day").toDate(), y: 30 }, + { x: moment(startOfToday).subtract(31, "day").toDate(), y: 35 }, + { x: moment(startOfToday).subtract(30, "day").toDate(), y: 34 }, + { x: moment(startOfToday).subtract(29, "day").toDate(), y: 35 }, + { x: moment(startOfToday).subtract(28, "day").toDate(), y: 33 }, + { x: moment(startOfToday).subtract(27, "day").toDate(), y: 40 }, + { x: moment(startOfToday).subtract(26, "day").toDate(), y: 35 }, + { x: moment(startOfToday).subtract(25, "day").toDate(), y: 30 }, + { x: moment(startOfToday).subtract(24, "day").toDate(), y: 35 }, + { x: moment(startOfToday).subtract(23, "day").toDate(), y: 15 }, + { x: moment(startOfToday).subtract(22, "day").toDate(), y: 30 }, + { x: moment(startOfToday).subtract(21, "day").toDate(), y: 35 }, + { x: moment(startOfToday).subtract(20, "day").toDate(), y: 34 }, + { x: moment(startOfToday).subtract(19, "day").toDate(), y: 80 }, + { x: moment(startOfToday).subtract(18, "day").toDate(), y: 70 }, + { x: moment(startOfToday).subtract(17, "day").toDate(), y: 95 }, + { x: moment(startOfToday).subtract(16, "day").toDate(), y: 90 }, + { x: moment(startOfToday).subtract(15, "day").toDate(), y: 85 }, + { x: moment(startOfToday).subtract(14, "day").toDate(), y: 70 }, + { x: moment(startOfToday).subtract(13, "day").toDate(), y: 75 }, + { x: moment(startOfToday).subtract(12, "day").toDate(), y: 69 }, + { x: moment(startOfToday).subtract(11, "day").toDate(), y: 75 }, + { x: moment(startOfToday).subtract(10, "day").toDate(), y: 81 }, + { x: moment(startOfToday).subtract(9, "day").toDate(), y: 93 }, + { x: moment(startOfToday).subtract(8, "day").toDate(), y: 83 }, + { x: moment(startOfToday).subtract(7, "day").toDate(), y: 70 }, + { x: moment(startOfToday).subtract(6, "day").toDate(), y: 74 }, + { x: moment(startOfToday).subtract(5, "day").toDate(), y: 73 }, + { x: moment(startOfToday).subtract(4, "day").toDate(), y: 68 }, + { x: moment(startOfToday).subtract(3, "day").toDate(), y: 72 }, + { x: moment(startOfToday).subtract(2, "day").toDate(), y: 61 }, + { x: moment(startOfToday).subtract(1, "day").toDate(), y: 69 }, + { x: moment(startOfToday).toDate(), y: 60 }, + ], + }, + { + id: "series-4", + name: "Austin", + description: "No links", + data: [ + { x: moment(startOfToday).subtract(59, "day").toDate(), y: 25 }, + { x: moment(startOfToday).subtract(58, "day").toDate(), y: 43 }, + { x: moment(startOfToday).subtract(57, "day").toDate(), y: 40 }, + { x: moment(startOfToday).subtract(56, "day").toDate(), y: 65 }, + { x: moment(startOfToday).subtract(55, "day").toDate(), y: 30 }, + { x: moment(startOfToday).subtract(54, "day").toDate(), y: 25 }, + { x: moment(startOfToday).subtract(53, "day").toDate(), y: 45 }, + { x: moment(startOfToday).subtract(52, "day").toDate(), y: 30 }, + { x: moment(startOfToday).subtract(51, "day").toDate(), y: 85 }, + { x: moment(startOfToday).subtract(50, "day").toDate(), y: 74 }, + { x: moment(startOfToday).subtract(49, "day").toDate(), y: 55 }, + { x: moment(startOfToday).subtract(48, "day").toDate(), y: 23 }, + { x: moment(startOfToday).subtract(47, "day").toDate(), y: 40 }, + { x: moment(startOfToday).subtract(46, "day").toDate(), y: 15 }, + { x: moment(startOfToday).subtract(45, "day").toDate(), y: 20 }, + { x: moment(startOfToday).subtract(44, "day").toDate(), y: 65 }, + { x: moment(startOfToday).subtract(43, "day").toDate(), y: 25 }, + { x: moment(startOfToday).subtract(42, "day").toDate(), y: 40 }, + { x: moment(startOfToday).subtract(41, "day").toDate(), y: 25 }, + { x: moment(startOfToday).subtract(40, "day").toDate(), y: 54 }, + { x: moment(startOfToday).subtract(39, "day").toDate(), y: 65 }, + { x: moment(startOfToday).subtract(38, "day").toDate(), y: 33 }, + { x: moment(startOfToday).subtract(37, "day").toDate(), y: 50 }, + { x: moment(startOfToday).subtract(36, "day").toDate(), y: 45 }, + { x: moment(startOfToday).subtract(35, "day").toDate(), y: 20 }, + { x: moment(startOfToday).subtract(34, "day").toDate(), y: 25 }, + { x: moment(startOfToday).subtract(33, "day").toDate(), y: 35 }, + { x: moment(startOfToday).subtract(32, "day").toDate(), y: 20 }, + { x: moment(startOfToday).subtract(31, "day").toDate(), y: 35 }, + { x: moment(startOfToday).subtract(30, "day").toDate(), y: 14 }, + { x: moment(startOfToday).subtract(29, "day").toDate(), y: 55 }, + { x: moment(startOfToday).subtract(28, "day").toDate(), y: 23 }, + { x: moment(startOfToday).subtract(27, "day").toDate(), y: 10 }, + { x: moment(startOfToday).subtract(26, "day").toDate(), y: 5 }, + { x: moment(startOfToday).subtract(25, "day").toDate(), y: 20 }, + { x: moment(startOfToday).subtract(24, "day").toDate(), y: 35 }, + { x: moment(startOfToday).subtract(23, "day").toDate(), y: 15 }, + { x: moment(startOfToday).subtract(22, "day").toDate(), y: 30 }, + { x: moment(startOfToday).subtract(21, "day").toDate(), y: 35 }, + { x: moment(startOfToday).subtract(20, "day").toDate(), y: 34 }, + { x: moment(startOfToday).subtract(19, "day").toDate(), y: 50 }, + { x: moment(startOfToday).subtract(18, "day").toDate(), y: 60 }, + { x: moment(startOfToday).subtract(17, "day").toDate(), y: 95 }, + { x: moment(startOfToday).subtract(16, "day").toDate(), y: 80 }, + { x: moment(startOfToday).subtract(15, "day").toDate(), y: 65 }, + { x: moment(startOfToday).subtract(14, "day").toDate(), y: 80 }, + { x: moment(startOfToday).subtract(13, "day").toDate(), y: 85 }, + { x: moment(startOfToday).subtract(12, "day").toDate(), y: 69 }, + { x: moment(startOfToday).subtract(11, "day").toDate(), y: 65 }, + { x: moment(startOfToday).subtract(10, "day").toDate(), y: 71 }, + { x: moment(startOfToday).subtract(9, "day").toDate(), y: 73 }, + { x: moment(startOfToday).subtract(8, "day").toDate(), y: 43 }, + { x: moment(startOfToday).subtract(7, "day").toDate(), y: 70 }, + { x: moment(startOfToday).subtract(6, "day").toDate(), y: 84 }, + { x: moment(startOfToday).subtract(5, "day").toDate(), y: 73 }, + { x: moment(startOfToday).subtract(4, "day").toDate(), y: 38 }, + { x: moment(startOfToday).subtract(3, "day").toDate(), y: 72 }, + { x: moment(startOfToday).subtract(2, "day").toDate(), y: 81 }, + { x: moment(startOfToday).subtract(1, "day").toDate(), y: 59 }, + { x: moment(startOfToday).toDate(), y: 60 }, + ], + }, +]; +// Setting the widget dimensions and position (this is for gridster) +const positions: Record = { + [widgetConfigs[0].id]: { + cols: 6, + rows: 6, + y: 0, + x: 0, + }, + [widgetConfigs[1].id]: { + cols: 6, + rows: 6, + y: 0, + x: 6, + }, +}; +\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\`, + "widget-types/timeseries/timeseries-widget-status-bar-example/timeseries-widget-status-bar-example.component.ts": \\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\`// © 2022 SolarWinds Worldwide, LLC. All rights reserved. +// +// Permission is hereby granted, free of charge, to any person obtaining a copy +// of this software and associated documentation files (the "Software"), to +// deal in the Software without restriction, including without limitation the +// rights to use, copy, modify, merge, publish, distribute, sublicense, and/or +// sell copies of the Software, and to permit persons to whom the Software is +// furnished to do so, subject to the following conditions: +// +// The above copyright notice and this permission notice shall be included in +// all copies or substantial portions of the Software. +// +// THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR +// IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, +// FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE +// AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER +// LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, +// OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN +// THE SOFTWARE. + +import { + ChangeDetectorRef, + Component, + Injectable, + OnInit, +} from "@angular/core"; +import { GridsterConfig, GridsterItem } from "angular-gridster2"; +import keyBy from "lodash/keyBy"; +import moment, { Moment } from "moment/moment"; +import { BehaviorSubject } from "rxjs"; + +import { + DataSourceService, + IDataSource, + IDataSourceOutput, + INovaFilters, + ITimeframe, +} from "@nova-ui/bits"; +import { CHART_PALETTE_CS_S_EXTENDED } from "@nova-ui/charts"; +import { + applyStatusEndpoints, + DATA_SOURCE, + DEFAULT_PIZZAGNA_ROOT, + IDashboard, + IProviderConfiguration, + ISerializableTimeframe, + ITimeseriesItemConfiguration, + ITimeseriesOutput, + ITimeseriesScaleConfig, + ITimeseriesWidgetConfig, + ITimeseriesWidgetData, + ITimeseriesWidgetSeriesData, + ITimeseriesWidgetStatusData, + IWidget, + LegendPlacement, + PizzagnaLayer, + ProviderRegistryService, + TimeseriesChartPreset, + TimeseriesScaleType, + WellKnownPathKey, + WellKnownProviders, + WidgetTypesService, +} from "@nova-ui/dashboards"; + +/** + * A simple Timeseries data source implementation with continuous (non-interval-based) output + */ +@Injectable() +export class TimeseriesStatusContinuousDataSource + extends DataSourceService + implements IDataSource> +{ + public static providerId = "TimeseriesStatusContinuousDataSource"; + + public busy = new BehaviorSubject(false); + + public async getFilteredData( + filters: INovaFilters + ): Promise< + IDataSourceOutput> + > { + // In this example we're using some static mock data located at the bottom of this file. In a real-world + // scenario, the data for the chart would likely be retrieved via an asynchronous backend call. + const data = getContinuousData(); + let filteredData = data; + + this.busy.next(true); + + // Filtering using the filter registered by the TimeFrameBar + const timeframeFilter = filters.timeframe?.value as ITimeframe; + if (timeframeFilter) { + filteredData = filteredData.map((item: ITimeseriesWidgetData) => ({ + id: item.id, + name: item.name, + description: item.description, + data: item.data.filter( + (seriesData: ITimeseriesWidgetSeriesData) => + filterDates( + seriesData.x, + timeframeFilter.startDatetime, + timeframeFilter.endDatetime + ) + ), + })); + + // apply endpoints on the filtered status data so that when the status chart is zoomed (filtered), + // each status visualizations is ensured to have valid start and end values + filteredData = applyStatusEndpoints( + timeframeFilter, + filteredData, + data + ); + } + + this.busy.next(false); + return { result: { series: filteredData } }; + } +} + +/** + * A simple Timeseries data source implementation with interval-based output + */ +@Injectable() +export class TimeseriesStatusIntervalDataSource + extends DataSourceService + implements IDataSource> +{ + public static providerId = "TimeseriesStatusIntervalDataSource"; + + public busy = new BehaviorSubject(false); + + public async getFilteredData( + filters: INovaFilters + ): Promise< + IDataSourceOutput> + > { + // In this example we're using some static mock data located at the bottom of this file. In a real-world + // scenario, the data for the chart would likely be retrieved via an asynchronous backend call. + const data = getIntervalData(); + let filteredData = data; + + this.busy.next(true); + + // Filtering using the filter registered by the TimeFrameBar + const timeframeFilter = filters.timeframe?.value as ITimeframe; + if (timeframeFilter) { + filteredData = filteredData.map((item: ITimeseriesWidgetData) => ({ + id: item.id, + name: item.name, + description: item.description, + data: item.data.filter( + (seriesData: ITimeseriesWidgetSeriesData) => + filterDates( + seriesData.x, + timeframeFilter.startDatetime, + timeframeFilter.endDatetime + ) + ), + })); + + // Note: There's no need to apply filter endpoints to the status data in this case since we know it's visualized in regular intervals + } + + this.busy.next(false); + return { result: { series: filteredData } }; + } +} + +function filterDates(dateToCheck: Date, startDate: Moment, endDate: Moment) { + const mom = moment(dateToCheck); + return ( + mom.isBetween(startDate, endDate) || + mom.isSame(startDate) || + mom.isSame(endDate) + ); +} + +/** + * A component that instantiates the dashboard + */ +@Component({ + selector: "timeseries-widget-status-bar-example", + templateUrl: "./timeseries-widget-status-bar-example.component.html", + styleUrls: ["./timeseries-widget-status-bar-example.component.less"], + standalone: false, +}) +export class TimeseriesWidgetStatusBarExampleComponent implements OnInit { + // This variable will hold all the data needed to define the layout and behavior of the widgets. + // Pass this to the dashboard component's dashboard input in the template. + public dashboard: IDashboard | undefined; + + // Angular gridster requires a configuration object even if it's empty. + // Pass this to the dashboard component's gridsterConfig input in the template. + public gridsterConfig: GridsterConfig = {}; + + // Boolean passed as an input to the dashboard. When true, widgets can be moved, resized, removed, or edited + public editMode: boolean = false; + + constructor( + // WidgetTypesService provides the widget's necessary structure information + private widgetTypesService: WidgetTypesService, + + // In general, the ProviderRegistryService is used for making entities available for injection into dynamically loaded components. + private providerRegistry: ProviderRegistryService, + private changeDetectorRef: ChangeDetectorRef + ) {} + + public ngOnInit(): void { + // Grabbing the widget's default template which will be needed as a parameter for setNode + const widgetTemplate = this.widgetTypesService.getWidgetType( + "timeseries", + 1 + ); + // Registering our data sources as dropdown options in the widget editor/configurator + // Note: This could also be done in the parent module's constructor so that + // multiple dashboards could have access to the same widget template modification. + this.widgetTypesService.setNode( + // This is the template we grabbed above with getWidgetType + widgetTemplate, + // We are setting the editor/configurator part of the widget template + "configurator", + // This indicates which node you are changing and we want to change + // the data source providers available for selection in the editor. + WellKnownPathKey.DataSourceProviders, + // We are setting the data sources available for selection in the editor + [ + TimeseriesStatusContinuousDataSource.providerId, + TimeseriesStatusIntervalDataSource.providerId, + ] + ); + + // Registering the data source for injection into the widget. + this.providerRegistry.setProviders({ + [TimeseriesStatusContinuousDataSource.providerId]: { + provide: DATA_SOURCE, + useClass: TimeseriesStatusContinuousDataSource, + deps: [], + }, + [TimeseriesStatusIntervalDataSource.providerId]: { + provide: DATA_SOURCE, + useClass: TimeseriesStatusIntervalDataSource, + deps: [], + }, + }); + + this.initializeDashboard(); + } + + /** Used for restoring widgets state */ + public reInitializeDashboard(): void { + // destroys the components and their providers so the dashboard can re init data + this.dashboard = undefined; + this.changeDetectorRef.detectChanges(); + + this.initializeDashboard(); + } + + public initializeDashboard(): void { + // We're using a static configuration object for this example, but this is where + // the widget's configuration could potentially be populated from a database + const widgetsWithStructure = widgetConfigs.map((w) => + this.widgetTypesService.mergeWithWidgetType(w) + ); + const widgetsIndex = keyBy(widgetsWithStructure, (w: IWidget) => w.id); + + // Finally, assigning the variables we created above to the dashboard + this.dashboard = { + positions, + widgets: widgetsIndex, + }; + } +} + +const widgetConfigs: IWidget[] = [ + { + id: "statusChartWidgetId", + type: "timeseries", + pizzagna: { + [PizzagnaLayer.Configuration]: { + [DEFAULT_PIZZAGNA_ROOT]: { + providers: { + [WellKnownProviders.DataSource]: { + // Setting the initially selected data source providerId + providerId: + TimeseriesStatusContinuousDataSource.providerId, + } as IProviderConfiguration, + }, + }, + header: { + properties: { + title: "Status Bar Chart with Continuous (Non-Interval) Scale", + subtitle: "Basic Timeseries Widget", + }, + }, + chart: { + providers: { + [WellKnownProviders.Adapter]: { + properties: { + // Setting the series and corresponding labels to initially display on the chart + series: [ + { + id: "series-1", + label: "Node Status", + selectedSeriesId: "series-1", + }, + { + id: "series-2", + label: "Node Status", + selectedSeriesId: "series-2", + }, + ] as ITimeseriesItemConfiguration[], + }, + } as Partial, + }, + properties: { + configuration: { + legendPlacement: LegendPlacement.Right, + enableZoom: true, + // Setting the preset to status bar + preset: TimeseriesChartPreset.StatusBar, + } as ITimeseriesWidgetConfig, + }, + }, + timeframeSelection: { + properties: { + // Setting the initial timeframe selected in the timeframe bar + timeframe: { + selectedPresetId: "last7Days", + } as ISerializableTimeframe, + maxDate: moment().format(), + }, + }, + }, + }, + }, + { + id: "statusIntervalChartWidgetId", + type: "timeseries", + pizzagna: { + [PizzagnaLayer.Configuration]: { + [DEFAULT_PIZZAGNA_ROOT]: { + providers: { + [WellKnownProviders.DataSource]: { + // Setting the initially selected data source providerId + providerId: + TimeseriesStatusIntervalDataSource.providerId, + } as IProviderConfiguration, + }, + }, + header: { + properties: { + title: "Status Bar Chart with Interval Scale", + subtitle: "Basic Timeseries Widget", + }, + }, + chart: { + providers: { + [WellKnownProviders.Adapter]: { + properties: { + // Setting the series and corresponding labels to initially display on the chart + series: [ + { + id: "series-1", + label: "Node Status", + selectedSeriesId: "series-1", + }, + { + id: "series-2", + label: "Node Status", + selectedSeriesId: "series-2", + }, + ] as ITimeseriesItemConfiguration[], + }, + } as Partial, + }, + properties: { + configuration: { + legendPlacement: LegendPlacement.Right, + enableZoom: true, + // Setting the preset to status bar + preset: TimeseriesChartPreset.StatusBar, + scales: { + x: { + type: TimeseriesScaleType.TimeInterval, + properties: { + // one-day interval in seconds + interval: 24 * 60 * 60, + }, + } as ITimeseriesScaleConfig, + }, + } as ITimeseriesWidgetConfig, + }, + }, + timeframeSelection: { + properties: { + // Setting the initial timeframe selected in the timeframe bar + timeframe: { + selectedPresetId: "last7Days", + } as ISerializableTimeframe, + maxDate: moment().format(), + }, + }, + }, + }, + }, +]; + +export const startOfToday = (): Moment => moment().startOf("day"); + +export const getContinuousData = + (): ITimeseriesWidgetData[] => { + const series: ITimeseriesWidgetData[] = [ + { + id: "series-1", + name: "Node Status", + description: "lastchance.demo.lab", + data: [ + // the 'x' value is set to the time and 'y' to the status at that given time + { + x: startOfToday().subtract(20, "day").toDate(), + y: Status.Warning, + }, + { + x: startOfToday().subtract(19, "day").toDate(), + y: Status.Down, + }, + { + x: startOfToday().subtract(17, "day").toDate(), + y: Status.Critical, + }, + { + x: startOfToday().subtract(16, "day").toDate(), + y: Status.Warning, + }, + { + x: startOfToday().subtract(15, "day").toDate(), + y: Status.Down, + }, + { + x: startOfToday().subtract(14, "day").toDate(), + y: Status.Critical, + }, + { + x: startOfToday().subtract(12, "day").toDate(), + y: Status.Unknown, + }, + { + x: startOfToday().subtract(10, "day").toDate(), + y: Status.Up, + }, + { + x: startOfToday().subtract(9, "day").toDate(), + y: Status.Critical, + }, + { + x: startOfToday().subtract(6, "day").toDate(), + y: Status.Up, + }, + { + x: startOfToday().subtract(3, "day").toDate(), + y: Status.Warning, + }, + { + x: startOfToday().subtract(2, "day").toDate(), + y: Status.Critical, + }, + { + x: startOfToday().subtract(1, "day").toDate(), + y: Status.Up, + }, + // This data point will be ignored and is only here to provide an endpoint for the previous status. + { x: moment().toDate(), y: Status.Up }, + ], + }, + { + id: "series-2", + name: "Node Status", + description: "newhope.demo.lab", + data: [ + { + x: startOfToday().subtract(19, "day").toDate(), + y: Status.Critical, + }, + { + x: startOfToday().subtract(18, "day").toDate(), + y: Status.Unknown, + }, + { + x: startOfToday().subtract(17, "day").toDate(), + y: Status.Warning, + }, + { + x: startOfToday().subtract(15, "day").toDate(), + y: Status.Down, + }, + { + x: startOfToday().subtract(8, "day").toDate(), + y: Status.Critical, + }, + { + x: startOfToday().subtract(7, "day").toDate(), + y: Status.Down, + }, + { + x: startOfToday().subtract(6, "day").toDate(), + y: Status.Up, + }, + { + x: startOfToday().subtract(5, "day").toDate(), + y: Status.Critical, + }, + { + x: startOfToday().subtract(4, "day").toDate(), + y: Status.Up, + }, + { + x: startOfToday().subtract(3, "day").toDate(), + y: Status.Warning, + }, + { + x: startOfToday().subtract(2, "day").toDate(), + y: Status.Up, + }, + { + x: startOfToday().subtract(1, "day").toDate(), + y: Status.Down, + }, + // This data point will be ignored and is only here to provide an endpoint for the previous status. + { x: moment().toDate(), y: Status.Down }, + ], + }, + ]; + + for (const s of series) { + // here are we setting the color and icon associated to the status for each data point + s.data = s.data.map((d: any, i: number) => ({ + ...d, + color: statusColors[d.y as Status], + // The thickness of the line is dependant on the status. If the status equals 'Up' then 'thick' is set to false. + thick: d.y !== Status.Up, + icon: "status_" + d.y, + })); + } + + return series; + }; + +// Note that the output of this function is spaced evenly at one-day intervals +export const getIntervalData = + (): ITimeseriesWidgetData[] => { + const series: ITimeseriesWidgetData[] = [ + { + id: "series-1", + name: "Node Status", + description: "lastchance.demo.lab", + data: [ + // the 'x' value is set to the time and 'y' to the status at that given time + { + x: startOfToday().subtract(20, "day").toDate(), + y: Status.Up, + }, + { + x: startOfToday().subtract(19, "day").toDate(), + y: Status.Critical, + }, + { + x: startOfToday().subtract(18, "day").toDate(), + y: Status.Warning, + }, + { + x: startOfToday().subtract(17, "day").toDate(), + y: Status.Down, + }, + { + x: startOfToday().subtract(16, "day").toDate(), + y: Status.Critical, + }, + { + x: startOfToday().subtract(15, "day").toDate(), + y: Status.Down, + }, + { + x: startOfToday().subtract(14, "day").toDate(), + y: Status.Up, + }, + { + x: startOfToday().subtract(13, "day").toDate(), + y: Status.Warning, + }, + { + x: startOfToday().subtract(12, "day").toDate(), + y: Status.Up, + }, + { + x: startOfToday().subtract(11, "day").toDate(), + y: Status.Critical, + }, + { + x: startOfToday().subtract(10, "day").toDate(), + y: Status.Up, + }, + { + x: startOfToday().subtract(9, "day").toDate(), + y: Status.Down, + }, + { + x: startOfToday().subtract(8, "day").toDate(), + y: Status.Critical, + }, + { + x: startOfToday().subtract(7, "day").toDate(), + y: Status.Warning, + }, + { + x: startOfToday().subtract(6, "day").toDate(), + y: Status.Down, + }, + { + x: startOfToday().subtract(5, "day").toDate(), + y: Status.Critical, + }, + { + x: startOfToday().subtract(4, "day").toDate(), + y: Status.Down, + }, + { + x: startOfToday().subtract(3, "day").toDate(), + y: Status.Up, + }, + { + x: startOfToday().subtract(2, "day").toDate(), + y: Status.Warning, + }, + { + x: startOfToday().subtract(1, "day").toDate(), + y: Status.Critical, + }, + { x: startOfToday().toDate(), y: Status.Up }, + ], + }, + { + id: "series-2", + name: "Node Status", + description: "newhope.demo.lab", + data: [ + { + x: startOfToday().subtract(20, "day").toDate(), + y: Status.Up, + }, + { + x: startOfToday().subtract(19, "day").toDate(), + y: Status.Up, + }, + { + x: startOfToday().subtract(18, "day").toDate(), + y: Status.Down, + }, + { + x: startOfToday().subtract(17, "day").toDate(), + y: Status.Critical, + }, + { + x: startOfToday().subtract(16, "day").toDate(), + y: Status.Down, + }, + { + x: startOfToday().subtract(15, "day").toDate(), + y: Status.Up, + }, + { + x: startOfToday().subtract(14, "day").toDate(), + y: Status.Critical, + }, + { + x: startOfToday().subtract(13, "day").toDate(), + y: Status.Up, + }, + { + x: startOfToday().subtract(12, "day").toDate(), + y: Status.Critical, + }, + { + x: startOfToday().subtract(11, "day").toDate(), + y: Status.Warning, + }, + { + x: startOfToday().subtract(10, "day").toDate(), + y: Status.Up, + }, + { + x: startOfToday().subtract(9, "day").toDate(), + y: Status.Down, + }, + { + x: startOfToday().subtract(8, "day").toDate(), + y: Status.Up, + }, + { + x: startOfToday().subtract(7, "day").toDate(), + y: Status.Down, + }, + { + x: startOfToday().subtract(6, "day").toDate(), + y: Status.Critical, + }, + { + x: startOfToday().subtract(5, "day").toDate(), + y: Status.Down, + }, + { + x: startOfToday().subtract(4, "day").toDate(), + y: Status.Up, + }, + { + x: startOfToday().subtract(3, "day").toDate(), + y: Status.Critical, + }, + { + x: startOfToday().subtract(2, "day").toDate(), + y: Status.Up, + }, + { + x: startOfToday().subtract(1, "day").toDate(), + y: Status.Warning, + }, + { x: startOfToday().toDate(), y: Status.Critical }, + ], + }, + ]; + + for (const s of series) { + // here are we setting the color and icon associated to the status for each data point + s.data = s.data.map((d: any, i: number) => ({ + ...d, + color: statusColors[d.y as Status], + // The thickness of the line is dependant on the status. If the status equals 'Up' then 'thick' is set to false. + thick: d.y !== Status.Up, + icon: "status_" + d.y, + })); + } + + return series; + }; + +// An enumeration of statuses +enum Status { + Unknown = "unknown", + Up = "up", + Warning = "warning", + Down = "down", + Critical = "critical", +} + +// This is the map used for setting the color of each status bar +const statusColors: Record = { + [Status.Unknown]: CHART_PALETTE_CS_S_EXTENDED[6], + [Status.Up]: CHART_PALETTE_CS_S_EXTENDED[8], + [Status.Warning]: CHART_PALETTE_CS_S_EXTENDED[4], + [Status.Down]: CHART_PALETTE_CS_S_EXTENDED[0], + [Status.Critical]: CHART_PALETTE_CS_S_EXTENDED[2], +}; + +// Setting the widget dimensions and position (this is for gridster) +const positions: Record = { + [widgetConfigs[0].id]: { + cols: 12, + rows: 4, + y: 0, + x: 0, + }, + [widgetConfigs[1].id]: { + cols: 12, + rows: 4, + y: 4, + x: 0, + }, +}; +\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\`, + "widget-types/view-components/kpi-tile-view-basic/kpi-tile-view-basic-example.component.ts": \\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\`// © 2022 SolarWinds Worldwide, LLC. All rights reserved. +// +// Permission is hereby granted, free of charge, to any person obtaining a copy +// of this software and associated documentation files (the "Software"), to +// deal in the Software without restriction, including without limitation the +// rights to use, copy, modify, merge, publish, distribute, sublicense, and/or +// sell copies of the Software, and to permit persons to whom the Software is +// furnished to do so, subject to the following conditions: +// +// The above copyright notice and this permission notice shall be included in +// all copies or substantial portions of the Software. +// +// THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR +// IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, +// FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE +// AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER +// LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, +// OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN +// THE SOFTWARE. + +import { Component } from "@angular/core"; +import { FormControl } from "@angular/forms"; + +type KpiTileState = "normal" | "loading" | "empty"; + +/** + * KPI Tile View - Playground example. + * Switch between all visual states (normal / loading / empty) and toggle + * interactivity to explore every variant the standalone tile supports. + */ +@Component({ + selector: "kpi-tile-view-basic-example", + templateUrl: "./kpi-tile-view-basic-example.component.html", + standalone: false, +}) +export class KpiTileViewBasicExampleComponent { + public readonly stateControl = new FormControl("normal", { + nonNullable: true, + }); + public interactive = false; + public lastClicked = ""; + + public readonly stateOptions: KpiTileState[] = ["normal", "loading", "empty"]; + + public onTileClick(label: string): void { + this.lastClicked = label; + } +} +\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\`, + "widget-types/view-components/kpi-tile-view-interactive/kpi-tile-view-interactive-example.component.ts": \\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\`// © 2022 SolarWinds Worldwide, LLC. All rights reserved. +// +// Permission is hereby granted, free of charge, to any person obtaining a copy +// of this software and associated documentation files (the "Software"), to +// deal in the Software without restriction, including without limitation the +// rights to use, copy, modify, merge, publish, distribute, sublicense, and/or +// sell copies of the Software, and to permit persons to whom the Software is +// furnished to do so, subject to the following conditions: +// +// The above copyright notice and this permission notice shall be included in +// all copies or substantial portions of the Software. +// +// THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR +// IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, +// FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE +// AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER +// LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, +// OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN +// THE SOFTWARE. + +import { Component, TemplateRef, ViewChild } from "@angular/core"; + +/** + * Interactive KPI Tile View example with custom value formatting + * and click event handling. + */ +@Component({ + selector: "kpi-tile-view-interactive-example", + templateUrl: "./kpi-tile-view-interactive-example.component.html", + standalone: false, +}) +export class KpiTileViewInteractiveExampleComponent { + public currentValue = 1_247; + public lastClickedTile = ""; + + @ViewChild("customValueTpl", { static: true }) + public customValueTpl: TemplateRef; + + public onTileClick(): void { + this.lastClickedTile = "Active Sessions"; + } + + public onUptimeClick(): void { + this.lastClickedTile = "Uptime"; + } +} +\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\`, + "widget-types/view-components/proportional-chart-view-playground/proportional-chart-view-playground-example.component.ts": \\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\`// © 2022 SolarWinds Worldwide, LLC. All rights reserved. +// +// Permission is hereby granted, free of charge, to any person obtaining a copy +// of this software and associated documentation files (the "Software"), to +// deal in the Software without restriction, including without limitation the +// rights to use, copy, modify, merge, publish, distribute, sublicense, and/or +// sell copies of the Software, and to permit persons to whom the Software is +// furnished to do so, subject to the following conditions: +// +// The above copyright notice and this permission notice shall be included in +// all copies or substantial portions of the Software. +// +// THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR +// IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, +// FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE +// AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER +// LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, +// OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN +// THE SOFTWARE. + +import { Component } from "@angular/core"; +import { FormControl } from "@angular/forms"; + +import { IProportionalDataItem } from "@nova-ui/dashboards"; + +type ProportionalChartType = "donut" | "pie" | "verticalBar" | "horizontalBar"; +type LegendPlacement = "right" | "bottom" | "none"; + +/** + * Proportional Chart View - Playground example. + * Lets you switch between all supported chart types and legend placements + * to see every visual variant the standalone view component provides. + */ +@Component({ + selector: "proportional-chart-view-playground-example", + templateUrl: "./proportional-chart-view-playground-example.component.html", + standalone: false, +}) +export class ProportionalChartViewPlaygroundExampleComponent { + public readonly chartTypeControl = new FormControl( + "donut", + { nonNullable: true } + ); + public readonly legendPlacementControl = new FormControl( + "right", + { nonNullable: true } + ); + + public readonly chartTypeOptions: ProportionalChartType[] = [ + "donut", + "pie", + "verticalBar", + "horizontalBar", + ]; + public readonly legendPlacementOptions: LegendPlacement[] = [ + "right", + "bottom", + "none", + ]; + + public colors: Record = { + down: "#dc3545", + up: "#2cc079", + warning: "#f3a002", + unknown: "#707070", + }; + + public chartData: Array = [ + { id: "up", name: "Up", value: 78 }, + { id: "down", name: "Down", value: 8 }, + { id: "warning", name: "Warning", value: 12 }, + { id: "unknown", name: "Unknown", value: 2 }, + ]; +} +\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\`, + "widget-types/view-components/view-components-docs.component.ts": \\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\`// © 2022 SolarWinds Worldwide, LLC. All rights reserved. +// +// Permission is hereby granted, free of charge, to any person obtaining a copy +// of this software and associated documentation files (the "Software"), to +// deal in the Software without restriction, including without limitation the +// rights to use, copy, modify, merge, publish, distribute, sublicense, and/or +// sell copies of the Software, and to permit persons to whom the Software is +// furnished to do so, subject to the following conditions: +// +// The above copyright notice and this permission notice shall be included in +// all copies or substantial portions of the Software. +// +// THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR +// IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, +// FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE +// AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER +// LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, +// OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN +// THE SOFTWARE. + +import { Component } from "@angular/core"; + +@Component({ + selector: "nui-view-components-docs", + templateUrl: "./view-components-docs.component.html", + standalone: false, +}) +export class ViewComponentsDocsComponent {} +\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\`, + "widget-types/view-components/view-components-docs.module.ts": \\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\`// © 2022 SolarWinds Worldwide, LLC. All rights reserved. +// +// Permission is hereby granted, free of charge, to any person obtaining a copy +// of this software and associated documentation files (the "Software"), to +// deal in the Software without restriction, including without limitation the +// rights to use, copy, modify, merge, publish, distribute, sublicense, and/or +// sell copies of the Software, and to permit persons to whom the Software is +// furnished to do so, subject to the following conditions: +// +// The above copyright notice and this permission notice shall be included in +// all copies or substantial portions of the Software. +// +// THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR +// IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, +// FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE +// AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER +// LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, +// OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN +// THE SOFTWARE. + +import { CommonModule } from "@angular/common"; +import { NgModule } from "@angular/core"; +import { ReactiveFormsModule } from "@angular/forms"; +import { RouterModule, Routes } from "@angular/router"; + +import { + NuiDocsModule, + NuiIconModule, + NuiMessageModule, + NuiFormFieldModule, + NuiSelectV2Module, + NuiSwitchModule, + DEMO_PATH_TOKEN, +} from "@nova-ui/bits"; +import { NuiDashboardViewsModule } from "@nova-ui/dashboards"; + +import { getDemoFiles } from "../../../../demo-files-factory"; +import { KpiTileViewBasicExampleComponent } from "./kpi-tile-view-basic/kpi-tile-view-basic-example.component"; +import { KpiTileViewInteractiveExampleComponent } from "./kpi-tile-view-interactive/kpi-tile-view-interactive-example.component"; +import { ProportionalChartViewPlaygroundExampleComponent } from "./proportional-chart-view-playground/proportional-chart-view-playground-example.component"; +import { ViewComponentsDocsComponent } from "./view-components-docs.component"; + +const routes: Routes = [ + { + path: "", + component: ViewComponentsDocsComponent, + data: { + srlc: { + hideIndicator: true, + }, + showThemeSwitcher: true, + }, + }, + { + path: "kpi-tile-view-basic", + component: KpiTileViewBasicExampleComponent, + data: { + srlc: { + hideIndicator: true, + }, + }, + }, + { + path: "proportional-chart-view-playground", + component: ProportionalChartViewPlaygroundExampleComponent, + data: { + srlc: { + hideIndicator: true, + }, + }, + }, +]; + +@NgModule({ + imports: [ + CommonModule, + ReactiveFormsModule, + RouterModule.forChild(routes), + NuiDocsModule, + NuiMessageModule, + NuiIconModule, + NuiFormFieldModule, + NuiSelectV2Module, + NuiSwitchModule, + NuiDashboardViewsModule, + ], + declarations: [ + ViewComponentsDocsComponent, + KpiTileViewBasicExampleComponent, + KpiTileViewInteractiveExampleComponent, + ProportionalChartViewPlaygroundExampleComponent, + ], + providers: [ + { + provide: DEMO_PATH_TOKEN, + useValue: getDemoFiles("view-components"), + }, + ], +}) +export default class ViewComponentsDocsModule {} +\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\`, + "widget-types/widget-types.module.ts": \\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\`// © 2022 SolarWinds Worldwide, LLC. All rights reserved. +// +// Permission is hereby granted, free of charge, to any person obtaining a copy +// of this software and associated documentation files (the "Software"), to +// deal in the Software without restriction, including without limitation the +// rights to use, copy, modify, merge, publish, distribute, sublicense, and/or +// sell copies of the Software, and to permit persons to whom the Software is +// furnished to do so, subject to the following conditions: +// +// The above copyright notice and this permission notice shall be included in +// all copies or substantial portions of the Software. +// +// THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR +// IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, +// FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE +// AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER +// LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, +// OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN +// THE SOFTWARE. + +import { NgModule, Type } from "@angular/core"; +import { RouterModule, Routes } from "@angular/router"; + +import { NuiDocsModule } from "@nova-ui/bits"; +import { + ConfiguratorHeadingService, + NuiDashboardsModule, +} from "@nova-ui/dashboards"; + +export enum WidgetTypesRoute { + kpi = "kpi", + riskScore = "risk-score", + timeseries = "timeseries", + table = "table", + proportional = "proportional", + embedded = "embedded", + drilldown = "drilldown", + viewComponents = "view-components", +} + +const routes: Routes = [ + { + path: WidgetTypesRoute.kpi, + loadChildren: async () => + import("./kpi/kpi-docs.module") as object as Promise>, + data: { + srlc: { + hideIndicator: true, + }, + }, + }, + { + path: WidgetTypesRoute.riskScore, + loadChildren: async () => + import("./risk-score/risk-score-docs.module") as object as Promise< + Type + >, + data: { + srlc: { + hideIndicator: true, + }, + }, + }, + { + path: WidgetTypesRoute.timeseries, + loadChildren: async () => + import("./timeseries/timeseries-docs.module") as object as Promise< + Type + >, + data: { + srlc: { + hideIndicator: true, + }, + }, + }, + { + path: WidgetTypesRoute.table, + loadChildren: async () => + import("./table/table-docs.module") as object as Promise>, + data: { + srlc: { + hideIndicator: true, + }, + }, + }, + { + path: WidgetTypesRoute.proportional, + loadChildren: async () => + import( + "./proportional/proportional-docs.module" + ) as object as Promise>, + data: { + srlc: { + hideIndicator: true, + }, + }, + }, + { + path: WidgetTypesRoute.embedded, + loadChildren: async () => + import( + "./embedded-content/embedded-content-docs.module" + ) as object as Promise>, + data: { + srlc: { + hideIndicator: true, + }, + }, + }, + { + path: WidgetTypesRoute.drilldown, + loadChildren: async () => + import( + "./drilldown/drilldown-widget-docs.module" + ) as object as Promise>, + data: { + srlc: { + hideIndicator: true, + }, + }, + }, + { + path: WidgetTypesRoute.viewComponents, + loadChildren: async () => + import( + "./view-components/view-components-docs.module" + ) as object as Promise>, + data: { + srlc: { + hideIndicator: true, + }, + }, + }, +]; + +@NgModule({ + imports: [ + RouterModule.forChild(routes), + NuiDocsModule, + NuiDashboardsModule, + ], + providers: [ConfiguratorHeadingService], +}) +export default class WidgetTypesModule {} +\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\`, +}; +\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\`, + "overview/hero/dashboard/hero-dashboard.component.ts": \\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\`// © 2022 SolarWinds Worldwide, LLC. All rights reserved. +// +// Permission is hereby granted, free of charge, to any person obtaining a copy +// of this software and associated documentation files (the "Software"), to +// deal in the Software without restriction, including without limitation the +// rights to use, copy, modify, merge, publish, distribute, sublicense, and/or +// sell copies of the Software, and to permit persons to whom the Software is +// furnished to do so, subject to the following conditions: +// +// The above copyright notice and this permission notice shall be included in +// all copies or substantial portions of the Software. +// +// THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR +// IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, +// FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE +// AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER +// LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, +// OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN +// THE SOFTWARE. + +import { HttpClient } from "@angular/common/http"; +import { + ChangeDetectionStrategy, + Component, + OnInit, + ViewEncapsulation, +} from "@angular/core"; +import keyBy from "lodash/keyBy"; + +import { LoggerService, ThemeSwitchService } from "@nova-ui/bits"; +import { + DATA_SOURCE, + IDashboard, + IWidget, + ProviderRegistryService, + WidgetTypesService, +} from "@nova-ui/dashboards"; + +import { positions, widgets } from "./widget-configs"; +import { + HarryPotterAverageRatingDataSource, + HarryPotterRatingsCountDataSource, +} from "../data/kpi-datasources"; +import { + BeerReviewCountsByCityMockDataSource, + BeerReviewCountsByCityMockDataSource2, +} from "../data/proportional-datasources"; +import { BeerDataSource } from "../data/table/beer-data-source"; +import { RandomUserDataSource } from "../data/table/random-user-data-source"; +import { + BeerVsReadingMockDataSource, + LoungingVsFrisbeeGolfMockDataSource, +} from "../data/timeseries-data-sources"; + +/** + * A component that instantiates the dashboard + */ +@Component({ + selector: "hero-dashboard", + templateUrl: "./hero-dashboard.component.html", + styleUrls: ["./hero-dashboard.component.less"], + encapsulation: ViewEncapsulation.Emulated, + changeDetection: ChangeDetectionStrategy.Default, + standalone: false, +}) +export class HeroDashboardComponent implements OnInit { + public dashboard: IDashboard = { + positions: {}, + widgets: {}, + }; + + public gridsterConfig = {}; + public editMode = false; + + constructor( + private providerRegistry: ProviderRegistryService, + public themeSwitcherService: ThemeSwitchService, + private widgetTypesService: WidgetTypesService + ) { + this.providerRegistry.setProviders({ + [HarryPotterAverageRatingDataSource.providerId]: { + provide: DATA_SOURCE, + useClass: HarryPotterAverageRatingDataSource, + deps: [HttpClient], + }, + [HarryPotterRatingsCountDataSource.providerId]: { + provide: DATA_SOURCE, + useClass: HarryPotterRatingsCountDataSource, + deps: [HttpClient], + }, + [BeerReviewCountsByCityMockDataSource.providerId]: { + provide: DATA_SOURCE, + useClass: BeerReviewCountsByCityMockDataSource, + deps: [], + }, + [BeerReviewCountsByCityMockDataSource2.providerId]: { + provide: DATA_SOURCE, + useClass: BeerReviewCountsByCityMockDataSource2, + deps: [], + }, + [RandomUserDataSource.providerId]: { + provide: DATA_SOURCE, + useClass: RandomUserDataSource, + deps: [LoggerService], + }, + [BeerDataSource.providerId]: { + provide: DATA_SOURCE, + useClass: BeerDataSource, + deps: [LoggerService], + }, + [BeerVsReadingMockDataSource.providerId]: { + provide: DATA_SOURCE, + useClass: BeerVsReadingMockDataSource, + deps: [], + }, + [LoungingVsFrisbeeGolfMockDataSource.providerId]: { + provide: DATA_SOURCE, + useClass: LoungingVsFrisbeeGolfMockDataSource, + deps: [], + }, + }); + } + + public ngOnInit(): void { + const widgetsWithStructure = widgets.map((w) => ({ + ...w, + pizzagna: { + ...this.widgetTypesService.getWidgetType(w.type, w.version) + .widget, + ...w.pizzagna, + }, + })); + const widgetsIndex = keyBy(widgetsWithStructure, (w: IWidget) => w.id); + + this.dashboard = { + positions: positions, + widgets: widgetsIndex, + }; + } +} +\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\`, + "overview/hero/dashboard/widget-configs.ts": \\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\`// © 2022 SolarWinds Worldwide, LLC. All rights reserved. +// +// Permission is hereby granted, free of charge, to any person obtaining a copy +// of this software and associated documentation files (the "Software"), to +// deal in the Software without restriction, including without limitation the +// rights to use, copy, modify, merge, publish, distribute, sublicense, and/or +// sell copies of the Software, and to permit persons to whom the Software is +// furnished to do so, subject to the following conditions: +// +// The above copyright notice and this permission notice shall be included in +// all copies or substantial portions of the Software. +// +// THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR +// IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, +// FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE +// AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER +// LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, +// OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN +// THE SOFTWARE. + +import { GridsterItem } from "angular-gridster2"; + +import { IWidget } from "@nova-ui/dashboards"; + +import { kpiConfig } from "../widget-configs/kpi"; +import { proportionalConfig } from "../widget-configs/proportional"; +import { tableConfig } from "../widget-configs/table"; +import { timeseriesConfig } from "../widget-configs/timeseries"; + +export const positions: Record = { + [tableConfig.id]: { + cols: 7, + rows: 7, + y: 0, + x: 0, + }, + [proportionalConfig.id]: { + cols: 5, + rows: 7, + y: 0, + x: 7, + }, + [kpiConfig.id]: { + cols: 6, + rows: 7, + y: 7, + x: 0, + }, + [timeseriesConfig.id]: { + cols: 6, + rows: 7, + y: 7, + x: 6, + }, +}; + +export const widgets: IWidget[] = [ + { + ...tableConfig, + }, + { + ...proportionalConfig, + }, + { + ...kpiConfig, + }, + { + ...timeseriesConfig, + }, +]; +\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\`, + "overview/hero/data/kpi-datasources.ts": \\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\`// © 2022 SolarWinds Worldwide, LLC. All rights reserved. +// +// Permission is hereby granted, free of charge, to any person obtaining a copy +// of this software and associated documentation files (the "Software"), to +// deal in the Software without restriction, including without limitation the +// rights to use, copy, modify, merge, publish, distribute, sublicense, and/or +// sell copies of the Software, and to permit persons to whom the Software is +// furnished to do so, subject to the following conditions: +// +// The above copyright notice and this permission notice shall be included in +// all copies or substantial portions of the Software. +// +// THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR +// IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, +// FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE +// AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER +// LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, +// OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN +// THE SOFTWARE. + +import { HttpClient, HttpErrorResponse } from "@angular/common/http"; +import { Injectable, OnDestroy } from "@angular/core"; +import { BehaviorSubject } from "rxjs"; +import { finalize } from "rxjs/operators"; + +import { DataSourceService, IFilteringOutputs } from "@nova-ui/bits"; +import { IKpiData } from "@nova-ui/dashboards"; + +import { GOOGLE_BOOKS_URL } from "./table/constants"; + +@Injectable() +export class HarryPotterAverageRatingDataSource + extends DataSourceService + implements OnDestroy +{ + public static providerId = "HarryPotterAverageRatingDataSource"; + + public busy = new BehaviorSubject(false); + + constructor(private http: HttpClient) { + super(); + } + + public async getFilteredData(): Promise { + this.busy.next(true); + return new Promise((resolve) => { + // *** Make a resource request to an external API (if needed) + this.http + .get(\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\`\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\${GOOGLE_BOOKS_URL}/5MQFrgEACAAJ\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\`) + .pipe(finalize(() => this.busy.next(false))) + .subscribe({ + next: (data: any) => { + resolve({ + result: { + value: data.volumeInfo.averageRating, + }, + }); + }, + error: (error: HttpErrorResponse) => { + resolve({ + result: null, + error: { + type: error.status, + }, + }); + }, + }); + }); + } + + public ngOnDestroy(): void { + this.outputsSubject.complete(); + } +} + +@Injectable() +export class HarryPotterRatingsCountDataSource + extends DataSourceService + implements OnDestroy +{ + public static providerId = "HarryPotterRatingsCountDataSource"; + + public busy = new BehaviorSubject(false); + + constructor(private http: HttpClient) { + super(); + } + + public async getFilteredData(): Promise { + this.busy.next(true); + return new Promise((resolve) => { + this.http + .get(\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\`\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\${GOOGLE_BOOKS_URL}/5MQFrgEACAAJ\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\`) + .pipe(finalize(() => this.busy.next(false))) + .subscribe({ + next: (data: any) => { + resolve({ + result: { + value: data.volumeInfo.ratingsCount, + }, + }); + }, + error: (error: HttpErrorResponse) => { + resolve({ + result: null, + error: { + type: error.status, + }, + }); + }, + }); + }); + } + + public ngOnDestroy(): void { + this.outputsSubject.complete(); + } +} +\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\`, + "overview/hero/data/proportional-datasources.ts": \\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\`// © 2022 SolarWinds Worldwide, LLC. All rights reserved. +// +// Permission is hereby granted, free of charge, to any person obtaining a copy +// of this software and associated documentation files (the "Software"), to +// deal in the Software without restriction, including without limitation the +// rights to use, copy, modify, merge, publish, distribute, sublicense, and/or +// sell copies of the Software, and to permit persons to whom the Software is +// furnished to do so, subject to the following conditions: +// +// The above copyright notice and this permission notice shall be included in +// all copies or substantial portions of the Software. +// +// THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR +// IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, +// FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE +// AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER +// LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, +// OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN +// THE SOFTWARE. + +import { Injectable, OnDestroy } from "@angular/core"; +import { BehaviorSubject } from "rxjs"; + +import { + DataSourceService, + IDataSource, + IFilteringOutputs, +} from "@nova-ui/bits"; + +import { + getMockBeerReviewCountsByCity, + getMockBeerReviewCountsByCity2, + IProportionalWidgetData, +} from "./widget-data"; + +@Injectable() +export class BeerReviewCountsByCityMockDataSource + extends DataSourceService + implements IDataSource, OnDestroy +{ + // This is the ID we'll use to identify the provider + public static providerId = "BeerReviewCountsByCityMockDataSource"; + public busy = new BehaviorSubject(false); + + public async getFilteredData(): Promise { + this.busy.next(true); + return new Promise((resolve) => { + setTimeout(() => { + this.outputsSubject.next({ + result: getMockBeerReviewCountsByCity(), + }); + this.busy.next(false); + }, 300); + }); + } + + public ngOnDestroy(): void { + this.outputsSubject.complete(); + } +} + +@Injectable() +export class BeerReviewCountsByCityMockDataSource2 + extends DataSourceService + implements IDataSource, OnDestroy +{ + // This is the ID we'll use to identify the provider + public static providerId = "BeerReviewCountsByCityMockDataSource2"; + public busy = new BehaviorSubject(false); + + public async getFilteredData(): Promise { + this.busy.next(true); + return new Promise((resolve) => { + setTimeout(() => { + this.outputsSubject.next({ + result: getMockBeerReviewCountsByCity2(), + }); + this.busy.next(false); + }, 1500); + }); + } + + public ngOnDestroy(): void { + this.outputsSubject.complete(); + } +} +\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\`, + "overview/hero/data/table/beer-data-source.ts": \\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\`// © 2022 SolarWinds Worldwide, LLC. All rights reserved. +// +// Permission is hereby granted, free of charge, to any person obtaining a copy +// of this software and associated documentation files (the "Software"), to +// deal in the Software without restriction, including without limitation the +// rights to use, copy, modify, merge, publish, distribute, sublicense, and/or +// sell copies of the Software, and to permit persons to whom the Software is +// furnished to do so, subject to the following conditions: +// +// The above copyright notice and this permission notice shall be included in +// all copies or substantial portions of the Software. +// +// THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR +// IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, +// FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE +// AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER +// LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, +// OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN +// THE SOFTWARE. + +import { ListRange } from "@angular/cdk/collections"; +import { Injectable } from "@angular/core"; +import isEqual from "lodash/isEqual"; +import orderBy from "lodash/orderBy"; +import { BehaviorSubject } from "rxjs"; + +import { + DataSourceService, + IDataField, + INovaFilteringOutputs, + INovaFilters, + ISorterFilter, + LoggerService, +} from "@nova-ui/bits"; + +import { IBrewDatasourceResponse, IBrewInfo } from "../types"; +import { BREW_API_URL } from "./constants"; + +@Injectable() +export class BeerDataSource extends DataSourceService { + public static providerId = "BeerDataSource"; + + private cache = Array.from({ length: 0 }); + private lastSortValue?: ISorterFilter; + private lastVirtualScroll?: ListRange; + private totalItems: number = 325; + + public page: number = 1; + public busy = new BehaviorSubject(false); + + public dataFields: Array = [ + { id: "id", label: "No", dataType: "number" }, + { id: "name", label: "Name", dataType: "string" }, + { id: "tagline", label: "Tagline", dataType: "string" }, + { id: "first_brewed", label: "First Brewed", dataType: "string" }, + { id: "description", label: "Description", dataType: "string" }, + { id: "brewers_tips", label: "Brewer's Tips", dataType: "string" }, + ]; + + constructor(private logger: LoggerService) { + super(); + } + + public async getFilteredData( + filters: INovaFilters + ): Promise { + const start = filters.virtualScroll?.value?.start ?? 0; + const end = filters.virtualScroll?.value?.end ?? 0; + const delta = end - start; + + // This condition handles sorting. We want to sort columns without fetching another chunk of data. + // Since the data is being fetched when scrolled, we compare virtual scroll indexes here in the condition as well. + if (filters.sorter?.value) { + if ( + !isEqual(this.lastSortValue, filters.sorter.value) && + isEqual(this.lastVirtualScroll, filters.virtualScroll?.value) + ) { + const totalPages = Math.ceil( + delta ? this.totalItems / delta : 1 + ); + const itemsPerPage: number = Math.max( + delta < 80 ? delta : 80, + 1 + ); + let response: Array | null = null; + let map: IBrewDatasourceResponse; + + if (filters.sorter?.value?.direction === "desc") { + this.cache = []; + for (let i = 0; i < this.page; ++i) { + response = await ( + await fetch( + \\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\`\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\${BREW_API_URL}/?page=\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\${ + totalPages - i || 1 + }&per_page=\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\${itemsPerPage}\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\` + ) + ).json(); + + // since the last page contains only 5 items we need to fetch another page to give virtual scroll enough space to work + if (response && response.length < itemsPerPage) { + this.page++; + } + map = { + brewInfo: response?.map((result: IBrewInfo) => ({ + id: result.id, + name: result.name, + tagline: result.tagline, + first_brewed: result.first_brewed, + description: result.description, + brewers_tips: result.brewers_tips, + })), + total: response?.length, + } as IBrewDatasourceResponse; + this.cache = + totalPages - i !== 0 + ? this.cache.concat(map.brewInfo) + : this.cache; + } + } + + if (filters.sorter?.value?.direction === "asc") { + this.cache = []; + for (let i = 0; i < this.page; i++) { + response = await ( + await fetch( + \\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\`\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\${BREW_API_URL}/?page=\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\${ + i + 1 + }&per_page=\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\${itemsPerPage}\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\` + ) + ).json(); + map = { + brewInfo: response?.map((result: IBrewInfo) => ({ + id: result.id, + name: result.name, + tagline: result.tagline, + first_brewed: result.first_brewed, + description: result.description, + brewers_tips: result.brewers_tips, + })), + total: response?.length, + } as IBrewDatasourceResponse; + this.cache = this.cache.concat(map.brewInfo); + } + } + + this.lastSortValue = filters.sorter?.value; + this.lastVirtualScroll = filters.virtualScroll?.value; + + return { + repeat: { itemsSource: this.sortData(this.cache, filters) }, + paginator: { total: this.totalItems }, + dataFields: this.dataFields, + }; + } + } + + this.busy.next(true); + return new Promise((resolve) => { + setTimeout(() => { + this.getData(start, end, filters).then( + (response: INovaFilteringOutputs) => { + if (!response) { + return; + } + + this.cache = this.cache.concat(response.brewInfo); + + this.dataSubject.next(this.cache); + resolve({ + repeat: { + itemsSource: this.sortData(this.cache, filters), + }, + paginator: { total: this.totalItems }, + dataFields: this.dataFields, + }); + + this.lastSortValue = filters.sorter?.value; + this.lastVirtualScroll = filters.virtualScroll?.value; + this.busy.next(false); + } + ); + }, 500); + }); + } + + public async getData( + start: number = 0, + end: number = 20, + filters: INovaFilters + ): Promise { + const delta = end - start; + const totalPages = Math.ceil(delta ? this.totalItems / delta : 1); + let response: Array | null = null; + // The api.punk.com is able to return only 80 items per page + const itemsPerPage: number = Math.max(delta < 80 ? delta : 80, 1); + + if (filters.sorter?.value?.direction === "asc") { + response = await ( + await fetch( + \\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\`\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\${BREW_API_URL}/?page=\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\${this.page}&per_page=\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\${itemsPerPage}\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\` + ) + ).json(); + } + + if (filters.sorter?.value?.direction === "desc") { + response = await ( + await fetch( + \\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\`\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\${BREW_API_URL}/?page=\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\${ + totalPages - this.page + }&per_page=\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\${itemsPerPage}\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\` + ) + ).json(); + } + + if (!filters.sorter) { + response = await ( + await fetch( + \\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\`\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\${BREW_API_URL}/?page=\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\${this.page}&per_page=\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\${itemsPerPage}\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\` + ) + ).json(); + } + return { + brewInfo: response?.map((result: IBrewInfo, i: number) => ({ + id: result.id, + name: result.name, + tagline: result.tagline, + first_brewed: result.first_brewed, + description: result.description, + brewers_tips: result.brewers_tips, + })), + total: response?.length, + } as IBrewDatasourceResponse; + } + + private sortData(data: IBrewInfo[], filters: INovaFilters) { + return orderBy( + data, + filters.sorter?.value?.sortBy, + filters.sorter?.value?.direction as "desc" | "asc" + ); + } +} +\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\`, + "overview/hero/data/table/constants.ts": \\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\`// © 2022 SolarWinds Worldwide, LLC. All rights reserved. +// +// Permission is hereby granted, free of charge, to any person obtaining a copy +// of this software and associated documentation files (the "Software"), to +// deal in the Software without restriction, including without limitation the +// rights to use, copy, modify, merge, publish, distribute, sublicense, and/or +// sell copies of the Software, and to permit persons to whom the Software is +// furnished to do so, subject to the following conditions: +// +// The above copyright notice and this permission notice shall be included in +// all copies or substantial portions of the Software. +// +// THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR +// IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, +// FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE +// AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER +// LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, +// OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN +// THE SOFTWARE. + +export const corsProxy = "https://cors-anywhere.herokuapp.com"; +export const RANDOMUSER_API_URL = "https://randomuser.me"; +export const BREW_API_URL = "https://api.punkapi.com/v2/beers"; +export const GOOGLE_BOOKS_URL = "https://www.googleapis.com/books/v1/volumes"; +export const apiRoute = "api/1.3"; +export const responseError = \\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\`Error responding from server. Please visit \\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\${RANDOMUSER_API_URL} and \\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\${corsProxy} to see if they're available\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\`; +\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\`, + "overview/hero/data/table/random-user-data-source.ts": \\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\`// © 2022 SolarWinds Worldwide, LLC. All rights reserved. +// +// Permission is hereby granted, free of charge, to any person obtaining a copy +// of this software and associated documentation files (the "Software"), to +// deal in the Software without restriction, including without limitation the +// rights to use, copy, modify, merge, publish, distribute, sublicense, and/or +// sell copies of the Software, and to permit persons to whom the Software is +// furnished to do so, subject to the following conditions: +// +// The above copyright notice and this permission notice shall be included in +// all copies or substantial portions of the Software. +// +// THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR +// IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, +// FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE +// AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER +// LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, +// OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN +// THE SOFTWARE. + +import { ListRange } from "@angular/cdk/collections"; +import { Injectable } from "@angular/core"; +import isEqual from "lodash/isEqual"; +import orderBy from "lodash/orderBy"; +import { BehaviorSubject } from "rxjs"; + +import { + DataSourceService, + IDataField, + INovaFilteringOutputs, + INovaFilters, + ISorterFilter, + LoggerService, +} from "@nova-ui/bits"; + +import { + IRandomUserResponse, + IRandomUserResults, + IRandomUserTableModel, + UsersQueryResponse, +} from "../types"; +import { + apiRoute, + corsProxy, + RANDOMUSER_API_URL, + responseError, +} from "./constants"; + +@Injectable() +export class RandomUserDataSource extends DataSourceService { + public static providerId = "RandomUserDataSource"; + + private readonly seed = "sw"; + + private cache = Array.from({ length: 0 }); + private lastSortValue?: ISorterFilter; + private lastVirtualScroll?: ListRange; + + public page: number = 1; + public busy = new BehaviorSubject(false); + + public dataFields: Array = [ + { id: "no", label: $localize\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\`No\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\`, dataType: "number" }, + { id: "nameTitle", label: $localize\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\`Title\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\`, dataType: "string" }, + { id: "nameFirst", label: $localize\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\`First\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\`, dataType: "string" }, + { id: "nameLast", label: $localize\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\`Last\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\`, dataType: "string" }, + { id: "gender", label: $localize\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\`Gender\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\`, dataType: "string" }, + { id: "country", label: $localize\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\`Country\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\`, dataType: "string" }, + { id: "city", label: $localize\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\`City\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\`, dataType: "string" }, + { id: "postcode", label: $localize\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\`Postcode\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\`, dataType: "number" }, + { id: "email", label: $localize\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\`E-Mail\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\`, dataType: "string" }, + { id: "cell", label: $localize\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\`Cell\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\`, dataType: "string" }, + ]; + + constructor(private logger: LoggerService) { + super(); + } + + public async getFilteredData( + filters: INovaFilters + ): Promise { + // This condition handles sorting. We want to sort columns without fetching another chunk of data. + // Since the data is being fetched when scrolled, we compare virtual scroll indexes here in the condition as well. + if (filters.sorter?.value) { + if ( + !isEqual(this.lastSortValue, filters.sorter.value) && + isEqual(this.lastVirtualScroll, filters.virtualScroll?.value) + ) { + this.lastSortValue = filters.sorter?.value; + this.lastVirtualScroll = filters.virtualScroll?.value; + + return { + repeat: { itemsSource: this.sortData(this.cache, filters) }, + paginator: { total: 200 }, + dataFields: this.dataFields, + }; + } + } + this.busy.next(true); + + const virtualScrollFilter = + filters.virtualScroll && filters.virtualScroll.value; + const start = virtualScrollFilter + ? filters.virtualScroll?.value.start + : 0; + const end = virtualScrollFilter ? filters.virtualScroll?.value.end : 0; + + // We're returning Promise with setTimeout here to make the response from the server longer, as the API being used sends responses + // almost immediately. We need it longer to be able the show the spinner component on data load + return new Promise((resolve) => { + setTimeout(() => { + this.getData(start, end).then( + (response: INovaFilteringOutputs | undefined) => { + if (!response) { + return; + } + + this.cache = this.cache.concat(response.users); + + this.dataSubject.next(this.cache); + resolve({ + repeat: { + itemsSource: this.sortData(this.cache, filters), + }, + // This API can return thousands of results, however doesn't return the max number of results, + // so we set the max number of result manually here. + paginator: { total: 200 }, + dataFields: this.dataFields, + }); + + this.lastSortValue = filters.sorter?.value; + this.lastVirtualScroll = filters.virtualScroll?.value; + this.busy.next(false); + } + ); + }, 300); + }); + } + + public async getData( + start: number = 0, + end: number = 20 + ): Promise { + let response: IRandomUserResponse | null = null; + try { + response = await ( + await fetch( + \\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\`\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\${corsProxy}/\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\${RANDOMUSER_API_URL}/\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\${apiRoute}/?page=\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\${ + this.page + }&results=\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\${end - start}&seed=\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\${this.seed}\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\` + ) + ).json(); + return { + users: response?.results.map( + (result: IRandomUserResults, i: number) => ({ + no: this.cache.length + i + 1, + nameTitle: result.name.title, + nameFirst: result.name.first, + nameLast: result.name.last, + gender: result.gender, + country: result.location.country, + city: result.location.city, + postcode: result.location.postcode, + email: result.email, + cell: result.cell, + }) + ), + total: response?.results.length, + start: start, + } as UsersQueryResponse; + } catch (e) { + this.logger.error(responseError); + } + } + + private sortData(data: IRandomUserTableModel[], filters: INovaFilters) { + return orderBy( + data, + filters.sorter?.value?.sortBy, + filters.sorter?.value?.direction as "desc" | "asc" + ); + } +} +\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\`, + "overview/hero/data/table/types.ts": \\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\`// © 2022 SolarWinds Worldwide, LLC. All rights reserved. +// +// Permission is hereby granted, free of charge, to any person obtaining a copy +// of this software and associated documentation files (the "Software"), to +// deal in the Software without restriction, including without limitation the +// rights to use, copy, modify, merge, publish, distribute, sublicense, and/or +// sell copies of the Software, and to permit persons to whom the Software is +// furnished to do so, subject to the following conditions: +// +// The above copyright notice and this permission notice shall be included in +// all copies or substantial portions of the Software. +// +// THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR +// IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, +// FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE +// AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER +// LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, +// OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN +// THE SOFTWARE. + +import { IDataField, INovaFilteringOutputs } from "@nova-ui/bits"; +export interface BasicTableModel { + position: number; + name: string; + features: any; + status: string; + checks: any; + "cpu-load": number; + firstUrl: string; + firstUrlLabel: string; + secondUrl: string; + secondUrlLabel: string; +} + +export interface ITableDataSourceOutput extends INovaFilteringOutputs { + dataFields: IDataField[]; +} +\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\`, + "overview/hero/data/timeseries-data-sources.ts": \\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\`// © 2022 SolarWinds Worldwide, LLC. All rights reserved. +// +// Permission is hereby granted, free of charge, to any person obtaining a copy +// of this software and associated documentation files (the "Software"), to +// deal in the Software without restriction, including without limitation the +// rights to use, copy, modify, merge, publish, distribute, sublicense, and/or +// sell copies of the Software, and to permit persons to whom the Software is +// furnished to do so, subject to the following conditions: +// +// The above copyright notice and this permission notice shall be included in +// all copies or substantial portions of the Software. +// +// THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR +// IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, +// FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE +// AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER +// LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, +// OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN +// THE SOFTWARE. + +import { Injectable } from "@angular/core"; +import { Moment } from "moment/moment"; +import { BehaviorSubject } from "rxjs"; + +import { DataSourceService, IDataSource, INovaFilters } from "@nova-ui/bits"; +import { + ITimeseriesOutput, + ITimeseriesWidgetData, + ITimeseriesWidgetSeriesData, +} from "@nova-ui/dashboards"; + +import { + BEER_VS_READING_DATA, + LOUNGING_VS_ULTIMATE_FRISBEE_DATA, +} from "./widget-data"; + +@Injectable() +export class BeerVsReadingMockDataSource + extends DataSourceService + implements IDataSource +{ + public static providerId = "BeerVsReadingMockDataSource"; + + public busy = new BehaviorSubject(false); + + constructor() { + super(); + } + + public async getFilteredData( + filters: INovaFilters + ): Promise { + this.busy.next(true); + const result = await delay( + { series: getData(filters, BEER_VS_READING_DATA) }, + 1000 + ); + this.busy.next(false); + return result; + } +} + +@Injectable() +export class LoungingVsFrisbeeGolfMockDataSource + extends DataSourceService + implements IDataSource +{ + public static providerId = "LoungingVsFrisbeeGolfMockDataSource"; + + public busy = new BehaviorSubject(false); + + constructor() { + super(); + } + + public async getFilteredData( + filters: INovaFilters + ): Promise { + this.busy.next(true); + const result = await delay( + { series: getData(filters, LOUNGING_VS_ULTIMATE_FRISBEE_DATA) }, + 1000 + ); + this.busy.next(false); + return result; + } +} + +function getData( + filters: INovaFilters, + data: ITimeseriesWidgetData[] +): ITimeseriesWidgetData[] { + const timeframeFilter = filters.timeframe; + let filteredData = data; + // TIME FRAME PICKER FILTERING + if (timeframeFilter) { + filteredData = filteredData.map((item: ITimeseriesWidgetData) => ({ + id: item.id, + name: item.name, + description: item.description, + data: item.data.filter((seriesData: ITimeseriesWidgetSeriesData) => + filterDates( + seriesData.x, + timeframeFilter.value.startDatetime, + timeframeFilter.value.endDatetime + ) + ), + })); + } + + return filteredData; +} + +function filterDates(dateToCheck: Moment, startDate: Moment, endDate: Moment) { + return ( + dateToCheck.isBetween(startDate, endDate) || + dateToCheck.isSame(startDate) || + dateToCheck.isSame(endDate) + ); +} + +async function delay( + value: ITimeseriesOutput, + ms: number +): Promise { + return new Promise((resolve) => setTimeout(() => resolve(value), ms)); +} +\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\`, + "overview/hero/data/types.ts": \\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\`// © 2022 SolarWinds Worldwide, LLC. All rights reserved. +// +// Permission is hereby granted, free of charge, to any person obtaining a copy +// of this software and associated documentation files (the "Software"), to +// deal in the Software without restriction, including without limitation the +// rights to use, copy, modify, merge, publish, distribute, sublicense, and/or +// sell copies of the Software, and to permit persons to whom the Software is +// furnished to do so, subject to the following conditions: +// +// The above copyright notice and this permission notice shall be included in +// all copies or substantial portions of the Software. +// +// THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR +// IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, +// FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE +// AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER +// LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, +// OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN +// THE SOFTWARE. + +export interface UsersQueryResponse { + users: IRandomUserTableModel[]; + total: number; + start: number; +} + +export interface IRandomUserResponse { + info: Array; + results: Array; +} + +export interface IRandomUserInfo { + page: number; + results: number; + seed: string; + version: string; +} + +export interface IRandomUserResults { + cell: string; + dob: { + age: number; + date: string; + }; + email: string; + gender: string; + id: any; + location: IRandomUserLocation; + login: { + md5: string; + password: string; + salt: string; + sha1: string; + sha256: string; + username: string; + uuid: string; + }; + name: { + title: string; + first: string; + last: string; + }; + nat: string; + phone: string; + picture: { + large: string; + medium: string; + thumbnail: string; + }; + registered: { + date: string; + age: number; + }; +} + +export interface IRandomUserTableModel { + no: number; + nameTitle: string; + nameFirst: string; + nameLast: string; + gender: string; + country: string; + city: string; + postcode: number; + email: string; + cell: string; +} + +export interface IRandomUserLocation { + city: string; + coordinates: { latitude: string; longitude: string }; + country: string; + postcode: number; + state: string; + street: { number: number; name: string }; + timezone: any; +} + +export interface IBrewInfo { + id: number; + name: string; + tagline: string; + first_brewed: string; + description: string; + brewers_tips: string; +} + +export interface IBrewDatasourceResponse { + brewInfo: IBrewInfo[]; + total: number; +} +\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\`, + "overview/hero/data/widget-data.ts": \\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\`// © 2022 SolarWinds Worldwide, LLC. All rights reserved. +// +// Permission is hereby granted, free of charge, to any person obtaining a copy +// of this software and associated documentation files (the "Software"), to +// deal in the Software without restriction, including without limitation the +// rights to use, copy, modify, merge, publish, distribute, sublicense, and/or +// sell copies of the Software, and to permit persons to whom the Software is +// furnished to do so, subject to the following conditions: +// +// The above copyright notice and this permission notice shall be included in +// all copies or substantial portions of the Software. +// +// THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR +// IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, +// FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE +// AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER +// LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, +// OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN +// THE SOFTWARE. + +import moment from "moment/moment"; + +import { ITimeseriesWidgetData } from "@nova-ui/dashboards"; + +import { BasicTableModel } from "./table/types"; + +export interface IProportionalWidgetData { + id: string; + name: string; + data: number[]; + link: string; + value: string; +} + +export function getMockBeerReviewCountsByCity(): IProportionalWidgetData[] { + return [ + { + id: "Brno", + name: "Brno", + data: [Math.round(Math.random() * 100000)], + link: "https://en.wikipedia.org/wiki/Brno", + value: "Brno", + }, + { + id: "kyiv", + name: "Kyiv", + data: [Math.round(Math.random() * 100000)], + link: "https://en.wikipedia.org/wiki/Kyiv", + value: "Kyiv", + }, + { + id: "austin", + name: "Austin", + data: [Math.round(Math.random() * 100000)], + link: "https://en.wikipedia.org/wiki/Austin", + value: "Austin", + }, + { + id: "lisbon", + name: "Lisbon", + data: [Math.round(Math.random() * 100000)], + link: "https://en.wikipedia.org/wiki/Lisbon", + value: "Lisbon", + }, + { + id: "sydney", + name: "Sydney", + data: [Math.round(Math.random() * 100000)], + link: "https://en.wikipedia.org/wiki/Sydney", + value: "Sydney", + }, + { + id: "nur-sultan", + name: "Nur-Sultan", + data: [Math.round(Math.random() * 100000)], + link: "https://en.wikipedia.org/wiki/Nur-Sultan", + value: "Nur-Sultan", + }, + ].sort((a, b) => a.data[0] - b.data[0]); +} + +export function getMockBeerReviewCountsByCity2(): IProportionalWidgetData[] { + return [ + { + id: "london", + name: "London", + data: [Math.round(Math.random() * 100000)], + link: "https://en.wikipedia.org/wiki/London", + value: "London", + }, + { + id: "paris", + name: "Paris", + data: [Math.round(Math.random() * 100000)], + link: "https://en.wikipedia.org/wiki/Paris", + value: "Paris", + }, + { + id: "rio", + name: "Rio", + data: [Math.round(Math.random() * 100000)], + link: "https://en.wikipedia.org/wiki/Rio_de_Janeiro", + value: "Rio", + }, + ].sort((a, b) => a.data[0] - b.data[0]); +} + +export const BEER_VS_READING_DATA: ITimeseriesWidgetData[] = [ + { + id: "series-1", + name: "Beer Tasting", + description: "Havin' some suds", + data: [ + { x: moment().subtract(10, "day"), y: 30 }, + { x: moment().subtract(9, "day"), y: 35 }, + { x: moment().subtract(8, "day"), y: 33 }, + { x: moment().subtract(7, "day"), y: 40 }, + { x: moment().subtract(6, "day"), y: 35 }, + { x: moment().subtract(5, "day"), y: 30 }, + { x: moment().subtract(4, "day"), y: 35 }, + { x: moment().subtract(3, "day"), y: 15 }, + { x: moment().subtract(2, "day"), y: 30 }, + { x: moment().subtract(1, "day"), y: 35 }, + { x: moment().subtract(24, "hour"), y: 34 }, + { x: moment().subtract(15, "hour"), y: 33 }, + { x: moment().subtract(10, "hour"), y: 35 }, + { x: moment().subtract(5, "hour"), y: 36 }, + { x: moment().subtract(1, "hour"), y: 34 }, + { x: moment().subtract(50, "minute"), y: 33 }, + { x: moment().subtract(40, "minute"), y: 30 }, + { x: moment().subtract(30, "minute"), y: 32 }, + { x: moment().subtract(20, "minute"), y: 31 }, + { x: moment().subtract(10, "minute"), y: 34 }, + ], + }, + { + id: "series-2", + name: "Reading", + description: "Hittin' the books", + data: [ + { x: moment().subtract(10, "day"), y: 60 }, + { x: moment().subtract(9, "day"), y: 64 }, + { x: moment().subtract(8, "day"), y: 70 }, + { x: moment().subtract(7, "day"), y: 55 }, + { x: moment().subtract(6, "day"), y: 55 }, + { x: moment().subtract(5, "day"), y: 45 }, + { x: moment().subtract(4, "day"), y: 10 }, + { x: moment().subtract(3, "day"), y: 65 }, + { x: moment().subtract(2, "day"), y: 35 }, + { x: moment().subtract(1, "day"), y: 60 }, + { x: moment().subtract(24, "hour"), y: 61 }, + { x: moment().subtract(15, "hour"), y: 65 }, + { x: moment().subtract(10, "hour"), y: 63 }, + { x: moment().subtract(5, "hour"), y: 58 }, + { x: moment().subtract(1, "hour"), y: 64 }, + { x: moment().subtract(50, "minute"), y: 63 }, + { x: moment().subtract(40, "minute"), y: 60 }, + { x: moment().subtract(30, "minute"), y: 62 }, + { x: moment().subtract(20, "minute"), y: 61 }, + { x: moment().subtract(10, "minute"), y: 62 }, + ], + }, +]; + +export const LOUNGING_VS_ULTIMATE_FRISBEE_DATA: ITimeseriesWidgetData[] = [ + { + id: "series-a", + name: "Lounging", + description: "Shootin' the Breeze", + data: [ + { x: moment().subtract(10, "day"), y: 10 }, + { x: moment().subtract(9, "day"), y: 15 }, + { x: moment().subtract(8, "day"), y: 13 }, + { x: moment().subtract(7, "day"), y: 20 }, + { x: moment().subtract(6, "day"), y: 15 }, + { x: moment().subtract(5, "day"), y: 10 }, + { x: moment().subtract(4, "day"), y: 15 }, + { x: moment().subtract(3, "day"), y: 5 }, + { x: moment().subtract(2, "day"), y: 10 }, + { x: moment().subtract(1, "day"), y: 15 }, + { x: moment().subtract(24, "hour"), y: 14 }, + { x: moment().subtract(15, "hour"), y: 13 }, + { x: moment().subtract(10, "hour"), y: 15 }, + { x: moment().subtract(5, "hour"), y: 16 }, + { x: moment().subtract(1, "hour"), y: 14 }, + { x: moment().subtract(50, "minute"), y: 13 }, + { x: moment().subtract(40, "minute"), y: 10 }, + { x: moment().subtract(30, "minute"), y: 12 }, + { x: moment().subtract(20, "minute"), y: 11 }, + { x: moment().subtract(10, "minute"), y: 14 }, + ], + }, + { + id: "series-b", + name: "Frisbee Golfing", + description: "Golfin' with a disc", + data: [ + { x: moment().subtract(10, "day"), y: 80 }, + { x: moment().subtract(9, "day"), y: 84 }, + { x: moment().subtract(8, "day"), y: 80 }, + { x: moment().subtract(7, "day"), y: 75 }, + { x: moment().subtract(6, "day"), y: 95 }, + { x: moment().subtract(5, "day"), y: 85 }, + { x: moment().subtract(4, "day"), y: 80 }, + { x: moment().subtract(3, "day"), y: 85 }, + { x: moment().subtract(2, "day"), y: 85 }, + { x: moment().subtract(1, "day"), y: 80 }, + { x: moment().subtract(24, "hour"), y: 81 }, + { x: moment().subtract(15, "hour"), y: 85 }, + { x: moment().subtract(10, "hour"), y: 83 }, + { x: moment().subtract(5, "hour"), y: 88 }, + { x: moment().subtract(1, "hour"), y: 84 }, + { x: moment().subtract(50, "minute"), y: 83 }, + { x: moment().subtract(40, "minute"), y: 80 }, + { x: moment().subtract(30, "minute"), y: 82 }, + { x: moment().subtract(20, "minute"), y: 81 }, + { x: moment().subtract(10, "minute"), y: 82 }, + ], + }, +]; + +export const TABLE_DATA: BasicTableModel[] = [ + { + position: 1, + name: "FOCUS-SVR-02258", + features: ["remote-access-vpn-tunnel", "patch-manager01"], + status: "Active", + checks: { + icon: "status_up", + num: 25, + }, + "cpu-load": 86, + firstUrl: "https://en.wikipedia.org/wiki/Brno", + firstUrlLabel: "Brno", + secondUrl: "https://en.wikipedia.org/wiki/VMware_Workstation", + secondUrlLabel: "Workstation", + }, + { + position: 2, + name: "FOCUS-SVR-03312", + features: ["tools", "database", "orion-ape-backup"], + status: "Active", + checks: { + icon: "status_critical", + num: 25, + }, + "cpu-load": 47, + firstUrl: "https://en.wikipedia.org/wiki/Brno", + firstUrlLabel: "Brno", + secondUrl: "https://en.wikipedia.org/wiki/VMware_Workstation", + secondUrlLabel: "Workstation", + }, + { + position: 3, + name: "FOCUS-SVR-02258", + features: [ + "remote-access-vpn-tunnel", + "database", + "orion-ape-backup", + "patch-manager01", + ], + status: "Active", + checks: { + icon: "status_down", + num: 25, + }, + "cpu-load": 53, + firstUrl: "https://en.wikipedia.org/wiki/Kyiv", + firstUrlLabel: "Kyiv", + secondUrl: "https://en.wikipedia.org/wiki/VMware_Workstation", + secondUrlLabel: "Workstation", + }, + { + position: 4, + name: "Man-LT-JYJ4AD5", + features: [ + "remote-access-vpn-tunnel", + "tools", + "database", + "orion-ape-backup", + ], + status: "Active", + checks: { + icon: "status_up", + num: 25, + }, + "cpu-load": 32, + firstUrl: "https://en.wikipedia.org/wiki/Kyiv", + firstUrlLabel: "Kyiv", + secondUrl: "https://en.wikipedia.org/wiki/VirtualBox", + secondUrlLabel: "VirtualBox", + }, + { + position: 5, + name: "Man-LT-JYJ425", + features: [ + "remote-access-vpn-tunnel", + "tools", + "database", + "orion-ape-backup", + ], + status: "Active", + checks: { + icon: "status_up", + num: 25, + }, + "cpu-load": 22, + firstUrl: "https://en.wikipedia.org/wiki/Kyiv", + firstUrlLabel: "Kyiv", + secondUrl: "https://en.wikipedia.org/wiki/VirtualBox", + secondUrlLabel: "VirtualBox", + }, + { + position: 6, + name: "Man-LT-JYJ4333", + features: [ + "remote-access-vpn-tunnel", + "tools", + "database", + "orion-ape-backup", + ], + status: "Active", + checks: { + icon: "status_up", + num: 25, + }, + "cpu-load": 12, + firstUrl: "https://en.wikipedia.org/wiki/Kyiv", + firstUrlLabel: "Kyiv", + secondUrl: "https://en.wikipedia.org/wiki/VirtualBox", + secondUrlLabel: "VirtualBox", + }, + { + position: 7, + name: "FOCUS-SVR-02258", + features: ["remote-access-vpn-tunnel", "patch-manager01"], + status: "Active", + checks: { + icon: "status_up", + num: 25, + }, + "cpu-load": 86, + firstUrl: "https://en.wikipedia.org/wiki/Austin", + firstUrlLabel: "Austin", + secondUrl: "https://en.wikipedia.org/wiki/VMware_Workstation", + secondUrlLabel: "Workstation", + }, + { + position: 8, + name: "Man-LT-JYJ4AD5", + features: [ + "remote-access-vpn-tunnel", + "tools", + "database", + "orion-ape-backup", + "patch-manager01", + ], + status: "Active", + checks: { + icon: "status_inactive", + num: 25, + }, + "cpu-load": 35, + firstUrl: "https://en.wikipedia.org/wiki/Austin", + firstUrlLabel: "Austin", + secondUrl: "https://en.wikipedia.org/wiki/VMware_Workstation", + secondUrlLabel: "Workstation", + }, + { + position: 9, + name: "Man-LT-JYJ4AD5", + features: [ + "remote-access-vpn-tunnel", + "tools", + "database", + "patch-manager01", + ], + status: "Active", + checks: { + icon: "status_up", + num: 25, + }, + "cpu-load": 32, + firstUrl: "https://en.wikipedia.org/wiki/Brno", + firstUrlLabel: "Brno", + secondUrl: "https://en.wikipedia.org/wiki/VMware_Workstation", + secondUrlLabel: "Workstation", + }, + { + position: 10, + name: "Man-LT-JYJ4AD5", + features: [ + "remote-access-vpn-tunnel", + "database", + "orion-ape-backup", + "patch-manager01", + ], + status: "Active", + checks: { + icon: "status_up", + num: 25, + }, + "cpu-load": 64, + firstUrl: "https://en.wikipedia.org/wiki/Kyiv", + firstUrlLabel: "Kyiv", + secondUrl: "https://en.wikipedia.org/wiki/VMware_Workstation", + secondUrlLabel: "Workstation", + }, + { + position: 11, + name: "Man-LT-111", + features: [], + status: "Active", + checks: { + icon: "status_external", + num: 25, + }, + "cpu-load": 55, + firstUrl: "https://en.wikipedia.org/wiki/Brno", + firstUrlLabel: "Brno", + secondUrl: "https://en.wikipedia.org/wiki/VMware_Workstation", + secondUrlLabel: "Workstation", + }, + { + position: 12, + name: "Man-LT-2222", + features: [ + "remote-access-vpn-tunnel", + "tools", + "database", + "orion-ape-backup", + "patch-manager01", + ], + status: "Active", + checks: { + icon: "status_inactive", + num: 25, + }, + "cpu-load": 34, + firstUrl: "https://en.wikipedia.org/wiki/Brno", + firstUrlLabel: "Brno", + secondUrl: "https://en.wikipedia.org/wiki/VMware_Workstation", + secondUrlLabel: "Workstation", + }, + { + position: 13, + name: "Man-LT-333333", + features: [ + "remote-access-vpn-tunnel", + "tools", + "database", + "patch-manager01", + ], + status: "Active", + checks: { + icon: "status_up", + num: 25, + }, + "cpu-load": 56, + firstUrl: "https://en.wikipedia.org/wiki/Kyiv", + firstUrlLabel: "Kyiv", + secondUrl: "https://en.wikipedia.org/wiki/VMware_Workstation", + secondUrlLabel: "Workstation", + }, + { + position: 14, + name: "Man-LT-444444", + features: [ + "remote-access-vpn-tunnel", + "database", + "orion-ape-backup", + "patch-manager01", + ], + status: "Active", + checks: { + icon: "status_up", + num: 25, + }, + "cpu-load": 26, + firstUrl: "https://en.wikipedia.org/wiki/Kyiv", + firstUrlLabel: "Kyiv", + secondUrl: "https://en.wikipedia.org/wiki/VMware_Workstation", + secondUrlLabel: "Workstation", + }, + { + position: 15, + name: "Man-LT-555555", + features: [ + "remote-access-vpn-tunnel", + "database", + "orion-ape-backup", + "patch-manager01", + ], + status: "Active", + checks: { + icon: "status_up", + num: 25, + }, + "cpu-load": 76, + firstUrl: "https://en.wikipedia.org/wiki/Austin", + firstUrlLabel: "Austin", + secondUrl: "https://en.wikipedia.org/wiki/VMware_Workstation", + secondUrlLabel: "Workstation", + }, + { + position: 16, + name: "FOCUS-SVR-02258", + features: ["remote-access-vpn-tunnel", "patch-manager01"], + status: "Active", + checks: { + icon: "status_up", + num: 25, + }, + "cpu-load": 86, + firstUrl: "https://en.wikipedia.org/wiki/Brno", + firstUrlLabel: "Brno", + secondUrl: "https://en.wikipedia.org/wiki/VMware_Workstation", + secondUrlLabel: "Workstation", + }, + { + position: 17, + name: "FOCUS-SVR-03312", + features: ["tools", "database", "orion-ape-backup"], + status: "Active", + checks: { + icon: "status_critical", + num: 25, + }, + "cpu-load": 47, + firstUrl: "https://en.wikipedia.org/wiki/Brno", + firstUrlLabel: "Brno", + secondUrl: "https://en.wikipedia.org/wiki/VMware_Workstation", + secondUrlLabel: "Workstation", + }, + { + position: 18, + name: "FOCUS-SVR-02258", + features: [ + "remote-access-vpn-tunnel", + "database", + "orion-ape-backup", + "patch-manager01", + ], + status: "Active", + checks: { + icon: "status_down", + num: 25, + }, + "cpu-load": 53, + firstUrl: "https://en.wikipedia.org/wiki/Kyiv", + firstUrlLabel: "Kyiv", + secondUrl: "https://en.wikipedia.org/wiki/VMware_Workstation", + secondUrlLabel: "Workstation", + }, + { + position: 19, + name: "Man-LT-JYJ4AD5", + features: [ + "remote-access-vpn-tunnel", + "tools", + "database", + "orion-ape-backup", + ], + status: "Active", + checks: { + icon: "status_up", + num: 25, + }, + "cpu-load": 32, + firstUrl: "https://en.wikipedia.org/wiki/Kyiv", + firstUrlLabel: "Kyiv", + secondUrl: "https://en.wikipedia.org/wiki/VirtualBox", + secondUrlLabel: "VirtualBox", + }, + { + position: 20, + name: "Man-LT-JYJ425", + features: [ + "remote-access-vpn-tunnel", + "tools", + "database", + "orion-ape-backup", + ], + status: "Active", + checks: { + icon: "status_up", + num: 25, + }, + "cpu-load": 22, + firstUrl: "https://en.wikipedia.org/wiki/Kyiv", + firstUrlLabel: "Kyiv", + secondUrl: "https://en.wikipedia.org/wiki/VirtualBox", + secondUrlLabel: "VirtualBox", + }, + { + position: 21, + name: "Man-LT-JYJ4333", + features: [ + "remote-access-vpn-tunnel", + "tools", + "database", + "orion-ape-backup", + ], + status: "Active", + checks: { + icon: "status_up", + num: 25, + }, + "cpu-load": 12, + firstUrl: "https://en.wikipedia.org/wiki/Kyiv", + firstUrlLabel: "Kyiv", + secondUrl: "https://en.wikipedia.org/wiki/VirtualBox", + secondUrlLabel: "VirtualBox", + }, + { + position: 22, + name: "FOCUS-SVR-02258", + features: ["remote-access-vpn-tunnel", "patch-manager01"], + status: "Active", + checks: { + icon: "status_up", + num: 25, + }, + "cpu-load": 86, + firstUrl: "https://en.wikipedia.org/wiki/Austin", + firstUrlLabel: "Austin", + secondUrl: "https://en.wikipedia.org/wiki/VMware_Workstation", + secondUrlLabel: "Workstation", + }, + { + position: 23, + name: "Man-LT-JYJ4AD5", + features: [ + "remote-access-vpn-tunnel", + "tools", + "database", + "orion-ape-backup", + "patch-manager01", + ], + status: "Active", + checks: { + icon: "status_inactive", + num: 25, + }, + "cpu-load": 35, + firstUrl: "https://en.wikipedia.org/wiki/Austin", + firstUrlLabel: "Austin", + secondUrl: "https://en.wikipedia.org/wiki/VMware_Workstation", + secondUrlLabel: "Workstation", + }, + { + position: 24, + name: "Man-LT-JYJ4AD5", + features: [ + "remote-access-vpn-tunnel", + "tools", + "database", + "patch-manager01", + ], + status: "Active", + checks: { + icon: "status_up", + num: 25, + }, + "cpu-load": 32, + firstUrl: "https://en.wikipedia.org/wiki/Brno", + firstUrlLabel: "Brno", + secondUrl: "https://en.wikipedia.org/wiki/VMware_Workstation", + secondUrlLabel: "Workstation", + }, + { + position: 25, + name: "Man-LT-JYJ4AD5", + features: [ + "remote-access-vpn-tunnel", + "database", + "orion-ape-backup", + "patch-manager01", + ], + status: "Active", + checks: { + icon: "status_up", + num: 25, + }, + "cpu-load": 64, + firstUrl: "https://en.wikipedia.org/wiki/Kyiv", + firstUrlLabel: "Kyiv", + secondUrl: "https://en.wikipedia.org/wiki/VMware_Workstation", + secondUrlLabel: "Workstation", + }, + { + position: 26, + name: "Man-LT-111", + features: [], + status: "Active", + checks: { + icon: "status_external", + num: 25, + }, + "cpu-load": 55, + firstUrl: "https://en.wikipedia.org/wiki/Brno", + firstUrlLabel: "Brno", + secondUrl: "https://en.wikipedia.org/wiki/VMware_Workstation", + secondUrlLabel: "Workstation", + }, + { + position: 27, + name: "Man-LT-2222", + features: [ + "remote-access-vpn-tunnel", + "tools", + "database", + "orion-ape-backup", + "patch-manager01", + ], + status: "Active", + checks: { + icon: "status_inactive", + num: 25, + }, + "cpu-load": 34, + firstUrl: "https://en.wikipedia.org/wiki/Brno", + firstUrlLabel: "Brno", + secondUrl: "https://en.wikipedia.org/wiki/VMware_Workstation", + secondUrlLabel: "Workstation", + }, + { + position: 28, + name: "Man-LT-333333", + features: [ + "remote-access-vpn-tunnel", + "tools", + "database", + "patch-manager01", + ], + status: "Active", + checks: { + icon: "status_up", + num: 25, + }, + "cpu-load": 56, + firstUrl: "https://en.wikipedia.org/wiki/Kyiv", + firstUrlLabel: "Kyiv", + secondUrl: "https://en.wikipedia.org/wiki/VMware_Workstation", + secondUrlLabel: "Workstation", + }, + { + position: 29, + name: "Man-LT-444444", + features: [ + "remote-access-vpn-tunnel", + "database", + "orion-ape-backup", + "patch-manager01", + ], + status: "Active", + checks: { + icon: "status_up", + num: 25, + }, + "cpu-load": 26, + firstUrl: "https://en.wikipedia.org/wiki/Kyiv", + firstUrlLabel: "Kyiv", + secondUrl: "https://en.wikipedia.org/wiki/VMware_Workstation", + secondUrlLabel: "Workstation", + }, + { + position: 30, + name: "Man-LT-555555", + features: [ + "remote-access-vpn-tunnel", + "database", + "orion-ape-backup", + "patch-manager01", + ], + status: "Active", + checks: { + icon: "status_up", + num: 25, + }, + "cpu-load": 76, + firstUrl: "https://en.wikipedia.org/wiki/Austin", + firstUrlLabel: "Austin", + secondUrl: "https://en.wikipedia.org/wiki/VMware_Workstation", + secondUrlLabel: "Workstation", + }, + { + position: 31, + name: "FOCUS-SVR-02258", + features: ["remote-access-vpn-tunnel", "patch-manager01"], + status: "Active", + checks: { + icon: "status_up", + num: 25, + }, + "cpu-load": 86, + firstUrl: "https://en.wikipedia.org/wiki/Brno", + firstUrlLabel: "Brno", + secondUrl: "https://en.wikipedia.org/wiki/VMware_Workstation", + secondUrlLabel: "Workstation", + }, + { + position: 32, + name: "FOCUS-SVR-03312", + features: ["tools", "database", "orion-ape-backup"], + status: "Active", + checks: { + icon: "status_critical", + num: 25, + }, + "cpu-load": 47, + firstUrl: "https://en.wikipedia.org/wiki/Brno", + firstUrlLabel: "Brno", + secondUrl: "https://en.wikipedia.org/wiki/VMware_Workstation", + secondUrlLabel: "Workstation", + }, + { + position: 33, + name: "FOCUS-SVR-02258", + features: [ + "remote-access-vpn-tunnel", + "database", + "orion-ape-backup", + "patch-manager01", + ], + status: "Active", + checks: { + icon: "status_down", + num: 25, + }, + "cpu-load": 53, + firstUrl: "https://en.wikipedia.org/wiki/Kyiv", + firstUrlLabel: "Kyiv", + secondUrl: "https://en.wikipedia.org/wiki/VMware_Workstation", + secondUrlLabel: "Workstation", + }, + { + position: 34, + name: "Man-LT-JYJ4AD5", + features: [ + "remote-access-vpn-tunnel", + "tools", + "database", + "orion-ape-backup", + ], + status: "Active", + checks: { + icon: "status_up", + num: 25, + }, + "cpu-load": 32, + firstUrl: "https://en.wikipedia.org/wiki/Kyiv", + firstUrlLabel: "Kyiv", + secondUrl: "https://en.wikipedia.org/wiki/VirtualBox", + secondUrlLabel: "VirtualBox", + }, + { + position: 35, + name: "Man-LT-JYJ425", + features: [ + "remote-access-vpn-tunnel", + "tools", + "database", + "orion-ape-backup", + ], + status: "Active", + checks: { + icon: "status_up", + num: 25, + }, + "cpu-load": 22, + firstUrl: "https://en.wikipedia.org/wiki/Kyiv", + firstUrlLabel: "Kyiv", + secondUrl: "https://en.wikipedia.org/wiki/VirtualBox", + secondUrlLabel: "VirtualBox", + }, + { + position: 36, + name: "Man-LT-JYJ4333", + features: [ + "remote-access-vpn-tunnel", + "tools", + "database", + "orion-ape-backup", + ], + status: "Active", + checks: { + icon: "status_up", + num: 25, + }, + "cpu-load": 12, + firstUrl: "https://en.wikipedia.org/wiki/Kyiv", + firstUrlLabel: "Kyiv", + secondUrl: "https://en.wikipedia.org/wiki/VirtualBox", + secondUrlLabel: "VirtualBox", + }, + { + position: 37, + name: "FOCUS-SVR-02258", + features: ["remote-access-vpn-tunnel", "patch-manager01"], + status: "Active", + checks: { + icon: "status_up", + num: 25, + }, + "cpu-load": 86, + firstUrl: "https://en.wikipedia.org/wiki/Austin", + firstUrlLabel: "Austin", + secondUrl: "https://en.wikipedia.org/wiki/VMware_Workstation", + secondUrlLabel: "Workstation", + }, + { + position: 38, + name: "Man-LT-JYJ4AD5", + features: [ + "remote-access-vpn-tunnel", + "tools", + "database", + "orion-ape-backup", + "patch-manager01", + ], + status: "Active", + checks: { + icon: "status_inactive", + num: 25, + }, + "cpu-load": 35, + firstUrl: "https://en.wikipedia.org/wiki/Austin", + firstUrlLabel: "Austin", + secondUrl: "https://en.wikipedia.org/wiki/VMware_Workstation", + secondUrlLabel: "Workstation", + }, + { + position: 39, + name: "Man-LT-JYJ4AD5", + features: [ + "remote-access-vpn-tunnel", + "tools", + "database", + "patch-manager01", + ], + status: "Active", + checks: { + icon: "status_up", + num: 25, + }, + "cpu-load": 32, + firstUrl: "https://en.wikipedia.org/wiki/Brno", + firstUrlLabel: "Brno", + secondUrl: "https://en.wikipedia.org/wiki/VMware_Workstation", + secondUrlLabel: "Workstation", + }, + { + position: 40, + name: "Man-LT-JYJ4AD5", + features: [ + "remote-access-vpn-tunnel", + "database", + "orion-ape-backup", + "patch-manager01", + ], + status: "Active", + checks: { + icon: "status_up", + num: 25, + }, + "cpu-load": 64, + firstUrl: "https://en.wikipedia.org/wiki/Kyiv", + firstUrlLabel: "Kyiv", + secondUrl: "https://en.wikipedia.org/wiki/VMware_Workstation", + secondUrlLabel: "Workstation", + }, + { + position: 41, + name: "Man-LT-111", + features: [], + status: "Active", + checks: { + icon: "status_external", + num: 25, + }, + "cpu-load": 55, + firstUrl: "https://en.wikipedia.org/wiki/Brno", + firstUrlLabel: "Brno", + secondUrl: "https://en.wikipedia.org/wiki/VMware_Workstation", + secondUrlLabel: "Workstation", + }, + { + position: 42, + name: "Man-LT-2222", + features: [ + "remote-access-vpn-tunnel", + "tools", + "database", + "orion-ape-backup", + "patch-manager01", + ], + status: "Active", + checks: { + icon: "status_inactive", + num: 25, + }, + "cpu-load": 34, + firstUrl: "https://en.wikipedia.org/wiki/Brno", + firstUrlLabel: "Brno", + secondUrl: "https://en.wikipedia.org/wiki/VMware_Workstation", + secondUrlLabel: "Workstation", + }, + { + position: 43, + name: "Man-LT-333333", + features: [ + "remote-access-vpn-tunnel", + "tools", + "database", + "patch-manager01", + ], + status: "Active", + checks: { + icon: "status_up", + num: 25, + }, + "cpu-load": 56, + firstUrl: "https://en.wikipedia.org/wiki/Kyiv", + firstUrlLabel: "Kyiv", + secondUrl: "https://en.wikipedia.org/wiki/VMware_Workstation", + secondUrlLabel: "Workstation", + }, + { + position: 44, + name: "Man-LT-444444", + features: [ + "remote-access-vpn-tunnel", + "database", + "orion-ape-backup", + "patch-manager01", + ], + status: "Active", + checks: { + icon: "status_up", + num: 25, + }, + "cpu-load": 26, + firstUrl: "https://en.wikipedia.org/wiki/Kyiv", + firstUrlLabel: "Kyiv", + secondUrl: "https://en.wikipedia.org/wiki/VMware_Workstation", + secondUrlLabel: "Workstation", + }, + { + position: 45, + name: "Man-LT-555555", + features: [ + "remote-access-vpn-tunnel", + "database", + "orion-ape-backup", + "patch-manager01", + ], + status: "Active", + checks: { + icon: "status_up", + num: 25, + }, + "cpu-load": 76, + firstUrl: "https://en.wikipedia.org/wiki/Austin", + firstUrlLabel: "Austin", + secondUrl: "https://en.wikipedia.org/wiki/VMware_Workstation", + secondUrlLabel: "Workstation", + }, +]; +\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\`, + "overview/hero/widget-configs/kpi.ts": \\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\`// © 2022 SolarWinds Worldwide, LLC. All rights reserved. +// +// Permission is hereby granted, free of charge, to any person obtaining a copy +// of this software and associated documentation files (the "Software"), to +// deal in the Software without restriction, including without limitation the +// rights to use, copy, modify, merge, publish, distribute, sublicense, and/or +// sell copies of the Software, and to permit persons to whom the Software is +// furnished to do so, subject to the following conditions: +// +// The above copyright notice and this permission notice shall be included in +// all copies or substantial portions of the Software. +// +// THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR +// IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, +// FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE +// AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER +// LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, +// OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN +// THE SOFTWARE. + +import { + DEFAULT_PIZZAGNA_ROOT, + IProviderConfiguration, + IRefresherProperties, + IWidget, + KpiComponent, + NOVA_KPI_DATASOURCE_ADAPTER, + PizzagnaLayer, + WellKnownProviders, +} from "@nova-ui/dashboards"; + +import { + HarryPotterAverageRatingDataSource, + HarryPotterRatingsCountDataSource, +} from "../data/kpi-datasources"; + +export const kpiConfig: IWidget = { + id: "kpiWidgetId", + type: "kpi", + pizzagna: { + [PizzagnaLayer.Configuration]: { + [DEFAULT_PIZZAGNA_ROOT]: { + providers: { + [WellKnownProviders.Refresher]: { + properties: { + // Configuring the refresher interval so that our data source is invoked every ten minutes + interval: 60 * 10, + enabled: true, + } as IRefresherProperties, + } as Partial, + }, + }, + header: { + properties: { + title: "Harry Potter and the Sorcerer's Stone", + subtitle: "By J. K. Rowling", + }, + }, + tiles: { + properties: { + nodes: ["kpi1", "kpi2"], + }, + }, + kpi1: { + id: "kpi1", + componentType: KpiComponent.lateLoadKey, + properties: { + widgetData: { + label: "Average Rating", + backgroundColor: "var(--nui-color-chart-three)", + units: "out of 5 Stars", + }, + }, + providers: { + [WellKnownProviders.DataSource]: { + // Setting the data source providerId for the tile with id "kpi1" + providerId: + HarryPotterAverageRatingDataSource.providerId, + } as IProviderConfiguration, + [WellKnownProviders.Adapter]: { + providerId: NOVA_KPI_DATASOURCE_ADAPTER, + properties: { + componentId: "kpi1", + propertyPath: "widgetData", + }, + } as IProviderConfiguration, + }, + }, + kpi2: { + id: "kpi2", + componentType: KpiComponent.lateLoadKey, + properties: { + widgetData: { + label: "Reader Feedback", + units: "Ratings", + }, + }, + providers: { + [WellKnownProviders.DataSource]: { + // Setting the data source providerId for the tile with id "kpi" + providerId: + HarryPotterRatingsCountDataSource.providerId, + } as IProviderConfiguration, + [WellKnownProviders.Adapter]: { + providerId: NOVA_KPI_DATASOURCE_ADAPTER, + properties: { + componentId: "kpi2", + propertyPath: "widgetData", + }, + } as IProviderConfiguration, + }, + }, + }, + }, +}; +\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\`, + "overview/hero/widget-configs/proportional.ts": \\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\`// © 2022 SolarWinds Worldwide, LLC. All rights reserved. +// +// Permission is hereby granted, free of charge, to any person obtaining a copy +// of this software and associated documentation files (the "Software"), to +// deal in the Software without restriction, including without limitation the +// rights to use, copy, modify, merge, publish, distribute, sublicense, and/or +// sell copies of the Software, and to permit persons to whom the Software is +// furnished to do so, subject to the following conditions: +// +// The above copyright notice and this permission notice shall be included in +// all copies or substantial portions of the Software. +// +// THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR +// IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, +// FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE +// AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER +// LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, +// OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN +// THE SOFTWARE. + +import { + DEFAULT_PIZZAGNA_ROOT, + IProportionalWidgetChartOptions, + IProviderConfiguration, + IWidget, + LegendPlacement, + PizzagnaLayer, + ProportionalWidgetChartTypes, + WellKnownProviders, +} from "@nova-ui/dashboards"; + +import { BeerReviewCountsByCityMockDataSource } from "../data/proportional-datasources"; + +export const proportionalConfig: IWidget = { + id: "proportionalWidgetId", + type: "proportional", + pizzagna: { + [PizzagnaLayer.Configuration]: { + [DEFAULT_PIZZAGNA_ROOT]: { + providers: { + [WellKnownProviders.Refresher]: { + properties: { + interval: 0, + }, + }, + }, + }, + header: { + properties: { + title: "Beer Review Tally by City", + subtitle: "These People Love Beer", + }, + }, + chart: { + providers: { + [WellKnownProviders.DataSource]: { + providerId: + BeerReviewCountsByCityMockDataSource.providerId, + } as IProviderConfiguration, + }, + properties: { + configuration: { + chartOptions: { + type: ProportionalWidgetChartTypes.DonutChart, + legendPlacement: LegendPlacement.Right, + } as IProportionalWidgetChartOptions, + }, + }, + }, + }, + }, +}; +\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\`, + "overview/hero/widget-configs/risk-score.ts": \\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\`// © 2023 SolarWinds Worldwide, LLC. All rights reserved. +// +// Permission is hereby granted, free of charge, to any person obtaining a copy +// of this software and associated documentation files (the "Software"), to +// deal in the Software without restriction, including without limitation the +// rights to use, copy, modify, merge, publish, distribute, sublicense, and/or +// sell copies of the Software, and to permit persons to whom the Software is +// furnished to do so, subject to the following conditions: +// +// The above copyright notice and this permission notice shall be included in +// all copies or substantial portions of the Software. +// +// THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR +// IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, +// FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE +// AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER +// LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, +// OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN +// THE SOFTWARE. + +import { + DEFAULT_PIZZAGNA_ROOT, + IProviderConfiguration, + IRefresherProperties, + IWidget, + RiskScoreTileComponent, + NOVA_KPI_DATASOURCE_ADAPTER, + PizzagnaLayer, + WellKnownProviders, +} from "@nova-ui/dashboards"; + +import { HarryPotterAverageRatingDataSource } from "../data/kpi-datasources"; + +export const riskScoreConfig: IWidget = { + id: "riskScoreWidgetId", + type: "risk-score", + pizzagna: { + [PizzagnaLayer.Configuration]: { + [DEFAULT_PIZZAGNA_ROOT]: { + providers: { + [WellKnownProviders.Refresher]: { + properties: { + // Configuring the refresher interval so that our data source is invoked every ten minutes + interval: 60 * 10, + enabled: true, + } as IRefresherProperties, + } as Partial, + }, + }, + header: { + properties: { + title: "Harry Potter and the Sorcerer's Stone", + subtitle: "By J. K. Rowling", + }, + }, + tiles: { + properties: { + nodes: ["riskScore1"], + }, + }, + riskScore1: { + id: "riskScore1", + componentType: RiskScoreTileComponent.lateLoadKey, + properties: { + widgetData: { + minValue: 0, + maxValue: 5, + useStaticLabel: false, + staticLabel: undefined, + label: \\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\`Average Rating\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\`, + description: \\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\`Harry Potter and the Sorcerer's Stone By J. K. Rowling Average Rating Risk Score\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\`, + }, + }, + providers: { + [WellKnownProviders.DataSource]: { + // Setting the data source providerId for the tile with id "riskScore1" + providerId: + HarryPotterAverageRatingDataSource.providerId, + } as IProviderConfiguration, + [WellKnownProviders.Adapter]: { + providerId: NOVA_KPI_DATASOURCE_ADAPTER, + properties: { + componentId: "riskScore1", + propertyPath: "widgetData", + }, + } as IProviderConfiguration, + }, + }, + }, + }, +}; +\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\`, + "overview/hero/widget-configs/table.ts": \\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\`// © 2022 SolarWinds Worldwide, LLC. All rights reserved. +// +// Permission is hereby granted, free of charge, to any person obtaining a copy +// of this software and associated documentation files (the "Software"), to +// deal in the Software without restriction, including without limitation the +// rights to use, copy, modify, merge, publish, distribute, sublicense, and/or +// sell copies of the Software, and to permit persons to whom the Software is +// furnished to do so, subject to the following conditions: +// +// The above copyright notice and this permission notice shall be included in +// all copies or substantial portions of the Software. +// +// THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR +// IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, +// FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE +// AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER +// LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, +// OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN +// THE SOFTWARE. + +import { + ITableWidgetColumnConfig, + ITableWidgetSorterConfig, + IWidget, + PizzagnaLayer, + RawFormatterComponent, + WellKnownProviders, +} from "@nova-ui/dashboards"; + +import { BeerDataSource } from "../data/table/beer-data-source"; + +export const tableConfig: IWidget = { + id: "tableWidgetId", + type: "table", + pizzagna: { + [PizzagnaLayer.Configuration]: { + header: { + properties: { + title: "Stupendous Suds", + subtitle: "Try These Brilliant Brews", + }, + }, + table: { + providers: { + [WellKnownProviders.DataSource]: { + providerId: BeerDataSource.providerId, + }, + }, + properties: { + configuration: { + columns: [ + { + id: "column1", + label: "Beer Name", + isActive: true, + width: 185, + formatter: { + componentType: + RawFormatterComponent.lateLoadKey, + properties: { + dataFieldIds: { + value: "name", + }, + }, + }, + }, + { + id: "column2", + label: "Tagline", + isActive: true, + width: 250, + formatter: { + componentType: + RawFormatterComponent.lateLoadKey, + properties: { + dataFieldIds: { + value: "tagline", + }, + }, + }, + }, + { + id: "column3", + label: "First Brewed", + isActive: true, + formatter: { + componentType: + RawFormatterComponent.lateLoadKey, + properties: { + dataFieldIds: { + value: "first_brewed", + }, + }, + }, + }, + { + id: "column4", + label: "Description", + isActive: true, + width: 275, + formatter: { + componentType: + RawFormatterComponent.lateLoadKey, + properties: { + dataFieldIds: { + value: "description", + }, + }, + }, + }, + ] as ITableWidgetColumnConfig[], + sorterConfiguration: { + descendantSorting: false, + sortBy: "", + } as ITableWidgetSorterConfig, + hasVirtualScroll: true, + }, + }, + }, + }, + }, +}; +\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\`, + "overview/hero/widget-configs/timeseries.ts": \\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\`// © 2022 SolarWinds Worldwide, LLC. All rights reserved. +// +// Permission is hereby granted, free of charge, to any person obtaining a copy +// of this software and associated documentation files (the "Software"), to +// deal in the Software without restriction, including without limitation the +// rights to use, copy, modify, merge, publish, distribute, sublicense, and/or +// sell copies of the Software, and to permit persons to whom the Software is +// furnished to do so, subject to the following conditions: +// +// The above copyright notice and this permission notice shall be included in +// all copies or substantial portions of the Software. +// +// THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR +// IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, +// FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE +// AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER +// LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, +// OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN +// THE SOFTWARE. + +import moment from "moment/moment"; + +import { + DEFAULT_PIZZAGNA_ROOT, + IProviderConfiguration, + ISerializableTimeframe, + ITimeseriesItemConfiguration, + IWidget, + LegendPlacement, + WellKnownProviders, +} from "@nova-ui/dashboards"; + +import { BeerVsReadingMockDataSource } from "../data/timeseries-data-sources"; + +export const timeseriesConfig: IWidget = { + id: "timeseriesWidgetId", + type: "timeseries", + pizzagna: { + configuration: { + [DEFAULT_PIZZAGNA_ROOT]: { + providers: { + [WellKnownProviders.DataSource]: { + providerId: BeerVsReadingMockDataSource.providerId, + } as IProviderConfiguration, + }, + }, + header: { + properties: { + title: "Primary Leisure Activity Over Time", + subtitle: "Survey of 1000 Solarians", + }, + }, + chart: { + providers: { + [WellKnownProviders.Adapter]: { + properties: { + series: [ + { + id: "series-1", + label: "Beer Tasting", + selectedSeriesId: "series-1", + }, + { + id: "series-2", + label: "Reading", + selectedSeriesId: "series-2", + }, + ] as ITimeseriesItemConfiguration[], + }, + } as Partial, + }, + properties: { + configuration: { + legendPlacement: LegendPlacement.Right, + enableZoom: true, + leftAxisLabel: "Solarians (%)", + }, + }, + }, + timeframeSelection: { + properties: { + timeframe: { + selectedPresetId: "last7Days", + } as ISerializableTimeframe, + minDate: moment().subtract(10, "days").format(), + maxDate: moment().format(), + }, + }, + }, + }, +}; +\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\`, + "overview/overview-docs.component.ts": \\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\`// © 2022 SolarWinds Worldwide, LLC. All rights reserved. +// +// Permission is hereby granted, free of charge, to any person obtaining a copy +// of this software and associated documentation files (the "Software"), to +// deal in the Software without restriction, including without limitation the +// rights to use, copy, modify, merge, publish, distribute, sublicense, and/or +// sell copies of the Software, and to permit persons to whom the Software is +// furnished to do so, subject to the following conditions: +// +// The above copyright notice and this permission notice shall be included in +// all copies or substantial portions of the Software. +// +// THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR +// IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, +// FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE +// AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER +// LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, +// OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN +// THE SOFTWARE. + +import { Component } from "@angular/core"; + +@Component({ + selector: "nui-dashboard-overview-docs", + templateUrl: "./overview-docs.component.html", + standalone: false, +}) +export class OverviewDocsComponent {} +\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\`, + "overview/overview.module.ts": \\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\`// © 2022 SolarWinds Worldwide, LLC. All rights reserved. +// +// Permission is hereby granted, free of charge, to any person obtaining a copy +// of this software and associated documentation files (the "Software"), to +// deal in the Software without restriction, including without limitation the +// rights to use, copy, modify, merge, publish, distribute, sublicense, and/or +// sell copies of the Software, and to permit persons to whom the Software is +// furnished to do so, subject to the following conditions: +// +// The above copyright notice and this permission notice shall be included in +// all copies or substantial portions of the Software. +// +// THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR +// IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, +// FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE +// AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER +// LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, +// OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN +// THE SOFTWARE. + +import { CommonModule } from "@angular/common"; +import { NgModule } from "@angular/core"; +import { RouterModule } from "@angular/router"; + +import { + NuiBusyModule, + NuiButtonModule, + NuiDocsModule, + NuiIconModule, + NuiMessageModule, + NuiSwitchModule, +} from "@nova-ui/bits"; +import { + ConfiguratorHeadingService, + IFormatterDefinition, + LinkFormatterComponent, + NuiDashboardsModule, + WellKnownPathKey, + WidgetTypesService, +} from "@nova-ui/dashboards"; + +import { HeroDashboardComponent } from "./hero/dashboard/hero-dashboard.component"; +import { + HarryPotterAverageRatingDataSource, + HarryPotterRatingsCountDataSource, +} from "./hero/data/kpi-datasources"; +import { + BeerReviewCountsByCityMockDataSource, + BeerReviewCountsByCityMockDataSource2, +} from "./hero/data/proportional-datasources"; +import { BeerDataSource } from "./hero/data/table/beer-data-source"; +import { RandomUserDataSource } from "./hero/data/table/random-user-data-source"; +import { + BeerVsReadingMockDataSource, + LoungingVsFrisbeeGolfMockDataSource, +} from "./hero/data/timeseries-data-sources"; +import { OverviewDocsComponent } from "./overview-docs.component"; + +const routes = [ + { + path: "", + component: OverviewDocsComponent, + data: { + srlc: { + hideIndicator: true, + }, + showThemeSwitcher: true, + }, + }, + { + path: "hero", + component: HeroDashboardComponent, + data: { + srlc: { + hideIndicator: true, + }, + }, + }, +]; + +@NgModule({ + imports: [ + CommonModule, + NuiDashboardsModule, + NuiBusyModule, + NuiButtonModule, + NuiDocsModule, + NuiMessageModule, + NuiSwitchModule, + NuiIconModule, + RouterModule.forChild(routes), + ], + declarations: [OverviewDocsComponent, HeroDashboardComponent], + providers: [ConfiguratorHeadingService], +}) +export default class OverviewModule { + constructor(private widgetTypesService: WidgetTypesService) { + this.setupDataSourceProviders(); + this.setupProportionalLegendFormatters(); + } + + private setupDataSourceProviders() { + this.setDataSourceProviders("table", [ + RandomUserDataSource.providerId, + BeerDataSource.providerId, + ]); + this.setDataSourceProviders("kpi", [ + HarryPotterAverageRatingDataSource.providerId, + HarryPotterRatingsCountDataSource.providerId, + ]); + this.setDataSourceProviders("risk-score", [ + HarryPotterAverageRatingDataSource.providerId, + HarryPotterRatingsCountDataSource.providerId, + ]); + this.setDataSourceProviders("proportional", [ + BeerReviewCountsByCityMockDataSource.providerId, + BeerReviewCountsByCityMockDataSource2.providerId, + ]); + this.setDataSourceProviders("timeseries", [ + BeerVsReadingMockDataSource.providerId, + LoungingVsFrisbeeGolfMockDataSource.providerId, + ]); + } + + private setDataSourceProviders(type: string, providers: string[]) { + const widgetTemplate = this.widgetTypesService.getWidgetType(type, 1); + this.widgetTypesService.setNode( + widgetTemplate, + "configurator", + WellKnownPathKey.DataSourceProviders, + providers + ); + } + + private setupProportionalLegendFormatters() { + const formatters: IFormatterDefinition[] = [ + { + componentType: LinkFormatterComponent.lateLoadKey, + label: $localize\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\`Link\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\`, + dataTypes: { + value: "label", + link: "link", + }, + }, + ]; + + const widgetTemplate = this.widgetTypesService.getWidgetType( + "proportional", + 1 + ); + this.widgetTypesService.setNode( + widgetTemplate, + "configurator", + WellKnownPathKey.Formatters, + formatters + ); + } +} +\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\`, + "tutorials/customization/configurator-section/custom-configurator-section/custom-configurator-section.example.component.ts": \\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\`// © 2022 SolarWinds Worldwide, LLC. All rights reserved. +// +// Permission is hereby granted, free of charge, to any person obtaining a copy +// of this software and associated documentation files (the "Software"), to +// deal in the Software without restriction, including without limitation the +// rights to use, copy, modify, merge, publish, distribute, sublicense, and/or +// sell copies of the Software, and to permit persons to whom the Software is +// furnished to do so, subject to the following conditions: +// +// The above copyright notice and this permission notice shall be included in +// all copies or substantial portions of the Software. +// +// THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR +// IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, +// FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE +// AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER +// LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, +// OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN +// THE SOFTWARE. + +import { HttpClient, HttpErrorResponse } from "@angular/common/http"; +import { + ChangeDetectionStrategy, + ChangeDetectorRef, + Component, + EventEmitter, + Injectable, + Input, + OnChanges, + OnDestroy, + OnInit, + Output, + SimpleChanges, +} from "@angular/core"; +import { FormBuilder, FormGroup, Validators } from "@angular/forms"; +import { GridsterConfig, GridsterItem } from "angular-gridster2"; +// eslint-disable-next-line import/no-deprecated +import { BehaviorSubject, combineLatest, Observable } from "rxjs"; +// eslint-disable-next-line import/no-deprecated +import { finalize, map, startWith } from "rxjs/operators"; + +import { DataSourceService, IFilteringOutputs } from "@nova-ui/bits"; +import { + ComponentRegistryService, + DATA_SOURCE, + DEFAULT_PIZZAGNA_ROOT, + IDashboard, + IHasChangeDetector, + IHasForm, + IKpiData, + IProviderConfiguration, + IRefresherProperties, + IWidget, + IWidgets, + KpiComponent, + NOVA_KPI_DATASOURCE_ADAPTER, + PizzagnaLayer, + ProviderRegistryService, + WellKnownPathKey, + WellKnownProviders, + WidgetTypesService, +} from "@nova-ui/dashboards"; + +/** + * A custom version of the KpiDescriptionConfigurationComponent provided by the dashboards framework. + * --- + * For this example, the existing background color selection functionality has been replaced by custom + * template content. + */ +@Component({ + selector: "custom-kpi-description-configuration", + template: \\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\` + + +
+
+ + + +
+ + +
+
+ Custom Content +
+
+ The default version of this configurator section + displays a background color selector here. +
+
+ + +
+ + + +
+
+
+ \\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\`, + styleUrls: ["./custom-configurator-section.example.component.less"], + changeDetection: ChangeDetectionStrategy.OnPush, + standalone: false, +}) +// Remember to declare this class in the parent module +export class CustomKpiDescriptionConfigurationComponent + implements OnInit, OnChanges, IHasChangeDetector, IHasForm +{ + // Ensure that the lateLoadKey value matches class name + public static lateLoadKey = "CustomKpiDescriptionConfigurationComponent"; + + @Input() componentId: string; + @Input() configurableUnits: boolean; + + @Input() label: string = ""; + @Input() units: string = ""; + + @Output() formReady = new EventEmitter(); + + public form: FormGroup; + public subtitle$: Observable; + + constructor( + public changeDetector: ChangeDetectorRef, + private formBuilder: FormBuilder + ) {} + + public ngOnInit(): void { + this.form = this.formBuilder.group({ + label: [this.label, [Validators.required]], + }); + + if (this.configurableUnits) { + this.form.addControl("units", this.formBuilder.control(this.units)); + } + + const label = this.form.get("label"); + // eslint-disable-next-line import/no-deprecated + const labelValue = label?.valueChanges.pipe(startWith(label?.value)); + + // eslint-disable-next-line import/no-deprecated + this.subtitle$ = combineLatest([ + labelValue?.pipe(map((t) => t || $localize\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\`no label\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\`)), + ]).pipe(map((labels) => labels.join(", "))); + + this.formReady.emit(this.form); + } + + public ngOnChanges(changes: SimpleChanges): void { + if (changes.label) { + this.form.patchValue({ label: changes.label.currentValue }); + } + if (changes.units) { + this.form.patchValue({ units: changes.units.currentValue }); + } + } +} + +/** + * A simple KPI data source to retrieve the average rating of Harry Potter and the Sorcerer's Stone (book) via googleapis + */ +@Injectable() +export class AverageRatingKpiDataSource + extends DataSourceService + implements OnDestroy +{ + // This is the ID we'll use to identify the provider + public static providerId = "AverageRatingKpiDataSource"; + + // Use this subject to communicate the data source's busy state + public busy = new BehaviorSubject(false); + + constructor(private http: HttpClient) { + super(); + } + + // In this example, getFilteredData is invoked every 10 minutes (Take a look at the refresher + // provider definition in the widget configuration below to see how the interval is set) + public async getFilteredData(): Promise { + this.busy.next(true); + return new Promise((resolve) => { + // *** Make a resource request to an external API (if needed) + this.http + .get("https://www.googleapis.com/books/v1/volumes/5MQFrgEACAAJ") + .pipe(finalize(() => this.busy.next(false))) + .subscribe({ + next: (data: any) => { + resolve({ + result: { + value: data.volumeInfo.averageRating, + }, + }); + }, + error: (error: HttpErrorResponse) => { + resolve({ + result: null, + error: { + type: error.status, + }, + }); + }, + }); + }); + } + + public ngOnDestroy(): void { + this.outputsSubject.complete(); + } +} + +/** + * A simple KPI data source to retrieve the ratings count of Harry Potter and the Sorcerer's Stone (book) via googleapis + */ +@Injectable() +export class RatingsCountKpiDataSource + extends DataSourceService + implements OnDestroy +{ + public static providerId = "RatingsCountKpiDataSource"; + + // Use this subject to communicate the data source's busy state + public busy = new BehaviorSubject(false); + + constructor(private http: HttpClient) { + super(); + } + + public async getFilteredData(): Promise { + this.busy.next(true); + return new Promise((resolve) => { + this.http + .get("https://www.googleapis.com/books/v1/volumes/5MQFrgEACAAJ") + .pipe(finalize(() => this.busy.next(false))) + .subscribe({ + next: (data: any) => { + resolve({ + result: { + value: data.volumeInfo.ratingsCount, + }, + }); + }, + error: (error: HttpErrorResponse) => { + resolve({ + result: null, + error: { + type: error.status, + }, + }); + }, + }); + }); + } + + public ngOnDestroy(): void { + this.outputsSubject.complete(); + } +} + +/** + * A component that instantiates the dashboard + */ +@Component({ + selector: "custom-configurator-section-example", + templateUrl: "./custom-configurator-section.example.component.html", + styleUrls: ["./custom-configurator-section.example.component.less"], + standalone: false, +}) +export class CustomConfiguratorSectionExampleComponent implements OnInit { + // This variable will hold all the data needed to define the layout and behavior of the widgets. + // Pass this to the dashboard component's dashboard input in the template. + public dashboard: IDashboard | undefined; + + // Angular gridster requires a configuration object even if it's empty. + // Pass this to the dashboard component's gridsterConfig input in the template. + public gridsterConfig: GridsterConfig = {}; + + // Boolean which dashboard takes in as an input if its true it allows you to move widgets around. + public editMode: boolean = false; + + constructor( + // WidgetTypesService provides the widget's necessary structure information + private widgetTypesService: WidgetTypesService, + + // In general, the ProviderRegistryService is used for making entities available for injection into dynamically loaded components. + private providerRegistry: ProviderRegistryService, + + // Inject the ComponentRegistryService to make our custom component available for late loading by the dashboards framework + private componentRegistry: ComponentRegistryService, + + private changeDetectorRef: ChangeDetectorRef + ) {} + + public ngOnInit(): void { + // Grab the widget's default template which will be needed as a parameter for setNode. + const widgetTemplate = this.widgetTypesService.getWidgetType("kpi", 1); + + // Replace the default KPI description configuration component with our custom one. + // Note: This could also be done in the parent module's constructor to give + // multiple dashboards access to the same custom configurator section. + this.widgetTypesService.setNode( + widgetTemplate, + "configurator", + WellKnownPathKey.TileDescriptionConfigComponentType, + CustomKpiDescriptionConfigurationComponent.lateLoadKey + ); + + // Register the custom configurator section with the component registry to make it available + // for late loading by the dashboards framework. + this.componentRegistry.registerByLateLoadKey( + CustomKpiDescriptionConfigurationComponent + ); + + // Register our data sources as dropdown options in the widget editor/configurator + // Note: This could also be done in the parent module's constructor so that + // multiple dashboards could have access to the same widget template modification. + this.widgetTypesService.setNode( + // This is the template we grabbed above with getWidgetType + widgetTemplate, + // We are setting the editor/configurator part of the widget template + "configurator", + // This indicates which node you are changing and we want to change + // the data source providers available for selection in the editor. + WellKnownPathKey.DataSourceProviders, + // We are setting the data sources available for selection in the editor + [ + AverageRatingKpiDataSource.providerId, + RatingsCountKpiDataSource.providerId, + ] + ); + + // Register the data sources available for injection into the KPI tiles. + // Note: Each tile of a KPI widget is assigned its own instance of a data source + this.providerRegistry.setProviders({ + [AverageRatingKpiDataSource.providerId]: { + provide: DATA_SOURCE, + useClass: AverageRatingKpiDataSource, + // Any dependencies that need to be injected into the provider must be listed here + deps: [HttpClient], + }, + [RatingsCountKpiDataSource.providerId]: { + provide: DATA_SOURCE, + useClass: RatingsCountKpiDataSource, + deps: [HttpClient], + }, + }); + + this.initializeDashboard(); + } + + public initializeDashboard(): void { + // We're using a static configuration object for this example (see widgetConfig at the bottom of the file), + // but this is where the widget's configuration could potentially be populated from a database + const kpiWidget = widgetConfig; + const widgetIndex: IWidgets = { + // Complete the KPI widget with information coming from its type definition + [kpiWidget.id]: + this.widgetTypesService.mergeWithWidgetType(kpiWidget), + }; + + // Setting the widget dimensions and position (this is for gridster) + const positions: Record = { + [kpiWidget.id]: { + cols: 4, + rows: 6, + y: 0, + x: 0, + }, + }; + + // Finally, assigning the variables we created above to the dashboard + this.dashboard = { + positions, + widgets: widgetIndex, + }; + } + + /** Used for restoring widgets state */ + public reInitializeDashboard(): void { + // destroys the components and their providers so the dashboard can re init data + this.dashboard = undefined; + this.changeDetectorRef.detectChanges(); + + this.initializeDashboard(); + } +} + +const widgetConfig: IWidget = { + id: "widget1", + type: "kpi", + pizzagna: { + [PizzagnaLayer.Configuration]: { + [DEFAULT_PIZZAGNA_ROOT]: { + providers: { + [WellKnownProviders.Refresher]: { + properties: { + // Configuring the refresher interval so that our data source is invoked every ten minutes + interval: 60 * 10, + enabled: true, + } as IRefresherProperties, + } as Partial, + }, + }, + header: { + properties: { + title: "Harry Potter and the Sorcerer's Stone", + subtitle: "By J. K. Rowling", + }, + }, + tiles: { + properties: { + nodes: ["kpi1"], + }, + }, + kpi1: { + id: "kpi1", + componentType: KpiComponent.lateLoadKey, + properties: { + widgetData: { + units: "out of 5 Stars", + label: "Average Rating", + }, + }, + providers: { + [WellKnownProviders.DataSource]: { + // Setting the data source providerId for the tile with id "kpi1" + providerId: AverageRatingKpiDataSource.providerId, + } as IProviderConfiguration, + [WellKnownProviders.Adapter]: { + providerId: NOVA_KPI_DATASOURCE_ADAPTER, + properties: { + componentId: "kpi1", + propertyPath: "widgetData", + }, + } as IProviderConfiguration, + }, + }, + }, + }, +}; +\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\`, + "tutorials/customization/configurator-section/custom-configurator-section-docs.component.ts": \\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\`// © 2022 SolarWinds Worldwide, LLC. All rights reserved. +// +// Permission is hereby granted, free of charge, to any person obtaining a copy +// of this software and associated documentation files (the "Software"), to +// deal in the Software without restriction, including without limitation the +// rights to use, copy, modify, merge, publish, distribute, sublicense, and/or +// sell copies of the Software, and to permit persons to whom the Software is +// furnished to do so, subject to the following conditions: +// +// The above copyright notice and this permission notice shall be included in +// all copies or substantial portions of the Software. +// +// THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR +// IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, +// FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE +// AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER +// LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, +// OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN +// THE SOFTWARE. + +import { Component } from "@angular/core"; + +@Component({ + selector: "custom-configurator-section-docs", + templateUrl: "./custom-configurator-section-docs.component.html", + standalone: false, +}) +export class CustomConfiguratorSectionDocsComponent {} +\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\`, + "tutorials/customization/configurator-section/custom-configurator-section.module.ts": \\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\`// © 2022 SolarWinds Worldwide, LLC. All rights reserved. +// +// Permission is hereby granted, free of charge, to any person obtaining a copy +// of this software and associated documentation files (the "Software"), to +// deal in the Software without restriction, including without limitation the +// rights to use, copy, modify, merge, publish, distribute, sublicense, and/or +// sell copies of the Software, and to permit persons to whom the Software is +// furnished to do so, subject to the following conditions: +// +// The above copyright notice and this permission notice shall be included in +// all copies or substantial portions of the Software. +// +// THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR +// IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, +// FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE +// AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER +// LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, +// OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN +// THE SOFTWARE. + +import { CommonModule } from "@angular/common"; +import { HttpClientModule } from "@angular/common/http"; +import { NgModule } from "@angular/core"; +import { ReactiveFormsModule } from "@angular/forms"; +import { RouterModule } from "@angular/router"; + +import { + NuiButtonModule, + NuiDocsModule, + NuiFormFieldModule, + NuiIconModule, + NuiMessageModule, + NuiSwitchModule, + NuiTextboxModule, + DEMO_PATH_TOKEN, +} from "@nova-ui/bits"; +import { + NuiDashboardConfiguratorModule, + NuiDashboardsModule, +} from "@nova-ui/dashboards"; + +import { + CustomConfiguratorSectionExampleComponent, + CustomKpiDescriptionConfigurationComponent, +} from "./custom-configurator-section/custom-configurator-section.example.component"; +import { CustomConfiguratorSectionDocsComponent } from "./custom-configurator-section-docs.component"; +import { getDemoFiles } from "../../../../../demo-files-factory"; + +const routes = [ + { + path: "", + component: CustomConfiguratorSectionDocsComponent, + data: { + srlc: { + hideIndicator: true, + }, + showThemeSwitcher: true, + }, + }, + { + path: "example", + component: CustomConfiguratorSectionExampleComponent, + data: { + srlc: { + hideIndicator: true, + }, + }, + }, +]; + +@NgModule({ + imports: [ + CommonModule, + ReactiveFormsModule, + HttpClientModule, + NuiDashboardsModule, + NuiDashboardConfiguratorModule, + NuiDocsModule, + NuiFormFieldModule, + NuiIconModule, + NuiMessageModule, + NuiSwitchModule, + NuiTextboxModule, + NuiButtonModule, + RouterModule.forChild(routes), + ], + declarations: [ + CustomConfiguratorSectionDocsComponent, + CustomKpiDescriptionConfigurationComponent, + CustomConfiguratorSectionExampleComponent, + ], + providers: [ + { + provide: DEMO_PATH_TOKEN, + useValue: getDemoFiles("configurator-section"), + }, + ], +}) +export default class CustomConfiguratorSectionModule {} +\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\`, + "tutorials/customization/customization.module.ts": \\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\`// © 2022 SolarWinds Worldwide, LLC. All rights reserved. +// +// Permission is hereby granted, free of charge, to any person obtaining a copy +// of this software and associated documentation files (the "Software"), to +// deal in the Software without restriction, including without limitation the +// rights to use, copy, modify, merge, publish, distribute, sublicense, and/or +// sell copies of the Software, and to permit persons to whom the Software is +// furnished to do so, subject to the following conditions: +// +// The above copyright notice and this permission notice shall be included in +// all copies or substantial portions of the Software. +// +// THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR +// IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, +// FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE +// AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER +// LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, +// OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN +// THE SOFTWARE. + +import { NgModule, Type } from "@angular/core"; +import { RouterModule, Routes } from "@angular/router"; + +import { ConfiguratorHeadingService } from "@nova-ui/dashboards"; + +enum CustomizationModuleRoute { + ConfiguratorSection = "configurator-section", + Widget = "widget", + Formatter = "formatter", + DataSourceConfigurator = "data-source-configurator", +} + +const routes: Routes = [ + { + path: CustomizationModuleRoute.ConfiguratorSection, + loadChildren: async () => + import( + "./configurator-section/custom-configurator-section.module" + ) as object as Promise>, + }, + { + path: CustomizationModuleRoute.Widget, + loadChildren: async () => + import("./widget/custom-widget.module") as object as Promise< + Type + >, + }, + { + path: CustomizationModuleRoute.Formatter, + loadChildren: async () => + import("./formatter/custom-formatter.module") as object as Promise< + Type + >, + }, + { + path: CustomizationModuleRoute.DataSourceConfigurator, + loadChildren: async () => + import( + "./data-source-configurator/custom-data-source-configurator.module" + ) as object as Promise>, + }, +]; + +@NgModule({ + imports: [RouterModule.forChild(routes)], + providers: [ConfiguratorHeadingService], +}) +export default class CustomizationModule {} +\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\`, + "tutorials/customization/data-source-configurator/custom-data-source-configurator-docs.component.ts": \\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\`// © 2022 SolarWinds Worldwide, LLC. All rights reserved. +// +// Permission is hereby granted, free of charge, to any person obtaining a copy +// of this software and associated documentation files (the "Software"), to +// deal in the Software without restriction, including without limitation the +// rights to use, copy, modify, merge, publish, distribute, sublicense, and/or +// sell copies of the Software, and to permit persons to whom the Software is +// furnished to do so, subject to the following conditions: +// +// The above copyright notice and this permission notice shall be included in +// all copies or substantial portions of the Software. +// +// THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR +// IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, +// FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE +// AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER +// LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, +// OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN +// THE SOFTWARE. + +import { Component } from "@angular/core"; + +@Component({ + selector: "nui-custom-data-source-configurator-docs", + templateUrl: "./custom-data-source-configurator-docs.component.html", + standalone: false, +}) +export class CustomDataSourceConfiguratorDocComponent {} +\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\`, + "tutorials/customization/data-source-configurator/custom-data-source-configurator.module.ts": \\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\`// © 2022 SolarWinds Worldwide, LLC. All rights reserved. +// +// Permission is hereby granted, free of charge, to any person obtaining a copy +// of this software and associated documentation files (the "Software"), to +// deal in the Software without restriction, including without limitation the +// rights to use, copy, modify, merge, publish, distribute, sublicense, and/or +// sell copies of the Software, and to permit persons to whom the Software is +// furnished to do so, subject to the following conditions: +// +// The above copyright notice and this permission notice shall be included in +// all copies or substantial portions of the Software. +// +// THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR +// IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, +// FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE +// AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER +// LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, +// OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN +// THE SOFTWARE. + +import { NgModule } from "@angular/core"; +import { ReactiveFormsModule } from "@angular/forms"; +import { RouterModule, Routes } from "@angular/router"; + +// eslint-disable-next-line max-len +import { + NuiButtonModule, + NuiDocsModule, + NuiFormFieldModule, + NuiIconModule, + NuiMessageModule, + NuiSelectV2Module, + NuiSwitchModule, + NuiTextboxModule, + NuiValidationMessageModule, + DEMO_PATH_TOKEN, +} from "@nova-ui/bits"; +import { + NuiDashboardConfiguratorModule, + NuiDashboardsModule, +} from "@nova-ui/dashboards"; + +import { CustomDataSourceConfiguratorDocComponent } from "./custom-data-source-configurator-docs.component"; +import { + CustomDataSourceConfiguratorExampleComponent, + HarryPotterDataSourceConfiguratorComponent, +} from "./example/custom-data-source-configurator-example.component"; +import { getDemoFiles } from "../../../../../demo-files-factory"; + +const routes: Routes = [ + { + path: "", + component: CustomDataSourceConfiguratorDocComponent, + data: { + srlc: { + hideIndicator: true, + }, + showThemeSwitcher: true, + }, + }, +]; + +@NgModule({ + imports: [ + RouterModule.forChild(routes), + NuiDocsModule, + NuiButtonModule, + NuiMessageModule, + NuiDashboardConfiguratorModule, + NuiDashboardsModule, + NuiFormFieldModule, + NuiTextboxModule, + NuiSwitchModule, + NuiSelectV2Module, + NuiValidationMessageModule, + NuiIconModule, + ReactiveFormsModule, + ], + declarations: [ + CustomDataSourceConfiguratorDocComponent, + CustomDataSourceConfiguratorExampleComponent, + HarryPotterDataSourceConfiguratorComponent, + ], + providers: [ + { + provide: DEMO_PATH_TOKEN, + useValue: getDemoFiles("data-source-configurator"), + }, + ], +}) +export default class CustomDataSourceConfiguratorModuleRoute {} +\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\`, + "tutorials/customization/data-source-configurator/example/custom-data-source-configurator-example.component.ts": \\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\`// © 2022 SolarWinds Worldwide, LLC. All rights reserved. +// +// Permission is hereby granted, free of charge, to any person obtaining a copy +// of this software and associated documentation files (the "Software"), to +// deal in the Software without restriction, including without limitation the +// rights to use, copy, modify, merge, publish, distribute, sublicense, and/or +// sell copies of the Software, and to permit persons to whom the Software is +// furnished to do so, subject to the following conditions: +// +// The above copyright notice and this permission notice shall be included in +// all copies or substantial portions of the Software. +// +// THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR +// IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, +// FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE +// AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER +// LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, +// OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN +// THE SOFTWARE. + +import { HttpClient, HttpErrorResponse } from "@angular/common/http"; +import { + ChangeDetectorRef, + Component, + Inject, + Injectable, + Injector, + OnDestroy, + OnInit, +} from "@angular/core"; +import { FormBuilder, Validators } from "@angular/forms"; +import { GridsterConfig, GridsterItem } from "angular-gridster2"; +import { BehaviorSubject } from "rxjs"; +import { finalize } from "rxjs/operators"; + +import { + DataSourceService, + EventBus, + IEvent, + IFilteringOutputs, + LoggerService, +} from "@nova-ui/bits"; +import { + ComponentRegistryService, + ConfiguratorHeadingService, + DataSourceConfigurationV2Component, + DATA_SOURCE, + DEFAULT_PIZZAGNA_ROOT, + IConfigurable, + IDashboard, + IKpiData, + IProperties, + IProviderConfiguration, + IRefresherProperties, + IWidget, + IWidgets, + KpiComponent, + NOVA_KPI_DATASOURCE_ADAPTER, + PizzagnaLayer, + PIZZAGNA_EVENT_BUS, + ProviderRegistryService, + WellKnownPathKey, + WellKnownProviders, + WidgetTypesService, +} from "@nova-ui/dashboards"; + +/** + * This component will serve as the data source accordion in the configurator. + */ +@Component({ + selector: "harry-potter-data-source-configurator", + styleUrls: ["./custom-data-source-configurator-example.component.less"], + template: \\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\` + +
+ +
+ Data Source +
+ Harry Potter Books +
+
+
+
+ + + + {{ book.title }} + + + +
+
+ + + + {{ metric.label }} + + + +
+
+ \\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\`, + standalone: false, +}) +@Injectable() +export class HarryPotterDataSourceConfiguratorComponent + extends DataSourceConfigurationV2Component + implements OnInit +{ + // This lateLoadKey allows the component to be able to be registered by the componentRegistry + public static lateLoadKey = "HarryPotterDataSourceConfiguratorComponent"; + + // Array of books that will populate the book select + public books = [ + { + id: "5MQFrgEACAAJ", + title: $localize\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\`Harry Potter and the Sorcerer's Stone\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\`, + }, + { + id: "5iTebBW-w7QC", + title: $localize\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\`Harry Potter and the Chamber of Secrets\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\`, + }, + ]; + + // Array of metrics that will populate the metric select + public metrics = [ + { + id: "averageRating", + label: $localize\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\`Average Rating\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\`, + }, + { + id: "ratingsCount", + label: $localize\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\`Ratings Count\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\`, + }, + ]; + + // These need to be injected because DataSourceConfigurationV2Component uses them + constructor( + changeDetector: ChangeDetectorRef, + configuratorHeading: ConfiguratorHeadingService, + formBuilder: FormBuilder, + providerRegistryService: ProviderRegistryService, + @Inject(PIZZAGNA_EVENT_BUS) eventBus: EventBus, + injector: Injector, + logger: LoggerService + ) { + super( + changeDetector, + configuratorHeading, + formBuilder, + providerRegistryService, + eventBus, + injector, + logger + ); + } + + // Overriding 'ngOnInit' to add custom controls to the 'properties' form group + public ngOnInit(): void { + super.ngOnInit(); + + // Overriding the 'properties' control on the form to create a form group that accommodates our custom properties + this.form.setControl( + "properties", + this.formBuilder.group({ + bookId: [this.properties?.bookId ?? "", Validators.required], + metric: [this.properties?.metric ?? "", Validators.required], + }) + ); + // The default data source control has a required validator we're removing that validator here since we aren't using it. + this.form.setControl("dataSource", this.formBuilder.control(null)); + // Here we set the providerId to our only data source so when a new tile gets created it will default to it. + this.form.get("providerId")?.setValue(AcmeKpiDataSource.providerId); + // Here we subscribe to the form and if there are any changes we invoke the data source + this.form.valueChanges.subscribe((value) => { + if (!value.providerId) { + return; + } + this.invokeDataSource(value); + }); + } +} + +/** + * A simple KPI data source to retrieve the average rating of Harry Potter and the Sorcerer's Stone (book) via googleapis + */ +@Injectable() +export class AcmeKpiDataSource + extends DataSourceService + implements OnDestroy, IConfigurable +{ + // This is the ID we'll use to identify the provider + public static providerId = "AcmeKpiDataSource"; + + // Use this subject to communicate the data source's busy state + public busy = new BehaviorSubject(false); + + public properties: IProperties; + + constructor(private http: HttpClient) { + super(); + } + + // This function MUST be implemented in order to receive property updates from our configurator + public updateConfiguration(properties: IProperties): void { + // Saving the properties because we will need it for this data source. + this.properties = properties; + } + + // In this example, getFilteredData is invoked every 10 minutes (Take a look at the refresher + // provider definition in the widget configuration below to see how the interval is set) + public async getFilteredData(): Promise { + // For loading indicator to show + this.busy.next(true); + return new Promise((resolve) => { + // *** Make a resource request to an external API (if needed) + this.http + .get( + \\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\`https://www.googleapis.com/books/v1/volumes/\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\${this.properties?.bookId}\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\` + ) + // For loading indicator to be hidden + .pipe(finalize(() => this.busy.next(false))) + .subscribe({ + next: (data: any) => { + resolve({ + result: { + value: data.volumeInfo[this.properties?.metric], + }, + }); + }, + error: (error: HttpErrorResponse) => { + resolve({ + result: null, + error: { + type: error.status, + }, + }); + }, + }); + }); + } + + public ngOnDestroy(): void { + this.outputsSubject.complete(); + } +} + +/** + * A component that instantiates the dashboard + */ +@Component({ + selector: "custom-data-source-configurator-example", + templateUrl: "./custom-data-source-configurator-example.component.html", + styleUrls: ["./custom-data-source-configurator-example.component.less"], + standalone: false, +}) +export class CustomDataSourceConfiguratorExampleComponent implements OnInit { + // This variable will hold all the data needed to define the layout and behavior of the widgets. + // Pass this to the dashboard component's dashboard input in the template. + public dashboard: IDashboard; + + // Angular gridster requires a configuration object even if it's empty. + // Pass this to the dashboard component's gridsterConfig input in the template. + public gridsterConfig: GridsterConfig = {}; + + // Boolean which dashboard takes in as an input if its true it allows you to move widgets around. + public editMode: boolean = false; + + constructor( + // WidgetTypesService provides the widget's necessary structure information + private widgetTypesService: WidgetTypesService, + + // In general, the ProviderRegistryService is used for making entities available for injection into dynamically loaded components. + private providerRegistry: ProviderRegistryService, + + // Inject the ComponentRegistryService to make our custom component available for late loading by the dashboards framework + private componentRegistry: ComponentRegistryService + ) {} + + public ngOnInit(): void { + // Registering the new data source configurator so it can be used. + this.componentRegistry.registerByLateLoadKey( + HarryPotterDataSourceConfiguratorComponent + ); + // Registering the data source for injection into the KPI tile. + // Note: Each tile of a KPI widget is assigned its own instance of the data source + this.providerRegistry.setProviders({ + [AcmeKpiDataSource.providerId]: { + provide: DATA_SOURCE, + useClass: AcmeKpiDataSource, + // Any dependencies that need to be injected into the provider must be listed here + deps: [HttpClient], + }, + }); + + const kpiWidgetTemplate = this.widgetTypesService.getWidgetType( + "kpi", + 1 + ); + + this.widgetTypesService.setNode( + // This is the template we grabbed above with getWidgetType + kpiWidgetTemplate, + // We are setting the editor/configurator part of the widget template + "configurator", + // This is the path to go to the data source config component type. + WellKnownPathKey.DataSourceConfigComponentType, + // We are changing it to use the component we just created above instead of the default. + HarryPotterDataSourceConfiguratorComponent.lateLoadKey + ); + + // We're using a static configuration object for this example, but this is where + // the widget's configuration could potentially be populated from a database + const kpiWidget = widgetConfig; + const widgetIndex: IWidgets = { + // Complete the KPI widget with information coming from its type definition + [kpiWidget.id]: + this.widgetTypesService.mergeWithWidgetType(kpiWidget), + }; + + // Setting the widget dimensions and position (this is for gridster) + const positions: Record = { + [kpiWidget.id]: { + cols: 4, + rows: 6, + y: 0, + x: 0, + }, + }; + + // Finally, assigning the variables we created above to the dashboard + this.dashboard = { + positions, + widgets: widgetIndex, + }; + } +} + +const widgetConfig: IWidget = { + id: "widget1", + type: "kpi", + pizzagna: { + [PizzagnaLayer.Configuration]: { + [DEFAULT_PIZZAGNA_ROOT]: { + providers: { + [WellKnownProviders.Refresher]: { + properties: { + // Configuring the refresher interval so that our data source is invoked every ten minutes + interval: 60 * 10, + enabled: true, + } as IRefresherProperties, + } as Partial, + }, + }, + header: { + properties: { + title: "Harry Potter and the Sorcerer's Stone", + subtitle: "By J. K. Rowling", + }, + }, + tiles: { + properties: { + nodes: ["kpi1"], + }, + }, + kpi1: { + id: "kpi1", + componentType: KpiComponent.lateLoadKey, + properties: { + widgetData: { + units: "out of 5 Stars", + label: "Average Rating", + }, + }, + providers: { + [WellKnownProviders.DataSource]: { + // Setting the data source providerId for the tile with id "kpi1" + providerId: AcmeKpiDataSource.providerId, + properties: { + bookId: "5MQFrgEACAAJ", + metric: "averageRating", + }, + } as IProviderConfiguration, + [WellKnownProviders.Adapter]: { + providerId: NOVA_KPI_DATASOURCE_ADAPTER, + properties: { + componentId: "kpi1", + propertyPath: "widgetData", + }, + } as IProviderConfiguration, + }, + }, + }, + }, +}; +\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\`, + "tutorials/customization/formatter/custom-formatter.module.ts": \\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\`// © 2022 SolarWinds Worldwide, LLC. All rights reserved. +// +// Permission is hereby granted, free of charge, to any person obtaining a copy +// of this software and associated documentation files (the "Software"), to +// deal in the Software without restriction, including without limitation the +// rights to use, copy, modify, merge, publish, distribute, sublicense, and/or +// sell copies of the Software, and to permit persons to whom the Software is +// furnished to do so, subject to the following conditions: +// +// The above copyright notice and this permission notice shall be included in +// all copies or substantial portions of the Software. +// +// THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR +// IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, +// FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE +// AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER +// LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, +// OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN +// THE SOFTWARE. + +import { NgModule } from "@angular/core"; +import { ReactiveFormsModule } from "@angular/forms"; +import { RouterModule, Routes } from "@angular/router"; + +// eslint-disable-next-line max-len +import { + NuiButtonModule, + NuiDocsModule, + NuiFormFieldModule, + NuiIconModule, + NuiMessageModule, + NuiSelectV2Module, + NuiSwitchModule, + NuiTextboxModule, + NuiValidationMessageModule, + DEMO_PATH_TOKEN, +} from "@nova-ui/bits"; +import { NuiDashboardsModule } from "@nova-ui/dashboards"; + +import { CustomDonutContentFormatterDocComponent } from "./donut-content-formatter-example/custom-donut-content-formatter-docs.component"; +import { + CustomDonutContentFormatterComponent, + CustomDonutContentFormatterConfiguratorComponent, + CustomDonutContentFormatterExampleComponent, +} from "./donut-content-formatter-example/custom-donut-content-formatter-example.component"; +import { CustomFormatterDocComponent } from "./formatter-example/custom-formatter-docs.component"; +import { + CustomFormatterComponent, + CustomFormatterConfiguratorComponent, + CustomFormatterExampleComponent, +} from "./formatter-example/custom-formatter-example.component"; +import { getDemoFiles } from "../../../../../demo-files-factory"; + +const routes: Routes = [ + { + path: "table-formatter", + component: CustomFormatterDocComponent, + data: { + srlc: { + hideIndicator: true, + }, + showThemeSwitcher: true, + }, + }, + { + path: "donut-content-formatter", + component: CustomDonutContentFormatterDocComponent, + data: { + srlc: { + hideIndicator: true, + }, + showThemeSwitcher: true, + }, + }, +]; + +@NgModule({ + imports: [ + RouterModule.forChild(routes), + NuiDocsModule, + NuiButtonModule, + NuiMessageModule, + NuiDashboardsModule, + NuiFormFieldModule, + NuiTextboxModule, + NuiSwitchModule, + NuiSelectV2Module, + NuiValidationMessageModule, + NuiIconModule, + ReactiveFormsModule, + ], + declarations: [ + CustomDonutContentFormatterComponent, + CustomDonutContentFormatterExampleComponent, + CustomDonutContentFormatterConfiguratorComponent, + CustomDonutContentFormatterDocComponent, + CustomFormatterDocComponent, + CustomFormatterExampleComponent, + CustomFormatterConfiguratorComponent, + CustomFormatterComponent, + ], + providers: [ + { + provide: DEMO_PATH_TOKEN, + useValue: getDemoFiles("formatter"), + }, + ], +}) +export default class CustomFormatterModuleRoute {} +\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\`, + "tutorials/customization/formatter/donut-content-formatter-example/custom-donut-content-formatter-docs.component.ts": \\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\`// © 2022 SolarWinds Worldwide, LLC. All rights reserved. +// +// Permission is hereby granted, free of charge, to any person obtaining a copy +// of this software and associated documentation files (the "Software"), to +// deal in the Software without restriction, including without limitation the +// rights to use, copy, modify, merge, publish, distribute, sublicense, and/or +// sell copies of the Software, and to permit persons to whom the Software is +// furnished to do so, subject to the following conditions: +// +// The above copyright notice and this permission notice shall be included in +// all copies or substantial portions of the Software. +// +// THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR +// IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, +// FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE +// AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER +// LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, +// OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN +// THE SOFTWARE. + +import { Component } from "@angular/core"; + +@Component({ + selector: "nui-custom-donut-content-formatter-docs", + templateUrl: "./custom-donut-content-formatter-docs.component.html", + standalone: false, +}) +export class CustomDonutContentFormatterDocComponent {} +\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\`, + "tutorials/customization/formatter/donut-content-formatter-example/custom-donut-content-formatter-example.component.ts": \\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\`// © 2022 SolarWinds Worldwide, LLC. All rights reserved. +// +// Permission is hereby granted, free of charge, to any person obtaining a copy +// of this software and associated documentation files (the "Software"), to +// deal in the Software without restriction, including without limitation the +// rights to use, copy, modify, merge, publish, distribute, sublicense, and/or +// sell copies of the Software, and to permit persons to whom the Software is +// furnished to do so, subject to the following conditions: +// +// The above copyright notice and this permission notice shall be included in +// all copies or substantial portions of the Software. +// +// THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR +// IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, +// FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE +// AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER +// LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, +// OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN +// THE SOFTWARE. + +import { HttpClient } from "@angular/common/http"; +import { + ChangeDetectorRef, + Component, + Injectable, + Input, + OnChanges, + OnDestroy, + OnInit, + SimpleChanges, +} from "@angular/core"; +import { FormBuilder, FormGroup, Validators } from "@angular/forms"; +import { GridsterConfig, GridsterItem } from "angular-gridster2"; +import { Subject } from "rxjs"; +import { takeUntil, tap } from "rxjs/operators"; + +import { + DataSourceService, + IconService, + IDataSource, + IFilteringOutputs, + LoggerService, +} from "@nova-ui/bits"; +import { + ChartAssist, + IAccessors, + IChartAssistEvent, + IChartAssistSeries, +} from "@nova-ui/charts"; +import { + ComponentRegistryService, + ConfiguratorHeadingService, + DATA_SOURCE, + DonutChartFormatterConfiguratorComponent, + DonutContentPercentageConfigurationComponent, + DonutContentPercentageFormatterComponent, + DonutContentSumFormatterComponent, + IDashboard, + IFormatterDefinition, + IHasChangeDetector, + IProperties, + IProportionalWidgetChartOptions, + IProportionalWidgetConfig, + IProviderConfiguration, + IWidget, + IWidgets, + LegendPlacement, + PizzagnaLayer, + ProportionalWidgetChartTypes, + ProviderRegistryService, + WellKnownPathKey, + WellKnownProviders, + WidgetTypesService, +} from "@nova-ui/dashboards"; + +export enum Units { + Days = "Day(s)", + Weeks = "Week(s)", + Hours = "Hour(s)", +} + +@Component({ + selector: "custom-donut-content-formatter", + host: { class: "d-flex flex-column align-items-center" }, + template: \\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\` +
+ + {{ chartMetric || properties?.currentMetric || data[0].id }} +
+
+ {{ chartContent }} +
+
+ {{ units }} +
+
\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\`, + styleUrls: ["./custom-donut-content-formatter-example.component.less"], + standalone: false, +}) +export class CustomDonutContentFormatterComponent + implements IHasChangeDetector, OnInit, OnChanges +{ + public static lateLoadKey = "CustomDonutContentFormatterComponent"; + + // Used to emphasize the chart series when user interacts either with the chart legend, or chart segments. + public emphasizedSeriesData: IChartAssistSeries | undefined; + + // Current raw value of the metric to display + public currentMetricData: number; + + // Metric value rendered inside the template, when user selects a metric, and gets automatically recalculated depending on selected units + public chartContent: number; + + // Metric value rendered inside the template, when user interacts with either chart legend, or chart segments + public chartMetric: number; + + // Units which user can select from the configuration + public units: Units = Units.Days; + + private readonly destroy$ = new Subject(); + + constructor(public changeDetector: ChangeDetectorRef) {} + + // The data we receive from the chart, including metrics names and their values + @Input() data: IChartAssistSeries[]; + + // We use this chart assist instance to subscribe to the events triggered when an interaction with the chart occurs + @Input() chartAssist: ChartAssist; + + // These are the current properties from pizzagna. Used to use data set at the configuration layer + @Input() properties: IProperties; + + public ngOnChanges(changes: SimpleChanges): void { + if (changes.properties || !this.properties) { + // If current metric is not in the list of metrics any more we fall back to the very first one from the list we get from the datasource + this.currentMetricData = + this.data.find( + (item) => item.id === this.properties?.currentMetric + )?.data[0] || this.data[0].data[0]; + + // We either take the selected value, or fall back to the preselected default one + this.units = this.properties?.units || this.units; + } + + this.setContentValue(); + } + + public ngOnInit(): void { + // Here 'chartAssistSubject' is the entity that emits events every time user interacts with either chart legend, or chart segments. + // Subscribing to properly react on these kind of events + this.chartAssist.chartAssistSubject + .pipe( + tap( + (data: IChartAssistEvent) => + (this.emphasizedSeriesData = this.data.find( + (item) => item.id === data.payload.seriesId + )) + ), + tap(() => this.setContentValue()), + tap(() => this.setMetricValue()), + takeUntil(this.destroy$) + ) + .subscribe(); + } + + public getConvertedData(emphData: number): number { + // Recalculating data depending on the units user selected from the configuration view + switch (this.units) { + case Units.Weeks: + return this.emphasizedSeriesData + ? this.convertToWeeks(emphData) + : this.convertToWeeks(this.currentMetricData); + + case Units.Hours: + return this.emphasizedSeriesData + ? this.convertToHours(emphData) + : this.convertToHours(this.currentMetricData); + + default: + return this.emphasizedSeriesData + ? emphData + : this.currentMetricData; + } + } + + public setContentValue(): void { + this.chartContent = this.getConvertedData( + this.emphasizedSeriesData?.data[0] + ); + } + + public setMetricValue(): void { + this.chartMetric = this.emphasizedSeriesData + ? this.data.find( + (item) => + this.getConvertedData(item.data[0]) === + this.getConvertedData(this.emphasizedSeriesData?.data[0]) + )?.id + : // if metric was not initially selected we fall back to the very first one + this.properties?.currentMetric || this.data[0].id; + } + + private convertToWeeks(days: number | undefined): number { + return days ? Number((days / 7).toFixed(2)) : 0; + } + + private convertToHours(days: number | undefined): number { + return days ? Number((days * 24).toFixed(2)) : 0; + } +} + +@Component({ + selector: "custom-donut-content-formatter-configurator", + styleUrls: ["./custom-donut-content-formatter-example.component.less"], + template: \\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\` +
+
+ + + + {{ itemValue?.name }} + + + + This field is required + + +
+
+ + + + {{ itemValue }} + + + + This field is required + + +
+
+ \\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\`, + standalone: false, +}) +export class CustomDonutContentFormatterConfiguratorComponent + extends DonutChartFormatterConfiguratorComponent + implements OnChanges, OnInit, IHasChangeDetector +{ + public static lateLoadKey = "CustomFormatterConfiguratorComponent"; + + constructor( + changeDetector: ChangeDetectorRef, + formBuilder: FormBuilder, + logger: LoggerService, + public iconService: IconService, + public configuratorHeading: ConfiguratorHeadingService + ) { + super(changeDetector, formBuilder, logger); + } + + public availableUnits: Units[] = [Units.Days, Units.Hours, Units.Weeks]; + + protected addCustomFormControls(form: FormGroup): void { + form.addControl( + "units", + this.formBuilder.control(Units.Days, Validators.required) + ); + } +} + +/** + * A component that instantiates the dashboard + */ +@Component({ + selector: "custom-donut-content-formatter-example", + templateUrl: "./custom-donut-content-formatter-example.component.html", + styleUrls: ["./custom-donut-content-formatter-example.component.less"], + standalone: false, +}) +export class CustomDonutContentFormatterExampleComponent implements OnInit { + public editMode: boolean = false; + // This variable will hold all the data needed to define the layout and behavior of the widgets. + // Pass this to the dashboard component's dashboard input in the template. + public dashboard: IDashboard | undefined; + + // Angular gridster requires a configuration object even if it's empty. + // Pass this to the dashboard component's gridsterConfig input in the template. + public gridsterConfig: GridsterConfig = {}; + + constructor( + // WidgetTypesService provides the widget's necessary structure information + private widgetTypesService: WidgetTypesService, + // In general, the ProviderRegistryService is used for making entities available for injection into dynamically loaded components. + private providerRegistry: ProviderRegistryService, + // Inject the ComponentRegistryService to make our custom component available for late loading by the dashboards framework + private componentRegistry: ComponentRegistryService, + private changeDetectorRef: ChangeDetectorRef + ) { + // Register the custom configurator component with the component registry to make it available + // for late loading by the dashboard framework. + this.componentRegistry.registerByLateLoadKey( + CustomDonutContentFormatterConfiguratorComponent + ); + // Register the custom formatter component with the component registry to make it available + // for late loading by the dashboard framework. + this.componentRegistry.registerByLateLoadKey( + CustomDonutContentFormatterComponent + ); + + // Grab the widget's default template which will be needed as a parameter for setNode below. + const proportional = this.widgetTypesService.getWidgetType( + "proportional", + 1 + ); + + const donutFormatters: IFormatterDefinition[] = [ + { + componentType: DonutContentSumFormatterComponent.lateLoadKey, + label: $localize\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\`Sum\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\`, + } as IFormatterDefinition, + { + componentType: + DonutContentPercentageFormatterComponent.lateLoadKey, + label: $localize\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\`Percentage\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\`, + configurationComponent: + DonutContentPercentageConfigurationComponent.lateLoadKey, + } as IFormatterDefinition, + { + componentType: CustomDonutContentFormatterComponent.lateLoadKey, + label: $localize\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\`Custom\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\`, + // This is a custom configurator that will pop up below the formatter once it gets selected + configurationComponent: + CustomDonutContentFormatterConfiguratorComponent.lateLoadKey, + } as IFormatterDefinition, + ]; + + this.widgetTypesService.setNode( + // This is the template we grabbed above with getWidgetType + proportional, + // We are setting the editor/configurator part of the widget template + "configurator", + // This indicates which node you are changing and we want to change the formatters available for selection in the editor. + WellKnownPathKey.Formatters, + // We are setting the available formatters with the array we created above. + donutFormatters + ); + + // This sets the donut chart's datasource to have the StatusesExampleDatasource so the drop down is filled similar to the line above. + this.widgetTypesService.setNode( + proportional, + "configurator", + WellKnownPathKey.DataSourceProviders, + [StatusesExampleDatasource.providerId] + ); + + // Registering the data source for injection into the widget. + this.providerRegistry.setProviders({ + [StatusesExampleDatasource.providerId]: { + provide: DATA_SOURCE, + useClass: StatusesExampleDatasource, + // Any dependencies that need to be injected into the provider must be listed here + deps: [], + }, + }); + } + + public ngOnInit(): void { + this.initializeDashboard(); + } + + /** Used for restoring widgets state */ + public reInitializeDashboard(): void { + // destroys the components and their providers so the dashboard can re init data + this.dashboard = undefined; + this.changeDetectorRef.detectChanges(); + + this.initializeDashboard(); + } + + public initializeDashboard(): void { + // We're using a static configuration object for this example, but this is where + // the widget's configuration could potentially be populated from a database + const proportionalWidget = widgetConfig; + const widgetIndex: IWidgets = { + // Enhance the widget with information coming from it's type definition + [proportionalWidget.id]: + this.widgetTypesService.mergeWithWidgetType(proportionalWidget), + }; + + // Setting the widget dimensions and position (this is for gridster) + const positions: Record = { + [proportionalWidget.id]: { + cols: 12, + rows: 6, + y: 0, + x: 0, + }, + }; + + // Finally, assigning the variables we created above to the dashboard + this.dashboard = { + positions, + widgets: widgetIndex, + }; + } +} + +export interface IStatusesWidgetData { + id: string; + name: string; + data: number[]; +} + +export const randomStatusesWidgetData: IStatusesWidgetData[] = [ + { + id: "Down", + name: "Down", + data: [Math.round(Math.random() * 100)], + }, + { + id: "Critical", + name: "Critical", + data: [Math.round(Math.random() * 100)], + }, + { + id: "Warning", + name: "Warning", + data: [Math.round(Math.random() * 100)], + }, + { + id: "Unknown", + name: "Unknown", + data: [Math.round(Math.random() * 100)], + }, + { + id: "Up", + name: "Up", + data: [Math.round(Math.random() * 100)], + }, + { + id: "Unmanaged", + name: "Unmanaged", + data: [Math.round(Math.random() * 100)], + }, +]; + +@Injectable() +export class StatusesExampleDatasource + extends DataSourceService + implements IDataSource, OnDestroy +{ + public static providerId = "StatusesExampleDatasource"; + + public busy = new Subject(); + + constructor(private http: HttpClient) { + super(); + } + + public async getFilteredData(): Promise { + this.busy.next(true); + + return new Promise((resolve) => { + setTimeout(() => { + resolve({ + result: randomStatusesWidgetData, + }); + this.busy.next(false); + }, 1000); + }); + } + + public ngOnDestroy(): void { + this.outputsSubject.complete(); + } +} + +export const widgetConfig: IWidget = { + id: "proportionalWidgetId", + type: "proportional", + pizzagna: { + [PizzagnaLayer.Configuration]: { + header: { + properties: { + title: "Proportional Widget!", + subtitle: "Proportional widget with legend formatters", + }, + }, + chart: { + providers: { + [WellKnownProviders.DataSource]: { + providerId: StatusesExampleDatasource.providerId, + } as IProviderConfiguration, + }, + properties: { + configuration: { + interactive: true, + chartOptions: { + type: ProportionalWidgetChartTypes.DonutChart, + legendPlacement: LegendPlacement.Right, + contentFormatter: { + componentType: + CustomDonutContentFormatterComponent.lateLoadKey, + properties: { + // here you can set the default value for the metric you receive. If not set the first one from the list will be taken + currentMetric: "Down", + // here you set the default value for your custom controls. If not set the first one from the list will be taken + units: Units.Weeks, + }, + }, + } as IProportionalWidgetChartOptions, + chartColors: [ + "var(--nui-color-chart-eight)", + "var(--nui-color-chart-nine)", + "var(--nui-color-chart-ten)", + ], + } as IProportionalWidgetConfig, + }, + }, + }, + }, +}; +\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\`, + "tutorials/customization/formatter/formatter-example/custom-formatter-docs.component.ts": \\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\`// © 2022 SolarWinds Worldwide, LLC. All rights reserved. +// +// Permission is hereby granted, free of charge, to any person obtaining a copy +// of this software and associated documentation files (the "Software"), to +// deal in the Software without restriction, including without limitation the +// rights to use, copy, modify, merge, publish, distribute, sublicense, and/or +// sell copies of the Software, and to permit persons to whom the Software is +// furnished to do so, subject to the following conditions: +// +// The above copyright notice and this permission notice shall be included in +// all copies or substantial portions of the Software. +// +// THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR +// IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, +// FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE +// AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER +// LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, +// OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN +// THE SOFTWARE. + +import { Component } from "@angular/core"; + +@Component({ + selector: "nui-custom-formatter-docs", + templateUrl: "./custom-formatter-docs.component.html", + standalone: false, +}) +export class CustomFormatterDocComponent {} +\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\`, + "tutorials/customization/formatter/formatter-example/custom-formatter-example.component.ts": \\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\`// © 2022 SolarWinds Worldwide, LLC. All rights reserved. +// +// Permission is hereby granted, free of charge, to any person obtaining a copy +// of this software and associated documentation files (the "Software"), to +// deal in the Software without restriction, including without limitation the +// rights to use, copy, modify, merge, publish, distribute, sublicense, and/or +// sell copies of the Software, and to permit persons to whom the Software is +// furnished to do so, subject to the following conditions: +// +// The above copyright notice and this permission notice shall be included in +// all copies or substantial portions of the Software. +// +// THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR +// IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, +// FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE +// AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER +// LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, +// OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN +// THE SOFTWARE. + +import { ListRange } from "@angular/cdk/collections"; +import { ChangeDetectorRef, Component, Input, OnInit } from "@angular/core"; +import { FormBuilder, FormGroup, Validators } from "@angular/forms"; +import { GridsterConfig, GridsterItem } from "angular-gridster2"; +import isEqual from "lodash/isEqual"; +import orderBy from "lodash/orderBy"; +import { BehaviorSubject } from "rxjs"; + +import { + DataSourceService, + IconService, + IDataField, + INovaFilteringOutputs, + INovaFilters, + ISorterFilter, + LoggerService, +} from "@nova-ui/bits"; +import { + ComponentRegistryService, + ConfiguratorHeadingService, + DATA_SOURCE, + FormatterConfiguratorComponent, + IDashboard, + IDataSourceOutput, + IFormatterDefinition, + IHasChangeDetector, + ITableWidgetColumnConfig, + ITableWidgetSorterConfig, + IWidget, + IWidgets, + PizzagnaLayer, + ProviderRegistryService, + RawFormatterComponent, + TableFormatterRegistryService, + WellKnownPathKey, + WellKnownProviders, + WidgetTypesService, +} from "@nova-ui/dashboards"; + +export const BREW_API_URL = "https://api.punkapi.com/v2/beers"; + +@Component({ + selector: "custom-formatter", + host: { class: "d-flex" }, + template: \\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\` +
+
+ +
+
+ {{ data.value }} +
+
+ \\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\`, + styleUrls: ["./custom-formatter-example.component.less"], + standalone: false, +}) +export class CustomFormatterComponent implements IHasChangeDetector { + public static lateLoadKey = "CustomFormatterComponent"; + + constructor(public changeDetector: ChangeDetectorRef) {} + + @Input() public data: any; + @Input() public icon: string; + @Input() public threshold: string; + + public isAboveThreshold(): boolean { + return parseFloat(this.threshold) <= this.data.value; + } +} + +@Component({ + selector: "custom-formatter-configurator", + styleUrls: ["./custom-formatter-example.component.less"], + template: \\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\` +
+
+ + + + {{ item.label }} + + + + This field is required + + +
+
+ + + + + + + + This field is required + + +
+
+ + + + + This field is required + + +
+
+ +
+
+ +
+ + +
+ + + Select Item + +
+ \\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\`, + standalone: false, +}) +export class CustomFormatterConfiguratorComponent + extends FormatterConfiguratorComponent + implements OnInit, IHasChangeDetector +{ + public static lateLoadKey = "CustomFormatterConfiguratorComponent"; + + constructor( + changeDetector: ChangeDetectorRef, + configuratorHeading: ConfiguratorHeadingService, + formBuilder: FormBuilder, + logger: LoggerService, + public iconService: IconService + ) { + super(changeDetector, configuratorHeading, formBuilder, logger); + } + + public formatterFormGroup: FormGroup; + // This array is where the icon names will be stored + public options: string[] = []; + + public ngOnInit(): void { + for (const icon of this.iconService.icons) { + if (icon.category === "severity") { + this.options.push(icon.name); + } + } + } + + protected addCustomFormControls(form: FormGroup): void { + form.addControl( + "icon", + this.formBuilder.control("", Validators.required) + ); + form.addControl( + "threshold", + this.formBuilder.control(null, Validators.required) + ); + } +} + +/** + * A component that instantiates the dashboard + */ +@Component({ + selector: "custom-formatter-example", + templateUrl: "./custom-formatter-example.component.html", + styleUrls: ["./custom-formatter-example.component.less"], + standalone: false, +}) +export class CustomFormatterExampleComponent implements OnInit { + public editMode: boolean = false; + // This variable will hold all the data needed to define the layout and behavior of the widgets. + // Pass this to the dashboard component's dashboard input in the template. + public dashboard: IDashboard | undefined; + + // Angular gridster requires a configuration object even if it's empty. + // Pass this to the dashboard component's gridsterConfig input in the template. + public gridsterConfig: GridsterConfig = {}; + + constructor( + // WidgetTypesService provides the widget's necessary structure information + private widgetTypesService: WidgetTypesService, + // In general, the ProviderRegistryService is used for making entities available for injection into dynamically loaded components. + private providerRegistry: ProviderRegistryService, + // Inject the ComponentRegistryService to make our custom component available for late loading by the dashboards framework + private componentRegistry: ComponentRegistryService, + private tableFormatterRegistryService: TableFormatterRegistryService, + private changeDetectorRef: ChangeDetectorRef + ) { + // Register the custom configurator component with the component registry to make it available + // for late loading by the dashboard framework. + this.componentRegistry.registerByLateLoadKey( + CustomFormatterConfiguratorComponent + ); + // Register the custom formatter component with the component registry to make it available + // for late loading by the dashboard framework. + this.componentRegistry.registerByLateLoadKey(CustomFormatterComponent); + + // Grab the widget's default template which will be needed as a parameter for setNode below. + const table = this.widgetTypesService.getWidgetType("table", 1); + + const tableFormatters: IFormatterDefinition[] = [ + { + // This will be the component that will format the data + componentType: RawFormatterComponent.lateLoadKey, + // This is the label for what the formatter is selected in the drop down + label: $localize\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\`:table formatter|:No formatter\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\`, + // This says what datatype the formatter supports. If the value node is null, it accepts any data type. + dataTypes: { + // @ts-ignore: Ignoring compiler error to keep the same flow + value: null, + }, + }, + { + componentType: CustomFormatterComponent.lateLoadKey, + label: $localize\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\`:table formatter|:Custom formatter\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\`, + // This is a custom configurator that will pop up below the formatter once it gets selected + configurationComponent: + CustomFormatterConfiguratorComponent.lateLoadKey, + // This says what data types the formatter supports. + // In this case, it supports abv values only. + // If you look below in the table data source you'll see where we define our column's data types. + dataTypes: { + value: ["abv"], + }, + }, + ]; + + // Registering the formatters + this.tableFormatterRegistryService.addItems(tableFormatters); + + // This sets the table's datasource to have the BeerDataSource so the drop down is filled similar to the line above. + this.widgetTypesService.setNode( + table, + "configurator", + WellKnownPathKey.DataSourceProviders, + [BeerDataSource.providerId] + ); + + // Registering the data source for injection into the widget. + this.providerRegistry.setProviders({ + [BeerDataSource.providerId]: { + provide: DATA_SOURCE, + useClass: BeerDataSource, + // Any dependencies that need to be injected into the provider must be listed here + deps: [], + }, + }); + } + + public ngOnInit(): void { + this.initializeDashboard(); + } + + /** Used for restoring widgets state */ + public reInitializeDashboard(): void { + // destroys the components and their providers so the dashboard can re init data + this.dashboard = undefined; + this.changeDetectorRef.detectChanges(); + + this.initializeDashboard(); + } + + public initializeDashboard(): void { + // We're using a static configuration object for this example, but this is where + // the widget's configuration could potentially be populated from a database + const tableWidget = widgetConfig; + const widgetIndex: IWidgets = { + // Enhance the widget with information coming from it's type definition + [tableWidget.id]: + this.widgetTypesService.mergeWithWidgetType(tableWidget), + }; + + // Setting the widget dimensions and position (this is for gridster) + const positions: Record = { + [tableWidget.id]: { + cols: 12, + rows: 6, + y: 0, + x: 0, + }, + }; + + // Finally, assigning the variables we created above to the dashboard + this.dashboard = { + positions, + widgets: widgetIndex, + }; + } +} + +export interface IBrewInfo { + id: number; + name: string; + tagline: string; + first_brewed: string; + description: string; + brewers_tips: string; + abv: number; +} + +export interface IBrewDatasourceResponse { + brewInfo: IBrewInfo[]; + total: number; +} + +export class BeerDataSource extends DataSourceService { + public static providerId = "BeerDataSource"; + + private cache = Array.from({ length: 0 }); + private lastSortValue?: ISorterFilter; + private lastVirtualScroll?: ListRange; + // For simplicity, the totalItems value is hard-coded here, but in a real-world scenario the value would likely be retrieved via an async backend call + private totalItems: number = 325; + + public page: number = 1; + public busy = new BehaviorSubject(false); + + public dataFields: Array = [ + { id: "id", label: "No", dataType: "number" }, + { id: "name", label: "Name", dataType: "string" }, + { id: "tagline", label: "Tagline", dataType: "string" }, + { id: "first_brewed", label: "First Brewed", dataType: "string" }, + { id: "description", label: "Description", dataType: "string" }, + { id: "brewers_tips", label: "Brewer's Tips", dataType: "string" }, + // We are giving this field a custom data type of 'abv' so the dropdown in the custom formatter configurator can use it to filter out other data types + { id: "abv", label: "Alcohol By Volume", dataType: "abv" }, + ]; + + constructor(private logger: LoggerService) { + super(); + } + + public async getFilteredData( + filters: INovaFilters + ): Promise> { + const start = filters.virtualScroll?.value?.start ?? 0; + const end = filters.virtualScroll?.value?.end ?? 0; + const delta = end - start; + + // Note: We should start with a clean cache every time first page is requested + if (start === 0) { + this.cache = []; + } + + // This condition handles sorting. We want to sort columns without fetching another chunk of data. + // Since the data is being fetched when scrolled, we compare virtual scroll indexes here in the condition as well. + if (filters.sorter?.value) { + if ( + !isEqual(this.lastSortValue, filters.sorter.value) && + filters.virtualScroll?.value.start === 0 && + !!this.lastVirtualScroll + ) { + const totalPages = Math.ceil( + delta ? this.totalItems / delta : 1 + ); + const itemsPerPage: number = Math.max( + delta < 80 ? delta : 80, + 1 + ); + let response: Array | null = null; + let map: IBrewDatasourceResponse; + + if (filters.sorter?.value?.direction === "desc") { + this.cache = []; + for (let i = 0; i < this.page; ++i) { + response = await ( + await fetch( + \\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\`\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\${BREW_API_URL}/?page=\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\${ + totalPages - i || 1 + }&per_page=\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\${itemsPerPage}\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\` + ) + ).json(); + + // since the last page contains only 5 items we need to fetch another page to give virtual scroll enough space to work + if (response && response.length < itemsPerPage) { + this.page++; + } + map = { + brewInfo: response?.map((result: IBrewInfo) => ({ + id: result.id, + name: result.name, + tagline: result.tagline, + first_brewed: result.first_brewed, + description: result.description, + brewers_tips: result.brewers_tips, + })), + total: response?.length, + } as IBrewDatasourceResponse; + this.cache = + totalPages - i !== 0 + ? this.cache.concat(map.brewInfo) + : this.cache; + } + } + + if (filters.sorter?.value?.direction === "asc") { + this.cache = []; + for (let i = 0; i < this.page; i++) { + response = await ( + await fetch( + \\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\`\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\${BREW_API_URL}/?page=\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\${ + i + 1 + }&per_page=\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\${itemsPerPage}\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\` + ) + ).json(); + map = { + brewInfo: response?.map((result: IBrewInfo) => ({ + id: result.id, + name: result.name, + tagline: result.tagline, + first_brewed: result.first_brewed, + description: result.description, + brewers_tips: result.brewers_tips, + })), + total: response?.length, + } as IBrewDatasourceResponse; + this.cache = this.cache.concat(map.brewInfo); + } + } + + this.lastSortValue = filters.sorter?.value; + this.lastVirtualScroll = filters.virtualScroll?.value; + + return { + result: { + repeat: { + itemsSource: this.sortData(this.cache, filters), + }, + paginator: { total: this.totalItems }, + dataFields: this.dataFields, + }, + }; + } + } + + this.busy.next(true); + return new Promise((resolve) => { + setTimeout(() => { + this.getData(start, end, filters).then( + (response: INovaFilteringOutputs) => { + if (!response) { + return; + } + + this.cache = this.cache.concat(response.brewInfo); + + this.dataSubject.next(this.cache); + resolve({ + result: { + repeat: { + itemsSource: this.sortData( + this.cache, + filters + ), + }, + paginator: { total: this.totalItems }, + dataFields: this.dataFields, + }, + }); + + this.lastSortValue = filters.sorter?.value; + this.lastVirtualScroll = filters.virtualScroll?.value; + this.busy.next(false); + } + ); + }, 500); + }); + } + + public async getData( + start: number = 0, + end: number = 20, + filters: INovaFilters + ): Promise { + const delta = end - start; + const totalPages = Math.ceil(delta ? this.totalItems / delta : 1); + let response: Array | null = null; + // The api.punk.com is able to return only 80 items per page + const itemsPerPage: number = Math.max(delta < 80 ? delta : 80, 1); + + if (filters.sorter?.value?.direction === "asc") { + response = await ( + await fetch( + \\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\`\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\${BREW_API_URL}/?page=\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\${this.page}&per_page=\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\${itemsPerPage}\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\` + ) + ).json(); + } + + if (filters.sorter?.value?.direction === "desc") { + response = await ( + await fetch( + \\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\`\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\${BREW_API_URL}/?page=\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\${ + totalPages - this.page + }&per_page=\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\${itemsPerPage}\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\` + ) + ).json(); + } + + if (!filters.sorter) { + response = await ( + await fetch( + \\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\`\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\${BREW_API_URL}/?page=\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\${this.page}&per_page=\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\${itemsPerPage}\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\` + ) + ).json(); + } + return { + brewInfo: response?.map((result: IBrewInfo, i: number) => ({ + id: result.id, + abv: result.abv, + name: result.name, + tagline: result.tagline, + first_brewed: result.first_brewed, + description: result.description, + brewers_tips: result.brewers_tips, + })), + total: response?.length, + } as IBrewDatasourceResponse; + } + + private sortData(data: IBrewInfo[], filters: INovaFilters) { + return orderBy( + data, + filters.sorter?.value?.sortBy, + filters.sorter?.value?.direction as "desc" | "asc" + ); + } +} + +export const widgetConfig: IWidget = { + id: "tableWidgetId", + type: "table", + pizzagna: { + [PizzagnaLayer.Configuration]: { + header: { + properties: { + title: "Stupendous Suds", + subtitle: "Try These Brilliant Brews", + }, + }, + table: { + providers: { + [WellKnownProviders.DataSource]: { + providerId: BeerDataSource.providerId, + }, + }, + properties: { + configuration: { + columns: [ + { + id: "column1", + label: "Beer Name", + isActive: true, + width: 185, + formatter: { + componentType: + RawFormatterComponent.lateLoadKey, + properties: { + dataFieldIds: { + value: "name", + }, + }, + }, + }, + { + id: "column2", + label: "Tagline", + isActive: true, + width: 250, + formatter: { + componentType: + RawFormatterComponent.lateLoadKey, + properties: { + dataFieldIds: { + value: "tagline", + }, + }, + }, + }, + { + id: "column3", + label: "Alcohol By Volume", + isActive: true, + width: 150, + formatter: { + componentType: + CustomFormatterComponent.lateLoadKey, + properties: { + dataFieldIds: { + value: "abv", + }, + icon: "severity_error", + threshold: "5", + }, + }, + }, + { + id: "column4", + label: "Description", + isActive: true, + formatter: { + componentType: + RawFormatterComponent.lateLoadKey, + properties: { + dataFieldIds: { + value: "description", + }, + }, + }, + }, + ] as ITableWidgetColumnConfig[], + sorterConfiguration: { + descendantSorting: false, + sortBy: "", + } as ITableWidgetSorterConfig, + hasVirtualScroll: true, + }, + }, + }, + }, + }, +}; +\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\`, + "tutorials/customization/widget/custom-widget-docs.component.ts": \\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\`// © 2022 SolarWinds Worldwide, LLC. All rights reserved. +// +// Permission is hereby granted, free of charge, to any person obtaining a copy +// of this software and associated documentation files (the "Software"), to +// deal in the Software without restriction, including without limitation the +// rights to use, copy, modify, merge, publish, distribute, sublicense, and/or +// sell copies of the Software, and to permit persons to whom the Software is +// furnished to do so, subject to the following conditions: +// +// The above copyright notice and this permission notice shall be included in +// all copies or substantial portions of the Software. +// +// THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR +// IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, +// FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE +// AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER +// LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, +// OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN +// THE SOFTWARE. + +import { Component } from "@angular/core"; + +@Component({ + selector: "custom-widget-docs", + templateUrl: "./custom-widget-docs.component.html", + standalone: false, +}) +export class CustomWidgetDocsComponent {} +\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\`, + "tutorials/customization/widget/custom-widget.component.ts": \\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\`// © 2022 SolarWinds Worldwide, LLC. All rights reserved. +// +// Permission is hereby granted, free of charge, to any person obtaining a copy +// of this software and associated documentation files (the "Software"), to +// deal in the Software without restriction, including without limitation the +// rights to use, copy, modify, merge, publish, distribute, sublicense, and/or +// sell copies of the Software, and to permit persons to whom the Software is +// furnished to do so, subject to the following conditions: +// +// The above copyright notice and this permission notice shall be included in +// all copies or substantial portions of the Software. +// +// THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR +// IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, +// FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE +// AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER +// LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, +// OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN +// THE SOFTWARE. + +import { + ChangeDetectionStrategy, + ChangeDetectorRef, + Component, + EventEmitter, + HostBinding, + Input, + OnChanges, + OnInit, + Output, + SimpleChanges, +} from "@angular/core"; +import { FormBuilder, FormGroup, Validators } from "@angular/forms"; +import { GridsterConfig, GridsterItem } from "angular-gridster2"; + +import { IMenuItem } from "@nova-ui/bits"; +import { + ComponentRegistryService, + ConfiguratorHeadingService, + DEFAULT_PIZZAGNA_ROOT, + EVENT_PROXY, + FormStackComponent, + IConverterFormPartsProperties, + IDashboard, + IHasChangeDetector, + IHasForm, + IProviderConfiguration, + IWidget, + IWidgets, + IWidgetTypeDefinition, + NOVA_GENERIC_CONVERTER, + NOVA_TITLE_AND_DESCRIPTION_CONVERTER, + PizzagnaLayer, + refresher, + StackComponent, + TitleAndDescriptionConfigurationComponent, + WellKnownPathKey, + WellKnownProviders, + widgetBodyContentNodes, + WidgetConfiguratorSectionComponent, + WidgetTypesService, + WIDGET_BODY, + WIDGET_HEADER, + WIDGET_LOADING, +} from "@nova-ui/dashboards"; + +// The custom widget type name we'll use +const CUSTOM_WIDGET_TYPENAME = "example-custom-widget"; +// The path key we'll use for image selection in the configurator definition +const IMAGE_SELECTION_CONFIGURATOR_PATH_KEY = "imageSelection"; + +@Component({ + selector: "custom-widget-body", + // A simple template for our custom widget + template: \\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\`\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\`, + styleUrls: ["./custom-widget.component.less"], + changeDetection: ChangeDetectionStrategy.OnPush, + standalone: false, +}) +// Remember to declare this class in the parent module +export class CustomWidgetBodyContentComponent implements IHasChangeDetector { + // Ensure that the lateLoadKey value matches class name + public static lateLoadKey = "CustomWidgetBodyContentComponent"; + + // Optionally, providing an input for styling of the host element + @Input() @HostBinding("class") public elementClass = ""; + + // We'll map this input with the configurator form using the NOVA_GENERIC_CONVERTER. + // See the customWidget definition at the bottom of the file. + @Input() public imageSource: string; + + // Injecting the ChangeDetectorRef to implement IHasChangeDetector. + // This allows the dashboard framework to reliably propagate component property changes to the DOM. + constructor(public changeDetector: ChangeDetectorRef) {} +} + +/** + * A custom configurator section component for selecting the image source for the custom widget + */ +@Component({ + selector: "custom-configurator-section", + template: \\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\` + + + + +
+ + +
+ Image Selection +
+ {{ imageDisplayValue }} +
+
+
+
+ + + + + {{ item.title }} + + + +
+
+ \\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\`, + styleUrls: ["./custom-widget.component.less"], + changeDetection: ChangeDetectionStrategy.OnPush, + standalone: false, +}) +// Remember to declare this class in the parent module +export class CustomConfiguratorSectionComponent + implements OnInit, OnChanges, IHasChangeDetector, IHasForm +{ + // Ensure that the lateLoadKey value matches the class name + public static lateLoadKey = "CustomConfiguratorSectionComponent"; + + /** + * This input serves as the itemsSource a user can select an image from. + */ + @Input() imageItems: IMenuItem[] = []; + /** + * This property holds the currently selected image source string. + */ + @Input() imageSource: string; + + /** + * An output for emitting formReady to allow the immediate parent formGroup component to register us as a form control + * in the larger form. In this case, the immediate parent would be the WidgetConfiguratorSectionComponent as specified + * in the customWidget configurator definition at the bottom of this file. + */ + @Output() formReady = new EventEmitter(); + + public form: FormGroup; + public imageDisplayValue: string; + + constructor( + public changeDetector: ChangeDetectorRef, + private formBuilder: FormBuilder, + public configuratorHeading: ConfiguratorHeadingService + ) {} + + public ngOnInit(): void { + // Initializing the form + this.form = this.formBuilder.group({ + // Note: When using the NOVA_GENERIC_CONVERTER, the form control name, in this case 'imageSource', must match the input name on + // this component as well as that of the corresponding property on the custom widget body component. + imageSource: [{}, [Validators.required]], + }); + + // Emitting the formReady as described above. + this.formReady.emit(this.form); + } + + public ngOnChanges(changes: SimpleChanges): void { + if (changes.imageSource && !changes.imageSource.isFirstChange()) { + const previousValue: string = changes.imageSource.previousValue; + if (previousValue !== this.imageSource) { + // Setting the display value according to the current imageSource value + this.imageDisplayValue = this.imageItems.find( + (item: IMenuItem) => item.url === this.imageSource + )?.title; + + // Updating the form when the imageSource input gets updated + this.form.get("imageSource")?.setValue(this.imageSource); + } + } + } + + public onChanged(newValue: string): void { + // Keeping the display value updated as the user changes the dropdown selection + this.imageDisplayValue = this.imageItems.find( + (item: IMenuItem) => item.url === newValue + )?.title; + } +} + +/** + * The component that instantiates the dashboard + */ +@Component({ + selector: "custom-widget", + templateUrl: "./custom-widget.component.html", + styleUrls: ["./custom-widget.component.less"], + standalone: false, +}) +export class CustomWidgetComponent implements OnInit { + // This variable will hold all the data needed to define the layout and behavior of the widgets. + // Pass this to the dashboard component's dashboard input in the template. + public dashboard: IDashboard | undefined; + + // Angular gridster requires a configuration object even if it's empty. + // Pass this to the dashboard component's gridsterConfig input in the template. + public gridsterConfig: GridsterConfig = {}; + + // Boolean which dashboard takes in as an input if its true it allows you to move widgets around. + public editMode: boolean = false; + + constructor( + // WidgetTypesService provides the widget's necessary structure information + private widgetTypesService: WidgetTypesService, + + // Inject the ComponentRegistryService to make our custom component available for late loading by the dashboards framework + private componentRegistry: ComponentRegistryService, + private changeDetectorRef: ChangeDetectorRef + ) {} + + public ngOnInit(): void { + // Register the custom widget type and custom components + // Note: This could also be done in the parent module's constructor so that + // multiple dashboards could have access to the same registrations. + this.prepareNovaDashboards(); + + // Register some image items as dropdown options in the widget editor/configurator + // Note: This could also be done in the parent module's constructor so that + // multiple dashboards could have access to the same dropdown options. + this.registerImageOptions(); + + // Initialize our current instance of a dashboard with an instance of our custom widget + this.initializeDashboard(); + } + + /** Used for restoring widgets state */ + public reInitializeDashboard(): void { + // destroys the components and their providers so the dashboard can re init data + this.dashboard = undefined; + this.changeDetectorRef.detectChanges(); + + this.initializeDashboard(); + } + + public initializeDashboard(): void { + // We're using a static configuration object for this example (see widgetConfig at the bottom of the file), + // but this is where the widget's configuration could potentially be populated from a database + const widget = widgetConfig; + + // Create an index of widgets complete with structure and configuration to assign to the dashboard + const widgets: IWidgets = { + // Complete the custom widget with structure information coming from its type definition + [widget.id]: this.widgetTypesService.mergeWithWidgetType(widget), + }; + + // Setting the widget dimensions and position (this is for gridster) + const positions: Record = { + [widget.id]: { + cols: 4, + rows: 11, + y: 0, + x: 0, + }, + }; + + // Finally, assigning the variables we created above to the dashboard + this.dashboard = { positions, widgets }; + } + + private prepareNovaDashboards() { + // Register the custom widget type + this.widgetTypesService.registerWidgetType( + CUSTOM_WIDGET_TYPENAME, + 1, + customWidget + ); + + // Register the custom widget body component with the component registry to make it available + // for late loading by the dashboard framework. + this.componentRegistry.registerByLateLoadKey( + CustomWidgetBodyContentComponent + ); + + // Register the custom configurator section with the component registry to make it available + // for late loading by the dashboard framework. + this.componentRegistry.registerByLateLoadKey( + CustomConfiguratorSectionComponent + ); + } + + private registerImageOptions() { + // Grab the widget's default template which will be needed as a parameter for setNode below. + const widgetTemplate = this.widgetTypesService.getWidgetType( + CUSTOM_WIDGET_TYPENAME, + 1 + ); + + // Register some image items as dropdown options in the widget editor/configurator + this.widgetTypesService.setNode( + // This is the template we grabbed above with getWidgetType + widgetTemplate, + // We are setting the editor/configurator part of the widget template + "configurator", + // This indicates which node you are changing and we want to change the image items available for selection in the editor. + // For reference, see the 'paths' property of the custom widget's IWidgetTypeDefinition at the bottom of this file. + IMAGE_SELECTION_CONFIGURATOR_PATH_KEY, + // We are setting the image items available for selection in the editor. 'imageItems' is defined + // at the bottom of this file. + imageItems + ); + } +} + +/*************************************************************************************************** + * This is the type definition of our custom widget + ***************************************************************************************************/ +const customWidget: IWidgetTypeDefinition = { + /*************************************************************************************************** + * Paths to important settings in this type definition + ***************************************************************************************************/ + paths: { + widget: { + [WellKnownPathKey.Root]: DEFAULT_PIZZAGNA_ROOT, + }, + configurator: { + [WellKnownPathKey.Root]: DEFAULT_PIZZAGNA_ROOT, + // for the custom configuration component, this is the path for the list of image items available for selection + [IMAGE_SELECTION_CONFIGURATOR_PATH_KEY]: + "imageSelection.properties.imageItems", + }, + }, + /*************************************************************************************************** + * Widget section describes the structural part of the custom widget + ***************************************************************************************************/ + widget: { + [PizzagnaLayer.Structure]: { + [DEFAULT_PIZZAGNA_ROOT]: { + id: DEFAULT_PIZZAGNA_ROOT, + // base layout of the widget - all components referenced herein will be stacked in a column + componentType: StackComponent.lateLoadKey, + providers: { + // When enabled, this provider emits the REFRESH event on the pizzagna event bus every X seconds + [WellKnownProviders.Refresher]: refresher(), + // event proxy manages the transmission of events between widget and dashboard such as the WIDGET_EDIT and WIDGET_REMOVE events + [WellKnownProviders.EventProxy]: EVENT_PROXY, + }, + properties: { + // these values reference child components in the widget structure defined below + nodes: ["header", "loading", "body"], + }, + }, + // standard widget header + header: WIDGET_HEADER, + // this is the loading bar below the header + loading: WIDGET_LOADING, + // the body node + body: WIDGET_BODY, + + // retrieving the definitions for the body content nodes. the argument corresponds to the main content node key + ...widgetBodyContentNodes("mainContent"), + + // the component that supplies the content of our custom widget + mainContent: { + id: "mainContent", + componentType: CustomWidgetBodyContentComponent.lateLoadKey, + properties: { + elementClass: "d-flex w-100 justify-content-center", + }, + }, + }, + [PizzagnaLayer.Configuration]: { + [DEFAULT_PIZZAGNA_ROOT]: { + id: DEFAULT_PIZZAGNA_ROOT, + providers: { + // default refresher configuration + [WellKnownProviders.Refresher]: refresher(false, 60), + }, + }, + // default header configuration + header: { + properties: { + title: $localize\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\`Empty Custom Widget\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\`, + }, + }, + }, + }, + /*************************************************************************************************** + * Configurator section describes the form that's used to configure the widget + ***************************************************************************************************/ + configurator: { + [PizzagnaLayer.Structure]: { + [DEFAULT_PIZZAGNA_ROOT]: { + id: DEFAULT_PIZZAGNA_ROOT, + // base layout of the configurator - all form components referenced herein will be stacked in a column + componentType: FormStackComponent.lateLoadKey, + properties: { + elementClass: + "flex-grow-1 overflow-auto nui-scroll-shadows", + // these values reference child components laid out in this form (defined below) + nodes: ["presentation", "customConfig"], + }, + }, + // /presentation + presentation: { + id: "presentation", + componentType: WidgetConfiguratorSectionComponent.lateLoadKey, + properties: { + headerText: $localize\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\`Presentation\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\`, + nodes: ["titleAndDescription"], + }, + }, + // /presentation/titleAndDescription + titleAndDescription: { + id: "titleAndDescription", + componentType: + TitleAndDescriptionConfigurationComponent.lateLoadKey, + providers: { + converter: { + providerId: NOVA_TITLE_AND_DESCRIPTION_CONVERTER, + } as IProviderConfiguration, + }, + }, + // /customConfig + customConfig: { + id: "customConfig", + componentType: WidgetConfiguratorSectionComponent.lateLoadKey, + properties: { + headerText: $localize\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\`Custom Widget Configuration\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\`, + nodes: ["imageSelection"], + }, + }, + // /customConfig/imageSelection + imageSelection: { + id: "imageSelection", + // Here's where we set the configurator to use our custom configurator section + componentType: CustomConfiguratorSectionComponent.lateLoadKey, + properties: { + // This corresponds to the 'imageItems' input on the custom configurator section component + // which defines the list of image items to pick from. The empty value shown here is overridden + // in the 'registerImageOptions' method above. + imageItems: [] as IMenuItem[], + }, + providers: { + // Using the generic converter to map the selected image source between the widget and the form + [WellKnownProviders.Converter]: { + providerId: NOVA_GENERIC_CONVERTER, + properties: { + formParts: [ + { + // Setting up the generic converter to update the 'imageSource' property of the custom widget 'mainContent' component + previewPath: "mainContent.properties", + // Note: To use the NOVA_GENERIC_CONVERTER, the linked properties must have the same name between the configurator + // section component and the widget 'mainContent' component. Additionally, the property name must match the formControl + // name used in the configurator section component. In this case, the common name among all three is 'imageSource'. + keys: ["imageSource"], + }, + ] as IConverterFormPartsProperties[], + }, + } as IProviderConfiguration, + }, + }, + }, + }, +}; + +// For this example, we're using static items for the image selection dropdown. In a more realistic scenario, +// the items available for selection might come from a backend database. +const imageItems = [ + { + title: "Harry Potter Book Cover", + url: "https://imgc.allpostersimages.com/img/print/u-g-F8PQ9I0.jpg?w=550&h=550&p=0", + }, + { + title: "Harry Potter Movie Poster", + url: "https://images-na.ssl-images-amazon.com/images/I/81gpmMdKOHL._AC_SY741_.jpg", + }, +] as IMenuItem[]; + +// We're using a static configuration object for this example. In a more realistic scenario, +// a widget's configuration would likely be stored in a database. +const widgetConfig: IWidget = { + id: "widget1", + // This custom type is registered in the 'prepareNovaDashboards' method above. + type: CUSTOM_WIDGET_TYPENAME, + pizzagna: { + [PizzagnaLayer.Configuration]: { + header: { + // Setting the initial property values for the WidgetHeaderComponent + properties: { + title: "Harry Potter and the Sorcerer's Stone", + subtitle: "By J. K. Rowling", + }, + }, + mainContent: { + properties: { + // Setting the initial value for the 'imageSource' property on our custom widget body + imageSource: imageItems[0].url, + }, + }, + }, + }, +}; +\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\`, + "tutorials/customization/widget/custom-widget.module.ts": \\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\`// © 2022 SolarWinds Worldwide, LLC. All rights reserved. +// +// Permission is hereby granted, free of charge, to any person obtaining a copy +// of this software and associated documentation files (the "Software"), to +// deal in the Software without restriction, including without limitation the +// rights to use, copy, modify, merge, publish, distribute, sublicense, and/or +// sell copies of the Software, and to permit persons to whom the Software is +// furnished to do so, subject to the following conditions: +// +// The above copyright notice and this permission notice shall be included in +// all copies or substantial portions of the Software. +// +// THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR +// IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, +// FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE +// AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER +// LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, +// OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN +// THE SOFTWARE. + +import { CommonModule } from "@angular/common"; +import { HttpClientModule } from "@angular/common/http"; +import { NgModule } from "@angular/core"; +import { ReactiveFormsModule } from "@angular/forms"; +import { RouterModule } from "@angular/router"; + +import { + NuiButtonModule, + NuiDocsModule, + NuiFormFieldModule, + NuiIconModule, + NuiImageModule, + NuiMessageModule, + NuiSelectV2Module, + NuiSwitchModule, + DEMO_PATH_TOKEN, +} from "@nova-ui/bits"; +import { + NuiDashboardConfiguratorModule, + NuiDashboardsModule, +} from "@nova-ui/dashboards"; + +import { CustomWidgetDocsComponent } from "./custom-widget-docs.component"; +import { + CustomConfiguratorSectionComponent, + CustomWidgetBodyContentComponent, + CustomWidgetComponent, +} from "./custom-widget.component"; +import { getDemoFiles } from "../../../../../demo-files-factory"; + +const routes = [ + { + path: "", + component: CustomWidgetDocsComponent, + data: { + srlc: { + hideIndicator: true, + }, + showThemeSwitcher: true, + }, + }, + { + path: "example", + component: CustomWidgetComponent, + data: { + srlc: { + hideIndicator: true, + }, + }, + }, +]; + +@NgModule({ + imports: [ + CommonModule, + ReactiveFormsModule, + HttpClientModule, + NuiDashboardsModule, + NuiDashboardConfiguratorModule, + NuiDocsModule, + NuiFormFieldModule, + NuiIconModule, + NuiImageModule, + NuiMessageModule, + NuiSelectV2Module, + NuiSwitchModule, + NuiButtonModule, + RouterModule.forChild(routes), + ], + declarations: [ + CustomWidgetDocsComponent, + CustomConfiguratorSectionComponent, + CustomWidgetBodyContentComponent, + CustomWidgetComponent, + ], + providers: [ + { + provide: DEMO_PATH_TOKEN, + useValue: getDemoFiles("widget"), + }, + ], +}) +export default class CustomWidgetModule {} +\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\`, + "tutorials/data-source-setup/data-source-setup-docs.component.ts": \\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\`// © 2022 SolarWinds Worldwide, LLC. All rights reserved. +// +// Permission is hereby granted, free of charge, to any person obtaining a copy +// of this software and associated documentation files (the "Software"), to +// deal in the Software without restriction, including without limitation the +// rights to use, copy, modify, merge, publish, distribute, sublicense, and/or +// sell copies of the Software, and to permit persons to whom the Software is +// furnished to do so, subject to the following conditions: +// +// The above copyright notice and this permission notice shall be included in +// all copies or substantial portions of the Software. +// +// THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR +// IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, +// FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE +// AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER +// LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, +// OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN +// THE SOFTWARE. + +import { Component } from "@angular/core"; + +@Component({ + selector: "nui-dashboard-data-source-docs", + templateUrl: "./data-source-setup-docs.component.html", + standalone: false, +}) +export class DataSourceDocsComponent {} +\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\`, + "tutorials/data-source-setup/data-source-setup.component.ts": \\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\`// © 2022 SolarWinds Worldwide, LLC. All rights reserved. +// +// Permission is hereby granted, free of charge, to any person obtaining a copy +// of this software and associated documentation files (the "Software"), to +// deal in the Software without restriction, including without limitation the +// rights to use, copy, modify, merge, publish, distribute, sublicense, and/or +// sell copies of the Software, and to permit persons to whom the Software is +// furnished to do so, subject to the following conditions: +// +// The above copyright notice and this permission notice shall be included in +// all copies or substantial portions of the Software. +// +// THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR +// IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, +// FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE +// AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER +// LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, +// OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN +// THE SOFTWARE. + +import { HttpClient, HttpErrorResponse } from "@angular/common/http"; +import { Component, Injectable, OnDestroy, OnInit } from "@angular/core"; +import { GridsterConfig, GridsterItem } from "angular-gridster2"; +import { BehaviorSubject } from "rxjs"; +import { finalize } from "rxjs/operators"; + +import { DataSourceService, IFilteringOutputs } from "@nova-ui/bits"; +import { + DATA_SOURCE, + DEFAULT_PIZZAGNA_ROOT, + IDashboard, + IKpiData, + IProviderConfiguration, + IRefresherProperties, + IWidget, + IWidgets, + KpiComponent, + NOVA_KPI_DATASOURCE_ADAPTER, + PizzagnaLayer, + ProviderRegistryService, + WellKnownProviders, + WidgetTypesService, +} from "@nova-ui/dashboards"; + +/** + * A simple KPI data source to retrieve the average rating of Harry Potter and the Sorcerer's Stone (book) via googleapis + */ +@Injectable() +export class AverageRatingKpiDataSource + extends DataSourceService + implements OnDestroy +{ + // This is the ID we'll use to identify the provider + public static providerId = "AverageRatingKpiDataSource"; + + // Use this subject to communicate the data source's busy state + public busy = new BehaviorSubject(false); + + constructor(private http: HttpClient) { + super(); + } + + // In this example, getFilteredData is invoked every 10 minutes (Take a look at the refresher + // provider definition in the widget configuration below to see how the interval is set) + public async getFilteredData(): Promise { + this.busy.next(true); + return new Promise((resolve) => { + // *** Make a resource request to an external API (if needed) + this.http + .get("https://www.googleapis.com/books/v1/volumes/5MQFrgEACAAJ") + .pipe(finalize(() => this.busy.next(false))) + .subscribe({ + next: (data: any) => { + resolve({ + result: { + value: data.volumeInfo.averageRating, + }, + }); + }, + error: (error: HttpErrorResponse) => { + resolve({ + result: null, + error: { + type: error.status, + }, + }); + }, + }); + }); + } + + public ngOnDestroy(): void { + this.outputsSubject.complete(); + } +} + +/** + * A component that instantiates the dashboard + */ +@Component({ + selector: "data-source-setup", + templateUrl: "./data-source-setup.component.html", + styleUrls: ["./data-source-setup.component.less"], + standalone: false, +}) +export class DataSourceSetupComponent implements OnInit { + // This variable will hold all the data needed to define the layout and behavior of the widgets. + // Pass this to the dashboard component's dashboard input in the template. + public dashboard: IDashboard; + + // Angular gridster requires a configuration object even if it's empty. + // Pass this to the dashboard component's gridsterConfig input in the template. + public gridsterConfig: GridsterConfig = {}; + + constructor( + // WidgetTypesService provides the widget's necessary structure information + private widgetTypesService: WidgetTypesService, + + // In general, the ProviderRegistryService is used for making entities available for injection into dynamically loaded components. + private providerRegistry: ProviderRegistryService + ) {} + + public ngOnInit(): void { + // Registering the data source for injection into the KPI tile. + // Note: Each tile of a KPI widget is assigned its own instance of the data source + this.providerRegistry.setProviders({ + [AverageRatingKpiDataSource.providerId]: { + provide: DATA_SOURCE, + useClass: AverageRatingKpiDataSource, + // Any dependencies that need to be injected into the provider must be listed here + deps: [HttpClient], + }, + }); + // We're using a static configuration object for this example, but this is where + // the widget's configuration could potentially be populated from a database + const kpiWidget = widgetConfig; + const widgetIndex: IWidgets = { + // Complete the KPI widget with information coming from its type definition + [kpiWidget.id]: + this.widgetTypesService.mergeWithWidgetType(kpiWidget), + }; + + // Setting the widget dimensions and position (this is for gridster) + const positions: Record = { + [kpiWidget.id]: { + cols: 4, + rows: 6, + y: 0, + x: 0, + }, + }; + + // Finally, assigning the variables we created above to the dashboard + this.dashboard = { + positions, + widgets: widgetIndex, + }; + } +} + +const widgetConfig: IWidget = { + id: "widget1", + type: "kpi", + pizzagna: { + [PizzagnaLayer.Configuration]: { + [DEFAULT_PIZZAGNA_ROOT]: { + providers: { + [WellKnownProviders.Refresher]: { + properties: { + // Configuring the refresher interval so that our data source is invoked every ten minutes + interval: 60 * 10, + enabled: true, + } as IRefresherProperties, + } as Partial, + }, + }, + header: { + properties: { + title: "Harry Potter and the Sorcerer's Stone", + subtitle: "By J. K. Rowling", + }, + }, + tiles: { + properties: { + nodes: ["kpi1"], + }, + }, + kpi1: { + id: "kpi1", + componentType: KpiComponent.lateLoadKey, + properties: { + widgetData: { + units: "out of 5 Stars", + label: "Average Rating", + }, + }, + providers: { + [WellKnownProviders.DataSource]: { + // Setting the data source providerId for the tile with id "kpi1" + providerId: AverageRatingKpiDataSource.providerId, + } as IProviderConfiguration, + [WellKnownProviders.Adapter]: { + providerId: NOVA_KPI_DATASOURCE_ADAPTER, + properties: { + componentId: "kpi1", + propertyPath: "widgetData", + }, + } as IProviderConfiguration, + }, + }, + }, + }, +}; +\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\`, + "tutorials/data-source-setup/data-source-setup.module.ts": \\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\`// © 2022 SolarWinds Worldwide, LLC. All rights reserved. +// +// Permission is hereby granted, free of charge, to any person obtaining a copy +// of this software and associated documentation files (the "Software"), to +// deal in the Software without restriction, including without limitation the +// rights to use, copy, modify, merge, publish, distribute, sublicense, and/or +// sell copies of the Software, and to permit persons to whom the Software is +// furnished to do so, subject to the following conditions: +// +// The above copyright notice and this permission notice shall be included in +// all copies or substantial portions of the Software. +// +// THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR +// IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, +// FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE +// AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER +// LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, +// OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN +// THE SOFTWARE. + +import { CommonModule } from "@angular/common"; +import { HttpClientModule } from "@angular/common/http"; +import { NgModule } from "@angular/core"; +import { RouterModule } from "@angular/router"; + +import { + NuiDocsModule, + NuiMessageModule, + DEMO_PATH_TOKEN, +} from "@nova-ui/bits"; +import { NuiDashboardsModule } from "@nova-ui/dashboards"; + +import { DataSourceDocsComponent } from "./data-source-setup-docs.component"; +import { DataSourceSetupComponent } from "./data-source-setup.component"; +import { getDemoFiles } from "../../../../demo-files-factory"; + +const routes = [ + { + path: "", + component: DataSourceDocsComponent, + data: { + srlc: { + hideIndicator: true, + }, + showThemeSwitcher: true, + }, + }, + { + path: "example", + component: DataSourceSetupComponent, + data: { + srlc: { + hideIndicator: true, + }, + }, + }, +]; + +@NgModule({ + imports: [ + CommonModule, + HttpClientModule, + NuiDashboardsModule, + NuiDocsModule, + NuiMessageModule, + RouterModule.forChild(routes), + ], + declarations: [DataSourceDocsComponent, DataSourceSetupComponent], + providers: [ + { + provide: DEMO_PATH_TOKEN, + useValue: getDemoFiles("data-source-setup"), + }, + ], +}) +export default class DataSourceSetupModule {} +\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\`, + "tutorials/dynamic-header-links/dynamic-header-links-docs.component.ts": \\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\`// © 2022 SolarWinds Worldwide, LLC. All rights reserved. +// +// Permission is hereby granted, free of charge, to any person obtaining a copy +// of this software and associated documentation files (the "Software"), to +// deal in the Software without restriction, including without limitation the +// rights to use, copy, modify, merge, publish, distribute, sublicense, and/or +// sell copies of the Software, and to permit persons to whom the Software is +// furnished to do so, subject to the following conditions: +// +// The above copyright notice and this permission notice shall be included in +// all copies or substantial portions of the Software. +// +// THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR +// IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, +// FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE +// AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER +// LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, +// OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN +// THE SOFTWARE. + +import { Component } from "@angular/core"; + +@Component({ + selector: "nui-dynamic-header-links-docs", + templateUrl: "./dynamic-header-links-docs.component.html", + standalone: false, +}) +export class DynamicHeaderLinksDocsComponent {} +\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\`, + "tutorials/dynamic-header-links/dynamic-header-links-docs.module.ts": \\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\`// © 2022 SolarWinds Worldwide, LLC. All rights reserved. +// +// Permission is hereby granted, free of charge, to any person obtaining a copy +// of this software and associated documentation files (the "Software"), to +// deal in the Software without restriction, including without limitation the +// rights to use, copy, modify, merge, publish, distribute, sublicense, and/or +// sell copies of the Software, and to permit persons to whom the Software is +// furnished to do so, subject to the following conditions: +// +// The above copyright notice and this permission notice shall be included in +// all copies or substantial portions of the Software. +// +// THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR +// IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, +// FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE +// AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER +// LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, +// OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN +// THE SOFTWARE. + +import { NgModule } from "@angular/core"; +import { RouterModule } from "@angular/router"; + +import { DynamicHeaderLinksDocsComponent } from "./dynamic-header-links-docs.component"; + +const routes = [ + { + path: "", + component: DynamicHeaderLinksDocsComponent, + data: { + srlc: { + hideIndicator: true, + }, + showThemeSwitcher: true, + }, + }, +]; + +@NgModule({ + imports: [RouterModule.forChild(routes)], + declarations: [DynamicHeaderLinksDocsComponent], +}) +export default class DynamicHeaderLinksDocsModule {} +\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\`, + "tutorials/hello-dashboards/hello-dashboards-docs.component.ts": \\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\`// © 2022 SolarWinds Worldwide, LLC. All rights reserved. +// +// Permission is hereby granted, free of charge, to any person obtaining a copy +// of this software and associated documentation files (the "Software"), to +// deal in the Software without restriction, including without limitation the +// rights to use, copy, modify, merge, publish, distribute, sublicense, and/or +// sell copies of the Software, and to permit persons to whom the Software is +// furnished to do so, subject to the following conditions: +// +// The above copyright notice and this permission notice shall be included in +// all copies or substantial portions of the Software. +// +// THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR +// IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, +// FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE +// AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER +// LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, +// OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN +// THE SOFTWARE. + +import { Component } from "@angular/core"; + +@Component({ + selector: "nui-dashboard-hello-dashboards-docs", + templateUrl: "./hello-dashboards-docs.component.html", + standalone: false, +}) +export class HelloDashboardsDocsComponent {} +\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\`, + "tutorials/hello-dashboards/hello-dashboards-example/hello-dashboards-example.component.ts": \\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\`// © 2022 SolarWinds Worldwide, LLC. All rights reserved. +// +// Permission is hereby granted, free of charge, to any person obtaining a copy +// of this software and associated documentation files (the "Software"), to +// deal in the Software without restriction, including without limitation the +// rights to use, copy, modify, merge, publish, distribute, sublicense, and/or +// sell copies of the Software, and to permit persons to whom the Software is +// furnished to do so, subject to the following conditions: +// +// The above copyright notice and this permission notice shall be included in +// all copies or substantial portions of the Software. +// +// THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR +// IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, +// FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE +// AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER +// LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, +// OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN +// THE SOFTWARE. + +import { Component, OnInit } from "@angular/core"; +import { GridsterConfig, GridsterItem } from "angular-gridster2"; + +import { + IDashboard, + IWidget, + IWidgets, + KpiComponent, + PizzagnaLayer, + WidgetTypesService, +} from "@nova-ui/dashboards"; + +/** + * A component that instantiates the dashboard + */ +@Component({ + selector: "hello-dashboards-example", + templateUrl: "./hello-dashboards-example.component.html", + styleUrls: ["./hello-dashboards-example.component.less"], + standalone: false, +}) +export class HelloDashboardsExampleComponent implements OnInit { + // This variable will have all the data needed to render the widgets widgets. + // Pass this to the dashboard component's dashboard input. + public dashboard: IDashboard; + // Angular gridster requires a configuration object even if its empty. + // Pass this to the dashboard component's gridsterConfig input. + public gridsterConfig: GridsterConfig = {}; + + // WidgetTypesService provides the widget's necessary structure information + constructor(private widgetTypesService: WidgetTypesService) {} + + public ngOnInit(): void { + // Here we are hard-coding the widget config for this example, but this is where you + // could potentially populate the widget's configuration from a database + const kpiWidget = widgetConfig; + const widgetIndex: IWidgets = { + // Complete the KPI widget with information coming from its type definition + [kpiWidget.id]: + this.widgetTypesService.mergeWithWidgetType(kpiWidget), + }; + // Setting widget position and dimensions (this is for gridster) + const positions: Record = { + [kpiWidget.id]: { + cols: 4, + rows: 6, + y: 0, + x: 0, + }, + }; + // Finally, assigning the variables we created above to the dashboard + this.dashboard = { + positions, + widgets: widgetIndex, + }; + } +} + +// In a real-world scenario, this configuration would typically be fetched from a database or at least live in another file +const widgetConfig: IWidget = { + id: "widget1", + type: "kpi", + pizzagna: { + [PizzagnaLayer.Configuration]: { + header: { + properties: { + title: "Hello, KPI Widget!", + subtitle: "A Venue for Meaningful Values", + }, + }, + tiles: { + properties: { + nodes: ["kpi1"], + }, + }, + kpi1: { + id: "kpi1", + componentType: KpiComponent.lateLoadKey, + properties: { + widgetData: { + id: "totalStorage", + value: 1, + label: "Total storage", + units: "TB", + }, + }, + }, + }, + }, +}; +\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\`, + "tutorials/hello-dashboards/hello-dashboards.module.ts": \\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\`// © 2022 SolarWinds Worldwide, LLC. All rights reserved. +// +// Permission is hereby granted, free of charge, to any person obtaining a copy +// of this software and associated documentation files (the "Software"), to +// deal in the Software without restriction, including without limitation the +// rights to use, copy, modify, merge, publish, distribute, sublicense, and/or +// sell copies of the Software, and to permit persons to whom the Software is +// furnished to do so, subject to the following conditions: +// +// The above copyright notice and this permission notice shall be included in +// all copies or substantial portions of the Software. +// +// THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR +// IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, +// FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE +// AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER +// LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, +// OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN +// THE SOFTWARE. + +import { CommonModule } from "@angular/common"; +import { NgModule } from "@angular/core"; +import { RouterModule } from "@angular/router"; + +import { + NuiDocsModule, + NuiMessageModule, + DEMO_PATH_TOKEN, +} from "@nova-ui/bits"; +import { NuiDashboardsModule } from "@nova-ui/dashboards"; + +import { HelloDashboardsDocsComponent } from "./hello-dashboards-docs.component"; +import { HelloDashboardsExampleComponent } from "./hello-dashboards-example/hello-dashboards-example.component"; +import { getDemoFiles } from "../../../../demo-files-factory"; + +const routes = [ + { + path: "", + component: HelloDashboardsDocsComponent, + data: { + srlc: { + hideIndicator: true, + }, + showThemeSwitcher: true, + }, + }, + { + path: "example", + component: HelloDashboardsExampleComponent, + data: { + srlc: { + hideIndicator: true, + }, + }, + }, +]; + +@NgModule({ + imports: [ + CommonModule, + NuiDashboardsModule, + NuiDocsModule, + NuiMessageModule, + RouterModule.forChild(routes), + ], + declarations: [ + HelloDashboardsDocsComponent, + HelloDashboardsExampleComponent, + ], + providers: [ + { + provide: DEMO_PATH_TOKEN, + useValue: getDemoFiles("hello-dashboards"), + }, + ], +}) +export default class HelloDashboardsModule {} +\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\`, + "tutorials/persistence-handler-setup/persistence-handler-setup-docs.component.ts": \\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\`// © 2022 SolarWinds Worldwide, LLC. All rights reserved. +// +// Permission is hereby granted, free of charge, to any person obtaining a copy +// of this software and associated documentation files (the "Software"), to +// deal in the Software without restriction, including without limitation the +// rights to use, copy, modify, merge, publish, distribute, sublicense, and/or +// sell copies of the Software, and to permit persons to whom the Software is +// furnished to do so, subject to the following conditions: +// +// The above copyright notice and this permission notice shall be included in +// all copies or substantial portions of the Software. +// +// THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR +// IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, +// FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE +// AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER +// LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, +// OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN +// THE SOFTWARE. + +import { Component } from "@angular/core"; + +@Component({ + selector: "nui-dashboard-persistence-handler-setup-docs", + templateUrl: "./persistence-handler-setup-docs.component.html", + standalone: false, +}) +export class PersistenceHandlerSetupDocsComponent {} +\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\`, + "tutorials/persistence-handler-setup/persistence-handler-setup.component.ts": \\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\`// © 2022 SolarWinds Worldwide, LLC. All rights reserved. +// +// Permission is hereby granted, free of charge, to any person obtaining a copy +// of this software and associated documentation files (the "Software"), to +// deal in the Software without restriction, including without limitation the +// rights to use, copy, modify, merge, publish, distribute, sublicense, and/or +// sell copies of the Software, and to permit persons to whom the Software is +// furnished to do so, subject to the following conditions: +// +// The above copyright notice and this permission notice shall be included in +// all copies or substantial portions of the Software. +// +// THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR +// IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, +// FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE +// AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER +// LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, +// OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN +// THE SOFTWARE. + +import { HttpClient, HttpErrorResponse } from "@angular/common/http"; +import { + ChangeDetectorRef, + Component, + Injectable, + OnDestroy, + OnInit, +} from "@angular/core"; +import { GridsterConfig, GridsterItem } from "angular-gridster2"; +import { BehaviorSubject, Observable, Subject } from "rxjs"; +import { finalize } from "rxjs/operators"; + +import { + DataSourceService, + IFilteringOutputs, + ToastService, + uuid, +} from "@nova-ui/bits"; +import { + DATA_SOURCE, + DEFAULT_PIZZAGNA_ROOT, + IDashboard, + IDashboardPersistenceHandler, + IKpiData, + IProviderConfiguration, + IRefresherProperties, + IWidget, + IWidgets, + KpiComponent, + NOVA_KPI_DATASOURCE_ADAPTER, + PizzagnaLayer, + ProviderRegistryService, + WellKnownPathKey, + WellKnownProviders, + WidgetTypesService, +} from "@nova-ui/dashboards"; + +/** + * A simple persistence handler that is tied to the widget editor directive + */ +@Injectable() +// The realizer of IDashboardPersistenceHandler may implement a trySubmit and/or a tryRemove method. +export class PersistenceHandler implements IDashboardPersistenceHandler { + // This variable is just to show how to handle error handling. + private persistenceSucceeded: boolean = true; + + // The example uses the toast service to demonstrate the + // invocation of each of the persistence handler callbacks + constructor(private toastService: ToastService) { + // toastService options to let it sit on the page for 2 seconds. + this.toastService.setConfig({ + timeOut: 2000, + }); + } + + // This method will be invoked anytime the widget editor form gets submitted. + public trySubmit = (widget: IWidget): Observable => { + // Since we are working asynchronously, we'll return a subject. So, after the submit attempt + // succeeds or fails, we can let the subscriber know the result. + const subject = new Subject(); + + if (!widget.id) { + // Creates an id if the widget has no id. + // (This step will make more sense in the context of the widget cloning tutorial + // in which we handle the persistence of a newly created widget.) + widget.id = uuid(); + } + + // For this example, we're using a setTimeout to mock an asynchronous persistence request to a backend + setTimeout(() => { + if (this.persistenceSucceeded) { + // Passes along the new widget after one second. + subject.next(widget); + // Toast on the page on success. + this.toastService.success({ + title: $localize\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\`Submit succeeded.\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\`, + }); + } else { + const errorText = $localize\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\`Submit failed.\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\`; + // Toast on the page on failure. + this.toastService.error({ title: errorText }); + // Makes the subject say there is an error. + subject.error(errorText); + } + // Completes the subject so whoever subscribes to it knows its finished. + subject.complete(); + }, 1000); + + // Returns the subject as an observable. + return subject.asObservable(); + }; + + // This method will be invoked anytime there's a widget removal attempt. + public tryRemove = (widgetId: string): Observable => { + const subject = new Subject(); + + setTimeout(() => { + if (this.persistenceSucceeded) { + // Pass through the id of the widget that was removed. + subject.next(widgetId); + this.toastService.success({ + title: $localize\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\`Removal succeeded.\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\`, + }); + } else { + const errorText = $localize\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\`Removal failed.\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\`; + this.toastService.error({ title: errorText }); + subject.error(errorText); + } + subject.complete(); + }, 1000); + + return subject.asObservable(); + }; +} + +/** + * A component that instantiates the dashboard + */ +@Component({ + selector: "persistence-handler-setup", + templateUrl: "./persistence-handler-setup.component.html", + styleUrls: ["./persistence-handler-setup.component.less"], + // Here we provide our persistence handler at the component level; this can also be done in the module. + providers: [PersistenceHandler], + standalone: false, +}) +export class PersistenceHandlerSetupComponent implements OnInit { + // This variable will hold all the data needed to define the layout and behavior of the widgets. + // Pass this to the dashboard component's dashboard input in the template. + public dashboard: IDashboard | undefined; + + // Angular gridster requires a configuration object even if it's empty. + // Pass this to the dashboard component's gridsterConfig input in the template. + public gridsterConfig: GridsterConfig = {}; + + // Boolean which dashboard takes in as an input if its true it allows you to move widgets around. + public editMode: boolean = false; + + constructor( + // WidgetTypesService provides the widget's necessary structure information + private widgetTypesService: WidgetTypesService, + + // In general, the ProviderRegistryService is used for making entities available for injection into dynamically loaded components. + private providerRegistry: ProviderRegistryService, + + // We are injecting the PersistenceHandler we created and assigning it to a property we use in the template. + public persistenceHandler: PersistenceHandler, + private changeDetectorRef: ChangeDetectorRef + ) {} + + public ngOnInit(): void { + // Grabbing the widget's default template which will be needed as a parameter for setNode + const widgetTemplate = this.widgetTypesService.getWidgetType("kpi", 1); + // Registering our data sources as dropdown options in the widget editor/configurator + // Note: This could also be done in the parent module's constructor so that + // multiple dashboards could have access to the same widget template modification. + this.widgetTypesService.setNode( + // This is the template we grabbed above with getWidgetType + widgetTemplate, + // We are setting the editor/configurator part of the widget template + "configurator", + // This indicates which node you are changing and we want to change + // the data source providers available for selection in the editor. + WellKnownPathKey.DataSourceProviders, + // We are setting the data sources available for selection in the editor + [ + AverageRatingKpiDataSource.providerId, + RatingsCountKpiDataSource.providerId, + ] + ); + + // Registering the data sources available for injection into the KPI tiles. + // Note: Each tile of a KPI widget is assigned its own instance of a data source + this.providerRegistry.setProviders({ + [AverageRatingKpiDataSource.providerId]: { + provide: DATA_SOURCE, + useClass: AverageRatingKpiDataSource, + // Any dependencies that need to be injected into the provider must be listed here + deps: [HttpClient], + }, + [RatingsCountKpiDataSource.providerId]: { + provide: DATA_SOURCE, + useClass: RatingsCountKpiDataSource, + deps: [HttpClient], + }, + }); + + this.initializeDashboard(); + } + + /** Used for restoring widgets state */ + public reInitializeDashboard(): void { + // destroys the components and their providers so the dashboard can re init data + this.dashboard = undefined; + this.changeDetectorRef.detectChanges(); + + this.initializeDashboard(); + } + + public initializeDashboard(): void { + // We're using a static configuration object for this example (see widgetConfig at the bottom of the file), + // but this is where the widget's configuration could potentially be populated from a database + const kpiWidget = widgetConfig; + const widgetIndex: IWidgets = { + // Complete the KPI widget with information coming from its type definition + [kpiWidget.id]: + this.widgetTypesService.mergeWithWidgetType(kpiWidget), + }; + + // Setting the widget dimensions and position (this is for gridster) + const positions: Record = { + [kpiWidget.id]: { + cols: 4, + rows: 6, + y: 0, + x: 0, + }, + }; + + // Finally, assigning the variables we created above to the dashboard + this.dashboard = { + positions, + widgets: widgetIndex, + }; + } +} + +/** + * A simple KPI data source to retrieve the average rating of Harry Potter and the Sorcerer's Stone (book) via googleapis + */ +@Injectable() +export class AverageRatingKpiDataSource + extends DataSourceService + implements OnDestroy +{ + // This is the ID we'll use to identify the provider + public static providerId = "AverageRatingKpiDataSource"; + + // Use this subject to communicate the data source's busy state + public busy = new BehaviorSubject(false); + + constructor(private http: HttpClient) { + super(); + } + + // In this example, getFilteredData is invoked every 10 minutes (Take a look at the refresher + // provider definition in the widget configuration below to see how the interval is set) + public async getFilteredData(): Promise { + this.busy.next(true); + return new Promise((resolve) => { + // *** Make a resource request to an external API (if needed) + this.http + .get("https://www.googleapis.com/books/v1/volumes/5MQFrgEACAAJ") + .pipe(finalize(() => this.busy.next(false))) + .subscribe({ + next: (data: any) => { + resolve({ + result: { + value: data.volumeInfo.averageRating, + }, + }); + }, + error: (error: HttpErrorResponse) => { + resolve({ + result: null, + error: { + type: error.status, + }, + }); + }, + }); + }); + } + + public ngOnDestroy(): void { + this.outputsSubject.complete(); + } +} + +/** + * A simple KPI data source to retrieve the ratings count of Harry Potter and the Sorcerer's Stone (book) via googleapis + */ +@Injectable() +export class RatingsCountKpiDataSource + extends DataSourceService + implements OnDestroy +{ + public static providerId = "RatingsCountKpiDataSource"; + + // Use this subject to communicate the data source's busy state + public busy = new BehaviorSubject(false); + + constructor(private http: HttpClient) { + super(); + } + + public async getFilteredData(): Promise { + this.busy.next(true); + return new Promise((resolve) => { + this.http + .get("https://www.googleapis.com/books/v1/volumes/5MQFrgEACAAJ") + .pipe(finalize(() => this.busy.next(false))) + .subscribe({ + next: (data: any) => { + resolve({ + result: { + value: data.volumeInfo.ratingsCount, + }, + }); + }, + error: (error: HttpErrorResponse) => { + resolve({ + result: null, + error: { + type: error.status, + }, + }); + }, + }); + }); + } + + public ngOnDestroy(): void { + this.outputsSubject.complete(); + } +} + +const widgetConfig: IWidget = { + id: "widget1", + type: "kpi", + pizzagna: { + [PizzagnaLayer.Configuration]: { + [DEFAULT_PIZZAGNA_ROOT]: { + providers: { + [WellKnownProviders.Refresher]: { + properties: { + // Configuring the refresher interval so that our data source is invoked every ten minutes + interval: 60 * 10, + enabled: true, + } as IRefresherProperties, + } as Partial, + }, + }, + header: { + properties: { + title: "Harry Potter and the Sorcerer's Stone", + subtitle: "By J. K. Rowling", + }, + }, + tiles: { + properties: { + nodes: ["kpi1"], + }, + }, + kpi1: { + id: "kpi1", + componentType: KpiComponent.lateLoadKey, + properties: { + widgetData: { + units: "out of 5 Stars", + label: "Average Rating", + }, + }, + providers: { + [WellKnownProviders.DataSource]: { + // Setting the data source providerId for the tile with id "kpi1" + providerId: AverageRatingKpiDataSource.providerId, + } as IProviderConfiguration, + [WellKnownProviders.Adapter]: { + providerId: NOVA_KPI_DATASOURCE_ADAPTER, + properties: { + componentId: "kpi1", + propertyPath: "widgetData", + }, + } as IProviderConfiguration, + }, + }, + }, + }, +}; +\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\`, + "tutorials/persistence-handler-setup/persistence-handler-setup.module.ts": \\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\`// © 2022 SolarWinds Worldwide, LLC. All rights reserved. +// +// Permission is hereby granted, free of charge, to any person obtaining a copy +// of this software and associated documentation files (the "Software"), to +// deal in the Software without restriction, including without limitation the +// rights to use, copy, modify, merge, publish, distribute, sublicense, and/or +// sell copies of the Software, and to permit persons to whom the Software is +// furnished to do so, subject to the following conditions: +// +// The above copyright notice and this permission notice shall be included in +// all copies or substantial portions of the Software. +// +// THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR +// IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, +// FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE +// AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER +// LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, +// OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN +// THE SOFTWARE. + +import { CommonModule } from "@angular/common"; +import { HttpClientModule } from "@angular/common/http"; +import { NgModule } from "@angular/core"; +import { RouterModule } from "@angular/router"; + +import { + NuiButtonModule, + NuiDocsModule, + NuiMessageModule, + NuiSwitchModule, + NuiToastModule, + DEMO_PATH_TOKEN, +} from "@nova-ui/bits"; +import { NuiDashboardsModule } from "@nova-ui/dashboards"; + +import { PersistenceHandlerSetupDocsComponent } from "./persistence-handler-setup-docs.component"; +import { PersistenceHandlerSetupComponent } from "./persistence-handler-setup.component"; +import { getDemoFiles } from "../../../../demo-files-factory"; + +const routes = [ + { + path: "", + component: PersistenceHandlerSetupDocsComponent, + data: { + srlc: { + hideIndicator: true, + }, + showThemeSwitcher: true, + }, + }, + { + path: "example", + component: PersistenceHandlerSetupComponent, + data: { + srlc: { + hideIndicator: true, + }, + }, + }, +]; + +@NgModule({ + imports: [ + CommonModule, + HttpClientModule, + NuiDashboardsModule, + NuiDocsModule, + NuiMessageModule, + NuiSwitchModule, + NuiToastModule, + NuiButtonModule, + RouterModule.forChild(routes), + ], + declarations: [ + PersistenceHandlerSetupDocsComponent, + PersistenceHandlerSetupComponent, + ], + providers: [ + { + provide: DEMO_PATH_TOKEN, + useValue: getDemoFiles("persistence-handler-setup"), + }, + ], +}) +export default class PersistenceHandlerSetupModule {} +\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\`, + "tutorials/tutorials.module.ts": \\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\`// © 2022 SolarWinds Worldwide, LLC. All rights reserved. +// +// Permission is hereby granted, free of charge, to any person obtaining a copy +// of this software and associated documentation files (the "Software"), to +// deal in the Software without restriction, including without limitation the +// rights to use, copy, modify, merge, publish, distribute, sublicense, and/or +// sell copies of the Software, and to permit persons to whom the Software is +// furnished to do so, subject to the following conditions: +// +// The above copyright notice and this permission notice shall be included in +// all copies or substantial portions of the Software. +// +// THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR +// IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, +// FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE +// AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER +// LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, +// OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN +// THE SOFTWARE. + +import { NgModule, Type } from "@angular/core"; +import { RouterModule, Routes } from "@angular/router"; + +import { ConfiguratorHeadingService } from "@nova-ui/dashboards"; + +export enum TutorialsModuleRoute { + HelloDashboards = "hello-dashboards", + DataSource = "data-source-setup", + WidgetEditor = "widget-editor-setup", + SubmitHandler = "persistence-handler-setup", + WidgetCreation = "widget-creation", + Customization = "customization", + WidgetErrorHandling = "widget-error-handling", + DynamicHeaderLinks = "dynamic-header-links", +} + +const routes: Routes = [ + { + path: TutorialsModuleRoute.HelloDashboards, + loadChildren: async () => + import( + "./hello-dashboards/hello-dashboards.module" + ) as object as Promise>, + }, + { + path: TutorialsModuleRoute.DataSource, + loadChildren: async () => + import( + "./data-source-setup/data-source-setup.module" + ) as object as Promise>, + }, + { + path: TutorialsModuleRoute.WidgetEditor, + loadChildren: async () => + import( + "./widget-editor-setup/widget-editor-setup.module" + ) as object as Promise>, + }, + { + path: TutorialsModuleRoute.SubmitHandler, + loadChildren: async () => + import( + "./persistence-handler-setup/persistence-handler-setup.module" + ) as object as Promise>, + }, + { + path: TutorialsModuleRoute.WidgetCreation, + loadChildren: async () => + import( + "./widget-creation/widget-creation.module" + ) as object as Promise>, + }, + { + path: TutorialsModuleRoute.Customization, + loadChildren: async () => + import("./customization/customization.module") as object as Promise< + Type + >, + }, + { + path: TutorialsModuleRoute.WidgetErrorHandling, + loadChildren: async () => + import( + "./widget-error-handling/widget-error-handling.module" + ) as object as Promise>, + }, + { + path: TutorialsModuleRoute.DynamicHeaderLinks, + loadChildren: async () => + import( + "./dynamic-header-links/dynamic-header-links-docs.module" + ) as object as Promise>, + }, +]; + +@NgModule({ + imports: [RouterModule.forChild(routes)], + providers: [ConfiguratorHeadingService], +}) +export default class TutorialsModule {} +\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\`, + "tutorials/widget-creation/widget-creation-docs.component.ts": \\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\`// © 2022 SolarWinds Worldwide, LLC. All rights reserved. +// +// Permission is hereby granted, free of charge, to any person obtaining a copy +// of this software and associated documentation files (the "Software"), to +// deal in the Software without restriction, including without limitation the +// rights to use, copy, modify, merge, publish, distribute, sublicense, and/or +// sell copies of the Software, and to permit persons to whom the Software is +// furnished to do so, subject to the following conditions: +// +// The above copyright notice and this permission notice shall be included in +// all copies or substantial portions of the Software. +// +// THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR +// IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, +// FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE +// AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER +// LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, +// OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN +// THE SOFTWARE. + +import { Component } from "@angular/core"; + +@Component({ + selector: "nui-dashboard-widget-creation-docs", + templateUrl: "./widget-creation-docs.component.html", + standalone: false, +}) +export class WidgetCreationDocsComponent {} +\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\`, + "tutorials/widget-creation/widget-creation.component.ts": \\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\`// © 2022 SolarWinds Worldwide, LLC. All rights reserved. +// +// Permission is hereby granted, free of charge, to any person obtaining a copy +// of this software and associated documentation files (the "Software"), to +// deal in the Software without restriction, including without limitation the +// rights to use, copy, modify, merge, publish, distribute, sublicense, and/or +// sell copies of the Software, and to permit persons to whom the Software is +// furnished to do so, subject to the following conditions: +// +// The above copyright notice and this permission notice shall be included in +// all copies or substantial portions of the Software. +// +// THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR +// IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, +// FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE +// AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER +// LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, +// OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN +// THE SOFTWARE. + +import { HttpClient, HttpErrorResponse } from "@angular/common/http"; +import { + Component, + EventEmitter, + Injectable, + OnDestroy, + OnInit, + Output, + ViewChild, +} from "@angular/core"; +import { GridsterConfig, GridsterItem } from "angular-gridster2"; +import { BehaviorSubject, Observable, Subject } from "rxjs"; +import { finalize, take, takeUntil } from "rxjs/operators"; + +import { + DataSourceService, + IFilteringOutputs, + ToastService, + uuid, +} from "@nova-ui/bits"; +import { + DashboardComponent, + DATA_SOURCE, + DEFAULT_PIZZAGNA_ROOT, + IDashboard, + IDashboardPersistenceHandler, + IDataSourceOutput, + IKpiData, + IProviderConfiguration, + IRefresherProperties, + IWidget, + IWidgets, + IWidgetSelector, + IWidgetTemplateSelector, + KpiComponent, + NOVA_KPI_DATASOURCE_ADAPTER, + PizzagnaLayer, + ProviderRegistryService, + WellKnownPathKey, + WellKnownProviders, + WidgetClonerService, + WidgetTypesService, +} from "@nova-ui/dashboards"; + +// Interface of a widget item +interface IWidgetItem { + name: string; + widget: IWidget; +} + +// This component acts as the first step, or page, in the wizard where the user selects a wizard type to create. +// It's recommended to have this component in a different file. For this tutorial, it's included in the same +// file for simplicity. +@Component({ + selector: "widget-template-selection", + styleUrls: ["./widget-creation.component.less"], + template: \\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\` +
+ + +
+ + +
+
{{ item.name }}
+
+
+ \\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\`, + standalone: false, +}) +export class WidgetTemplateSelectionComponent + implements IWidgetTemplateSelector, OnInit +{ + // This output will notify the wizard that a widget has been selected. + @Output() public widgetSelected = new EventEmitter(); + + public widgetItems: IWidgetItem[] = []; + public widgetSelection: IWidgetItem[]; + + constructor(private widgetTypesService: WidgetTypesService) {} + + public ngOnInit(): void { + // Here we combine the widget structure from the WidgetTypesService with the corresponding widget + // configuration to create an array of widget objects for the itemSource on the repeat component. + this.widgetItems = [ + { + name: "Fully Configured KPI Widget", + widget: this.widgetTypesService.mergeWithWidgetType( + fullKpiWidgetConfig + ), + }, + { + name: "Unconfigured Proportional Widget", + // Note that 'partialPropWidgetConfig' sets 'metadata.needsConfiguration' to true. + // When this widget is selected in the wizard, the 'Create Widget' button will be hidden + // to guide the user to the second step where they can complete the configuration. + widget: this.widgetTypesService.mergeWithWidgetType( + partialPropWidgetConfig + ), + }, + ]; + + // You can optionally auto-select a widget by doing the following + // this.onSelect([this.widgetItems[0]]); + } + + public onSelect(selectedItems: any[]): void { + // We emit the selected widget to communicate the selection to the configurator + this.widgetSelected.emit(selectedItems[0].widget); + this.widgetSelection = selectedItems; + } +} + +/** + * A simple persistence handler that is tied to the widget editor directive + */ +@Injectable() +// The realizer of IDashboardPersistenceHandler may implement a trySubmit and/or a tryRemove method. +export class PersistenceHandler implements IDashboardPersistenceHandler { + // This variable is just to show how to handle error handling. + private persistenceSucceeded: boolean = true; + + // The example uses the toast service to demonstrate the + // invocation of each of the persistence handler callbacks + constructor(private toastService: ToastService) { + // toastService options to let it sit on the page for 2 seconds. + this.toastService.setConfig({ + timeOut: 2000, + }); + } + + // This method will be invoked anytime the widget editor form gets submitted. + public trySubmit = (widget: IWidget): Observable => { + // Since we are working asynchronously, we'll return a subject. So, after the submit attempt + // succeeds or fails, we can let the subscriber know the result. + const subject = new Subject(); + + if (!widget.id) { + // Creates an id if the widget has no id. + // (This step will make more sense in the context of the widget cloning tutorial + // in which we handle the persistence of a newly created widget.) + widget.id = uuid(); + } + + // For this example, we're using a setTimeout to mock an asynchronous persistence request to a backend + setTimeout(() => { + if (this.persistenceSucceeded) { + // Passes along the new widget after one second. + subject.next(widget); + // Toast on the page on success. + this.toastService.success({ + title: $localize\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\`Submit succeeded.\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\`, + }); + } else { + const errorText = $localize\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\`Submit failed.\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\`; + // Toast on the page on failure. + this.toastService.error({ title: errorText }); + // Makes the subject say there is an error. + subject.error(errorText); + } + // Completes the subject so whoever subscribes to it knows its finished. + subject.complete(); + }, 1000); + + // Returns the subject as an observable. + return subject.asObservable(); + }; + + // This method will be invoked anytime there's a widget removal attempt. + public tryRemove = (widgetId: string): Observable => { + const subject = new Subject(); + + setTimeout(() => { + if (this.persistenceSucceeded) { + // Pass through the id of the widget that was removed. + subject.next(widgetId); + this.toastService.success({ + title: $localize\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\`Removal success\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\`, + }); + } else { + const errorText = $localize\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\`Removal failed.\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\`; + this.toastService.error({ title: errorText }); + subject.error(errorText); + } + subject.complete(); + }, 1000); + + return subject.asObservable(); + }; +} + +/** + * A component that instantiates the dashboard + */ +@Component({ + selector: "widget-creation", + templateUrl: "./widget-creation.component.html", + styleUrls: ["./widget-creation.component.less"], + // Here we provide our persistence handler at the component level; this can also be done in the module. + providers: [PersistenceHandler], + standalone: false, +}) +export class WidgetCreationComponent implements OnInit { + // The WidgetClonerService will need this for updating the dashboard + @ViewChild(DashboardComponent, { static: true }) + dashboardComponent: DashboardComponent; + // This variable will hold all the data needed to define the layout and behavior of the widgets. + // Pass this to the dashboard component's dashboard input in the template. + public dashboard: IDashboard; + + // Angular gridster requires a configuration object even if it's empty. + // Pass this to the dashboard component's gridsterConfig input in the template. + public gridsterConfig: GridsterConfig = { + // These values will be used to set the initial widget dimensions on creation. + // If not set, they each default to 6. + defaultItemCols: 3, + defaultItemRows: 5, + }; + + // Boolean the dashboard takes in as an input; if it's set to true + // the dashboard allows you to resize widgets and move them around. + public editMode: boolean = false; + + // Subject used for auto-unsubscribing from subscriptions on component destruction + private readonly destroy$ = new Subject(); + + constructor( + // WidgetTypesService provides the widget's necessary structure information + private widgetTypesService: WidgetTypesService, + + // In general, the ProviderRegistryService is used for making entities available for injection into dynamically loaded components. + private providerRegistry: ProviderRegistryService, + + // Injecting the PersistenceHandler we created and assigning it to a property we use in the template. + public persistenceHandler: PersistenceHandler, + + // Injecting the cloner service which is needed for opening up the cloner wizard. + private widgetClonerService: WidgetClonerService + ) {} + + public ngOnInit(): void { + // Grabbing the widget's default template which will be needed as a parameter for setNode + const kpiTemplate = this.widgetTypesService.getWidgetType("kpi", 1); + const proportionalTemplate = this.widgetTypesService.getWidgetType( + "proportional", + 1 + ); + + // Registering our data sources as dropdown options in the widget editor/configurator + // Note: This could also be done in the parent module's constructor so that + // multiple dashboards could have access to the same widget template modification. + this.widgetTypesService.setNode( + // This is the template we grabbed above with getWidgetType + proportionalTemplate, + // Setting the editor/configurator part of the widget template + "configurator", + // This indicates which node you are changing and we want to change + // the data source providers available for selection in the editor. + WellKnownPathKey.DataSourceProviders, + // Setting the data sources available for selection in the editor + [RandomCitiesProportionalDataSource.providerId] + ); + + // Same as above, but for the KPI data sources + this.widgetTypesService.setNode( + kpiTemplate, + "configurator", + WellKnownPathKey.DataSourceProviders, + [ + AverageRatingKpiDataSource.providerId, + RatingsCountKpiDataSource.providerId, + ] + ); + + // Registering the data sources available for injection into the KPI tiles and proportional widget. + // Note: Each tile of a KPI widget is assigned its own instance of a data source. + this.providerRegistry.setProviders({ + [AverageRatingKpiDataSource.providerId]: { + provide: DATA_SOURCE, + useClass: AverageRatingKpiDataSource, + // Any dependencies that need to be injected into the provider must be listed here + deps: [HttpClient], + }, + [RatingsCountKpiDataSource.providerId]: { + provide: DATA_SOURCE, + useClass: RatingsCountKpiDataSource, + deps: [HttpClient], + }, + [RandomCitiesProportionalDataSource.providerId]: { + provide: DATA_SOURCE, + useClass: RandomCitiesProportionalDataSource, + deps: [], + }, + }); + + this.initializeDashboard(); + } + + public onCreateWidget(): void { + const widgetSelector: IWidgetSelector = { + // Template ref of the dashboard component. + dashboardComponent: this.dashboardComponent, + // A trySubmit function; in this case, we use the trySubmit from the PersistenceHandler created in the previous tutorial. + trySubmit: this.persistenceHandler.trySubmit, + // WidgetTemplateSelectionComponent will act as step one of the wizard to allow the user to select which widget will be cloned. + widgetSelectionComponentType: WidgetTemplateSelectionComponent, + }; + this.widgetClonerService + .open(widgetSelector) + .pipe( + // Auto-unsubscribe after one emission or on component destruction + take(1), + takeUntil(this.destroy$) + ) + .subscribe(); + } + + public initializeDashboard(): void { + // We're using a static configuration object for this example (see widgetConfig at the bottom of the file), + // but this is where the widget's configuration could potentially be populated from a database + const kpiWidget = fullKpiWidgetConfig; + const widgetIndex: IWidgets = { + // Complete the KPI widget with information coming from its type definition + [kpiWidget.id]: + this.widgetTypesService.mergeWithWidgetType(kpiWidget), + }; + + // Setting the widget dimensions and position (this is for gridster) + // Note: If no position is given for a widget the 'defaultItemCols' and 'defaultItemRows' properties + // from the gridsterConfig will be used for the dimensions + const positions: Record = { + [kpiWidget.id]: { + cols: 3, + rows: 5, + y: 0, + x: 0, + }, + }; + + // Finally, assigning the variables we created above to the dashboard + this.dashboard = { + positions, + widgets: widgetIndex, + }; + } +} + +// Interface for each data point in a proportional widget. +interface IProportionalWidgetData { + id: string; + name: string; + data: number[]; + icon: string; + link: string; + value: string; +} + +@Injectable() +export class RandomCitiesProportionalDataSource implements OnDestroy { + public static providerId = "RandomCitiesProportionalDataSource"; + + public outputsSubject = new Subject< + IDataSourceOutput + >(); + + // Every time applyFilters gets ran we are changing the data source. + public applyFilters(): void { + setTimeout(() => { + this.outputsSubject.next({ + result: this.getRandomProportionalWidgetData(), + }); + }, 1000); + } + + public ngOnDestroy(): void { + this.outputsSubject.complete(); + } + + private getRandomProportionalWidgetData(): IProportionalWidgetData[] { + return [ + { + id: "Down", + name: "Down", + data: [Math.round(Math.random() * 100)], + icon: "status_down", + link: "https://en.wikipedia.org/wiki/Brno", + value: "Brno", + }, + { + id: "Critical", + name: "Critical", + data: [Math.round(Math.random() * 100)], + icon: "status_critical", + link: "https://en.wikipedia.org/wiki/Kyiv", + value: "Kyiv", + }, + { + id: "Warning", + name: "Warning", + data: [Math.round(Math.random() * 100)], + icon: "status_warning", + link: "https://en.wikipedia.org/wiki/Austin", + value: "Austin", + }, + { + id: "Unknown", + name: "Unknown", + data: [Math.round(Math.random() * 100)], + icon: "status_unknown", + link: "https://en.wikipedia.org/wiki/Lisbon", + value: "Lisbon", + }, + { + id: "Up", + name: "Up", + data: [Math.round(Math.random() * 100)], + icon: "status_up", + link: "https://en.wikipedia.org/wiki/Sydney", + value: "Sydney", + }, + { + id: "Unmanaged", + name: "Unmanaged", + data: [Math.round(Math.random() * 100)], + icon: "status_unmanaged", + link: "https://en.wikipedia.org/wiki/Nur-Sultan", + value: "Nur-Sultan", + }, + ]; + } +} + +/** + * A simple KPI data source to retrieve the average rating of Harry Potter and the Sorcerer's Stone (book) via googleapis + */ +@Injectable() +export class AverageRatingKpiDataSource + extends DataSourceService + implements OnDestroy +{ + // This is the ID we'll use to identify the provider + public static providerId = "AverageRatingKpiDataSource"; + + // Use this subject to communicate the data source's busy state + public busy = new BehaviorSubject(false); + + constructor(private http: HttpClient) { + super(); + } + + // In this example, getFilteredData is invoked every 10 minutes (Take a look at the refresher + // provider definition in the widget configuration below to see how the interval is set) + public async getFilteredData(): Promise { + this.busy.next(true); + return new Promise((resolve) => { + // *** Make a resource request to an external API (if needed) + this.http + .get("https://www.googleapis.com/books/v1/volumes/5MQFrgEACAAJ") + .pipe(finalize(() => this.busy.next(false))) + .subscribe({ + next: (data: any) => { + resolve({ + result: { + value: data.volumeInfo.averageRating, + }, + }); + }, + error: (error: HttpErrorResponse) => { + resolve({ + result: null, + error: { + type: error.status, + }, + }); + }, + }); + }); + } + + public ngOnDestroy(): void { + this.outputsSubject.complete(); + } +} + +/** + * A simple KPI data source to retrieve the ratings count of Harry Potter and the Sorcerer's Stone (book) via googleapis + */ +@Injectable() +export class RatingsCountKpiDataSource + extends DataSourceService + implements OnDestroy +{ + public static providerId = "RatingsCountKpiDataSource"; + + // Use this subject to communicate the data source's busy state + public busy = new BehaviorSubject(false); + + constructor(private http: HttpClient) { + super(); + } + + public async getFilteredData(): Promise { + this.busy.next(true); + return new Promise((resolve) => { + this.http + .get("https://www.googleapis.com/books/v1/volumes/5MQFrgEACAAJ") + .pipe(finalize(() => this.busy.next(false))) + .subscribe({ + next: (data: any) => { + resolve({ + result: { + value: data.volumeInfo.ratingsCount, + }, + }); + }, + error: (error: HttpErrorResponse) => { + resolve({ + result: null, + error: { + type: error.status, + }, + }); + }, + }); + }); + } + + public ngOnDestroy(): void { + this.outputsSubject.complete(); + } +} + +const fullKpiWidgetConfig: IWidget = { + id: "widget1", + type: "kpi", + pizzagna: { + [PizzagnaLayer.Configuration]: { + [DEFAULT_PIZZAGNA_ROOT]: { + providers: { + [WellKnownProviders.Refresher]: { + properties: { + // Configuring the refresher interval so that our data source is invoked every ten minutes + interval: 60 * 10, + enabled: true, + } as IRefresherProperties, + } as Partial, + }, + }, + header: { + properties: { + title: "Harry Potter and the Sorcerer's Stone", + subtitle: "By J. K. Rowling", + }, + }, + tiles: { + properties: { + nodes: ["kpi1"], + }, + }, + kpi1: { + id: "kpi1", + componentType: KpiComponent.lateLoadKey, + properties: { + widgetData: { + units: \\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\`out of 5 Stars\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\`, + label: \\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\`Average Rating\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\`, + }, + }, + providers: { + [WellKnownProviders.DataSource]: { + // Setting the data source providerId for the tile with id "kpi1" + providerId: AverageRatingKpiDataSource.providerId, + } as IProviderConfiguration, + [WellKnownProviders.Adapter]: { + providerId: NOVA_KPI_DATASOURCE_ADAPTER, + properties: { + componentId: "kpi1", + propertyPath: "widgetData", + }, + } as IProviderConfiguration, + }, + }, + }, + }, +}; + +const partialPropWidgetConfig: IWidget = { + id: "widget2", + type: "proportional", + metadata: { + // Set 'needsConfiguration' to true if the widget needs further configuration before it can be + // placed on the dashboard. The "Create Widget" button will be hidden in the wizard when this + // widget is selected. + needsConfiguration: true, + }, + pizzagna: { + [PizzagnaLayer.Configuration]: { + header: { + properties: { + title: "*New Proportional Widget*", + }, + }, + }, + }, +}; +\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\`, + "tutorials/widget-creation/widget-creation.module.ts": \\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\`// © 2022 SolarWinds Worldwide, LLC. All rights reserved. +// +// Permission is hereby granted, free of charge, to any person obtaining a copy +// of this software and associated documentation files (the "Software"), to +// deal in the Software without restriction, including without limitation the +// rights to use, copy, modify, merge, publish, distribute, sublicense, and/or +// sell copies of the Software, and to permit persons to whom the Software is +// furnished to do so, subject to the following conditions: +// +// The above copyright notice and this permission notice shall be included in +// all copies or substantial portions of the Software. +// +// THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR +// IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, +// FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE +// AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER +// LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, +// OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN +// THE SOFTWARE. + +import { CommonModule } from "@angular/common"; +import { HttpClientModule } from "@angular/common/http"; +import { NgModule } from "@angular/core"; +import { RouterModule } from "@angular/router"; + +import { + NuiButtonModule, + NuiDocsModule, + NuiImageModule, + NuiMessageModule, + NuiRepeatModule, + NuiSwitchModule, + NuiToastModule, + DEMO_PATH_TOKEN, +} from "@nova-ui/bits"; +import { NuiDashboardsModule } from "@nova-ui/dashboards"; + +import { WidgetCreationDocsComponent } from "./widget-creation-docs.component"; +import { + WidgetCreationComponent, + WidgetTemplateSelectionComponent, +} from "./widget-creation.component"; +import { getDemoFiles } from "../../../../demo-files-factory"; + +const routes = [ + { + path: "", + component: WidgetCreationDocsComponent, + data: { + srlc: { + hideIndicator: true, + }, + showThemeSwitcher: true, + }, + }, + { + path: "example", + component: WidgetCreationComponent, + data: { + srlc: { + hideIndicator: true, + }, + }, + }, +]; + +@NgModule({ + imports: [ + CommonModule, + HttpClientModule, + NuiDashboardsModule, + NuiDocsModule, + NuiMessageModule, + NuiSwitchModule, + NuiToastModule, + NuiButtonModule, + NuiRepeatModule, + NuiImageModule, + RouterModule.forChild(routes), + ], + declarations: [ + WidgetCreationDocsComponent, + WidgetCreationComponent, + WidgetTemplateSelectionComponent, + ], + providers: [ + { + provide: DEMO_PATH_TOKEN, + useValue: getDemoFiles("widget-creation"), + }, + ], +}) +export default class WidgetCreationModule {} +\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\`, + "tutorials/widget-editor-setup/widget-editor-setup-docs.component.ts": \\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\`// © 2022 SolarWinds Worldwide, LLC. All rights reserved. +// +// Permission is hereby granted, free of charge, to any person obtaining a copy +// of this software and associated documentation files (the "Software"), to +// deal in the Software without restriction, including without limitation the +// rights to use, copy, modify, merge, publish, distribute, sublicense, and/or +// sell copies of the Software, and to permit persons to whom the Software is +// furnished to do so, subject to the following conditions: +// +// The above copyright notice and this permission notice shall be included in +// all copies or substantial portions of the Software. +// +// THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR +// IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, +// FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE +// AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER +// LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, +// OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN +// THE SOFTWARE. + +import { Component } from "@angular/core"; + +@Component({ + selector: "nui-dashboard-widget-editor-docs", + templateUrl: "./widget-editor-setup-docs.component.html", + standalone: false, +}) +export class WidgetEditorDocsComponent {} +\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\`, + "tutorials/widget-editor-setup/widget-editor-setup.component.ts": \\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\`// © 2022 SolarWinds Worldwide, LLC. All rights reserved. +// +// Permission is hereby granted, free of charge, to any person obtaining a copy +// of this software and associated documentation files (the "Software"), to +// deal in the Software without restriction, including without limitation the +// rights to use, copy, modify, merge, publish, distribute, sublicense, and/or +// sell copies of the Software, and to permit persons to whom the Software is +// furnished to do so, subject to the following conditions: +// +// The above copyright notice and this permission notice shall be included in +// all copies or substantial portions of the Software. +// +// THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR +// IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, +// FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE +// AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER +// LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, +// OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN +// THE SOFTWARE. + +import { HttpClient, HttpErrorResponse } from "@angular/common/http"; +import { + ChangeDetectorRef, + Component, + Injectable, + OnDestroy, + OnInit, +} from "@angular/core"; +import { GridsterConfig, GridsterItem } from "angular-gridster2"; +import { BehaviorSubject } from "rxjs"; +import { finalize } from "rxjs/operators"; + +import { DataSourceService, IFilteringOutputs } from "@nova-ui/bits"; +import { + DATA_SOURCE, + DEFAULT_PIZZAGNA_ROOT, + IDashboard, + IKpiData, + IProviderConfiguration, + IRefresherProperties, + IWidget, + IWidgets, + KpiComponent, + NOVA_KPI_DATASOURCE_ADAPTER, + PizzagnaLayer, + ProviderRegistryService, + WellKnownPathKey, + WellKnownProviders, + WidgetTypesService, +} from "@nova-ui/dashboards"; + +/** + * A simple KPI data source to retrieve the average rating of Harry Potter and the Sorcerer's Stone (book) via googleapis + */ +@Injectable() +export class AverageRatingKpiDataSource + extends DataSourceService + implements OnDestroy +{ + // This is the ID we'll use to identify the provider + public static providerId = "AverageRatingKpiDataSource"; + + // Use this subject to communicate the data source's busy state + public busy = new BehaviorSubject(false); + + constructor(private http: HttpClient) { + super(); + } + + // In this example, getFilteredData is invoked every 10 minutes (Take a look at the refresher + // provider definition in the widget configuration below to see how the interval is set) + public async getFilteredData(): Promise { + this.busy.next(true); + return new Promise((resolve) => { + // *** Make a resource request to an external API (if needed) + this.http + .get("https://www.googleapis.com/books/v1/volumes/5MQFrgEACAAJ") + .pipe(finalize(() => this.busy.next(false))) + .subscribe({ + next: (data: any) => { + resolve({ + result: { + value: data.volumeInfo.averageRating, + }, + }); + }, + error: (error: HttpErrorResponse) => { + resolve({ + result: null, + error: { + type: error.status, + }, + }); + }, + }); + }); + } + + public ngOnDestroy(): void { + this.outputsSubject.complete(); + } +} + +/** + * A simple KPI data source to retrieve the ratings count of Harry Potter and the Sorcerer's Stone (book) via googleapis + */ +@Injectable() +export class RatingsCountKpiDataSource + extends DataSourceService + implements OnDestroy +{ + public static providerId = "RatingsCountKpiDataSource"; + + // Use this subject to communicate the data source's busy state + public busy = new BehaviorSubject(false); + + constructor(private http: HttpClient) { + super(); + } + + public async getFilteredData(): Promise { + this.busy.next(true); + return new Promise((resolve) => { + this.http + .get("https://www.googleapis.com/books/v1/volumes/5MQFrgEACAAJ") + .pipe(finalize(() => this.busy.next(false))) + .subscribe({ + next: (data: any) => { + resolve({ + result: { + value: data.volumeInfo.ratingsCount, + }, + }); + }, + error: (error: HttpErrorResponse) => { + resolve({ + result: null, + error: { + type: error.status, + }, + }); + }, + }); + }); + } + + public ngOnDestroy(): void { + this.outputsSubject.complete(); + } +} + +/** + * A component that instantiates the dashboard + */ +@Component({ + selector: "widget-editor-setup", + templateUrl: "./widget-editor-setup.component.html", + styleUrls: ["./widget-editor-setup.component.less"], + standalone: false, +}) +export class WidgetEditorSetupComponent implements OnInit { + // This variable will hold all the data needed to define the layout and behavior of the widgets. + // Pass this to the dashboard component's dashboard input in the template. + public dashboard: IDashboard | undefined; + + // Angular gridster requires a configuration object even if it's empty. + // Pass this to the dashboard component's gridsterConfig input in the template. + public gridsterConfig: GridsterConfig = {}; + + // Boolean which dashboard takes in as an input if its true it allows you to move widgets around. + public editMode: boolean = false; + + constructor( + // WidgetTypesService provides the widget's necessary structure information + private widgetTypesService: WidgetTypesService, + + // In general, the ProviderRegistryService is used for making entities available for injection into dynamically loaded components. + private providerRegistry: ProviderRegistryService, + private changeDetectorRef: ChangeDetectorRef + ) {} + + public ngOnInit(): void { + // Grabbing the widget's default template which will be needed as a parameter for setNode + const widgetTemplate = this.widgetTypesService.getWidgetType("kpi", 1); + // Registering our data sources as dropdown options in the widget editor/configurator + // Note: This could also be done in the parent module's constructor so that + // multiple dashboards could have access to the same widget template modification. + this.widgetTypesService.setNode( + // This is the template we grabbed above with getWidgetType + widgetTemplate, + // We are setting the editor/configurator part of the widget template + "configurator", + // This indicates which node you are changing and we want to change + // the data source providers available for selection in the editor. + WellKnownPathKey.DataSourceProviders, + // We are setting the data sources available for selection in the editor + [ + AverageRatingKpiDataSource.providerId, + RatingsCountKpiDataSource.providerId, + ] + ); + + // Registering the data sources available for injection into the KPI tiles. + // Note: Each tile of a KPI widget is assigned its own instance of a data source + this.providerRegistry.setProviders({ + [AverageRatingKpiDataSource.providerId]: { + provide: DATA_SOURCE, + useClass: AverageRatingKpiDataSource, + // Any dependencies that need to be injected into the provider must be listed here + deps: [HttpClient], + }, + [RatingsCountKpiDataSource.providerId]: { + provide: DATA_SOURCE, + useClass: RatingsCountKpiDataSource, + deps: [HttpClient], + }, + }); + + this.initializeDashboard(); + } + + /** Used for restoring widgets state */ + public reInitializeDashboard(): void { + // destroys the components and their providers so the dashboard can re init data + this.dashboard = undefined; + this.changeDetectorRef.detectChanges(); + + this.initializeDashboard(); + } + + public initializeDashboard(): void { + // We're using a static configuration object for this example (see widgetConfig at the bottom of the file), + // but this is where the widget's configuration could potentially be populated from a database + const kpiWidget = widgetConfig; + const widgetIndex: IWidgets = { + // Complete the KPI widget with information coming from its type definition + [kpiWidget.id]: + this.widgetTypesService.mergeWithWidgetType(kpiWidget), + }; + + // Setting the widget dimensions and position (this is for gridster) + const positions: Record = { + [kpiWidget.id]: { + cols: 4, + rows: 6, + y: 0, + x: 0, + }, + }; + + // Finally, assigning the variables we created above to the dashboard + this.dashboard = { + positions, + widgets: widgetIndex, + }; + } +} + +const widgetConfig: IWidget = { + id: "widget1", + type: "kpi", + pizzagna: { + [PizzagnaLayer.Configuration]: { + [DEFAULT_PIZZAGNA_ROOT]: { + providers: { + [WellKnownProviders.Refresher]: { + properties: { + // Configuring the refresher interval so that our data source is invoked every ten minutes + interval: 60 * 10, + enabled: true, + } as IRefresherProperties, + } as Partial, + }, + }, + header: { + properties: { + title: "Harry Potter and the Sorcerer's Stone", + subtitle: "By J. K. Rowling", + }, + }, + tiles: { + properties: { + nodes: ["kpi1"], + }, + }, + kpi1: { + id: "kpi1", + componentType: KpiComponent.lateLoadKey, + properties: { + widgetData: { + units: "out of 5 Stars", + label: "Average Rating", + }, + }, + providers: { + [WellKnownProviders.DataSource]: { + // Setting the data source providerId for the tile with id "kpi1" + providerId: AverageRatingKpiDataSource.providerId, + } as IProviderConfiguration, + [WellKnownProviders.Adapter]: { + providerId: NOVA_KPI_DATASOURCE_ADAPTER, + properties: { + componentId: "kpi1", + propertyPath: "widgetData", + }, + } as IProviderConfiguration, + }, + }, + }, + }, +}; +\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\`, + "tutorials/widget-editor-setup/widget-editor-setup.module.ts": \\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\`// © 2022 SolarWinds Worldwide, LLC. All rights reserved. +// +// Permission is hereby granted, free of charge, to any person obtaining a copy +// of this software and associated documentation files (the "Software"), to +// deal in the Software without restriction, including without limitation the +// rights to use, copy, modify, merge, publish, distribute, sublicense, and/or +// sell copies of the Software, and to permit persons to whom the Software is +// furnished to do so, subject to the following conditions: +// +// The above copyright notice and this permission notice shall be included in +// all copies or substantial portions of the Software. +// +// THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR +// IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, +// FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE +// AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER +// LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, +// OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN +// THE SOFTWARE. + +import { CommonModule } from "@angular/common"; +import { HttpClientModule } from "@angular/common/http"; +import { NgModule } from "@angular/core"; +import { RouterModule } from "@angular/router"; + +import { + NuiButtonModule, + NuiDocsModule, + NuiMessageModule, + NuiSwitchModule, + DEMO_PATH_TOKEN, +} from "@nova-ui/bits"; +import { NuiDashboardsModule } from "@nova-ui/dashboards"; + +import { WidgetEditorDocsComponent } from "./widget-editor-setup-docs.component"; +import { WidgetEditorSetupComponent } from "./widget-editor-setup.component"; +import { getDemoFiles } from "../../../../demo-files-factory"; + +const routes = [ + { + path: "", + component: WidgetEditorDocsComponent, + data: { + srlc: { + hideIndicator: true, + }, + showThemeSwitcher: true, + }, + }, + { + path: "example", + component: WidgetEditorSetupComponent, + data: { + srlc: { + hideIndicator: true, + }, + }, + }, +]; + +@NgModule({ + imports: [ + CommonModule, + HttpClientModule, + NuiDashboardsModule, + NuiDocsModule, + NuiMessageModule, + NuiSwitchModule, + NuiButtonModule, + RouterModule.forChild(routes), + ], + declarations: [WidgetEditorDocsComponent, WidgetEditorSetupComponent], + providers: [ + { + provide: DEMO_PATH_TOKEN, + useValue: getDemoFiles("widget-editor-setup"), + }, + ], +}) +export default class WidgetEditorSetupModule {} +\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\`, + "tutorials/widget-error-handling/widget-error-handling-docs.component.ts": \\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\`// © 2022 SolarWinds Worldwide, LLC. All rights reserved. +// +// Permission is hereby granted, free of charge, to any person obtaining a copy +// of this software and associated documentation files (the "Software"), to +// deal in the Software without restriction, including without limitation the +// rights to use, copy, modify, merge, publish, distribute, sublicense, and/or +// sell copies of the Software, and to permit persons to whom the Software is +// furnished to do so, subject to the following conditions: +// +// The above copyright notice and this permission notice shall be included in +// all copies or substantial portions of the Software. +// +// THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR +// IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, +// FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE +// AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER +// LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, +// OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN +// THE SOFTWARE. + +import { Component } from "@angular/core"; + +@Component({ + selector: "nui-widget-error-handling-docs", + templateUrl: "./widget-error-handling-docs.component.html", + standalone: false, +}) +export class WidgetErrorHandlingDocsComponent { + public fallbackAdapter = \\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\` +@Injectable() +export class StatusContentFallbackAdapter implements OnDestroy, IHasComponent { + + protected readonly destroy$ = new Subject(); + protected componentId: string; + + constructor(@Inject(PIZZAGNA_EVENT_BUS) protected eventBus: EventBus, + protected pizzagnaService: PizzagnaService) { + this.eventBus.getStream(DATA_SOURCE_OUTPUT) + .pipe(takeUntil(this.destroy$)).subscribe((event: IEvent>) => { + this.handleDataSourceOutput(event); + }); + } + + public ngOnDestroy(): void { + this.destroy$.next(); + this.destroy$.complete(); + } + + public setComponent(component: any, componentId: string) { + this.componentId = componentId; + } + + protected handleDataSourceOutput(event: IEvent>) { + this.pizzagnaService.setProperty({ + componentId: this.componentId, + propertyPath: ["fallbackKey"], + pizzagnaKey: PizzagnaLayer.Data, + }, typeof event.payload?.error?.type !== "undefined" ? event.payload?.error?.type.toString() : undefined); + } +}\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\`; + public errorsMap = \\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\` +export const ERROR_FALLBACK_MAP: Record = { + [HttpStatusCode.Unknown]: ErrorNodeKey.ErrorUnknown, + [HttpStatusCode.Forbidden]: ErrorNodeKey.ErrorForbidden, + [HttpStatusCode.NotFound]: ErrorNodeKey.ErrorNotFound, +}; +\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\`; + public errorNodes = \\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\` +export const ERROR_NODES: Record = { + [ErrorNodeKey.ErrorUnknown]: { + id: ErrorNodeKey.ErrorUnknown, + componentType: WidgetErrorComponent.lateLoadKey, + properties: { + image: "no-data-to-show", + title: $localize\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\`Whoops, something went wrong\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\`, + description: $localize\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\`There was an unexpected error.\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\`, + } as IWidgetErrorDisplayProperties, + }, + [ErrorNodeKey.ErrorForbidden]: { + id: ErrorNodeKey.ErrorForbidden, + componentType: WidgetErrorComponent.lateLoadKey, + properties: { + image: "no-data-to-show", + title: $localize\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\`403 - Forbidden\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\`, + description: $localize\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\`The requested action was forbidden.\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\`, + } as IWidgetErrorDisplayProperties, + }, + [ErrorNodeKey.ErrorNotFound]: { + id: ErrorNodeKey.ErrorNotFound, + componentType: WidgetErrorComponent.lateLoadKey, + properties: { + image: "no-data-to-show", + title: $localize\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\`404 - Not Found\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\`, + description: $localize\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\`The requested resource could not be found.\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\`, + } as IWidgetErrorDisplayProperties, + }, +};\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\`; + public widgetBodyContentNodesSignature = \\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\` +/** + * Retrieves an index of the basic widget body content nodes including fallback nodes + * + * @param mainContentNodeKey The key corresponding to the main body content node + * @param fallbackAdapterId The id for the adapter responsible for activating fallback content in case of an error + * @param fallbackMap A map of node keys to fallback content definitions + * @param fallbackNodes An index of fallback content definitions + * + * @returns An index of component configurations + */ +export function widgetBodyContentNodes( + mainContentNodeKey: string, + fallbackAdapterId = NOVA_STATUS_CONTENT_FALLBACK_ADAPTER, + fallbackMap: Record = ERROR_FALLBACK_MAP, + fallbackNodes: Record = ERROR_NODES +): Record { ... } +\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\`; +} +\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\`, + "tutorials/widget-error-handling/widget-error-handling.component.ts": \\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\`// © 2022 SolarWinds Worldwide, LLC. All rights reserved. +// +// Permission is hereby granted, free of charge, to any person obtaining a copy +// of this software and associated documentation files (the "Software"), to +// deal in the Software without restriction, including without limitation the +// rights to use, copy, modify, merge, publish, distribute, sublicense, and/or +// sell copies of the Software, and to permit persons to whom the Software is +// furnished to do so, subject to the following conditions: +// +// The above copyright notice and this permission notice shall be included in +// all copies or substantial portions of the Software. +// +// THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR +// IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, +// FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE +// AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER +// LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, +// OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN +// THE SOFTWARE. + +import { HttpClient, HttpErrorResponse } from "@angular/common/http"; +import { + ChangeDetectorRef, + Component, + Injectable, + OnDestroy, + OnInit, +} from "@angular/core"; +import { GridsterConfig, GridsterItem } from "angular-gridster2"; +import { BehaviorSubject } from "rxjs"; +import { finalize } from "rxjs/operators"; + +import { DataSourceService, IFilteringOutputs } from "@nova-ui/bits"; +import { + DATA_SOURCE, + DEFAULT_PIZZAGNA_ROOT, + HttpStatusCode, + IDashboard, + IKpiData, + IProviderConfiguration, + IRefresherProperties, + IWidget, + IWidgets, + KpiComponent, + NOVA_KPI_DATASOURCE_ADAPTER, + PizzagnaLayer, + ProviderRegistryService, + WellKnownPathKey, + WellKnownProviders, + WidgetTypesService, +} from "@nova-ui/dashboards"; + +/** + * A simple KPI data source to retrieve the average rating of Harry Potter and the Sorcerer's Stone (book) via googleapis + */ +@Injectable() +export class AverageRatingKpiDataSource + extends DataSourceService + implements OnDestroy +{ + // This is the ID we'll use to identify the provider + public static providerId = "AverageRatingKpiDataSource"; + + // Use this subject to communicate the data source's busy state + public busy = new BehaviorSubject(false); + + constructor(private http: HttpClient) { + super(); + } + + // In this example, getFilteredData is invoked every 10 minutes (Take a look at the refresher + // provider definition in the widget configuration below to see how the interval is set) + public async getFilteredData(): Promise { + this.busy.next(true); + return new Promise((resolve) => { + // *** Make a resource request to an external API (if needed) + this.http + .get("https://www.googleapis.com/books/v1/volumes/5MQFrgEACAAJ") + .pipe(finalize(() => this.busy.next(false))) + .subscribe({ + next: (data: any) => { + resolve({ + result: { + value: data.volumeInfo.averageRating, + }, + }); + }, + error: (error: HttpErrorResponse) => { + resolve({ + result: null, + error: { + type: error.status, + }, + }); + }, + }); + }); + } + + public ngOnDestroy(): void { + this.outputsSubject.complete(); + } +} + +/** + * A simple KPI data source to retrieve the average rating of Harry Potter and the Sorcerer's Stone (book) via googleapis + */ +@Injectable() +export class ErrorUnknownDataSource + extends DataSourceService + implements OnDestroy +{ + // This is the ID we'll use to identify the provider + public static providerId = "ErrorUnknownDataSource"; + + // Use this subject to communicate the data source's busy state + public busy = new BehaviorSubject(false); + + // In this example, getFilteredData is invoked every 10 minutes (Take a look at the refresher + // provider definition in the widget configuration below to see how the interval is set) + public async getFilteredData(): Promise { + this.busy.next(true); + const mockError = { + result: null, + error: { type: HttpStatusCode.Unknown }, + }; + this.busy.next(false); + return mockError; + } + + public ngOnDestroy(): void { + this.outputsSubject.complete(); + } +} + +/** + * A simple KPI data source to retrieve the ratings count of Harry Potter and the Sorcerer's Stone (book) via googleapis + */ +@Injectable() +export class ErrorForbiddenDataSource + extends DataSourceService + implements OnDestroy +{ + public static providerId = "ErrorForbiddenDataSource"; + + // Use this subject to communicate the data source's busy state + public busy = new BehaviorSubject(false); + + constructor(private http: HttpClient) { + super(); + } + + public async getFilteredData(): Promise { + this.busy.next(true); + // generate a 403 + return new Promise((resolve) => { + this.http + .get( + "http://www.mocky.io/v2/5ecc724a3200000f0023614a?mocky-delay=4000ms" + ) + .pipe(finalize(() => this.busy.next(false))) + .subscribe({ + error: (error: HttpErrorResponse) => { + resolve({ + result: null, + error: { + type: error.status, + }, + }); + }, + }); + }); + } + + public ngOnDestroy(): void { + this.outputsSubject.complete(); + } +} + +/** + * A simple KPI data source to retrieve the ratings count of Harry Potter and the Sorcerer's Stone (book) via googleapis + */ +@Injectable() +export class ErrorNotFoundDataSource + extends DataSourceService + implements OnDestroy +{ + public static providerId = "ErrorNotFoundDataSource"; + + // Use this subject to communicate the data source's busy state + public busy = new BehaviorSubject(false); + + constructor(private http: HttpClient) { + super(); + } + + public async getFilteredData(): Promise { + this.busy.next(true); + // generate a 404 + return new Promise((resolve) => { + this.http + .get( + "http://www.mocky.io/v2/5ec6bfd93200007800d75100?mocky-delay=1000ms" + ) + .pipe(finalize(() => this.busy.next(false))) + .subscribe({ + error: (error: HttpErrorResponse) => { + resolve({ + result: null, + error: { + type: error.status, + }, + }); + }, + }); + }); + } + + public ngOnDestroy(): void { + this.outputsSubject.complete(); + } +} + +/** + * A component that instantiates the dashboard + */ +@Component({ + selector: "widget-error-handling", + templateUrl: "./widget-error-handling.component.html", + styleUrls: ["./widget-error-handling.component.less"], + standalone: false, +}) +export class WidgetErrorHandlingComponent implements OnInit { + // This variable will hold all the data needed to define the layout and behavior of the widgets. + // Pass this to the dashboard component's dashboard input in the template. + public dashboard: IDashboard | undefined; + + // Angular gridster requires a configuration object even if it's empty. + // Pass this to the dashboard component's gridsterConfig input in the template. + public gridsterConfig: GridsterConfig = {}; + + // Boolean which dashboard takes in as an input if its true it allows you to move widgets around. + public editMode: boolean = false; + + constructor( + // WidgetTypesService provides the widget's necessary structure information + private widgetTypesService: WidgetTypesService, + + // In general, the ProviderRegistryService is used for making entities available for injection into dynamically loaded components. + private providerRegistry: ProviderRegistryService, + private changeDetectorRef: ChangeDetectorRef + ) {} + + public ngOnInit(): void { + // Grab the widget's default template which will be needed as a parameter for setNode. + const widgetTemplate = this.widgetTypesService.getWidgetType("kpi", 1); + // Register our data sources as dropdown options in the widget editor/configurator + // Note: This could also be done in the parent module's constructor so that + // multiple dashboards could have access to the same widget template modification. + this.widgetTypesService.setNode( + // This is the template we grabbed above with getWidgetType + widgetTemplate, + // We are setting the editor/configurator part of the widget template + "configurator", + // This indicates which node you are changing and we want to change + // the data source providers available for selection in the editor. + WellKnownPathKey.DataSourceProviders, + // We are setting the data sources available for selection in the editor + [ + ErrorUnknownDataSource.providerId, + ErrorForbiddenDataSource.providerId, + ErrorNotFoundDataSource.providerId, + AverageRatingKpiDataSource.providerId, + ] + ); + + // Register the data sources available for injection into the KPI tiles. + // Note: Each tile of a KPI widget is assigned its own instance of a data source + this.providerRegistry.setProviders({ + [ErrorUnknownDataSource.providerId]: { + provide: DATA_SOURCE, + useClass: ErrorUnknownDataSource, + // Any dependencies that need to be injected into the provider must be listed here + deps: [HttpClient], + }, + [ErrorForbiddenDataSource.providerId]: { + provide: DATA_SOURCE, + useClass: ErrorForbiddenDataSource, + deps: [HttpClient], + }, + [ErrorNotFoundDataSource.providerId]: { + provide: DATA_SOURCE, + useClass: ErrorNotFoundDataSource, + deps: [HttpClient], + }, + [AverageRatingKpiDataSource.providerId]: { + provide: DATA_SOURCE, + useClass: AverageRatingKpiDataSource, + // Any dependencies that need to be injected into the provider must be listed here + deps: [HttpClient], + }, + }); + + this.initializeDashboard(); + } + + /** Used for restoring widgets state */ + public reInitializeDashboard(): void { + // destroys the components and their providers so the dashboard can re init data + this.dashboard = undefined; + this.changeDetectorRef.detectChanges(); + + this.initializeDashboard(); + } + + public initializeDashboard(): void { + // We're using a static configuration object for this example (see widgetConfig at the bottom of the file), + // but this is where the widget's configuration could potentially be populated from a database + const kpiWidget = widgetConfig; + const widgetIndex: IWidgets = { + // Complete the KPI widget with information coming from its type definition + [kpiWidget.id]: + this.widgetTypesService.mergeWithWidgetType(kpiWidget), + }; + + // Setting the widget dimensions and position (this is for gridster) + const positions: Record = { + [kpiWidget.id]: { + cols: 4, + rows: 6, + y: 0, + x: 0, + }, + }; + + // Finally, assigning the variables we created above to the dashboard + this.dashboard = { + positions, + widgets: widgetIndex, + }; + } +} + +const widgetConfig: IWidget = { + id: "widget1", + type: "kpi", + pizzagna: { + [PizzagnaLayer.Configuration]: { + [DEFAULT_PIZZAGNA_ROOT]: { + providers: { + [WellKnownProviders.Refresher]: { + properties: { + // Configuring the refresher interval so that our data source is invoked every ten minutes + interval: 60 * 10, + enabled: true, + } as IRefresherProperties, + } as Partial, + }, + }, + header: { + properties: { + title: "Harry Potter and the Sorcerer's Stone", + subtitle: "By J. K. Rowling", + }, + }, + tiles: { + properties: { + nodes: ["kpi1"], + }, + }, + kpi1: { + id: "kpi1", + componentType: KpiComponent.lateLoadKey, + properties: { + widgetData: { + units: "out of 5 Stars", + label: "Average Rating", + }, + }, + providers: { + [WellKnownProviders.DataSource]: { + // Setting the data source providerId for the tile with id "kpi1" + providerId: ErrorUnknownDataSource.providerId, + } as IProviderConfiguration, + [WellKnownProviders.Adapter]: { + providerId: NOVA_KPI_DATASOURCE_ADAPTER, + properties: { + componentId: "kpi1", + propertyPath: "widgetData", + }, + } as IProviderConfiguration, + }, + }, + }, + }, +}; +\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\`, + "tutorials/widget-error-handling/widget-error-handling.module.ts": \\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\`// © 2022 SolarWinds Worldwide, LLC. All rights reserved. +// +// Permission is hereby granted, free of charge, to any person obtaining a copy +// of this software and associated documentation files (the "Software"), to +// deal in the Software without restriction, including without limitation the +// rights to use, copy, modify, merge, publish, distribute, sublicense, and/or +// sell copies of the Software, and to permit persons to whom the Software is +// furnished to do so, subject to the following conditions: +// +// The above copyright notice and this permission notice shall be included in +// all copies or substantial portions of the Software. +// +// THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR +// IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, +// FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE +// AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER +// LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, +// OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN +// THE SOFTWARE. + +import { CommonModule } from "@angular/common"; +import { HttpClientModule } from "@angular/common/http"; +import { NgModule } from "@angular/core"; +import { ReactiveFormsModule } from "@angular/forms"; +import { RouterModule } from "@angular/router"; + +import { + NuiButtonModule, + NuiDocsModule, + NuiFormFieldModule, + NuiIconModule, + NuiMessageModule, + NuiSwitchModule, + NuiTextboxModule, + DEMO_PATH_TOKEN, +} from "@nova-ui/bits"; +import { + NuiDashboardConfiguratorModule, + NuiDashboardsModule, +} from "@nova-ui/dashboards"; + +import { WidgetErrorHandlingDocsComponent } from "./widget-error-handling-docs.component"; +import { WidgetErrorHandlingComponent } from "./widget-error-handling.component"; +import { getDemoFiles } from "../../../../demo-files-factory"; + +const routes = [ + { + path: "", + component: WidgetErrorHandlingDocsComponent, + data: { + srlc: { + hideIndicator: true, + }, + showThemeSwitcher: true, + }, + }, + { + path: "example", + component: WidgetErrorHandlingComponent, + data: { + srlc: { + hideIndicator: true, + }, + }, + }, +]; + +@NgModule({ + imports: [ + CommonModule, + ReactiveFormsModule, + HttpClientModule, + NuiButtonModule, + NuiDashboardsModule, + NuiDashboardConfiguratorModule, + NuiDocsModule, + NuiFormFieldModule, + NuiIconModule, + NuiMessageModule, + NuiIconModule, + NuiTextboxModule, + NuiIconModule, + NuiSwitchModule, + RouterModule.forChild(routes), + ], + declarations: [ + WidgetErrorHandlingDocsComponent, + WidgetErrorHandlingComponent, + ], + providers: [ + { + provide: DEMO_PATH_TOKEN, + useValue: getDemoFiles("widget-error-handling"), + }, + ], +}) +export default class WidgetErrorHandlingModule {} +\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\`, + "types.ts": \\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\`// © 2022 SolarWinds Worldwide, LLC. All rights reserved. +// +// Permission is hereby granted, free of charge, to any person obtaining a copy +// of this software and associated documentation files (the "Software"), to +// deal in the Software without restriction, including without limitation the +// rights to use, copy, modify, merge, publish, distribute, sublicense, and/or +// sell copies of the Software, and to permit persons to whom the Software is +// furnished to do so, subject to the following conditions: +// +// The above copyright notice and this permission notice shall be included in +// all copies or substantial portions of the Software. +// +// THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR +// IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, +// FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE +// AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER +// LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, +// OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN +// THE SOFTWARE. + +export enum APOLLO_API_NAMESPACE { + COUNTRIES = "countries", +} +\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\`, + "widget-types/drilldown/drilldown-multi-request-widget/drilldown-multi-request-widget-example.component.ts": \\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\`// © 2022 SolarWinds Worldwide, LLC. All rights reserved. +// +// Permission is hereby granted, free of charge, to any person obtaining a copy +// of this software and associated documentation files (the "Software"), to +// deal in the Software without restriction, including without limitation the +// rights to use, copy, modify, merge, publish, distribute, sublicense, and/or +// sell copies of the Software, and to permit persons to whom the Software is +// furnished to do so, subject to the following conditions: +// +// The above copyright notice and this permission notice shall be included in +// all copies or substantial portions of the Software. +// +// THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR +// IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, +// FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE +// AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER +// LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, +// OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN +// THE SOFTWARE. + +import { HttpClient } from "@angular/common/http"; +import { + ChangeDetectorRef, + Component, + Injectable, + OnDestroy, + OnInit, +} from "@angular/core"; +import { GridsterConfig, GridsterItem } from "angular-gridster2"; +import { Apollo, gql } from "apollo-angular"; +import { BehaviorSubject, Observable, of, Subject } from "rxjs"; +// eslint-disable-next-line import/no-deprecated +import { finalize, map, switchMap, tap } from "rxjs/operators"; + +import { + DataSourceService, + IconStatus, + IDataField, + IFilters, + INovaFilters, +} from "@nova-ui/bits"; +import { + DATA_SOURCE, + DEFAULT_PIZZAGNA_ROOT, + IDashboard, + IDrilldownComponentsConfiguration, + IListWidgetConfiguration, + IProviderConfiguration, + IWidget, + IWidgets, + ListGroupItemComponent, + ListLeafItemComponent, + NOVA_DRILLDOWN_DATASOURCE_ADAPTER, + PizzagnaLayer, + ProviderRegistryService, + WellKnownPathKey, + WellKnownProviders, + WidgetTypesService, +} from "@nova-ui/dashboards"; + +import { APOLLO_API_NAMESPACE } from "../../../types"; + +/** + * A simple KPI data source to retrieve the average rating of Harry Potter and the Sorcerer's Stone (book) via googleapis + */ +@Injectable() +export class DrilldownDataSource + extends DataSourceService + implements OnDestroy +{ + // This is the ID we'll use to identify the provider + public static providerId = "DrilldownDataSource"; + + // Use this subject to communicate the data source's busy state + public busy = new BehaviorSubject(false); + + public dataFields: Partial[] = [ + { id: "Region", label: "Region name" }, + { id: "Subregion", label: "Subregion name" }, + ]; + + private drillState: string[] = []; + private groupBy: string[]; + private cache: any; + private lastDrillState: string[] = []; + private leafGroup: string = "country"; + private applyFilters$ = new Subject(); + + constructor(private http: HttpClient, private apollo: Apollo) { + super(); + + // TODO: remove Partial in vNext after marking dataType field as optional - NUI-5838 + ( + this.dataFieldsConfig.dataFields$ as BehaviorSubject< + Partial[] + > + ).next(this.dataFields); + + this.applyFilters$ + // eslint-disable-next-line import/no-deprecated + .pipe(switchMap((filters) => this.getData(filters))) + .subscribe(async (res) => { + this.outputsSubject.next(await this.getFilteredData(res)); + }); + } + + private groupedDataHistory: any[] = []; + + // In this example, getFilteredData is invoked every 10 minutes (Take a look at the refresher + // provider definition in the widget configuration below to see how the interval is set) + public async getFilteredData(data: any): Promise { + return of(data) + .pipe( + map((entries) => { + if (this.isDrillDown()) { + const activeDrillLvl = this.drillState.length; + const group = this.groupBy[activeDrillLvl]; + const lastGroupedValue = + this.getTransformedDataForGroup( + entries, + group, + getLast(this.drillState) + ); + + this.groupedDataHistory.push(lastGroupedValue); + + return lastGroupedValue; + } + + const mapIconsToEntries = entries.map((item: any) => ({ + ...item, + icon: "virtual-host", + icon_status: IconStatus.Up, + })); + this.groupedDataHistory.push(mapIconsToEntries); + const widgetInput = this.getOutput(entries); + + return widgetInput; + }) + ) + .toPromise(); + } + + public ngOnDestroy(): void { + this.outputsSubject.complete(); + } + + // redefine parent method + public async applyFilters(): Promise { + this.applyFilters$.next(this.getFilters()); + } + + private getQuery(key: string, value: string) { + const groupToRequestMap: Record = { + Region: \\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\`{ Region { name } }\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\`, + Subregion: \\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\`{ Subregion(filter: { region: { name: "\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\${value}" } } ) { name } }\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\`, + Country: \\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\`{ Country(filter: { subregion: { name: "\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\${value}" } } ) { name capital } }\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\`, + }; + + return gql\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\` + \\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\${groupToRequestMap[key]} + \\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\`; + } + + private getData(filters: INovaFilters): Observable { + this.drillState = filters.drillstate?.value; + this.groupBy = filters.group?.value; + const group = this.groupBy[this.drillState.length]; + const isDrillUp = this.drillState.length < this.lastDrillState.length; + + this.lastDrillState = [...this.drillState]; + + if (!this.drillState.length) { + this.groupedDataHistory.length = 0; + } + + this.busy.next(true); + + if (this.cache && (isDrillUp || this.isHome())) { + return of(this.cache).pipe( + map((data) => data.data[group]), + finalize(() => this.busy.next(false)) + ); + } else { + return this.apollo + .use(APOLLO_API_NAMESPACE.COUNTRIES) + .query({ + query: this.getQuery( + group || this.leafGroup, + getLast(this.drillState) + ), + }) + .pipe( + tap( + (data) => + (this.cache = { + data: { ...this.cache?.data, ...data?.data }, + }) + ), + map((data) => data.data[group || this.leafGroup]), + finalize(() => this.busy.next(false)) + ); + } + } + + private getTransformedDataForGroup( + data: any, + group: string, + drillStateValue: string + ) { + const fallback: string = \\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\`No \\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\${group} for \\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\${drillStateValue}\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\`; + const dataArr = Object.values(data).map((val: any) => ({ + id: val.name || fallback, + label: val.name || fallback, + statuses: [ + { key: "state_ok", value: val.name?.length }, + { + key: "status_unreachable", + value: generateNumberUpTo(100000), + }, + { key: "status_warning", value: generateNumberUpTo(10000) }, + { key: "status_unknown", value: generateNumberUpTo(1000) }, + ], + })); + + return dataArr; + } + + private isHome(): boolean { + return this.drillState.length === 0; + } + + private isDrillDown(): boolean { + return this.drillState.length !== this.groupBy.length; + } + + private getOutput(data: any) { + if (this.isHome()) { + this.groupedDataHistory.length = 0; + } + + const lastHistoryValue = getLast(this.groupedDataHistory); + + if (!lastHistoryValue) { + return data; + } + + return lastHistoryValue[getLast(this.drillState)] || lastHistoryValue; + } +} + +@Component({ + selector: "drilldown-multi-request-widget-example", + templateUrl: "./drilldown-multi-request-widget-example.component.html", + styleUrls: ["./drilldown-multi-request-widget-example.component.less"], + standalone: false, +}) +export class DrilldownMultiRequestWidgetExampleComponent implements OnInit { + // This variable will hold all the data needed to define the layout and behavior of the widgets. + // Pass this to the dashboard component's dashboard input in the template. + public dashboard: IDashboard | undefined; + + // Angular gridster requires a configuration object even if it's empty. + // Pass this to the dashboard component's gridsterConfig input in the template. + public gridsterConfig: GridsterConfig = {}; + + // Boolean passed as an input to the dashboard. When true, widgets can be moved, resized, removed, or edited + public editMode: boolean = false; + + constructor( + // WidgetTypesService provides the widget's necessary structure information + private widgetTypesService: WidgetTypesService, + private providerRegistry: ProviderRegistryService, + private changeDetectorRef: ChangeDetectorRef + ) {} + + public ngOnInit(): void { + // this.prepareNovaDashboards(); + this.initializeDashboard(); + const widgetTemplate = this.widgetTypesService.getWidgetType( + "drilldown", + 1 + ); + this.widgetTypesService.setNode( + widgetTemplate, + "configurator", + WellKnownPathKey.DataSourceProviders, + [DrilldownDataSource.providerId] + ); + + // Registering the data source for injection into the KPI tile. + // Note: Each tile of a KPI widget is assigned its own instance of the data source + this.providerRegistry.setProviders({ + [DrilldownDataSource.providerId]: { + provide: DATA_SOURCE, + useClass: DrilldownDataSource, + // Any dependencies that need to be injected into the provider must be listed here + deps: [HttpClient, Apollo], + }, + }); + } + + /** Used for restoring widgets state */ + public reInitializeDashboard(): void { + // destroys the components and their providers so the dashboard can re init data + this.dashboard = undefined; + this.changeDetectorRef.detectChanges(); + + this.initializeDashboard(); + } + + public initializeDashboard(): void { + // We're using a static configuration object for this example, but this is where + // the widget's configuration could potentially be populated from a database + const drilldownWidget = widgetConfig; + const widgets: IWidgets = { + // Complete the widget with information coming from its type definition + [drilldownWidget.id]: + this.widgetTypesService.mergeWithWidgetType(drilldownWidget), + }; + + // Setting the widget dimensions and position (this is for gridster) + const positions: Record = { + [drilldownWidget.id]: { + cols: 10, + rows: 10, + y: 0, + x: 0, + }, + }; + + // Finally, assigning the variables we created above to the dashboard + this.dashboard = { positions, widgets }; + } +} + +const widgetConfig: IWidget = { + id: "drilldown", + type: "drilldown", + pizzagna: { + [PizzagnaLayer.Configuration]: { + header: { + properties: { + title: "Drilldown Widget", + subtitle: "Countries BY continent THEN currency", + }, + }, + [DEFAULT_PIZZAGNA_ROOT]: { + providers: { + [WellKnownProviders.DataSource]: { + // Setting the data source providerId for the tile with id "kpi1" + providerId: DrilldownDataSource.providerId, + properties: {}, + } as IProviderConfiguration, + }, + }, + listWidget: { + providers: { + [WellKnownProviders.Adapter]: { + providerId: NOVA_DRILLDOWN_DATASOURCE_ADAPTER, + properties: { + // widget + navigationBarId: "navigationBar", + componentId: "listWidget", + dataPath: "data", + + // adapter props + drillstate: [], + groups: ["Region", "Subregion"], + groupBy: ["Region", "Subregion"], + + // components + componentsConfig: { + group: { + componentType: + ListGroupItemComponent.lateLoadKey, + properties: { + dataFieldIds: { + id: "id", + label: "label", + statuses: "statuses", + }, + }, + itemProperties: { + canNavigate: true, + }, + }, + leaf: { + componentType: + ListLeafItemComponent.lateLoadKey, + properties: { + dataFieldIds: { + icon: "icon", + status: "icon_status", + detailedUrl: "capital", + label: "name", + }, + }, + itemProperties: { + canNavigate: false, + }, + }, + } as IDrilldownComponentsConfiguration, + }, + }, + }, + properties: { + configuration: { + // FORMAT: + // componentType: ListLeafItemComponent.lateLoadKey, + // properties: { + // dataFieldIds: { + // icon: "", + // status: "code", + // detailedUrl: "capital", + // label: "name", + // }, + // }, + // + } as IListWidgetConfiguration, + }, + }, + }, + }, +}; + +const getLast = (arr: any[]) => arr[arr.length - 1]; + +const generateNumberUpTo = (upperLimit: number): number => + Math.floor(Math.random() * upperLimit + 1); +\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\`, + "widget-types/drilldown/drilldown-widget/data-mock.ts": \\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\`// © 2022 SolarWinds Worldwide, LLC. All rights reserved. +// +// Permission is hereby granted, free of charge, to any person obtaining a copy +// of this software and associated documentation files (the "Software"), to +// deal in the Software without restriction, including without limitation the +// rights to use, copy, modify, merge, publish, distribute, sublicense, and/or +// sell copies of the Software, and to permit persons to whom the Software is +// furnished to do so, subject to the following conditions: +// +// The above copyright notice and this permission notice shall be included in +// all copies or substantial portions of the Software. +// +// THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR +// IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, +// FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE +// AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER +// LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, +// OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN +// THE SOFTWARE. + +import { IconStatus } from "@nova-ui/bits"; + +export const GRAPH_DATA_MOCK = { + data: { + countries: [ + { + name: "Andorra", + code: "AD", + icon: "virtual-host", + icon_status: IconStatus.Up, + capital: "Andorra la Vella", + continent: { + name: "Europe", + }, + currency: "EUR", + languages: [ + { + name: "Catalan", + }, + ], + url: "https://en.wikipedia.org/wiki/Andorra", + }, + { + name: "United Arab Emirates", + code: "AE", + icon: "virtual-host", + icon_status: IconStatus.Up, + capital: "Abu Dhabi", + continent: { + name: "Asia", + }, + currency: "AED", + languages: [ + { + name: "Arabic", + }, + ], + url: "https://en.wikipedia.org/wiki/United_Arab_Emirates", + }, + { + name: "Afghanistan", + code: "AF", + icon: "virtual-host", + icon_status: IconStatus.Up, + capital: "Kabul", + continent: { + name: "Asia", + }, + currency: "AFN", + languages: [ + { + name: "Pashto", + }, + { + name: "Uzbek", + }, + { + name: "Turkmen", + }, + ], + url: "https://en.wikipedia.org/wiki/Afghanistan", + }, + { + name: "Antigua and Barbuda", + code: "AG", + icon: "virtual-host", + icon_status: IconStatus.Up, + capital: "Saint John's", + continent: { + name: "North America", + }, + currency: "XCD", + languages: [ + { + name: "English", + }, + ], + url: "https://en.wikipedia.org/wiki/Antigua_and_Barbuda", + }, + { + name: "Anguilla", + code: "AI", + icon: "virtual-host", + icon_status: IconStatus.Up, + capital: "The Valley", + continent: { + name: "North America", + }, + currency: "XCD", + languages: [ + { + name: "English", + }, + ], + url: "https://en.wikipedia.org/wiki/Anguilla", + }, + { + name: "Albania", + code: "AL", + icon: "virtual-host", + icon_status: IconStatus.Up, + capital: "Tirana", + continent: { + name: "Europe", + }, + currency: "ALL", + languages: [ + { + name: "Albanian", + }, + ], + url: "https://en.wikipedia.org/wiki/Albania", + }, + { + name: "Armenia", + code: "AM", + icon: "virtual-host", + icon_status: IconStatus.Up, + capital: "Yerevan", + continent: { + name: "Asia", + }, + currency: "AMD", + languages: [ + { + name: "Armenian", + }, + { + name: "Russian", + }, + ], + url: "https://en.wikipedia.org/wiki/Armenia", + }, + { + name: "Angola", + code: "AO", + icon: "virtual-host", + icon_status: IconStatus.Up, + capital: "Luanda", + continent: { + name: "Africa", + }, + currency: "AOA", + languages: [ + { + name: "Portuguese", + }, + ], + }, + { + name: "Antarctica", + code: "AQ", + icon: "virtual-host", + icon_status: IconStatus.Up, + capital: null, + continent: { + name: "Antarctica", + }, + currency: null, + languages: [], + }, + { + name: "Argentina", + code: "AR", + icon: "virtual-host", + icon_status: IconStatus.Up, + capital: "Buenos Aires", + continent: { + name: "South America", + }, + currency: "ARS", + languages: [ + { + name: "Spanish", + }, + { + name: "Guarani", + }, + ], + }, + { + name: "American Samoa", + code: "AS", + icon: "virtual-host", + icon_status: IconStatus.Up, + capital: "Pago Pago", + continent: { + name: "Oceania", + }, + currency: "USD", + languages: [ + { + name: "English", + }, + { + name: "Samoan", + }, + ], + }, + { + name: "Austria", + code: "AT", + icon: "virtual-host", + icon_status: IconStatus.Up, + capital: "Vienna", + continent: { + name: "Europe", + }, + currency: "EUR", + languages: [ + { + name: "German", + }, + ], + }, + { + name: "Australia", + code: "AU", + icon: "virtual-host", + icon_status: IconStatus.Up, + capital: "Canberra", + continent: { + name: "Oceania", + }, + currency: "AUD", + languages: [ + { + name: "English", + }, + ], + }, + { + name: "Aruba", + code: "AW", + icon: "virtual-host", + icon_status: IconStatus.Up, + capital: "Oranjestad", + continent: { + name: "North America", + }, + currency: "AWG", + languages: [ + { + name: "Dutch", + }, + { + name: "Panjabi / Punjabi", + }, + ], + }, + { + name: "Åland", + code: "AX", + icon: "virtual-host", + icon_status: IconStatus.Up, + capital: "Mariehamn", + continent: { + name: "Europe", + }, + currency: "EUR", + languages: [ + { + name: "Swedish", + }, + ], + }, + { + name: "Azerbaijan", + code: "AZ", + icon: "virtual-host", + icon_status: IconStatus.Up, + capital: "Baku", + continent: { + name: "Asia", + }, + currency: "AZN", + languages: [ + { + name: "Azerbaijani", + }, + ], + }, + { + name: "Bosnia and Herzegovina", + code: "BA", + icon: "virtual-host", + icon_status: IconStatus.Up, + capital: "Sarajevo", + continent: { + name: "Europe", + }, + currency: "BAM", + languages: [ + { + name: "Bosnian", + }, + { + name: "Croatian", + }, + { + name: "Serbian", + }, + ], + }, + { + name: "Barbados", + code: "BB", + icon: "virtual-host", + icon_status: IconStatus.Up, + capital: "Bridgetown", + continent: { + name: "North America", + }, + currency: "BBD", + languages: [ + { + name: "English", + }, + ], + }, + { + name: "Bangladesh", + code: "BD", + icon: "virtual-host", + icon_status: IconStatus.Up, + capital: "Dhaka", + continent: { + name: "Asia", + }, + currency: "BDT", + languages: [ + { + name: "Bengali", + }, + ], + }, + { + name: "Belgium", + code: "BE", + icon: "virtual-host", + icon_status: IconStatus.Up, + capital: "Brussels", + continent: { + name: "Europe", + }, + currency: "EUR", + languages: [ + { + name: "Dutch", + }, + { + name: "French", + }, + { + name: "German", + }, + ], + }, + { + name: "Burkina Faso", + code: "BF", + icon: "virtual-host", + icon_status: IconStatus.Up, + capital: "Ouagadougou", + continent: { + name: "Africa", + }, + currency: "XOF", + languages: [ + { + name: "French", + }, + { + name: "Peul", + }, + ], + }, + { + name: "Bulgaria", + code: "BG", + icon: "virtual-host", + icon_status: IconStatus.Up, + capital: "Sofia", + continent: { + name: "Europe", + }, + currency: "BGN", + languages: [ + { + name: "Bulgarian", + }, + ], + }, + { + name: "Bahrain", + code: "BH", + icon: "virtual-host", + icon_status: IconStatus.Up, + capital: "Manama", + continent: { + name: "Asia", + }, + currency: "BHD", + languages: [ + { + name: "Arabic", + }, + ], + }, + { + name: "Burundi", + code: "BI", + icon: "virtual-host", + icon_status: IconStatus.Up, + capital: "Bujumbura", + continent: { + name: "Africa", + }, + currency: "BIF", + languages: [ + { + name: "French", + }, + { + name: "Kirundi", + }, + ], + }, + { + name: "Benin", + code: "BJ", + icon: "virtual-host", + icon_status: IconStatus.Up, + capital: "Porto-Novo", + continent: { + name: "Africa", + }, + currency: "XOF", + languages: [ + { + name: "French", + }, + ], + }, + { + name: "Saint Barthélemy", + code: "BL", + icon: "virtual-host", + icon_status: IconStatus.Up, + capital: "Gustavia", + continent: { + name: "North America", + }, + currency: "EUR", + languages: [ + { + name: "French", + }, + ], + }, + { + name: "Bermuda", + code: "BM", + icon: "virtual-host", + icon_status: IconStatus.Up, + capital: "Hamilton", + continent: { + name: "North America", + }, + currency: "BMD", + languages: [ + { + name: "English", + }, + ], + }, + { + name: "Brunei", + code: "BN", + icon: "virtual-host", + icon_status: IconStatus.Up, + capital: "Bandar Seri Begawan", + continent: { + name: "Asia", + }, + currency: "BND", + languages: [ + { + name: "Malay", + }, + ], + }, + { + name: "Bolivia", + code: "BO", + icon: "virtual-host", + icon_status: IconStatus.Up, + capital: "Sucre", + continent: { + name: "South America", + }, + currency: "BOB,BOV", + languages: [ + { + name: "Spanish", + }, + { + name: "Aymara", + }, + { + name: "Quechua", + }, + ], + }, + { + name: "Bonaire", + code: "BQ", + icon: "virtual-host", + icon_status: IconStatus.Up, + capital: "Kralendijk", + continent: { + name: "North America", + }, + currency: "USD", + languages: [ + { + name: "Dutch", + }, + ], + }, + { + name: "Brazil", + code: "BR", + icon: "virtual-host", + icon_status: IconStatus.Up, + capital: "Brasília", + continent: { + name: "South America", + }, + currency: "BRL", + languages: [ + { + name: "Portuguese", + }, + ], + }, + { + name: "Bahamas", + code: "BS", + icon: "virtual-host", + icon_status: IconStatus.Up, + capital: "Nassau", + continent: { + name: "North America", + }, + currency: "BSD", + languages: [ + { + name: "English", + }, + ], + }, + { + name: "Bhutan", + code: "BT", + icon: "virtual-host", + icon_status: IconStatus.Up, + capital: "Thimphu", + continent: { + name: "Asia", + }, + currency: "BTN,INR", + languages: [ + { + name: "Dzongkha", + }, + ], + }, + { + name: "Bouvet Island", + code: "BV", + icon: "virtual-host", + icon_status: IconStatus.Up, + capital: null, + continent: { + name: "Antarctica", + }, + currency: "NOK", + languages: [ + { + name: "Norwegian", + }, + { + name: "Norwegian Bokmål", + }, + { + name: "Norwegian Nynorsk", + }, + ], + }, + { + name: "Botswana", + code: "BW", + icon: "virtual-host", + icon_status: IconStatus.Up, + capital: "Gaborone", + continent: { + name: "Africa", + }, + currency: "BWP", + languages: [ + { + name: "English", + }, + { + name: "Tswana", + }, + ], + }, + { + name: "Belarus", + code: "BY", + icon: "virtual-host", + icon_status: IconStatus.Up, + capital: "Minsk", + continent: { + name: "Europe", + }, + currency: "BYN", + languages: [ + { + name: "Belarusian", + }, + { + name: "Russian", + }, + ], + }, + { + name: "Belize", + code: "BZ", + icon: "virtual-host", + icon_status: IconStatus.Up, + capital: "Belmopan", + continent: { + name: "North America", + }, + currency: "BZD", + languages: [ + { + name: "English", + }, + { + name: "Spanish", + }, + ], + }, + { + name: "Canada", + code: "CA", + icon: "virtual-host", + icon_status: IconStatus.Up, + capital: "Ottawa", + continent: { + name: "North America", + }, + currency: "CAD", + languages: [ + { + name: "English", + }, + { + name: "French", + }, + ], + }, + { + name: "Cocos [Keeling] Islands", + code: "CC", + icon: "virtual-host", + icon_status: IconStatus.Up, + capital: "West Island", + continent: { + name: "Asia", + }, + currency: "AUD", + languages: [ + { + name: "English", + }, + ], + }, + { + name: "Democratic Republic of the Congo", + code: "CD", + icon: "virtual-host", + icon_status: IconStatus.Up, + capital: "Kinshasa", + continent: { + name: "Africa", + }, + currency: "CDF", + languages: [ + { + name: "French", + }, + { + name: "Lingala", + }, + { + name: "Kongo", + }, + { + name: "Swahili", + }, + { + name: "Luba-Katanga", + }, + ], + }, + { + name: "Central African Republic", + code: "CF", + icon: "virtual-host", + icon_status: IconStatus.Up, + capital: "Bangui", + continent: { + name: "Africa", + }, + currency: "XAF", + languages: [ + { + name: "French", + }, + { + name: "Sango", + }, + ], + }, + { + name: "Republic of the Congo", + code: "CG", + icon: "virtual-host", + icon_status: IconStatus.Up, + capital: "Brazzaville", + continent: { + name: "Africa", + }, + currency: "XAF", + languages: [ + { + name: "French", + }, + { + name: "Lingala", + }, + ], + }, + { + name: "Switzerland", + code: "CH", + icon: "virtual-host", + icon_status: IconStatus.Up, + capital: "Bern", + continent: { + name: "Europe", + }, + currency: "CHE,CHF,CHW", + languages: [ + { + name: "German", + }, + { + name: "French", + }, + { + name: "Italian", + }, + ], + }, + { + name: "Ivory Coast", + code: "CI", + icon: "virtual-host", + icon_status: IconStatus.Up, + capital: "Yamoussoukro", + continent: { + name: "Africa", + }, + currency: "XOF", + languages: [ + { + name: "French", + }, + ], + }, + { + name: "Cook Islands", + code: "CK", + icon: "virtual-host", + icon_status: IconStatus.Up, + capital: "Avarua", + continent: { + name: "Oceania", + }, + currency: "NZD", + languages: [ + { + name: "English", + }, + ], + }, + { + name: "Chile", + code: "CL", + icon: "virtual-host", + icon_status: IconStatus.Up, + capital: "Santiago", + continent: { + name: "South America", + }, + currency: "CLF,CLP", + languages: [ + { + name: "Spanish", + }, + ], + }, + { + name: "Cameroon", + code: "CM", + icon: "virtual-host", + icon_status: IconStatus.Up, + capital: "Yaoundé", + continent: { + name: "Africa", + }, + currency: "XAF", + languages: [ + { + name: "English", + }, + { + name: "French", + }, + ], + }, + { + name: "China", + code: "CN", + icon: "virtual-host", + icon_status: IconStatus.Up, + capital: "Beijing", + continent: { + name: "Asia", + }, + currency: "CNY", + languages: [ + { + name: "Chinese", + }, + ], + }, + { + name: "Colombia", + code: "CO", + icon: "virtual-host", + icon_status: IconStatus.Up, + capital: "Bogotá", + continent: { + name: "South America", + }, + currency: "COP", + languages: [ + { + name: "Spanish", + }, + ], + }, + { + name: "Costa Rica", + code: "CR", + icon: "virtual-host", + icon_status: IconStatus.Up, + capital: "San José", + continent: { + name: "North America", + }, + currency: "CRC", + languages: [ + { + name: "Spanish", + }, + ], + }, + { + name: "Cuba", + code: "CU", + icon: "virtual-host", + icon_status: IconStatus.Up, + capital: "Havana", + continent: { + name: "North America", + }, + currency: "CUC,CUP", + languages: [ + { + name: "Spanish", + }, + ], + }, + { + name: "Cape Verde", + code: "CV", + icon: "virtual-host", + icon_status: IconStatus.Up, + capital: "Praia", + continent: { + name: "Africa", + }, + currency: "CVE", + languages: [ + { + name: "Portuguese", + }, + ], + }, + { + name: "Curacao", + code: "CW", + icon: "virtual-host", + icon_status: IconStatus.Up, + capital: "Willemstad", + continent: { + name: "North America", + }, + currency: "ANG", + languages: [ + { + name: "Dutch", + }, + { + name: "Panjabi / Punjabi", + }, + { + name: "English", + }, + ], + }, + { + name: "Christmas Island", + code: "CX", + icon: "virtual-host", + icon_status: IconStatus.Up, + capital: "Flying Fish Cove", + continent: { + name: "Asia", + }, + currency: "AUD", + languages: [ + { + name: "English", + }, + ], + }, + { + name: "Cyprus", + code: "CY", + icon: "virtual-host", + icon_status: IconStatus.Up, + capital: "Nicosia", + continent: { + name: "Europe", + }, + currency: "EUR", + languages: [ + { + name: "Greek", + }, + { + name: "Turkish", + }, + { + name: "Armenian", + }, + ], + }, + { + name: "Czech Republic", + code: "CZ", + icon: "virtual-host", + icon_status: IconStatus.Up, + capital: "Prague", + continent: { + name: "Europe", + }, + currency: "CZK", + languages: [ + { + name: "Czech", + }, + { + name: "Slovak", + }, + ], + }, + { + name: "Germany", + code: "DE", + icon: "virtual-host", + icon_status: IconStatus.Up, + capital: "Berlin", + continent: { + name: "Europe", + }, + currency: "EUR", + languages: [ + { + name: "German", + }, + ], + }, + { + name: "Djibouti", + code: "DJ", + icon: "virtual-host", + icon_status: IconStatus.Up, + capital: "Djibouti", + continent: { + name: "Africa", + }, + currency: "DJF", + languages: [ + { + name: "French", + }, + { + name: "Arabic", + }, + ], + }, + { + name: "Denmark", + code: "DK", + icon: "virtual-host", + icon_status: IconStatus.Up, + capital: "Copenhagen", + continent: { + name: "Europe", + }, + currency: "DKK", + languages: [ + { + name: "Danish", + }, + ], + }, + { + name: "Dominica", + code: "DM", + icon: "virtual-host", + icon_status: IconStatus.Up, + capital: "Roseau", + continent: { + name: "North America", + }, + currency: "XCD", + languages: [ + { + name: "English", + }, + ], + }, + { + name: "Dominican Republic", + code: "DO", + icon: "virtual-host", + icon_status: IconStatus.Up, + capital: "Santo Domingo", + continent: { + name: "North America", + }, + currency: "DOP", + languages: [ + { + name: "Spanish", + }, + ], + }, + { + name: "Algeria", + code: "DZ", + icon: "virtual-host", + icon_status: IconStatus.Up, + capital: "Algiers", + continent: { + name: "Africa", + }, + currency: "DZD", + languages: [ + { + name: "Arabic", + }, + ], + }, + { + name: "Ecuador", + code: "EC", + icon: "virtual-host", + icon_status: IconStatus.Up, + capital: "Quito", + continent: { + name: "South America", + }, + currency: "USD", + languages: [ + { + name: "Spanish", + }, + ], + }, + { + name: "Estonia", + code: "EE", + icon: "virtual-host", + icon_status: IconStatus.Up, + capital: "Tallinn", + continent: { + name: "Europe", + }, + currency: "EUR", + languages: [ + { + name: "Estonian", + }, + ], + }, + { + name: "Egypt", + code: "EG", + icon: "virtual-host", + icon_status: IconStatus.Up, + capital: "Cairo", + continent: { + name: "Africa", + }, + currency: "EGP", + languages: [ + { + name: "Arabic", + }, + ], + }, + { + name: "Western Sahara", + code: "EH", + icon: "virtual-host", + icon_status: IconStatus.Up, + capital: "El Aaiún", + continent: { + name: "Africa", + }, + currency: "MAD,DZD,MRU", + languages: [ + { + name: "Spanish", + }, + ], + }, + { + name: "Eritrea", + code: "ER", + icon: "virtual-host", + icon_status: IconStatus.Up, + capital: "Asmara", + continent: { + name: "Africa", + }, + currency: "ERN", + languages: [ + { + name: "Tigrinya", + }, + { + name: "Arabic", + }, + { + name: "English", + }, + ], + }, + { + name: "Spain", + code: "ES", + icon: "virtual-host", + icon_status: IconStatus.Up, + capital: "Madrid", + continent: { + name: "Europe", + }, + currency: "EUR", + languages: [ + { + name: "Spanish", + }, + { + name: "Basque", + }, + { + name: "Catalan", + }, + { + name: "Galician", + }, + { + name: "Occitan", + }, + ], + }, + { + name: "Ethiopia", + code: "ET", + icon: "virtual-host", + icon_status: IconStatus.Up, + capital: "Addis Ababa", + continent: { + name: "Africa", + }, + currency: "ETB", + languages: [ + { + name: "Amharic", + }, + ], + }, + { + name: "Finland", + code: "FI", + icon: "virtual-host", + icon_status: IconStatus.Up, + capital: "Helsinki", + continent: { + name: "Europe", + }, + currency: "EUR", + languages: [ + { + name: "Finnish", + }, + { + name: "Swedish", + }, + ], + }, + { + name: "Fiji", + code: "FJ", + icon: "virtual-host", + icon_status: IconStatus.Up, + capital: "Suva", + continent: { + name: "Oceania", + }, + currency: "FJD", + languages: [ + { + name: "English", + }, + { + name: "Fijian", + }, + { + name: "Hindi", + }, + { + name: "Urdu", + }, + ], + }, + { + name: "Falkland Islands", + code: "FK", + icon: "virtual-host", + icon_status: IconStatus.Up, + capital: "Stanley", + continent: { + name: "South America", + }, + currency: "FKP", + languages: [ + { + name: "English", + }, + ], + }, + { + name: "Micronesia", + code: "FM", + icon: "virtual-host", + icon_status: IconStatus.Up, + capital: "Palikir", + continent: { + name: "Oceania", + }, + currency: "USD", + languages: [ + { + name: "English", + }, + ], + }, + { + name: "Faroe Islands", + code: "FO", + icon: "virtual-host", + icon_status: IconStatus.Up, + capital: "Tórshavn", + continent: { + name: "Europe", + }, + currency: "DKK", + languages: [ + { + name: "Faroese", + }, + ], + }, + { + name: "France", + code: "FR", + icon: "virtual-host", + icon_status: IconStatus.Up, + capital: "Paris", + continent: { + name: "Europe", + }, + currency: "EUR", + languages: [ + { + name: "French", + }, + ], + }, + { + name: "Gabon", + code: "GA", + icon: "virtual-host", + icon_status: IconStatus.Up, + capital: "Libreville", + continent: { + name: "Africa", + }, + currency: "XAF", + languages: [ + { + name: "French", + }, + ], + }, + { + name: "United Kingdom", + code: "GB", + icon: "virtual-host", + icon_status: IconStatus.Up, + capital: "London", + continent: { + name: "Europe", + }, + currency: "GBP", + languages: [ + { + name: "English", + }, + ], + }, + { + name: "Grenada", + code: "GD", + icon: "virtual-host", + icon_status: IconStatus.Up, + capital: "St. George's", + continent: { + name: "North America", + }, + currency: "XCD", + languages: [ + { + name: "English", + }, + ], + }, + { + name: "Georgia", + code: "GE", + icon: "virtual-host", + icon_status: IconStatus.Up, + capital: "Tbilisi", + continent: { + name: "Asia", + }, + currency: "GEL", + languages: [ + { + name: "Georgian", + }, + ], + }, + { + name: "French Guiana", + code: "GF", + icon: "virtual-host", + icon_status: IconStatus.Up, + capital: "Cayenne", + continent: { + name: "South America", + }, + currency: "EUR", + languages: [ + { + name: "French", + }, + ], + }, + { + name: "Guernsey", + code: "GG", + icon: "virtual-host", + icon_status: IconStatus.Up, + capital: "St. Peter Port", + continent: { + name: "Europe", + }, + currency: "GBP", + languages: [ + { + name: "English", + }, + { + name: "French", + }, + ], + }, + { + name: "Ghana", + code: "GH", + icon: "virtual-host", + icon_status: IconStatus.Up, + capital: "Accra", + continent: { + name: "Africa", + }, + currency: "GHS", + languages: [ + { + name: "English", + }, + ], + }, + { + name: "Gibraltar", + code: "GI", + icon: "virtual-host", + icon_status: IconStatus.Up, + capital: "Gibraltar", + continent: { + name: "Europe", + }, + currency: "GIP", + languages: [ + { + name: "English", + }, + ], + }, + { + name: "Greenland", + code: "GL", + icon: "virtual-host", + icon_status: IconStatus.Up, + capital: "Nuuk", + continent: { + name: "North America", + }, + currency: "DKK", + languages: [ + { + name: "Greenlandic", + }, + ], + }, + { + name: "Gambia", + code: "GM", + icon: "virtual-host", + icon_status: IconStatus.Up, + capital: "Banjul", + continent: { + name: "Africa", + }, + currency: "GMD", + languages: [ + { + name: "English", + }, + ], + }, + { + name: "Guinea", + code: "GN", + icon: "virtual-host", + icon_status: IconStatus.Up, + capital: "Conakry", + continent: { + name: "Africa", + }, + currency: "GNF", + languages: [ + { + name: "French", + }, + { + name: "Peul", + }, + ], + }, + { + name: "Guadeloupe", + code: "GP", + icon: "virtual-host", + icon_status: IconStatus.Up, + capital: "Basse-Terre", + continent: { + name: "North America", + }, + currency: "EUR", + languages: [ + { + name: "French", + }, + ], + }, + { + name: "Equatorial Guinea", + code: "GQ", + icon: "virtual-host", + icon_status: IconStatus.Up, + capital: "Malabo", + continent: { + name: "Africa", + }, + currency: "XAF", + languages: [ + { + name: "Spanish", + }, + { + name: "French", + }, + ], + }, + { + name: "Greece", + code: "GR", + icon: "virtual-host", + icon_status: IconStatus.Up, + capital: "Athens", + continent: { + name: "Europe", + }, + currency: "EUR", + languages: [ + { + name: "Greek", + }, + ], + }, + { + name: "South Georgia and the South Sandwich Islands", + code: "GS", + icon: "virtual-host", + icon_status: IconStatus.Up, + capital: "King Edward Point", + continent: { + name: "Antarctica", + }, + currency: "GBP", + languages: [ + { + name: "English", + }, + ], + }, + { + name: "Guatemala", + code: "GT", + icon: "virtual-host", + icon_status: IconStatus.Up, + capital: "Guatemala City", + continent: { + name: "North America", + }, + currency: "GTQ", + languages: [ + { + name: "Spanish", + }, + ], + }, + { + name: "Guam", + code: "GU", + icon: "virtual-host", + icon_status: IconStatus.Up, + capital: "Hagåtña", + continent: { + name: "Oceania", + }, + currency: "USD", + languages: [ + { + name: "English", + }, + { + name: "Chamorro", + }, + { + name: "Spanish", + }, + ], + }, + { + name: "Guinea-Bissau", + code: "GW", + icon: "virtual-host", + icon_status: IconStatus.Up, + capital: "Bissau", + continent: { + name: "Africa", + }, + currency: "XOF", + languages: [ + { + name: "Portuguese", + }, + ], + }, + { + name: "Guyana", + code: "GY", + icon: "virtual-host", + icon_status: IconStatus.Up, + capital: "Georgetown", + continent: { + name: "South America", + }, + currency: "GYD", + languages: [ + { + name: "English", + }, + ], + }, + { + name: "Hong Kong", + code: "HK", + icon: "virtual-host", + icon_status: IconStatus.Up, + capital: "City of Victoria", + continent: { + name: "Asia", + }, + currency: "HKD", + languages: [ + { + name: "Chinese", + }, + { + name: "English", + }, + ], + }, + { + name: "Heard Island and McDonald Islands", + code: "HM", + icon: "virtual-host", + icon_status: IconStatus.Up, + capital: null, + continent: { + name: "Antarctica", + }, + currency: "AUD", + languages: [ + { + name: "English", + }, + ], + }, + { + name: "Honduras", + code: "HN", + icon: "virtual-host", + icon_status: IconStatus.Up, + capital: "Tegucigalpa", + continent: { + name: "North America", + }, + currency: "HNL", + languages: [ + { + name: "Spanish", + }, + ], + }, + { + name: "Croatia", + code: "HR", + icon: "virtual-host", + icon_status: IconStatus.Up, + capital: "Zagreb", + continent: { + name: "Europe", + }, + currency: "HRK", + languages: [ + { + name: "Croatian", + }, + ], + }, + { + name: "Haiti", + code: "HT", + icon: "virtual-host", + icon_status: IconStatus.Up, + capital: "Port-au-Prince", + continent: { + name: "North America", + }, + currency: "HTG,USD", + languages: [ + { + name: "French", + }, + { + name: "Haitian", + }, + ], + }, + { + name: "Hungary", + code: "HU", + icon: "virtual-host", + icon_status: IconStatus.Up, + capital: "Budapest", + continent: { + name: "Europe", + }, + currency: "HUF", + languages: [ + { + name: "Hungarian", + }, + ], + }, + { + name: "Indonesia", + code: "ID", + icon: "virtual-host", + icon_status: IconStatus.Up, + capital: "Jakarta", + continent: { + name: "Asia", + }, + currency: "IDR", + languages: [ + { + name: "Indonesian", + }, + ], + }, + { + name: "Ireland", + code: "IE", + icon: "virtual-host", + icon_status: IconStatus.Up, + capital: "Dublin", + continent: { + name: "Europe", + }, + currency: "EUR", + languages: [ + { + name: "Irish", + }, + { + name: "English", + }, + ], + }, + { + name: "Israel", + code: "IL", + icon: "virtual-host", + icon_status: IconStatus.Up, + capital: "Jerusalem", + continent: { + name: "Asia", + }, + currency: "ILS", + languages: [ + { + name: "Hebrew", + }, + { + name: "Arabic", + }, + ], + }, + { + name: "Isle of Man", + code: "IM", + icon: "virtual-host", + icon_status: IconStatus.Up, + capital: "Douglas", + continent: { + name: "Europe", + }, + currency: "GBP", + languages: [ + { + name: "English", + }, + { + name: "Manx", + }, + ], + }, + { + name: "India", + code: "IN", + icon: "virtual-host", + icon_status: IconStatus.Up, + capital: "New Delhi", + continent: { + name: "Asia", + }, + currency: "INR", + languages: [ + { + name: "Hindi", + }, + { + name: "English", + }, + ], + }, + { + name: "British Indian Ocean Territory", + code: "IO", + icon: "virtual-host", + icon_status: IconStatus.Up, + capital: "Diego Garcia", + continent: { + name: "Asia", + }, + currency: "USD", + languages: [ + { + name: "English", + }, + ], + }, + { + name: "Iraq", + code: "IQ", + icon: "virtual-host", + icon_status: IconStatus.Up, + capital: "Baghdad", + continent: { + name: "Asia", + }, + currency: "IQD", + languages: [ + { + name: "Arabic", + }, + { + name: "Kurdish", + }, + ], + }, + { + name: "Iran", + code: "IR", + icon: "virtual-host", + icon_status: IconStatus.Up, + capital: "Tehran", + continent: { + name: "Asia", + }, + currency: "IRR", + languages: [ + { + name: "Persian", + }, + ], + }, + { + name: "Iceland", + code: "IS", + icon: "virtual-host", + icon_status: IconStatus.Up, + capital: "Reykjavik", + continent: { + name: "Europe", + }, + currency: "ISK", + languages: [ + { + name: "Icelandic", + }, + ], + }, + { + name: "Italy", + code: "IT", + icon: "virtual-host", + icon_status: IconStatus.Up, + capital: "Rome", + continent: { + name: "Europe", + }, + currency: "EUR", + languages: [ + { + name: "Italian", + }, + ], + }, + { + name: "Jersey", + code: "JE", + icon: "virtual-host", + icon_status: IconStatus.Up, + capital: "Saint Helier", + continent: { + name: "Europe", + }, + currency: "GBP", + languages: [ + { + name: "English", + }, + { + name: "French", + }, + ], + }, + { + name: "Jamaica", + code: "JM", + icon: "virtual-host", + icon_status: IconStatus.Up, + capital: "Kingston", + continent: { + name: "North America", + }, + currency: "JMD", + languages: [ + { + name: "English", + }, + ], + }, + { + name: "Jordan", + code: "JO", + icon: "virtual-host", + icon_status: IconStatus.Up, + capital: "Amman", + continent: { + name: "Asia", + }, + currency: "JOD", + languages: [ + { + name: "Arabic", + }, + ], + }, + { + name: "Japan", + code: "JP", + icon: "virtual-host", + icon_status: IconStatus.Up, + capital: "Tokyo", + continent: { + name: "Asia", + }, + currency: "JPY", + languages: [ + { + name: "Japanese", + }, + ], + }, + { + name: "Kenya", + code: "KE", + icon: "virtual-host", + icon_status: IconStatus.Up, + capital: "Nairobi", + continent: { + name: "Africa", + }, + currency: "KES", + languages: [ + { + name: "English", + }, + { + name: "Swahili", + }, + ], + }, + { + name: "Kyrgyzstan", + code: "KG", + icon: "virtual-host", + icon_status: IconStatus.Up, + capital: "Bishkek", + continent: { + name: "Asia", + }, + currency: "KGS", + languages: [ + { + name: "Kirghiz", + }, + { + name: "Russian", + }, + ], + }, + { + name: "Cambodia", + code: "KH", + icon: "virtual-host", + icon_status: IconStatus.Up, + capital: "Phnom Penh", + continent: { + name: "Asia", + }, + currency: "KHR", + languages: [ + { + name: "Cambodian", + }, + ], + }, + { + name: "Kiribati", + code: "KI", + icon: "virtual-host", + icon_status: IconStatus.Up, + capital: "South Tarawa", + continent: { + name: "Oceania", + }, + currency: "AUD", + languages: [ + { + name: "English", + }, + ], + }, + { + name: "Comoros", + code: "KM", + icon: "virtual-host", + icon_status: IconStatus.Up, + capital: "Moroni", + continent: { + name: "Africa", + }, + currency: "KMF", + languages: [ + { + name: "Arabic", + }, + { + name: "French", + }, + ], + }, + { + name: "Saint Kitts and Nevis", + code: "KN", + icon: "virtual-host", + icon_status: IconStatus.Up, + capital: "Basseterre", + continent: { + name: "North America", + }, + currency: "XCD", + languages: [ + { + name: "English", + }, + ], + }, + { + name: "North Korea", + code: "KP", + icon: "virtual-host", + icon_status: IconStatus.Up, + capital: "Pyongyang", + continent: { + name: "Asia", + }, + currency: "KPW", + languages: [ + { + name: "Korean", + }, + ], + }, + { + name: "South Korea", + code: "KR", + icon: "virtual-host", + icon_status: IconStatus.Up, + capital: "Seoul", + continent: { + name: "Asia", + }, + currency: "KRW", + languages: [ + { + name: "Korean", + }, + ], + }, + { + name: "Kuwait", + code: "KW", + icon: "virtual-host", + icon_status: IconStatus.Up, + capital: "Kuwait City", + continent: { + name: "Asia", + }, + currency: "KWD", + languages: [ + { + name: "Arabic", + }, + ], + }, + { + name: "Cayman Islands", + code: "KY", + icon: "virtual-host", + icon_status: IconStatus.Up, + capital: "George Town", + continent: { + name: "North America", + }, + currency: "KYD", + languages: [ + { + name: "English", + }, + ], + }, + { + name: "Kazakhstan", + code: "KZ", + icon: "virtual-host", + icon_status: IconStatus.Up, + capital: "Astana", + continent: { + name: "Asia", + }, + currency: "KZT", + languages: [ + { + name: "Kazakh", + }, + { + name: "Russian", + }, + ], + }, + { + name: "Laos", + code: "LA", + icon: "virtual-host", + icon_status: IconStatus.Up, + capital: "Vientiane", + continent: { + name: "Asia", + }, + currency: "LAK", + languages: [ + { + name: "Laotian", + }, + ], + }, + { + name: "Lebanon", + code: "LB", + icon: "virtual-host", + icon_status: IconStatus.Up, + capital: "Beirut", + continent: { + name: "Asia", + }, + currency: "LBP", + languages: [ + { + name: "Arabic", + }, + { + name: "French", + }, + ], + }, + { + name: "Saint Lucia", + code: "LC", + icon: "virtual-host", + icon_status: IconStatus.Up, + capital: "Castries", + continent: { + name: "North America", + }, + currency: "XCD", + languages: [ + { + name: "English", + }, + ], + }, + { + name: "Liechtenstein", + code: "LI", + icon: "virtual-host", + icon_status: IconStatus.Up, + capital: "Vaduz", + continent: { + name: "Europe", + }, + currency: "CHF", + languages: [ + { + name: "German", + }, + ], + }, + { + name: "Sri Lanka", + code: "LK", + icon: "virtual-host", + icon_status: IconStatus.Up, + capital: "Colombo", + continent: { + name: "Asia", + }, + currency: "LKR", + languages: [ + { + name: "Sinhalese", + }, + { + name: "Tamil", + }, + ], + }, + { + name: "Liberia", + code: "LR", + icon: "virtual-host", + icon_status: IconStatus.Up, + capital: "Monrovia", + continent: { + name: "Africa", + }, + currency: "LRD", + languages: [ + { + name: "English", + }, + ], + }, + { + name: "Lesotho", + code: "LS", + icon: "virtual-host", + icon_status: IconStatus.Up, + capital: "Maseru", + continent: { + name: "Africa", + }, + currency: "LSL,ZAR", + languages: [ + { + name: "English", + }, + { + name: "Southern Sotho", + }, + ], + }, + { + name: "Lithuania", + code: "LT", + icon: "virtual-host", + icon_status: IconStatus.Up, + capital: "Vilnius", + continent: { + name: "Europe", + }, + currency: "EUR", + languages: [ + { + name: "Lithuanian", + }, + ], + }, + { + name: "Luxembourg", + code: "LU", + icon: "virtual-host", + icon_status: IconStatus.Up, + capital: "Luxembourg", + continent: { + name: "Europe", + }, + currency: "EUR", + languages: [ + { + name: "French", + }, + { + name: "German", + }, + { + name: "Luxembourgish", + }, + ], + }, + { + name: "Latvia", + code: "LV", + icon: "virtual-host", + icon_status: IconStatus.Up, + capital: "Riga", + continent: { + name: "Europe", + }, + currency: "EUR", + languages: [ + { + name: "Latvian", + }, + ], + }, + { + name: "Libya", + code: "LY", + icon: "virtual-host", + icon_status: IconStatus.Up, + capital: "Tripoli", + continent: { + name: "Africa", + }, + currency: "LYD", + languages: [ + { + name: "Arabic", + }, + ], + }, + { + name: "Morocco", + code: "MA", + icon: "virtual-host", + icon_status: IconStatus.Up, + capital: "Rabat", + continent: { + name: "Africa", + }, + currency: "MAD", + languages: [ + { + name: "Arabic", + }, + ], + }, + { + name: "Monaco", + code: "MC", + icon: "virtual-host", + icon_status: IconStatus.Up, + capital: "Monaco", + continent: { + name: "Europe", + }, + currency: "EUR", + languages: [ + { + name: "French", + }, + ], + }, + { + name: "Moldova", + code: "MD", + icon: "virtual-host", + icon_status: IconStatus.Up, + capital: "Chișinău", + continent: { + name: "Europe", + }, + currency: "MDL", + languages: [ + { + name: "Romanian", + }, + ], + }, + { + name: "Montenegro", + code: "ME", + icon: "virtual-host", + icon_status: IconStatus.Up, + capital: "Podgorica", + continent: { + name: "Europe", + }, + currency: "EUR", + languages: [ + { + name: "Serbian", + }, + { + name: "Bosnian", + }, + { + name: "Albanian", + }, + { + name: "Croatian", + }, + ], + }, + { + name: "Saint Martin", + code: "MF", + icon: "virtual-host", + icon_status: IconStatus.Up, + capital: "Marigot", + continent: { + name: "North America", + }, + currency: "EUR", + languages: [ + { + name: "English", + }, + { + name: "French", + }, + { + name: "Dutch", + }, + ], + }, + { + name: "Madagascar", + code: "MG", + icon: "virtual-host", + icon_status: IconStatus.Up, + capital: "Antananarivo", + continent: { + name: "Africa", + }, + currency: "MGA", + languages: [ + { + name: "French", + }, + { + name: "Malagasy", + }, + ], + }, + { + name: "Marshall Islands", + code: "MH", + icon: "virtual-host", + icon_status: IconStatus.Up, + capital: "Majuro", + continent: { + name: "Oceania", + }, + currency: "USD", + languages: [ + { + name: "English", + }, + { + name: "Marshallese", + }, + ], + }, + { + name: "North Macedonia", + code: "MK", + icon: "virtual-host", + icon_status: IconStatus.Up, + capital: "Skopje", + continent: { + name: "Europe", + }, + currency: "MKD", + languages: [ + { + name: "Macedonian", + }, + ], + }, + { + name: "Mali", + code: "ML", + icon: "virtual-host", + icon_status: IconStatus.Up, + capital: "Bamako", + continent: { + name: "Africa", + }, + currency: "XOF", + languages: [ + { + name: "French", + }, + ], + }, + { + name: "Myanmar [Burma]", + code: "MM", + icon: "virtual-host", + icon_status: IconStatus.Up, + capital: "Naypyidaw", + continent: { + name: "Asia", + }, + currency: "MMK", + languages: [ + { + name: "Burmese", + }, + ], + }, + { + name: "Mongolia", + code: "MN", + icon: "virtual-host", + icon_status: IconStatus.Up, + capital: "Ulan Bator", + continent: { + name: "Asia", + }, + currency: "MNT", + languages: [ + { + name: "Mongolian", + }, + ], + }, + { + name: "Macao", + code: "MO", + icon: "virtual-host", + icon_status: IconStatus.Up, + capital: null, + continent: { + name: "Asia", + }, + currency: "MOP", + languages: [ + { + name: "Chinese", + }, + { + name: "Portuguese", + }, + ], + }, + { + name: "Northern Mariana Islands", + code: "MP", + icon: "virtual-host", + icon_status: IconStatus.Up, + capital: "Saipan", + continent: { + name: "Oceania", + }, + currency: "USD", + languages: [ + { + name: "English", + }, + { + name: "Chamorro", + }, + ], + }, + { + name: "Martinique", + code: "MQ", + icon: "virtual-host", + icon_status: IconStatus.Up, + capital: "Fort-de-France", + continent: { + name: "North America", + }, + currency: "EUR", + languages: [ + { + name: "French", + }, + ], + }, + { + name: "Mauritania", + code: "MR", + icon: "virtual-host", + icon_status: IconStatus.Up, + capital: "Nouakchott", + continent: { + name: "Africa", + }, + currency: "MRU", + languages: [ + { + name: "Arabic", + }, + ], + }, + { + name: "Montserrat", + code: "MS", + icon: "virtual-host", + icon_status: IconStatus.Up, + capital: "Plymouth", + continent: { + name: "North America", + }, + currency: "XCD", + languages: [ + { + name: "English", + }, + ], + }, + { + name: "Malta", + code: "MT", + icon: "virtual-host", + icon_status: IconStatus.Up, + capital: "Valletta", + continent: { + name: "Europe", + }, + currency: "EUR", + languages: [ + { + name: "Maltese", + }, + { + name: "English", + }, + ], + }, + { + name: "Mauritius", + code: "MU", + icon: "virtual-host", + icon_status: IconStatus.Up, + capital: "Port Louis", + continent: { + name: "Africa", + }, + currency: "MUR", + languages: [ + { + name: "English", + }, + ], + }, + { + name: "Maldives", + code: "MV", + icon: "virtual-host", + icon_status: IconStatus.Up, + capital: "Malé", + continent: { + name: "Asia", + }, + currency: "MVR", + languages: [ + { + name: "Divehi", + }, + ], + }, + { + name: "Malawi", + code: "MW", + icon: "virtual-host", + icon_status: IconStatus.Up, + capital: "Lilongwe", + continent: { + name: "Africa", + }, + currency: "MWK", + languages: [ + { + name: "English", + }, + { + name: "Chichewa", + }, + ], + }, + { + name: "Mexico", + code: "MX", + icon: "virtual-host", + icon_status: IconStatus.Up, + capital: "Mexico City", + continent: { + name: "North America", + }, + currency: "MXN", + languages: [ + { + name: "Spanish", + }, + ], + }, + { + name: "Malaysia", + code: "MY", + icon: "virtual-host", + icon_status: IconStatus.Up, + capital: "Kuala Lumpur", + continent: { + name: "Asia", + }, + currency: "MYR", + languages: [ + { + name: "Malay", + }, + ], + }, + { + name: "Mozambique", + code: "MZ", + icon: "virtual-host", + icon_status: IconStatus.Up, + capital: "Maputo", + continent: { + name: "Africa", + }, + currency: "MZN", + languages: [ + { + name: "Portuguese", + }, + ], + }, + { + name: "Namibia", + code: "NA", + icon: "virtual-host", + icon_status: IconStatus.Up, + capital: "Windhoek", + continent: { + name: "Africa", + }, + currency: "NAD,ZAR", + languages: [ + { + name: "English", + }, + { + name: "Afrikaans", + }, + ], + }, + { + name: "New Caledonia", + code: "NC", + icon: "virtual-host", + icon_status: IconStatus.Up, + capital: "Nouméa", + continent: { + name: "Oceania", + }, + currency: "XPF", + languages: [ + { + name: "French", + }, + ], + }, + { + name: "Niger", + code: "NE", + icon: "virtual-host", + icon_status: IconStatus.Up, + capital: "Niamey", + continent: { + name: "Africa", + }, + currency: "XOF", + languages: [ + { + name: "French", + }, + ], + }, + { + name: "Norfolk Island", + code: "NF", + icon: "virtual-host", + icon_status: IconStatus.Up, + capital: "Kingston", + continent: { + name: "Oceania", + }, + currency: "AUD", + languages: [ + { + name: "English", + }, + ], + }, + { + name: "Nigeria", + code: "NG", + icon: "virtual-host", + icon_status: IconStatus.Up, + capital: "Abuja", + continent: { + name: "Africa", + }, + currency: "NGN", + languages: [ + { + name: "English", + }, + ], + }, + { + name: "Nicaragua", + code: "NI", + icon: "virtual-host", + icon_status: IconStatus.Up, + capital: "Managua", + continent: { + name: "North America", + }, + currency: "NIO", + languages: [ + { + name: "Spanish", + }, + ], + }, + { + name: "Netherlands", + code: "NL", + icon: "virtual-host", + icon_status: IconStatus.Up, + capital: "Amsterdam", + continent: { + name: "Europe", + }, + currency: "EUR", + languages: [ + { + name: "Dutch", + }, + ], + }, + { + name: "Norway", + code: "NO", + icon: "virtual-host", + icon_status: IconStatus.Up, + capital: "Oslo", + continent: { + name: "Europe", + }, + currency: "NOK", + languages: [ + { + name: "Norwegian", + }, + { + name: "Norwegian Bokmål", + }, + { + name: "Norwegian Nynorsk", + }, + ], + }, + { + name: "Nepal", + code: "NP", + icon: "virtual-host", + icon_status: IconStatus.Up, + capital: "Kathmandu", + continent: { + name: "Asia", + }, + currency: "NPR", + languages: [ + { + name: "Nepali", + }, + ], + }, + { + name: "Nauru", + code: "NR", + icon: "virtual-host", + icon_status: IconStatus.Up, + capital: "Yaren", + continent: { + name: "Oceania", + }, + currency: "AUD", + languages: [ + { + name: "English", + }, + { + name: "Nauruan", + }, + ], + }, + { + name: "Niue", + code: "NU", + icon: "virtual-host", + icon_status: IconStatus.Up, + capital: "Alofi", + continent: { + name: "Oceania", + }, + currency: "NZD", + languages: [ + { + name: "English", + }, + ], + }, + { + name: "New Zealand", + code: "NZ", + icon: "virtual-host", + icon_status: IconStatus.Up, + capital: "Wellington", + continent: { + name: "Oceania", + }, + currency: "NZD", + languages: [ + { + name: "English", + }, + { + name: "Maori", + }, + ], + }, + { + name: "Oman", + code: "OM", + icon: "virtual-host", + icon_status: IconStatus.Up, + capital: "Muscat", + continent: { + name: "Asia", + }, + currency: "OMR", + languages: [ + { + name: "Arabic", + }, + ], + }, + { + name: "Panama", + code: "PA", + icon: "virtual-host", + icon_status: IconStatus.Up, + capital: "Panama City", + continent: { + name: "North America", + }, + currency: "PAB,USD", + languages: [ + { + name: "Spanish", + }, + ], + }, + { + name: "Peru", + code: "PE", + icon: "virtual-host", + icon_status: IconStatus.Up, + capital: "Lima", + continent: { + name: "South America", + }, + currency: "PEN", + languages: [ + { + name: "Spanish", + }, + ], + }, + { + name: "French Polynesia", + code: "PF", + icon: "virtual-host", + icon_status: IconStatus.Up, + capital: "Papeetē", + continent: { + name: "Oceania", + }, + currency: "XPF", + languages: [ + { + name: "French", + }, + ], + }, + { + name: "Papua New Guinea", + code: "PG", + icon: "virtual-host", + icon_status: IconStatus.Up, + capital: "Port Moresby", + continent: { + name: "Oceania", + }, + currency: "PGK", + languages: [ + { + name: "English", + }, + ], + }, + { + name: "Philippines", + code: "PH", + icon: "virtual-host", + icon_status: IconStatus.Up, + capital: "Manila", + continent: { + name: "Asia", + }, + currency: "PHP", + languages: [ + { + name: "English", + }, + ], + }, + { + name: "Pakistan", + code: "PK", + icon: "virtual-host", + icon_status: IconStatus.Up, + capital: "Islamabad", + continent: { + name: "Asia", + }, + currency: "PKR", + languages: [ + { + name: "English", + }, + { + name: "Urdu", + }, + ], + }, + { + name: "Poland", + code: "PL", + icon: "virtual-host", + icon_status: IconStatus.Up, + capital: "Warsaw", + continent: { + name: "Europe", + }, + currency: "PLN", + languages: [ + { + name: "Polish", + }, + ], + }, + { + name: "Saint Pierre and Miquelon", + code: "PM", + icon: "virtual-host", + icon_status: IconStatus.Up, + capital: "Saint-Pierre", + continent: { + name: "North America", + }, + currency: "EUR", + languages: [ + { + name: "French", + }, + ], + }, + { + name: "Pitcairn Islands", + code: "PN", + icon: "virtual-host", + icon_status: IconStatus.Up, + capital: "Adamstown", + continent: { + name: "Oceania", + }, + currency: "NZD", + languages: [ + { + name: "English", + }, + ], + }, + { + name: "Puerto Rico", + code: "PR", + icon: "virtual-host", + icon_status: IconStatus.Up, + capital: "San Juan", + continent: { + name: "North America", + }, + currency: "USD", + languages: [ + { + name: "Spanish", + }, + { + name: "English", + }, + ], + }, + { + name: "Palestine", + code: "PS", + icon: "virtual-host", + icon_status: IconStatus.Up, + capital: "Ramallah", + continent: { + name: "Asia", + }, + currency: "ILS", + languages: [ + { + name: "Arabic", + }, + ], + }, + { + name: "Portugal", + code: "PT", + icon: "virtual-host", + icon_status: IconStatus.Up, + capital: "Lisbon", + continent: { + name: "Europe", + }, + currency: "EUR", + languages: [ + { + name: "Portuguese", + }, + ], + }, + { + name: "Palau", + code: "PW", + icon: "virtual-host", + icon_status: IconStatus.Up, + capital: "Ngerulmud", + continent: { + name: "Oceania", + }, + currency: "USD", + languages: [ + { + name: "English", + }, + ], + }, + { + name: "Paraguay", + code: "PY", + icon: "virtual-host", + icon_status: IconStatus.Up, + capital: "Asunción", + continent: { + name: "South America", + }, + currency: "PYG", + languages: [ + { + name: "Spanish", + }, + { + name: "Guarani", + }, + ], + }, + { + name: "Qatar", + code: "QA", + icon: "virtual-host", + icon_status: IconStatus.Up, + capital: "Doha", + continent: { + name: "Asia", + }, + currency: "QAR", + languages: [ + { + name: "Arabic", + }, + ], + }, + { + name: "Réunion", + code: "RE", + icon: "virtual-host", + icon_status: IconStatus.Up, + capital: "Saint-Denis", + continent: { + name: "Africa", + }, + currency: "EUR", + languages: [ + { + name: "French", + }, + ], + }, + { + name: "Romania", + code: "RO", + icon: "virtual-host", + icon_status: IconStatus.Up, + capital: "Bucharest", + continent: { + name: "Europe", + }, + currency: "RON", + languages: [ + { + name: "Romanian", + }, + ], + }, + { + name: "Serbia", + code: "RS", + icon: "virtual-host", + icon_status: IconStatus.Up, + capital: "Belgrade", + continent: { + name: "Europe", + }, + currency: "RSD", + languages: [ + { + name: "Serbian", + }, + ], + }, + { + name: "Russia", + code: "RU", + icon: "virtual-host", + icon_status: IconStatus.Up, + capital: "Moscow", + continent: { + name: "Europe", + }, + currency: "RUB", + languages: [ + { + name: "Russian", + }, + ], + }, + { + name: "Rwanda", + code: "RW", + icon: "virtual-host", + icon_status: IconStatus.Up, + capital: "Kigali", + continent: { + name: "Africa", + }, + currency: "RWF", + languages: [ + { + name: "Rwandi", + }, + { + name: "English", + }, + { + name: "French", + }, + ], + }, + { + name: "Saudi Arabia", + code: "SA", + icon: "virtual-host", + icon_status: IconStatus.Up, + capital: "Riyadh", + continent: { + name: "Asia", + }, + currency: "SAR", + languages: [ + { + name: "Arabic", + }, + ], + }, + { + name: "Solomon Islands", + code: "SB", + icon: "virtual-host", + icon_status: IconStatus.Up, + capital: "Honiara", + continent: { + name: "Oceania", + }, + currency: "SBD", + languages: [ + { + name: "English", + }, + ], + }, + { + name: "Seychelles", + code: "SC", + icon: "virtual-host", + icon_status: IconStatus.Up, + capital: "Victoria", + continent: { + name: "Africa", + }, + currency: "SCR", + languages: [ + { + name: "French", + }, + { + name: "English", + }, + ], + }, + { + name: "Sudan", + code: "SD", + icon: "virtual-host", + icon_status: IconStatus.Up, + capital: "Khartoum", + continent: { + name: "Africa", + }, + currency: "SDG", + languages: [ + { + name: "Arabic", + }, + { + name: "English", + }, + ], + }, + { + name: "Sweden", + code: "SE", + icon: "virtual-host", + icon_status: IconStatus.Up, + capital: "Stockholm", + continent: { + name: "Europe", + }, + currency: "SEK", + languages: [ + { + name: "Swedish", + }, + ], + }, + { + name: "Singapore", + code: "SG", + icon: "virtual-host", + icon_status: IconStatus.Up, + capital: "Singapore", + continent: { + name: "Asia", + }, + currency: "SGD", + languages: [ + { + name: "English", + }, + { + name: "Malay", + }, + { + name: "Tamil", + }, + { + name: "Chinese", + }, + ], + }, + { + name: "Saint Helena", + code: "SH", + icon: "virtual-host", + icon_status: IconStatus.Up, + capital: "Jamestown", + continent: { + name: "Africa", + }, + currency: "SHP", + languages: [ + { + name: "English", + }, + ], + }, + { + name: "Slovenia", + code: "SI", + icon: "virtual-host", + icon_status: IconStatus.Up, + capital: "Ljubljana", + continent: { + name: "Europe", + }, + currency: "EUR", + languages: [ + { + name: "Slovenian", + }, + ], + }, + { + name: "Svalbard and Jan Mayen", + code: "SJ", + icon: "virtual-host", + icon_status: IconStatus.Up, + capital: "Longyearbyen", + continent: { + name: "Europe", + }, + currency: "NOK", + languages: [ + { + name: "Norwegian", + }, + ], + }, + { + name: "Slovakia", + code: "SK", + icon: "virtual-host", + icon_status: IconStatus.Up, + capital: "Bratislava", + continent: { + name: "Europe", + }, + currency: "EUR", + languages: [ + { + name: "Slovak", + }, + ], + }, + { + name: "Sierra Leone", + code: "SL", + icon: "virtual-host", + icon_status: IconStatus.Up, + capital: "Freetown", + continent: { + name: "Africa", + }, + currency: "SLL", + languages: [ + { + name: "English", + }, + ], + }, + { + name: "San Marino", + code: "SM", + icon: "virtual-host", + icon_status: IconStatus.Up, + capital: "City of San Marino", + continent: { + name: "Europe", + }, + currency: "EUR", + languages: [ + { + name: "Italian", + }, + ], + }, + { + name: "Senegal", + code: "SN", + icon: "virtual-host", + icon_status: IconStatus.Up, + capital: "Dakar", + continent: { + name: "Africa", + }, + currency: "XOF", + languages: [ + { + name: "French", + }, + ], + }, + { + name: "Somalia", + code: "SO", + icon: "virtual-host", + icon_status: IconStatus.Up, + capital: "Mogadishu", + continent: { + name: "Africa", + }, + currency: "SOS", + languages: [ + { + name: "Somalia", + }, + { + name: "Arabic", + }, + ], + }, + { + name: "Suriname", + code: "SR", + icon: "virtual-host", + icon_status: IconStatus.Up, + capital: "Paramaribo", + continent: { + name: "South America", + }, + currency: "SRD", + languages: [ + { + name: "Dutch", + }, + ], + }, + { + name: "South Sudan", + code: "SS", + icon: "virtual-host", + icon_status: IconStatus.Up, + capital: "Juba", + continent: { + name: "Africa", + }, + currency: "SSP", + languages: [ + { + name: "English", + }, + ], + }, + { + name: "São Tomé and Príncipe", + code: "ST", + icon: "virtual-host", + icon_status: IconStatus.Up, + capital: "São Tomé", + continent: { + name: "Africa", + }, + currency: "STN", + languages: [ + { + name: "Portuguese", + }, + ], + }, + { + name: "El Salvador", + code: "SV", + icon: "virtual-host", + icon_status: IconStatus.Up, + capital: "San Salvador", + continent: { + name: "North America", + }, + currency: "SVC,USD", + languages: [ + { + name: "Spanish", + }, + ], + }, + { + name: "Sint Maarten", + code: "SX", + icon: "virtual-host", + icon_status: IconStatus.Up, + capital: "Philipsburg", + continent: { + name: "North America", + }, + currency: "ANG", + languages: [ + { + name: "Dutch", + }, + { + name: "English", + }, + ], + }, + { + name: "Syria", + code: "SY", + icon: "virtual-host", + icon_status: IconStatus.Up, + capital: "Damascus", + continent: { + name: "Asia", + }, + currency: "SYP", + languages: [ + { + name: "Arabic", + }, + ], + }, + { + name: "Swaziland", + code: "SZ", + icon: "virtual-host", + icon_status: IconStatus.Up, + capital: "Lobamba", + continent: { + name: "Africa", + }, + currency: "SZL", + languages: [ + { + name: "English", + }, + { + name: "Swati", + }, + ], + }, + { + name: "Turks and Caicos Islands", + code: "TC", + icon: "virtual-host", + icon_status: IconStatus.Up, + capital: "Cockburn Town", + continent: { + name: "North America", + }, + currency: "USD", + languages: [ + { + name: "English", + }, + ], + }, + { + name: "Chad", + code: "TD", + icon: "virtual-host", + icon_status: IconStatus.Up, + capital: "N'Djamena", + continent: { + name: "Africa", + }, + currency: "XAF", + languages: [ + { + name: "French", + }, + { + name: "Arabic", + }, + ], + }, + { + name: "French Southern Territories", + code: "TF", + icon: "virtual-host", + icon_status: IconStatus.Up, + capital: "Port-aux-Français", + continent: { + name: "Antarctica", + }, + currency: "EUR", + languages: [ + { + name: "French", + }, + ], + }, + { + name: "Togo", + code: "TG", + icon: "virtual-host", + icon_status: IconStatus.Up, + capital: "Lomé", + continent: { + name: "Africa", + }, + currency: "XOF", + languages: [ + { + name: "French", + }, + ], + }, + { + name: "Thailand", + code: "TH", + icon: "virtual-host", + icon_status: IconStatus.Up, + capital: "Bangkok", + continent: { + name: "Asia", + }, + currency: "THB", + languages: [ + { + name: "Thai", + }, + ], + }, + { + name: "Tajikistan", + code: "TJ", + icon: "virtual-host", + icon_status: IconStatus.Up, + capital: "Dushanbe", + continent: { + name: "Asia", + }, + currency: "TJS", + languages: [ + { + name: "Tajik", + }, + { + name: "Russian", + }, + ], + }, + { + name: "Tokelau", + code: "TK", + icon: "virtual-host", + icon_status: IconStatus.Up, + capital: "Fakaofo", + continent: { + name: "Oceania", + }, + currency: "NZD", + languages: [ + { + name: "English", + }, + ], + }, + { + name: "East Timor", + code: "TL", + icon: "virtual-host", + icon_status: IconStatus.Up, + capital: "Dili", + continent: { + name: "Oceania", + }, + currency: "USD", + languages: [ + { + name: "Portuguese", + }, + ], + }, + { + name: "Turkmenistan", + code: "TM", + icon: "virtual-host", + icon_status: IconStatus.Up, + capital: "Ashgabat", + continent: { + name: "Asia", + }, + currency: "TMT", + languages: [ + { + name: "Turkmen", + }, + { + name: "Russian", + }, + ], + }, + { + name: "Tunisia", + code: "TN", + icon: "virtual-host", + icon_status: IconStatus.Up, + capital: "Tunis", + continent: { + name: "Africa", + }, + currency: "TND", + languages: [ + { + name: "Arabic", + }, + ], + }, + { + name: "Tonga", + code: "TO", + icon: "virtual-host", + icon_status: IconStatus.Up, + capital: "Nuku'alofa", + continent: { + name: "Oceania", + }, + currency: "TOP", + languages: [ + { + name: "English", + }, + { + name: "Tonga", + }, + ], + }, + { + name: "Turkey", + code: "TR", + icon: "virtual-host", + icon_status: IconStatus.Up, + capital: "Ankara", + continent: { + name: "Asia", + }, + currency: "TRY", + languages: [ + { + name: "Turkish", + }, + ], + }, + { + name: "Trinidad and Tobago", + code: "TT", + icon: "virtual-host", + icon_status: IconStatus.Up, + capital: "Port of Spain", + continent: { + name: "North America", + }, + currency: "TTD", + languages: [ + { + name: "English", + }, + ], + }, + { + name: "Tuvalu", + code: "TV", + icon: "virtual-host", + icon_status: IconStatus.Up, + capital: "Funafuti", + continent: { + name: "Oceania", + }, + currency: "AUD", + languages: [ + { + name: "English", + }, + ], + }, + { + name: "Taiwan", + code: "TW", + icon: "virtual-host", + icon_status: IconStatus.Up, + capital: "Taipei", + continent: { + name: "Asia", + }, + currency: "TWD", + languages: [ + { + name: "Chinese", + }, + ], + }, + { + name: "Tanzania", + code: "TZ", + icon: "virtual-host", + icon_status: IconStatus.Up, + capital: "Dodoma", + continent: { + name: "Africa", + }, + currency: "TZS", + languages: [ + { + name: "Swahili", + }, + { + name: "English", + }, + ], + }, + { + name: "Ukraine", + code: "UA", + icon: "virtual-host", + icon_status: IconStatus.Up, + capital: "Kyiv", + continent: { + name: "Europe", + }, + currency: "UAH", + languages: [ + { + name: "Ukrainian", + }, + ], + }, + { + name: "Uganda", + code: "UG", + icon: "virtual-host", + icon_status: IconStatus.Up, + capital: "Kampala", + continent: { + name: "Africa", + }, + currency: "UGX", + languages: [ + { + name: "English", + }, + { + name: "Swahili", + }, + ], + }, + { + name: "U.S. Minor Outlying Islands", + code: "UM", + icon: "virtual-host", + icon_status: IconStatus.Up, + capital: null, + continent: { + name: "Oceania", + }, + currency: "USD", + languages: [ + { + name: "English", + }, + ], + }, + { + name: "United States", + code: "US", + icon: "virtual-host", + icon_status: IconStatus.Up, + capital: "Washington D.C.", + continent: { + name: "North America", + }, + currency: "USD,USN,USS", + languages: [ + { + name: "English", + }, + ], + }, + { + name: "Uruguay", + code: "UY", + icon: "virtual-host", + icon_status: IconStatus.Up, + capital: "Montevideo", + continent: { + name: "South America", + }, + currency: "UYI,UYU", + languages: [ + { + name: "Spanish", + }, + ], + }, + { + name: "Uzbekistan", + code: "UZ", + icon: "virtual-host", + icon_status: IconStatus.Up, + capital: "Tashkent", + continent: { + name: "Asia", + }, + currency: "UZS", + languages: [ + { + name: "Uzbek", + }, + { + name: "Russian", + }, + ], + }, + { + name: "Vatican City", + code: "VA", + icon: "virtual-host", + icon_status: IconStatus.Up, + capital: "Vatican City", + continent: { + name: "Europe", + }, + currency: "EUR", + languages: [ + { + name: "Italian", + }, + { + name: "Latin", + }, + ], + }, + { + name: "Saint Vincent and the Grenadines", + code: "VC", + icon: "virtual-host", + icon_status: IconStatus.Up, + capital: "Kingstown", + continent: { + name: "North America", + }, + currency: "XCD", + languages: [ + { + name: "English", + }, + ], + }, + { + name: "Venezuela", + code: "VE", + icon: "virtual-host", + icon_status: IconStatus.Up, + capital: "Caracas", + continent: { + name: "South America", + }, + currency: "VES", + languages: [ + { + name: "Spanish", + }, + ], + }, + { + name: "British Virgin Islands", + code: "VG", + icon: "virtual-host", + icon_status: IconStatus.Up, + capital: "Road Town", + continent: { + name: "North America", + }, + currency: "USD", + languages: [ + { + name: "English", + }, + ], + }, + { + name: "U.S. Virgin Islands", + code: "VI", + icon: "virtual-host", + icon_status: IconStatus.Up, + capital: "Charlotte Amalie", + continent: { + name: "North America", + }, + currency: "USD", + languages: [ + { + name: "English", + }, + ], + }, + { + name: "Vietnam", + code: "VN", + icon: "virtual-host", + icon_status: IconStatus.Up, + capital: "Hanoi", + continent: { + name: "Asia", + }, + currency: "VND", + languages: [ + { + name: "Vietnamese", + }, + ], + }, + { + name: "Vanuatu", + code: "VU", + icon: "virtual-host", + icon_status: IconStatus.Up, + capital: "Port Vila", + continent: { + name: "Oceania", + }, + currency: "VUV", + languages: [ + { + name: "Bislama", + }, + { + name: "English", + }, + { + name: "French", + }, + ], + }, + { + name: "Wallis and Futuna", + code: "WF", + icon: "virtual-host", + icon_status: IconStatus.Up, + capital: "Mata-Utu", + continent: { + name: "Oceania", + }, + currency: "XPF", + languages: [ + { + name: "French", + }, + ], + }, + { + name: "Samoa", + code: "WS", + icon: "virtual-host", + icon_status: IconStatus.Up, + capital: "Apia", + continent: { + name: "Oceania", + }, + currency: "WST", + languages: [ + { + name: "Samoan", + }, + { + name: "English", + }, + ], + }, + { + name: "Kosovo", + code: "XK", + icon: "virtual-host", + icon_status: IconStatus.Up, + capital: "Pristina", + continent: { + name: "Europe", + }, + currency: "EUR", + languages: [ + { + name: "Albanian", + }, + { + name: "Serbian", + }, + ], + }, + { + name: "Yemen", + code: "YE", + icon: "virtual-host", + icon_status: IconStatus.Up, + capital: "Sana'a", + continent: { + name: "Asia", + }, + currency: "YER", + languages: [ + { + name: "Arabic", + }, + ], + }, + { + name: "Mayotte", + code: "YT", + icon: "virtual-host", + icon_status: IconStatus.Up, + capital: "Mamoudzou", + continent: { + name: "Africa", + }, + currency: "EUR", + languages: [ + { + name: "French", + }, + ], + }, + { + name: "South Africa", + code: "ZA", + icon: "virtual-host", + icon_status: IconStatus.Up, + capital: "Pretoria", + continent: { + name: "Africa", + }, + currency: "ZAR", + languages: [ + { + name: "Afrikaans", + }, + { + name: "English", + }, + { + name: "South Ndebele", + }, + { + name: "Southern Sotho", + }, + { + name: "Swati", + }, + { + name: "Tswana", + }, + { + name: "Tsonga", + }, + { + name: "Venda", + }, + { + name: "Xhosa", + }, + { + name: "Zulu", + }, + ], + }, + { + name: "Zambia", + code: "ZM", + icon: "virtual-host", + icon_status: IconStatus.Up, + capital: "Lusaka", + continent: { + name: "Africa", + }, + currency: "ZMW", + languages: [ + { + name: "English", + }, + ], + }, + { + name: "Zimbabwe", + code: "ZW", + icon: "virtual-host", + icon_status: IconStatus.Up, + capital: "Harare", + continent: { + name: "Africa", + }, + currency: "USD,ZAR,BWP,GBP,AUD,CNY,INR,JPY", + languages: [ + { + name: "English", + }, + { + name: "Shona", + }, + { + name: "North Ndebele", + }, + ], + }, + ], + }, +}; +\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\`, + "widget-types/drilldown/drilldown-widget/drilldown-widget-example.component.ts": \\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\`// © 2022 SolarWinds Worldwide, LLC. All rights reserved. +// +// Permission is hereby granted, free of charge, to any person obtaining a copy +// of this software and associated documentation files (the "Software"), to +// deal in the Software without restriction, including without limitation the +// rights to use, copy, modify, merge, publish, distribute, sublicense, and/or +// sell copies of the Software, and to permit persons to whom the Software is +// furnished to do so, subject to the following conditions: +// +// The above copyright notice and this permission notice shall be included in +// all copies or substantial portions of the Software. +// +// THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR +// IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, +// FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE +// AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER +// LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, +// OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN +// THE SOFTWARE. + +import { HttpClient } from "@angular/common/http"; +import { + ChangeDetectorRef, + Component, + Injectable, + OnDestroy, + OnInit, +} from "@angular/core"; +import { GridsterConfig, GridsterItem } from "angular-gridster2"; +import { Apollo, gql } from "apollo-angular"; +import groupBy from "lodash/groupBy"; +import { BehaviorSubject, Observable, of } from "rxjs"; +import { catchError, delay, filter, map } from "rxjs/operators"; + +import { + DataSourceFeatures, + IconStatus, + IDataField, + IDataSource, + IDataSourceFeatures, + IDataSourceFeaturesConfiguration, + INovaFilters, + LoggerService, + ServerSideDataSource, + IFilters, +} from "@nova-ui/bits"; +import { + DATA_SOURCE, + DEFAULT_PIZZAGNA_ROOT, + IDashboard, + IDrilldownComponentsConfiguration, + IListWidgetConfiguration, + IProviderConfiguration, + IWidget, + IWidgets, + ListGroupItemComponent, + ListLeafItemComponent, + NOVA_DRILLDOWN_DATASOURCE_ADAPTER, + PizzagnaLayer, + ProviderRegistryService, + WellKnownPathKey, + WellKnownProviders, + WidgetTypesService, +} from "@nova-ui/dashboards"; + +import { DrilldownDataSource } from "./mock-data-source"; + +/** + * A simple KPI data source to retrieve the average rating of Harry Potter and the Sorcerer's Stone (book) via googleapis + */ +@Injectable() +export class DrilldownDataSourceRealApi + extends ServerSideDataSource + implements OnDestroy, IDataSource +{ + // This is the ID we'll use to identify the provider + public static providerId = "DrilldownDataSourceRealApi"; + + // Use this subject to communicate the data source's busy state + public busy = new BehaviorSubject(false); + public dataFields: Partial[] = [ + { id: "regionName", label: "Region name" }, + { id: "subregionName", label: "Subregion name" }, + ]; + + public features: IDataSourceFeaturesConfiguration; + private supportedFeatures: IDataSourceFeatures = { + search: { enabled: true }, + }; + + private drillState: string[] = []; + private groupBy: string[]; + + constructor( + private logger: LoggerService, + private http: HttpClient, + private apollo: Apollo + ) { + super(); + this.features = new DataSourceFeatures(this.supportedFeatures); + // TODO: remove Partial in vNext after marking dataType field as optional - NUI-5838 + ( + this.dataFieldsConfig.dataFields$ as BehaviorSubject< + Partial[] + > + ).next(this.dataFields); + } + + private groupedDataHistory: Array> = []; + + // In this example, getFilteredData is invoked every 10 minutes (Take a look at the refresher + // provider definition in the widget configuration below to see how the interval is set) + public async getFilteredData(data: IFilters): Promise { + return of(data) + .pipe( + filter(() => !!this.drillState), + map((countries) => { + const lastHistory = () => getLast(this.groupedDataHistory); + + if (!this.drillState.length && !this.groupBy.length) { + return countries; + } + + // adding "ROOT" as a root level for drilling + const fullDrillState = ["ROOT", ...this.drillState]; + const activeDrillLvl = fullDrillState.length; + const historyLvl = this.groupedDataHistory.length; + + // checking how many lvls we have to group for drilling, in case some are missed + const drillLvlDiff = activeDrillLvl - historyLvl; + + if (!drillLvlDiff) { + return lastHistory() || countries; + } + + const drillToGroup = fullDrillState.slice( + fullDrillState.length - drillLvlDiff + ); + + for (const drill of drillToGroup) { + const drillIdx = fullDrillState.findIndex( + (v) => v === drill + ); + const group = this.groupBy[drillIdx]; + + if (group) { + const dataToGroup = lastHistory() + ? lastHistory()[drill] + : countries; + const lastGroupedValue = groupBy( + dataToGroup, + group + ); + + this.groupedDataHistory.push(lastGroupedValue); + } + } + + // take last if we have all data grouped + if (this.groupBy.length === this.drillState.length) { + return lastHistory()[getLast(this.drillState)]; + } + + // get groping and transform to raw data format + return this.getGroupsWidgetData(lastHistory()); + }) + ) + .toPromise(); + } + + public ngOnDestroy(): void { + this.outputsSubject.complete(); + } + + // This method is expected to return all data needed for repeat/paginator/filterGroups in order to work. + // In case of custom filtering participants feel free to extend INovaFilteringOutputs. + protected getBackendData(filters: INovaFilters): Observable { + const mainRequest = this.apollo.watchQuery<{ countries: any }>({ + query: this.generateQuery(filters), + }); + + return mainRequest.valueChanges.pipe( + // mock delay + delay(300), + // data mapping, !DS specific! + map((res) => res.data.countries), + // adds mock icons to be displayed on leaf nodes !DS specific! + map((res: any[]) => + res.map((v) => ({ + ...v, + icon: "virtual-host", + icon_status: IconStatus.Up, + subregionName: + v.subregion?.name || "No Subregion Specified", + regionName: + v.subregion?.region?.name || "No Region Specified", + })) + ), + catchError((e) => { + this.logger.error(e); + return of({} as any); + }) + ); + } + + private generateQuery(filters: INovaFilters) { + const { search } = filters; + const searchValue = search?.value ? \\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\`^[\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\${search.value}]*\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\` : ""; + + const queryString = \\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\` + query { + countries(filter: {name: {regex: "\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\${searchValue}"} }) { + name + native + capital + languages { + name + } + currencies + subdivisions { + name + } + } + } + \\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\`; + + return gql\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\` + \\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\${queryString} + \\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\`; + } + + // Overrides default ServerSideDataSource.beforeApplyFilters implementation + // to save some filters that are used internally + // -- !DS specific + protected beforeApplyFilters(filters: INovaFilters): void { + this.busy.next(true); + + this.drillState = filters.drillstate?.value; + this.groupBy = filters.group?.value; + + if (this.isHome()) { + this.groupedDataHistory.length = 0; + } + + if (this.isBack()) { + this.groupedDataHistory.length = this.groupedDataHistory.length - 1; + } + + if (this.getFilters()["search"] && this.filterChanged("search")) { + this.groupedDataHistory.length = 0; + } + } + + private getGroupsWidgetData(groupByObj: Record) { + return Object.keys(groupByObj).map((property) => ({ + id: property, + label: property, + // statuses that will be displayed on group item + statuses: [ + { key: "virtual-host", value: groupByObj[property].length }, + { + key: "acknowledge", + value: this.getPopulation(groupByObj[property]), + }, + ], + })); + } + + private isHome(): boolean { + return this.drillState?.length === 0; + } + + private isBack(): boolean { + return ( + this.groupedDataHistory?.length > this.drillState?.length && + !this.isHome() + ); + } + + /** + * Gets population for the country(ies) + */ + private getPopulation(countries: any[]) { + const totalPopulation = countries.reduce( + (acc, next) => (acc += next.population), + 0 + ); + return \\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\`\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\${totalPopulation * Math.pow(10, -3)} k\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\`; + } +} + +@Component({ + selector: "drilldown-widget-example", + templateUrl: "./drilldown-widget-example.component.html", + styleUrls: ["./drilldown-widget-example.component.less"], + standalone: false, +}) +export class DrilldownWidgetExampleComponent implements OnInit { + // This variable will hold all the data needed to define the layout and behavior of the widgets. + // Pass this to the dashboard component's dashboard input in the template. + public dashboard: IDashboard | undefined; + + // Angular gridster requires a configuration object even if it's empty. + // Pass this to the dashboard component's gridsterConfig input in the template. + public gridsterConfig: GridsterConfig = {}; + + // Boolean passed as an input to the dashboard. When true, widgets can be moved, resized, removed, or edited + public editMode: boolean = false; + + constructor( + // WidgetTypesService provides the widget's necessary structure information + private widgetTypesService: WidgetTypesService, + private providerRegistry: ProviderRegistryService, + private changeDetectorRef: ChangeDetectorRef + ) {} + + public ngOnInit(): void { + // Registering the data source for injection into the KPI tile. + // Note: Each tile of a KPI widget is assigned its own instance of the data source + this.providerRegistry.setProviders({ + [DrilldownDataSource.providerId]: { + provide: DATA_SOURCE, + useClass: DrilldownDataSource, + // Any dependencies that need to be injected into the provider must be listed here + deps: [HttpClient], + }, + [DrilldownDataSourceRealApi.providerId]: { + provide: DATA_SOURCE, + useClass: DrilldownDataSourceRealApi, + // Any dependencies that need to be injected into the provider must be listed here + deps: [LoggerService, HttpClient, Apollo], + }, + }); + + this.initializeDashboard(); + const widgetTemplate = this.widgetTypesService.getWidgetType( + "drilldown", + 1 + ); + this.widgetTypesService.setNode( + widgetTemplate, + "configurator", + WellKnownPathKey.DataSourceProviders, + [ + DrilldownDataSourceRealApi.providerId, + DrilldownDataSource.providerId, + ] + ); + } + + public initializeDashboard(): void { + // We're using a static configuration object for this example, but this is where + // the widget's configuration could potentially be populated from a database + const drilldownWidget = widgetConfig; + const widgets: IWidgets = { + // Complete the widget with information coming from its type definition + [drilldownWidget.id]: + this.widgetTypesService.mergeWithWidgetType(drilldownWidget), + }; + + // Setting the widget dimensions and position (this is for gridster) + const positions: Record = { + [drilldownWidget.id]: { + cols: 10, + rows: 10, + y: 0, + x: 0, + }, + }; + + // Finally, assigning the variables we created above to the dashboard + this.dashboard = { positions, widgets }; + } + + public reInitializeDashboard(): void { + // destroys the components and their providers so the dashboard can re init data + this.dashboard = undefined; + this.changeDetectorRef.detectChanges(); + + const adapterProperties = + widgetConfig.pizzagna[PizzagnaLayer.Configuration].listWidget + .providers?.adapter?.properties; + + if (adapterProperties) { + adapterProperties.drillstate = []; + } + + this.initializeDashboard(); + } +} + +const widgetConfig: IWidget = { + id: "drilldown", + type: "drilldown", + pizzagna: { + [PizzagnaLayer.Configuration]: { + [DEFAULT_PIZZAGNA_ROOT]: { + providers: { + [WellKnownProviders.DataSource]: { + // Setting the data source providerId for the tile with id "kpi1" + providerId: DrilldownDataSourceRealApi.providerId, + properties: {}, + } as IProviderConfiguration, + }, + }, + header: { + properties: { + title: "Drilldown Widget", + subtitle: "Search is case sensitive!", + }, + }, + listWidget: { + providers: { + [WellKnownProviders.Adapter]: { + providerId: NOVA_DRILLDOWN_DATASOURCE_ADAPTER, + properties: { + // widget + navigationBarId: "navigationBar", + componentId: "listWidget", + dataPath: "data", + + // adapter props + drillstate: [], + groupBy: ["regionName", "subregionName"], + groups: ["regionName", "subregionName"], + + // components + componentsConfig: { + group: { + componentType: + ListGroupItemComponent.lateLoadKey, + properties: { + dataFieldIds: { + id: "id", + label: "label", + statuses: "statuses", + }, + }, + itemProperties: { + canNavigate: true, + }, + }, + leaf: { + componentType: + ListLeafItemComponent.lateLoadKey, + properties: { + dataFieldIds: { + icon: "icon", + status: "icon_status", + detailedUrl: "capital", + label: "name", + }, + }, + itemProperties: { + canNavigate: false, + }, + }, + } as IDrilldownComponentsConfiguration, + }, + }, + }, + properties: { + configuration: { + // FORMAT: + // componentType: ListLeafItemComponent.lateLoadKey, + // properties: { + // dataFieldIds: { + // icon: "", + // status: "code", + // detailedUrl: "capital", + // label: "name", + // }, + // }, + // + } as IListWidgetConfiguration, + }, + }, + }, + }, +}; + +const getLast = (arr: any[]) => arr[arr.length - 1]; +\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\`, + "widget-types/drilldown/drilldown-widget/mock-data-source.ts": \\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\`// © 2022 SolarWinds Worldwide, LLC. All rights reserved. +// +// Permission is hereby granted, free of charge, to any person obtaining a copy +// of this software and associated documentation files (the "Software"), to +// deal in the Software without restriction, including without limitation the +// rights to use, copy, modify, merge, publish, distribute, sublicense, and/or +// sell copies of the Software, and to permit persons to whom the Software is +// furnished to do so, subject to the following conditions: +// +// The above copyright notice and this permission notice shall be included in +// all copies or substantial portions of the Software. +// +// THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR +// IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, +// FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE +// AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER +// LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, +// OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN +// THE SOFTWARE. + +import { Injectable, OnDestroy } from "@angular/core"; +import groupBy from "lodash/groupBy"; +import { BehaviorSubject, Observable, of, Subject } from "rxjs"; +import { + catchError, + delay, + finalize, + map, + // eslint-disable-next-line import/no-deprecated + switchMap, + tap, +} from "rxjs/operators"; + +import { + DataSourceService, + IDataField, + IDataSource, + IFilters, + INovaFilters, +} from "@nova-ui/bits"; + +import { GRAPH_DATA_MOCK } from "./data-mock"; + +/** + * A simple KPI data source to retrieve the average rating of Harry Potter and the Sorcerer's Stone (book) via googleapis + */ +@Injectable() +export class DrilldownDataSource + extends DataSourceService + implements IDataSource, OnDestroy +{ + // This is the ID we'll use to identify the provider + public static providerId = "DrilldownDataSource"; + + // Use this subject to communicate the data source's busy state + public busy = new BehaviorSubject(false); + public dataFields: Partial[] = [ + { id: "continent.name", label: "Continent name" }, + { id: "currency", label: "Currency" }, + ]; + + private drillState: string[] = []; + private groupBy: string[]; + private cache: any; + private applyFilters$ = new Subject(); + + constructor() { + super(); + + // TODO: remove Partial in vNext after marking dataType field as optional - NUI-5838 + ( + this.dataFieldsConfig.dataFields$ as BehaviorSubject< + Partial[] + > + ).next(this.dataFields); + + this.applyFilters$ + // eslint-disable-next-line import/no-deprecated + .pipe(switchMap((filters) => this.getData(filters))) + .subscribe(async (res) => { + this.outputsSubject.next(await this.getFilteredData(res)); + }); + } + + private groupedDataHistory: any[] = []; + + // In this example, getFilteredData is invoked every 10 minutes (Take a look at the refresher + // provider definition in the widget configuration below to see how the interval is set) + public async getFilteredData(data: any): Promise { + return of(data) + .pipe( + map((countries) => { + const widgetInput = this.getOutput(countries); + + if (this.isDrillDown()) { + const activeDrillLvl = this.drillState.length; + const group = this.groupBy[activeDrillLvl]; + const [lastGroupedValue, groupedData] = + this.getTransformedDataForGroup(widgetInput, group); + + this.groupedDataHistory.push(lastGroupedValue); + + return groupedData; + } + + return widgetInput; + }) + ) + .toPromise(); + } + + public ngOnDestroy(): void { + this.outputsSubject.complete(); + } + + // redefine parent method + public async applyFilters(): Promise { + this.applyFilters$.next(this.getFilters()); + } + + private getData(filters: INovaFilters): Observable { + this.drillState = filters.drillstate?.value; + this.groupBy = filters.group?.value; + + this.busy.next(true); + + return of(this.cache || GRAPH_DATA_MOCK).pipe( + delay(1000), + tap((data) => (this.cache = data)), + map((data) => data.data.countries), + catchError((e) => of([])), + finalize(() => this.busy.next(false)) + ); + } + + private getTransformedDataForGroup(data: any, groupName: string) { + const groupedDict = groupBy(data, groupName); + const dataArr = Object.keys(groupedDict).map((property) => ({ + id: property, + label: property, + // TODO: apply groups mapping here + statuses: [ + { key: "state_ok", value: groupedDict[property].length }, + { + key: "status_unreachable", + value: generateNumberUpTo(100000), + }, + { key: "status_warning", value: generateNumberUpTo(10000) }, + { key: "status_unknown", value: generateNumberUpTo(1000) }, + ], + })); + + return [groupedDict, dataArr]; + } + + private isHome(): boolean { + return !this.drillState || this.drillState.length === 0; + } + + private isBack(): boolean { + return ( + this.groupedDataHistory.length > this.drillState?.length && + !this.isHome() + ); + } + + private isDrillDown(): boolean { + return this.drillState?.length !== this.groupBy?.length; + } + + private getOutput(data: any) { + if (this.isHome()) { + this.groupedDataHistory.length = 0; + } + + if (this.isBack()) { + this.groupedDataHistory.length = this.groupedDataHistory.length - 1; + } + + const lastHistoryValue = getLast(this.groupedDataHistory); + + if (!lastHistoryValue) { + return data; + } + + return lastHistoryValue[getLast(this.drillState)] || lastHistoryValue; + } +} + +const getLast = (arr: any[]) => arr[arr.length - 1]; +const generateNumberUpTo = (upperLimit: number): number => + Math.floor(Math.random() * upperLimit + 1); +\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\`, + "widget-types/drilldown/drilldown-widget-docs.component.ts": \\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\`// © 2022 SolarWinds Worldwide, LLC. All rights reserved. +// +// Permission is hereby granted, free of charge, to any person obtaining a copy +// of this software and associated documentation files (the "Software"), to +// deal in the Software without restriction, including without limitation the +// rights to use, copy, modify, merge, publish, distribute, sublicense, and/or +// sell copies of the Software, and to permit persons to whom the Software is +// furnished to do so, subject to the following conditions: +// +// The above copyright notice and this permission notice shall be included in +// all copies or substantial portions of the Software. +// +// THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR +// IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, +// FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE +// AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER +// LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, +// OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN +// THE SOFTWARE. + +import { Component, OnInit } from "@angular/core"; + +import { mapContentFile } from "../../../../demo-files-factory"; + +@Component({ + selector: "nui-drilldown-docs", + templateUrl: "./drilldown-widget-docs.component.html", + standalone: false, +}) +export class DrilldownDocsComponent implements OnInit { + public widgetFileText = ""; + public configuratorFileText = ""; + + public predefinedGroping = \\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\` +listWidget: { + providers: { + [WellKnownProviders.Adapter]: { + providerId: NOVA_DRILLDOWN_DATASOURCE_ADAPTER, + properties: { + ... + // adapter props + drillstate: [], + groupBy: ["regionName", "subregionName"], + groups: ["regionName", "subregionName"], + ... + }, + }, + }, +}, +\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\`; + public featuredDeclaredText = \\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\` + private supportedFeatures: IDataSourceFeatures = { + search: { enabled: true }, + };\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\`; + public featuresUsedText = \\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\` + this.features = new DataSourceFeatures(this.supportedFeatures); + \\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\`; + + public async ngOnInit(): Promise { + this.widgetFileText = await import( + "./../../../../../../src/lib/widget-types/drilldown/drilldown-widget" + ).then(mapContentFile); + this.configuratorFileText = await import( + "./../../../../../../src/lib/widget-types/drilldown/drilldown-configurator" + ).then(mapContentFile); + } +} +\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\`, + "widget-types/drilldown/drilldown-widget-docs.module.ts": \\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\`// © 2022 SolarWinds Worldwide, LLC. All rights reserved. +// +// Permission is hereby granted, free of charge, to any person obtaining a copy +// of this software and associated documentation files (the "Software"), to +// deal in the Software without restriction, including without limitation the +// rights to use, copy, modify, merge, publish, distribute, sublicense, and/or +// sell copies of the Software, and to permit persons to whom the Software is +// furnished to do so, subject to the following conditions: +// +// The above copyright notice and this permission notice shall be included in +// all copies or substantial portions of the Software. +// +// THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR +// IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, +// FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE +// AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER +// LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, +// OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN +// THE SOFTWARE. + +import { NgModule } from "@angular/core"; +import { RouterModule, Routes } from "@angular/router"; + +// eslint-disable-next-line max-len +import { + NuiButtonModule, + NuiDocsModule, + NuiMessageModule, + NuiSwitchModule, + DEMO_PATH_TOKEN, +} from "@nova-ui/bits"; +import { NuiDashboardsModule } from "@nova-ui/dashboards"; + +import { DrilldownMultiRequestWidgetExampleComponent } from "./drilldown-multi-request-widget/drilldown-multi-request-widget-example.component"; +import { DrilldownWidgetExampleComponent } from "./drilldown-widget/drilldown-widget-example.component"; +import { DrilldownDocsComponent } from "./drilldown-widget-docs.component"; +import { getDemoFiles } from "../../../../demo-files-factory"; + +const routes: Routes = [ + { + path: "", + component: DrilldownDocsComponent, + data: { + srlc: { + hideIndicator: true, + }, + }, + }, + { + path: "example", + component: DrilldownWidgetExampleComponent, + data: { + srlc: { + hideIndicator: true, + }, + }, + }, + { + path: "multiple-requests", + component: DrilldownMultiRequestWidgetExampleComponent, + data: { + srlc: { + hideIndicator: true, + }, + }, + }, +]; + +@NgModule({ + imports: [ + RouterModule.forChild(routes), + NuiButtonModule, + NuiDocsModule, + NuiMessageModule, + NuiDashboardsModule, + NuiSwitchModule, + ], + declarations: [ + DrilldownDocsComponent, + DrilldownWidgetExampleComponent, + DrilldownMultiRequestWidgetExampleComponent, + ], + providers: [ + { + provide: DEMO_PATH_TOKEN, + useValue: getDemoFiles("drilldown"), + }, + ], +}) +export default class DrilldownDocsModule {} +\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\`, + "widget-types/embedded-content/embedded-content-docs.component.ts": \\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\`// © 2022 SolarWinds Worldwide, LLC. All rights reserved. +// +// Permission is hereby granted, free of charge, to any person obtaining a copy +// of this software and associated documentation files (the "Software"), to +// deal in the Software without restriction, including without limitation the +// rights to use, copy, modify, merge, publish, distribute, sublicense, and/or +// sell copies of the Software, and to permit persons to whom the Software is +// furnished to do so, subject to the following conditions: +// +// The above copyright notice and this permission notice shall be included in +// all copies or substantial portions of the Software. +// +// THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR +// IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, +// FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE +// AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER +// LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, +// OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN +// THE SOFTWARE. + +import { Component, OnInit } from "@angular/core"; + +import { mapContentFile } from "../../../../demo-files-factory"; + +@Component({ + selector: "nui-embedded-content-docs", + templateUrl: "./embedded-content-docs.component.html", + standalone: false, +}) +export class EmbeddedContentDocsComponent implements OnInit { + public embeddedContentWidgetFileText = ""; + public embeddedContentConfiguratorFileText = ""; + + public async ngOnInit(): Promise { + this.embeddedContentWidgetFileText = await import( + "./../../../../../../src/lib/widget-types/embedded-content/embedded-content-widget" + ).then(mapContentFile); + this.embeddedContentWidgetFileText = await import( + "./../../../../../../src/lib/widget-types/embedded-content/embedded-content-configurator" + ).then(mapContentFile); + } +} +\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\`, + "widget-types/embedded-content/embedded-content-docs.module.ts": \\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\`// © 2022 SolarWinds Worldwide, LLC. All rights reserved. +// +// Permission is hereby granted, free of charge, to any person obtaining a copy +// of this software and associated documentation files (the "Software"), to +// deal in the Software without restriction, including without limitation the +// rights to use, copy, modify, merge, publish, distribute, sublicense, and/or +// sell copies of the Software, and to permit persons to whom the Software is +// furnished to do so, subject to the following conditions: +// +// The above copyright notice and this permission notice shall be included in +// all copies or substantial portions of the Software. +// +// THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR +// IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, +// FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE +// AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER +// LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, +// OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN +// THE SOFTWARE. + +import { NgModule } from "@angular/core"; +import { RouterModule, Routes } from "@angular/router"; + +// eslint-disable-next-line max-len +import { + NuiButtonModule, + NuiDocsModule, + NuiMessageModule, + NuiSwitchModule, + DEMO_PATH_TOKEN, +} from "@nova-ui/bits"; +import { NuiDashboardsModule } from "@nova-ui/dashboards"; + +import { EmbeddedContentDocsComponent } from "./embedded-content-docs.component"; +import { EmbeddedContentWidgetExampleComponent } from "./embedded-content-widget-example/embedded-content-widget-example.component"; +import { getDemoFiles } from "../../../../demo-files-factory"; + +const routes: Routes = [ + { + path: "", + component: EmbeddedContentDocsComponent, + data: { + srlc: { + hideIndicator: true, + }, + }, + }, + { + path: "example", + component: EmbeddedContentWidgetExampleComponent, + data: { + srlc: { + hideIndicator: true, + }, + }, + }, +]; + +@NgModule({ + imports: [ + RouterModule.forChild(routes), + NuiButtonModule, + NuiDocsModule, + NuiMessageModule, + NuiDashboardsModule, + NuiSwitchModule, + ], + declarations: [ + EmbeddedContentDocsComponent, + EmbeddedContentWidgetExampleComponent, + ], + providers: [ + { + provide: DEMO_PATH_TOKEN, + useValue: getDemoFiles("embedded-content"), + }, + ], +}) +export default class EmbeddedContentDocsModule {} +\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\`, + "widget-types/embedded-content/embedded-content-widget-example/embedded-content-widget-example.component.ts": \\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\`// © 2022 SolarWinds Worldwide, LLC. All rights reserved. +// +// Permission is hereby granted, free of charge, to any person obtaining a copy +// of this software and associated documentation files (the "Software"), to +// deal in the Software without restriction, including without limitation the +// rights to use, copy, modify, merge, publish, distribute, sublicense, and/or +// sell copies of the Software, and to permit persons to whom the Software is +// furnished to do so, subject to the following conditions: +// +// The above copyright notice and this permission notice shall be included in +// all copies or substantial portions of the Software. +// +// THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR +// IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, +// FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE +// AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER +// LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, +// OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN +// THE SOFTWARE. + +import { ChangeDetectorRef, Component, OnInit } from "@angular/core"; +import { GridsterConfig, GridsterItem } from "angular-gridster2"; + +import { + ComponentRegistryService, + EmbeddedContentComponent, + EmbeddedContentConfigurationComponent, + EmbeddedContentMode, + IDashboard, + IWidget, + IWidgets, + PizzagnaLayer, + WidgetTypesService, +} from "@nova-ui/dashboards"; + +@Component({ + selector: "embedded-content-widget-example", + templateUrl: "./embedded-content-widget-example.component.html", + styleUrls: ["./embedded-content-widget-example.component.less"], + standalone: false, +}) +export class EmbeddedContentWidgetExampleComponent implements OnInit { + // This variable will hold all the data needed to define the layout and behavior of the widgets. + // Pass this to the dashboard component's dashboard input in the template. + public dashboard: IDashboard | undefined; + + // Angular gridster requires a configuration object even if it's empty. + // Pass this to the dashboard component's gridsterConfig input in the template. + public gridsterConfig: GridsterConfig = {}; + + // Boolean passed as an input to the dashboard. When true, widgets can be moved, resized, removed, or edited + public editMode: boolean = false; + + constructor( + // WidgetTypesService provides the widget's necessary structure information + private widgetTypesService: WidgetTypesService, + + private componentRegistry: ComponentRegistryService, + private changeDetectorRef: ChangeDetectorRef + ) {} + + public ngOnInit(): void { + this.prepareNovaDashboards(); + this.initializeDashboard(); + } + + /** Used for restoring widgets state */ + public reInitializeDashboard(): void { + // destroys the components and their providers so the dashboard can re init data + this.dashboard = undefined; + this.changeDetectorRef.detectChanges(); + + this.initializeDashboard(); + } + + public initializeDashboard(): void { + // We're using a static configuration object for this example, but this is where + // the widget's configuration could potentially be populated from a database + const embeddedContentWidget = widgetConfig; + const widgets: IWidgets = { + // Complete the widget with information coming from its type definition + [embeddedContentWidget.id]: + this.widgetTypesService.mergeWithWidgetType( + embeddedContentWidget + ), + }; + + // Setting the widget dimensions and position (this is for gridster) + const positions: Record = { + [embeddedContentWidget.id]: { + cols: 10, + rows: 10, + y: 0, + x: 0, + }, + }; + + // Finally, assigning the variables we created above to the dashboard + this.dashboard = { positions, widgets }; + } + + private prepareNovaDashboards() { + this.componentRegistry.registerByLateLoadKey(EmbeddedContentComponent); + this.componentRegistry.registerByLateLoadKey( + EmbeddedContentConfigurationComponent + ); + } +} + +const widgetConfig: IWidget = { + id: "embeddedContentWidgetId", + type: "embedded-content", + pizzagna: { + [PizzagnaLayer.Configuration]: { + header: { + properties: { + title: "Embedded Content Widget", + subtitle: "", + }, + }, + mainContent: { + properties: { + sanitized: true, + mode: EmbeddedContentMode.URL, + customEmbeddedContent: "https://www.ventusky.com/", + }, + }, + }, + }, +}; +\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\`, + "widget-types/kpi/kpi-docs.component.ts": \\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\`// © 2022 SolarWinds Worldwide, LLC. All rights reserved. +// +// Permission is hereby granted, free of charge, to any person obtaining a copy +// of this software and associated documentation files (the "Software"), to +// deal in the Software without restriction, including without limitation the +// rights to use, copy, modify, merge, publish, distribute, sublicense, and/or +// sell copies of the Software, and to permit persons to whom the Software is +// furnished to do so, subject to the following conditions: +// +// The above copyright notice and this permission notice shall be included in +// all copies or substantial portions of the Software. +// +// THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR +// IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, +// FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE +// AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER +// LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, +// OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN +// THE SOFTWARE. + +import { Component, OnInit } from "@angular/core"; + +import { mapContentFile } from "../../../../demo-files-factory"; + +@Component({ + selector: "nui-kpi-docs", + templateUrl: "./kpi-docs.component.html", + standalone: false, +}) +export class KpiDocsComponent implements OnInit { + public kpiWidgetFileText = ""; + public kpiConfiguratorFileText = ""; + + public async ngOnInit(): Promise { + this.kpiWidgetFileText = await import( + "./../../../../../../src/lib/widget-types/kpi/kpi-widget" + ).then(mapContentFile); + this.kpiConfiguratorFileText = await import( + "./../../../../../../src/lib/widget-types/kpi/kpi-configurator" + ).then(mapContentFile); + } +} +\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\`, + "widget-types/kpi/kpi-docs.module.ts": \\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\`// © 2022 SolarWinds Worldwide, LLC. All rights reserved. +// +// Permission is hereby granted, free of charge, to any person obtaining a copy +// of this software and associated documentation files (the "Software"), to +// deal in the Software without restriction, including without limitation the +// rights to use, copy, modify, merge, publish, distribute, sublicense, and/or +// sell copies of the Software, and to permit persons to whom the Software is +// furnished to do so, subject to the following conditions: +// +// The above copyright notice and this permission notice shall be included in +// all copies or substantial portions of the Software. +// +// THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR +// IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, +// FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE +// AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER +// LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, +// OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN +// THE SOFTWARE. + +import { NgModule } from "@angular/core"; +import { RouterModule, Routes } from "@angular/router"; + +import { + NuiButtonModule, + NuiDocsModule, + NuiMessageModule, + NuiSwitchModule, + DEMO_PATH_TOKEN, +} from "@nova-ui/bits"; +import { + KpiColorComparatorsRegistryService, + NuiDashboardsModule, +} from "@nova-ui/dashboards"; + +import { KpiDocsComponent } from "./kpi-docs.component"; +import { KpiSyncBrokerExampleComponent } from "./kpi-sync-broker/kpi-sync-broker-example.component"; +import { KpiSyncBrokerDocsComponent } from "./kpi-sync-broker-docs.component"; +import { KpiSyncBrokerForAllTilesExampleComponent } from "./kpi-sync-broker-for-all-tiles/kpi-sync-broker-for-all-tiles-example.component"; +import { KpiWidgetExampleComponent } from "./kpi-widget/kpi-widget-example.component"; +import { KpiWidgetBackgroundColorExampleComponent } from "./kpi-widget-background-color/kpi-widget-background-color-example.component"; +import { KpiWidgetBackgroundColorDocsComponent } from "./kpi-widget-background-color-docs.component"; +import { KpiWidgetInteractiveExampleComponent } from "./kpi-widget-interactive/kpi-widget-interactive-example.component"; +import { getDemoFiles } from "../../../../demo-files-factory"; + +const routes: Routes = [ + { + path: "", + component: KpiDocsComponent, + data: { + srlc: { + hideIndicator: true, + }, + showThemeSwitcher: true, + }, + }, + { + path: "example", + component: KpiWidgetExampleComponent, + data: { + srlc: { + hideIndicator: true, + }, + }, + }, + { + path: "background-color", + component: KpiWidgetBackgroundColorDocsComponent, + data: { + srlc: { + hideIndicator: true, + }, + }, + }, + { + path: "sync-broker", + component: KpiSyncBrokerDocsComponent, + data: { + srlc: { + hideIndicator: true, + }, + }, + }, +]; + +@NgModule({ + imports: [ + RouterModule.forChild(routes), + NuiButtonModule, + NuiDocsModule, + NuiMessageModule, + NuiDashboardsModule, + NuiSwitchModule, + ], + declarations: [ + KpiDocsComponent, + KpiWidgetExampleComponent, + KpiWidgetInteractiveExampleComponent, + KpiWidgetBackgroundColorDocsComponent, + KpiWidgetBackgroundColorExampleComponent, + KpiSyncBrokerDocsComponent, + KpiSyncBrokerExampleComponent, + KpiSyncBrokerForAllTilesExampleComponent, + ], + providers: [ + KpiColorComparatorsRegistryService, + { + provide: DEMO_PATH_TOKEN, + useValue: getDemoFiles("kpi"), + }, + ], +}) +export default class KpiDocsModule { + constructor( + private comparatorsRegistry: KpiColorComparatorsRegistryService + ) { + this.backgroundColorDocsSetup(); + } + + private backgroundColorDocsSetup() { + this.comparatorsRegistry.registerComparators({ + "!=": { + comparatorFn: (actual: any, reference: any) => + // eslint-disable-next-line eqeqeq + actual != reference, + label: "Not equal", + }, + }); + } +} +\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\`, + "widget-types/kpi/kpi-sync-broker/kpi-sync-broker-example.component.ts": \\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\`// © 2022 SolarWinds Worldwide, LLC. All rights reserved. +// +// Permission is hereby granted, free of charge, to any person obtaining a copy +// of this software and associated documentation files (the "Software"), to +// deal in the Software without restriction, including without limitation the +// rights to use, copy, modify, merge, publish, distribute, sublicense, and/or +// sell copies of the Software, and to permit persons to whom the Software is +// furnished to do so, subject to the following conditions: +// +// The above copyright notice and this permission notice shall be included in +// all copies or substantial portions of the Software. +// +// THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR +// IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, +// FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE +// AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER +// LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, +// OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN +// THE SOFTWARE. + +import { HttpClient, HttpErrorResponse } from "@angular/common/http"; +import { + ChangeDetectorRef, + Component, + Injectable, + OnDestroy, + OnInit, +} from "@angular/core"; +import { GridsterConfig, GridsterItem } from "angular-gridster2"; +import keyBy from "lodash/keyBy"; +import { BehaviorSubject, of } from "rxjs"; +import { delay, finalize, take } from "rxjs/operators"; + +import { DataSourceService, IFilteringOutputs } from "@nova-ui/bits"; +import { + DATA_SOURCE, + IDashboard, + IKpiData, + IProviderConfiguration, + IWidget, + KpiComponent, + NOVA_KPI_DATASOURCE_ADAPTER, + NOVA_KPI_SCALE_SYNC_BROKER, + PizzagnaLayer, + ProviderRegistryService, + WellKnownPathKey, + WellKnownProviders, + WidgetTypesService, +} from "@nova-ui/dashboards"; + +/** + * A simple KPI data source to retrieve the average rating of Harry Potter and the Sorcerer's Stone (book) via googleapis + */ +@Injectable() +export class AverageRatingKpiDataSource + extends DataSourceService + implements OnDestroy +{ + public static providerId = "AverageRatingKpiDataSource"; + public busy = new BehaviorSubject(false); + + constructor(private http: HttpClient) { + super(); + } + + public async getFilteredData(): Promise { + this.busy.next(true); + return new Promise((resolve) => { + // *** Make a resource request to an external API (if needed) + this.http + .get("https://www.googleapis.com/books/v1/volumes/5MQFrgEACAAJ") + .pipe(finalize(() => this.busy.next(false))) + .subscribe({ + next: (data: any) => { + resolve({ + result: { + value: data.volumeInfo.averageRating, + }, + }); + }, + error: (error: HttpErrorResponse) => { + resolve({ + result: null, + error: { + type: error.status, + }, + }); + }, + }); + }); + } + + public ngOnDestroy(): void { + this.outputsSubject.complete(); + } +} + +/** + * A simple KPI data source to retrieve the ratings count of Harry Potter and the Sorcerer's Stone (book) via googleapis + */ +@Injectable() +export class RatingsCountKpiDataSource + extends DataSourceService + implements OnDestroy +{ + public static providerId = "RatingsCountKpiDataSource"; + + // Use this subject to communicate the data source's busy state + public busy = new BehaviorSubject(false); + + constructor(private http: HttpClient) { + super(); + } + + public async getFilteredData(): Promise { + this.busy.next(true); + return new Promise((resolve) => { + this.http + .get("https://www.googleapis.com/books/v1/volumes/5MQFrgEACAAJ") + .pipe( + delay(2000), + finalize(() => this.busy.next(false)) + ) + .subscribe({ + next: (data: any) => { + resolve({ + result: { + value: data.volumeInfo.ratingsCount, + }, + }); + }, + error: (error: HttpErrorResponse) => { + resolve({ + result: null, + error: { + type: error.status, + }, + }); + }, + }); + }); + } + + public ngOnDestroy(): void { + this.outputsSubject.complete(); + } +} +/** + * A simple KPI data source to retrieve the ratings count of Harry Potter and the Sorcerer's Stone (book) via googleapis + */ +@Injectable() +export class MockKpiDataSource + extends DataSourceService + implements OnDestroy +{ + public static providerId = "MockKpiDataSource"; + + // Use this subject to communicate the data source's busy state + public busy = new BehaviorSubject(false); + + constructor() { + super(); + } + + public async getFilteredData(): Promise { + this.busy.next(true); + return new Promise((resolve) => { + of(3381342) + .pipe( + delay(5000), + take(1), + finalize(() => this.busy.next(false)) + ) + .subscribe({ + next: (data: any) => { + resolve({ + result: { + value: data, + }, + }); + }, + }); + }); + } + + public ngOnDestroy(): void { + this.outputsSubject.complete(); + } +} + +/** + * A component that instantiates the dashboard + */ +@Component({ + selector: "kpi-sync-broker-example", + templateUrl: "./kpi-sync-broker-example.component.html", + styleUrls: ["./kpi-sync-broker-example.component.less"], + standalone: false, +}) +export class KpiSyncBrokerExampleComponent implements OnInit { + public dashboard: IDashboard | undefined; + public gridsterConfig: GridsterConfig = {}; + public editMode: boolean = false; + + constructor( + private widgetTypesService: WidgetTypesService, + private providerRegistry: ProviderRegistryService, + private changeDetectorRef: ChangeDetectorRef + ) {} + + public ngOnInit(): void { + this.setupDashboard(); + + this.initializeDashboard(); + } + + private setupDashboard() { + const widgetTemplate = this.widgetTypesService.getWidgetType("kpi", 1); + + this.widgetTypesService.setNode( + widgetTemplate, + "configurator", + WellKnownPathKey.DataSourceProviders, + [ + AverageRatingKpiDataSource.providerId, + RatingsCountKpiDataSource.providerId, + MockKpiDataSource.providerId, + ] + ); + + this.providerRegistry.setProviders({ + [AverageRatingKpiDataSource.providerId]: { + provide: DATA_SOURCE, + useClass: AverageRatingKpiDataSource, + deps: [HttpClient], + }, + [RatingsCountKpiDataSource.providerId]: { + provide: DATA_SOURCE, + useClass: RatingsCountKpiDataSource, + deps: [HttpClient], + }, + [MockKpiDataSource.providerId]: { + provide: DATA_SOURCE, + useClass: MockKpiDataSource, + deps: [], + }, + }); + } + + /** Used for restoring widgets state */ + public reInitializeDashboard(): void { + // destroys the components and their providers so the dashboard can re init data + this.dashboard = undefined; + this.changeDetectorRef.detectChanges(); + + this.initializeDashboard(); + } + + private initializeDashboard(): void { + const widgetsWithStructure = widgetsConfig.map((w) => + this.widgetTypesService.mergeWithWidgetType(w) + ); + const widgetsIndex = keyBy(widgetsWithStructure, (w: IWidget) => w.id); + + const positions: Record = { + kpiWidgetId: { + cols: 3, + rows: 6, + y: 0, + x: 0, + }, + kpiWidgetId2: { + cols: 3, + rows: 6, + y: 0, + x: 0, + }, + }; + + this.dashboard = { + positions, + widgets: widgetsIndex, + }; + } +} + +const widgetsConfig: IWidget[] = [ + { + id: "kpiWidgetId", + type: "kpi", + pizzagna: { + [PizzagnaLayer.Configuration]: { + header: { + properties: { + title: "NO Sync Broker", + subtitle: "Values sizes are being not synced", + }, + }, + tiles: { + properties: { + nodes: ["kpi1", "kpi2", "kpi3"], + }, + }, + kpi1: { + id: "kpi1", + componentType: KpiComponent.lateLoadKey, + properties: { + widgetData: { + units: \\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\`out of 5 Stars\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\`, + label: \\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\`Average Rating\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\`, + backgroundColor: "lightpink", + }, + }, + providers: { + [WellKnownProviders.DataSource]: { + providerId: AverageRatingKpiDataSource.providerId, + } as IProviderConfiguration, + [WellKnownProviders.Adapter]: { + providerId: NOVA_KPI_DATASOURCE_ADAPTER, + properties: { + componentId: "kpi1", + propertyPath: "widgetData", + }, + } as IProviderConfiguration, + }, + }, + kpi2: { + id: "kpi2", + componentType: KpiComponent.lateLoadKey, + properties: { + widgetData: { + label: \\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\`Another label which might be a pretty long one\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\`, + units: \\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\`Which comes from somewhere\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\`, + backgroundColor: "skyblue", + }, + }, + providers: { + [WellKnownProviders.DataSource]: { + providerId: RatingsCountKpiDataSource.providerId, + } as IProviderConfiguration, + [WellKnownProviders.Adapter]: { + providerId: NOVA_KPI_DATASOURCE_ADAPTER, + properties: { + componentId: "kpi2", + propertyPath: "widgetData", + }, + } as IProviderConfiguration, + }, + }, + kpi3: { + id: "kpi3", + componentType: KpiComponent.lateLoadKey, + properties: { + widgetData: { + label: \\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\`Random\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\`, + units: \\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\`Data\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\`, + }, + }, + providers: { + [WellKnownProviders.DataSource]: { + providerId: MockKpiDataSource.providerId, + } as IProviderConfiguration, + [WellKnownProviders.Adapter]: { + providerId: NOVA_KPI_DATASOURCE_ADAPTER, + properties: { + componentId: "kpi3", + propertyPath: "widgetData", + }, + } as IProviderConfiguration, + }, + }, + }, + }, + }, + { + id: "kpiWidgetId2", + type: "kpi", + pizzagna: { + [PizzagnaLayer.Configuration]: { + header: { + properties: { + title: "WITH Sync Broker", + subtitle: + "Now the values of label, units, and value are being synced", + }, + }, + tiles: { + properties: { + nodes: ["kpi4", "kpi5", "kpi6"], + }, + providers: { + // This is where and how you set the sync broker provider + kpiScaleSyncBroker: { + providerId: NOVA_KPI_SCALE_SYNC_BROKER, + properties: { + scaleSyncConfig: [ + // You can decide which values to keep in sync. For instance, you can leave only 'label' id in the array below + { id: "value" }, + { id: "label" }, + { id: "units" }, + ], + }, + }, + }, + }, + kpi4: { + id: "kpi4", + componentType: KpiComponent.lateLoadKey, + properties: { + widgetData: { + units: \\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\`out of 5 Stars\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\`, + label: \\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\`Average Rating\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\`, + backgroundColor: "lightpink", + }, + }, + providers: { + [WellKnownProviders.DataSource]: { + providerId: AverageRatingKpiDataSource.providerId, + } as IProviderConfiguration, + [WellKnownProviders.Adapter]: { + providerId: NOVA_KPI_DATASOURCE_ADAPTER, + properties: { + componentId: "kpi4", + propertyPath: "widgetData", + }, + } as IProviderConfiguration, + }, + }, + kpi5: { + id: "kpi5", + componentType: KpiComponent.lateLoadKey, + properties: { + widgetData: { + label: \\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\`Another label which might be a pretty long one\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\`, + units: \\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\`Which comes from somewhere\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\`, + backgroundColor: "skyblue", + }, + }, + providers: { + [WellKnownProviders.DataSource]: { + providerId: RatingsCountKpiDataSource.providerId, + } as IProviderConfiguration, + [WellKnownProviders.Adapter]: { + providerId: NOVA_KPI_DATASOURCE_ADAPTER, + properties: { + componentId: "kpi5", + propertyPath: "widgetData", + }, + } as IProviderConfiguration, + }, + }, + kpi6: { + id: "kpi6", + componentType: KpiComponent.lateLoadKey, + properties: { + widgetData: { + label: \\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\`Random\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\`, + units: \\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\`Data\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\`, + }, + }, + providers: { + [WellKnownProviders.DataSource]: { + providerId: MockKpiDataSource.providerId, + } as IProviderConfiguration, + [WellKnownProviders.Adapter]: { + providerId: NOVA_KPI_DATASOURCE_ADAPTER, + properties: { + componentId: "kpi6", + propertyPath: "widgetData", + }, + } as IProviderConfiguration, + }, + }, + }, + }, + }, +]; +\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\`, + "widget-types/kpi/kpi-sync-broker-docs.component.ts": \\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\`// © 2022 SolarWinds Worldwide, LLC. All rights reserved. +// +// Permission is hereby granted, free of charge, to any person obtaining a copy +// of this software and associated documentation files (the "Software"), to +// deal in the Software without restriction, including without limitation the +// rights to use, copy, modify, merge, publish, distribute, sublicense, and/or +// sell copies of the Software, and to permit persons to whom the Software is +// furnished to do so, subject to the following conditions: +// +// The above copyright notice and this permission notice shall be included in +// all copies or substantial portions of the Software. +// +// THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR +// IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, +// FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE +// AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER +// LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, +// OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN +// THE SOFTWARE. + +import { Component } from "@angular/core"; + +@Component({ + selector: "kpi-sync-broker-docs", + templateUrl: "./kpi-sync-broker-docs.component.html", + standalone: false, +}) +export class KpiSyncBrokerDocsComponent { + public kpiScaleSyncBroker = \\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\` +"tiles": { + "providers": { + kpiScaleSyncBroker: { + providerId: NOVA_KPI_SCALE_SYNC_BROKER, + properties: { + scaleSyncConfig: [ + { id: "value" }, + { id: "label" }, + { id: "units" }, + ], + }, + }, + }, +}, +\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\`; + + public defineScaleBrokerOnDashboardSetup = \\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\` +// To add the sync broker globally to all the kpi tiles you may start with setting up the broker config +// Here you define which values to keep in sync +const brokerConfig = { + providerId: NOVA_KPI_SCALE_SYNC_BROKER, + properties: { + scaleSyncConfig: [ + { id: "value" }, + { id: "label" }, + { id: "units" }, + ], + }, + }; + +// And here is how you set the sync broker for every KPI widget in the dashboard. +// Later, you will be able to override this setting for each separate KPI widget in the configuration (just like it is shown in the third +// width of the example with the 'kpiWidgetId3') +this.widgetTypesService.setNode( + widgetTemplate, + "widget", + "tiles.providers.kpiScaleSyncBroker", + brokerConfig +); +\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\`; +} +\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\`, + "widget-types/kpi/kpi-sync-broker-for-all-tiles/kpi-sync-broker-for-all-tiles-example.component.ts": \\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\`// © 2022 SolarWinds Worldwide, LLC. All rights reserved. +// +// Permission is hereby granted, free of charge, to any person obtaining a copy +// of this software and associated documentation files (the "Software"), to +// deal in the Software without restriction, including without limitation the +// rights to use, copy, modify, merge, publish, distribute, sublicense, and/or +// sell copies of the Software, and to permit persons to whom the Software is +// furnished to do so, subject to the following conditions: +// +// The above copyright notice and this permission notice shall be included in +// all copies or substantial portions of the Software. +// +// THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR +// IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, +// FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE +// AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER +// LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, +// OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN +// THE SOFTWARE. + +import { HttpClient, HttpErrorResponse } from "@angular/common/http"; +import { + ChangeDetectorRef, + Component, + Injectable, + OnDestroy, + OnInit, +} from "@angular/core"; +import { GridsterConfig, GridsterItem } from "angular-gridster2"; +import keyBy from "lodash/keyBy"; +import { BehaviorSubject, of } from "rxjs"; +import { delay, finalize, take } from "rxjs/operators"; + +import { DataSourceService, IFilteringOutputs } from "@nova-ui/bits"; +import { + DATA_SOURCE, + IDashboard, + IKpiData, + IProviderConfiguration, + IWidget, + KpiComponent, + NOVA_KPI_DATASOURCE_ADAPTER, + NOVA_KPI_SCALE_SYNC_BROKER, + PizzagnaLayer, + ProviderRegistryService, + WellKnownPathKey, + WellKnownProviders, + WidgetTypesService, +} from "@nova-ui/dashboards"; + +/** + * A simple KPI data source to retrieve the average rating of Harry Potter and the Sorcerer's Stone (book) via googleapis + */ +@Injectable() +export class AverageRatingKpiDataSource + extends DataSourceService + implements OnDestroy +{ + public static providerId = "AverageRatingKpiDataSource"; + public busy = new BehaviorSubject(false); + + constructor(private http: HttpClient) { + super(); + } + + public async getFilteredData(): Promise { + this.busy.next(true); + return new Promise((resolve) => { + // *** Make a resource request to an external API (if needed) + this.http + .get("https://www.googleapis.com/books/v1/volumes/5MQFrgEACAAJ") + .pipe(finalize(() => this.busy.next(false))) + .subscribe({ + next: (data: any) => { + resolve({ + result: { + value: data.volumeInfo.averageRating, + }, + }); + }, + error: (error: HttpErrorResponse) => { + resolve({ + result: null, + error: { + type: error.status, + }, + }); + }, + }); + }); + } + + public ngOnDestroy(): void { + this.outputsSubject.complete(); + } +} + +/** + * A simple KPI data source to retrieve the ratings count of Harry Potter and the Sorcerer's Stone (book) via googleapis + */ +@Injectable() +export class RatingsCountKpiDataSource + extends DataSourceService + implements OnDestroy +{ + public static providerId = "RatingsCountKpiDataSource"; + + // Use this subject to communicate the data source's busy state + public busy = new BehaviorSubject(false); + + constructor(private http: HttpClient) { + super(); + } + + public async getFilteredData(): Promise { + this.busy.next(true); + return new Promise((resolve) => { + this.http + .get("https://www.googleapis.com/books/v1/volumes/5MQFrgEACAAJ") + .pipe( + delay(2000), + finalize(() => this.busy.next(false)) + ) + .subscribe({ + next: (data: any) => { + resolve({ + result: { + value: data.volumeInfo.ratingsCount, + }, + }); + }, + error: (error: HttpErrorResponse) => { + resolve({ + result: null, + error: { + type: error.status, + }, + }); + }, + }); + }); + } + + public ngOnDestroy(): void { + this.outputsSubject.complete(); + } +} +/** + * A simple KPI data source to retrieve the ratings count of Harry Potter and the Sorcerer's Stone (book) via googleapis + */ +@Injectable() +export class MockKpiDataSource + extends DataSourceService + implements OnDestroy +{ + public static providerId = "MockKpiDataSource"; + + // Use this subject to communicate the data source's busy state + public busy = new BehaviorSubject(false); + public value: number = 3381342; + + constructor() { + super(); + } + + public async getFilteredData(): Promise { + this.busy.next(true); + return new Promise((resolve) => { + of(this.value) + .pipe( + delay(5000), + take(1), + finalize(() => this.busy.next(false)) + ) + .subscribe({ + next: (data: any) => { + resolve({ + result: { + value: data, + }, + }); + }, + }); + }); + } + + public ngOnDestroy(): void { + this.outputsSubject.complete(); + } +} + +/** + * A component that instantiates the dashboard + */ +@Component({ + selector: "kpi-sync-broker-for-all-tiles-example", + templateUrl: "./kpi-sync-broker-for-all-tiles-example.component.html", + styleUrls: ["./kpi-sync-broker-for-all-tiles-example.component.less"], + standalone: false, +}) +export class KpiSyncBrokerForAllTilesExampleComponent implements OnInit { + public dashboard: IDashboard | undefined; + public gridsterConfig: GridsterConfig = {}; + public editMode: boolean = false; + + constructor( + private widgetTypesService: WidgetTypesService, + private providerRegistry: ProviderRegistryService, + private changeDetectorRef: ChangeDetectorRef + ) {} + + public ngOnInit(): void { + this.setupDashboard(); + + this.initializeDashboard(); + } + + /** Used for restoring widgets state */ + public reInitializeDashboard(): void { + // destroys the components and their providers so the dashboard can re init data + this.dashboard = undefined; + this.changeDetectorRef.detectChanges(); + + this.initializeDashboard(); + } + + private setupDashboard() { + // To add the sync broker globally to all the kpi tiles you may start with setting up the broker config + // Here you define which values to keep in sync + const brokerConfig = { + providerId: NOVA_KPI_SCALE_SYNC_BROKER, + properties: { + scaleSyncConfig: [ + { id: "value" }, + { id: "label" }, + { id: "units" }, + ], + }, + }; + const widgetTemplate = this.widgetTypesService.getWidgetType("kpi", 1); + + this.widgetTypesService.setNode( + widgetTemplate, + "configurator", + WellKnownPathKey.DataSourceProviders, + [ + AverageRatingKpiDataSource.providerId, + RatingsCountKpiDataSource.providerId, + MockKpiDataSource.providerId, + ] + ); + + // And here is how you set the sync broker for every KPI widget in the dashboard. + // Later, you will be able to override this setting for each separate KPI widget in the configuration (just like it is shown in the third + // width of the example with the 'kpiWidgetId3') + this.widgetTypesService.setNode( + widgetTemplate, + "widget", + "tiles.providers.kpiScaleSyncBroker", + brokerConfig + ); + + this.providerRegistry.setProviders({ + [AverageRatingKpiDataSource.providerId]: { + provide: DATA_SOURCE, + useClass: AverageRatingKpiDataSource, + deps: [HttpClient], + }, + [RatingsCountKpiDataSource.providerId]: { + provide: DATA_SOURCE, + useClass: RatingsCountKpiDataSource, + deps: [HttpClient], + }, + [MockKpiDataSource.providerId]: { + provide: DATA_SOURCE, + useClass: MockKpiDataSource, + deps: [], + }, + }); + } + + private initializeDashboard(): void { + const widgetsWithStructure = widgetsConfig.map((w) => + this.widgetTypesService.mergeWithWidgetType(w) + ); + const widgetsIndex = keyBy(widgetsWithStructure, (w: IWidget) => w.id); + + const positions: Record = { + kpiWidgetId: { + cols: 3, + rows: 6, + y: 0, + x: 0, + }, + kpiWidgetId2: { + cols: 3, + rows: 6, + y: 0, + x: 3, + }, + kpiWidgetId3: { + cols: 3, + rows: 6, + y: 0, + x: 6, + }, + }; + + this.dashboard = { + positions, + widgets: widgetsIndex, + }; + } +} + +const widgetsConfig: IWidget[] = [ + { + id: "kpiWidgetId", + type: "kpi", + pizzagna: { + [PizzagnaLayer.Configuration]: { + header: { + properties: { + title: "Sync Broker Applied for ALL Widgets", + subtitle: "Values are being synced", + }, + }, + tiles: { + properties: { + nodes: ["kpi1", "kpi2", "kpi3"], + }, + }, + kpi1: { + id: "kpi1", + componentType: KpiComponent.lateLoadKey, + properties: { + widgetData: { + units: \\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\`out of 5 Stars\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\`, + label: \\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\`Average Rating\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\`, + backgroundColor: "lightpink", + }, + }, + providers: { + [WellKnownProviders.DataSource]: { + providerId: AverageRatingKpiDataSource.providerId, + } as IProviderConfiguration, + [WellKnownProviders.Adapter]: { + providerId: NOVA_KPI_DATASOURCE_ADAPTER, + properties: { + componentId: "kpi1", + propertyPath: "widgetData", + }, + } as IProviderConfiguration, + }, + }, + kpi2: { + id: "kpi2", + componentType: KpiComponent.lateLoadKey, + properties: { + widgetData: { + label: \\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\`Another label which might be a pretty long one\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\`, + units: \\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\`Which comes from somewhere\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\`, + backgroundColor: "skyblue", + }, + }, + providers: { + [WellKnownProviders.DataSource]: { + providerId: RatingsCountKpiDataSource.providerId, + } as IProviderConfiguration, + [WellKnownProviders.Adapter]: { + providerId: NOVA_KPI_DATASOURCE_ADAPTER, + properties: { + componentId: "kpi2", + propertyPath: "widgetData", + }, + } as IProviderConfiguration, + }, + }, + kpi3: { + id: "kpi3", + componentType: KpiComponent.lateLoadKey, + properties: { + widgetData: { + label: \\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\`Random\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\`, + units: \\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\`Data\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\`, + }, + }, + providers: { + [WellKnownProviders.DataSource]: { + providerId: MockKpiDataSource.providerId, + } as IProviderConfiguration, + [WellKnownProviders.Adapter]: { + providerId: NOVA_KPI_DATASOURCE_ADAPTER, + properties: { + componentId: "kpi3", + propertyPath: "widgetData", + }, + } as IProviderConfiguration, + }, + }, + }, + }, + }, + { + id: "kpiWidgetId2", + type: "kpi", + pizzagna: { + [PizzagnaLayer.Configuration]: { + header: { + properties: { + title: "Sync Broker Applied for ALL Widgets", + subtitle: + "Now the values of label, units, and value are being synced", + }, + }, + tiles: { + properties: { + nodes: ["kpi1", "kpi2", "kpi3"], + }, + }, + kpi1: { + id: "kpi1", + componentType: KpiComponent.lateLoadKey, + properties: { + widgetData: { + units: \\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\`out of 5 Stars\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\`, + label: \\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\`Average Rating\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\`, + backgroundColor: "lightpink", + }, + }, + providers: { + [WellKnownProviders.DataSource]: { + providerId: AverageRatingKpiDataSource.providerId, + } as IProviderConfiguration, + [WellKnownProviders.Adapter]: { + providerId: NOVA_KPI_DATASOURCE_ADAPTER, + properties: { + componentId: "kpi1", + propertyPath: "widgetData", + }, + } as IProviderConfiguration, + }, + }, + kpi2: { + id: "kpi2", + componentType: KpiComponent.lateLoadKey, + properties: { + widgetData: { + label: \\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\`Another label which might be a pretty long one\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\`, + units: \\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\`Which comes from somewhere\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\`, + backgroundColor: "skyblue", + }, + }, + providers: { + [WellKnownProviders.DataSource]: { + providerId: RatingsCountKpiDataSource.providerId, + } as IProviderConfiguration, + [WellKnownProviders.Adapter]: { + providerId: NOVA_KPI_DATASOURCE_ADAPTER, + properties: { + componentId: "kpi2", + propertyPath: "widgetData", + }, + } as IProviderConfiguration, + }, + }, + kpi3: { + id: "kpi3", + componentType: KpiComponent.lateLoadKey, + properties: { + widgetData: { + label: \\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\`Random\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\`, + units: \\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\`Data\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\`, + }, + }, + providers: { + [WellKnownProviders.DataSource]: { + providerId: MockKpiDataSource.providerId, + } as IProviderConfiguration, + [WellKnownProviders.Adapter]: { + providerId: NOVA_KPI_DATASOURCE_ADAPTER, + properties: { + componentId: "kpi3", + propertyPath: "widgetData", + }, + } as IProviderConfiguration, + }, + }, + }, + }, + }, + { + id: "kpiWidgetId3", + type: "kpi", + pizzagna: { + [PizzagnaLayer.Configuration]: { + header: { + properties: { + title: "Here We Sync Only Labels and Units", + subtitle: + "Now only the label, and units are being synced", + }, + }, + tiles: { + properties: { + nodes: ["kpi1", "kpi2", "kpi3"], + }, + providers: { + // This is where and how you can override the globally set broker config + kpiScaleSyncBroker: { + providerId: NOVA_KPI_SCALE_SYNC_BROKER, + properties: { + scaleSyncConfig: [ + { id: "label" }, + { id: "units" }, + ], + }, + }, + }, + }, + kpi1: { + id: "kpi1", + componentType: KpiComponent.lateLoadKey, + properties: { + widgetData: { + units: \\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\`out of 5 Stars\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\`, + label: \\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\`Average Rating\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\`, + backgroundColor: "lightpink", + }, + }, + providers: { + [WellKnownProviders.DataSource]: { + providerId: AverageRatingKpiDataSource.providerId, + } as IProviderConfiguration, + [WellKnownProviders.Adapter]: { + providerId: NOVA_KPI_DATASOURCE_ADAPTER, + properties: { + componentId: "kpi1", + propertyPath: "widgetData", + }, + } as IProviderConfiguration, + }, + }, + kpi2: { + id: "kpi2", + componentType: KpiComponent.lateLoadKey, + properties: { + widgetData: { + label: \\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\`Another label which might be a pretty long one\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\`, + units: \\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\`Which comes from somewhere\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\`, + backgroundColor: "skyblue", + }, + }, + providers: { + [WellKnownProviders.DataSource]: { + providerId: RatingsCountKpiDataSource.providerId, + } as IProviderConfiguration, + [WellKnownProviders.Adapter]: { + providerId: NOVA_KPI_DATASOURCE_ADAPTER, + properties: { + componentId: "kpi2", + propertyPath: "widgetData", + }, + } as IProviderConfiguration, + }, + }, + kpi3: { + id: "kpi3", + componentType: KpiComponent.lateLoadKey, + properties: { + widgetData: { + label: \\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\`Random\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\`, + units: \\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\`Data\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\`, + }, + }, + providers: { + [WellKnownProviders.DataSource]: { + providerId: MockKpiDataSource.providerId, + } as IProviderConfiguration, + [WellKnownProviders.Adapter]: { + providerId: NOVA_KPI_DATASOURCE_ADAPTER, + properties: { + componentId: "kpi3", + propertyPath: "widgetData", + }, + } as IProviderConfiguration, + }, + }, + }, + }, + }, +]; +\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\`, + "widget-types/kpi/kpi-widget/kpi-widget-example.component.ts": \\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\`// © 2022 SolarWinds Worldwide, LLC. All rights reserved. +// +// Permission is hereby granted, free of charge, to any person obtaining a copy +// of this software and associated documentation files (the "Software"), to +// deal in the Software without restriction, including without limitation the +// rights to use, copy, modify, merge, publish, distribute, sublicense, and/or +// sell copies of the Software, and to permit persons to whom the Software is +// furnished to do so, subject to the following conditions: +// +// The above copyright notice and this permission notice shall be included in +// all copies or substantial portions of the Software. +// +// THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR +// IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, +// FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE +// AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER +// LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, +// OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN +// THE SOFTWARE. + +import { HttpClient, HttpErrorResponse } from "@angular/common/http"; +import { Component, Injectable, OnDestroy, OnInit } from "@angular/core"; +import { GridsterConfig, GridsterItem } from "angular-gridster2"; +import { BehaviorSubject } from "rxjs"; +import { finalize } from "rxjs/operators"; + +import { DataSourceService, IFilteringOutputs } from "@nova-ui/bits"; +import { + DATA_SOURCE, + DEFAULT_PIZZAGNA_ROOT, + IDashboard, + IKpiData, + IProviderConfiguration, + IRefresherProperties, + IWidget, + IWidgets, + KpiComponent, + NOVA_KPI_DATASOURCE_ADAPTER, + PizzagnaLayer, + ProviderRegistryService, + WellKnownPathKey, + WellKnownProviders, + WidgetTypesService, +} from "@nova-ui/dashboards"; + +/** + * A simple KPI data source to retrieve the average rating of Harry Potter and the Sorcerer's Stone (book) via googleapis + */ +@Injectable() +export class AverageRatingKpiDataSource + extends DataSourceService + implements OnDestroy +{ + // This is the ID we'll use to identify the provider + public static providerId = "AverageRatingKpiDataSource"; + + // Use this subject to communicate the data source's busy state + public busy = new BehaviorSubject(false); + + constructor(private http: HttpClient) { + super(); + } + + // In this example, getFilteredData is invoked every 10 minutes (Take a look at the refresher + // provider definition in the widget configuration below to see how the interval is set) + public async getFilteredData(): Promise { + this.busy.next(true); + return new Promise((resolve) => { + // *** Make a resource request to an external API (if needed) + this.http + .get("https://www.googleapis.com/books/v1/volumes/5MQFrgEACAAJ") + .pipe(finalize(() => this.busy.next(false))) + .subscribe({ + next: (data: any) => { + resolve({ + result: { + value: data.volumeInfo.averageRating, + }, + }); + }, + error: (error: HttpErrorResponse) => { + resolve({ + result: null, + error: { + type: error.status, + }, + }); + }, + }); + }); + } + + public ngOnDestroy(): void { + this.outputsSubject.complete(); + } +} + +/** + * A component that instantiates the dashboard + */ +@Component({ + selector: "kpi-widget-example", + templateUrl: "./kpi-widget-example.component.html", + styleUrls: ["./kpi-widget-example.component.less"], + standalone: false, +}) +export class KpiWidgetExampleComponent implements OnInit { + // This variable will hold all the data needed to define the layout and behavior of the widgets. + // Pass this to the dashboard component's dashboard input in the template. + public dashboard: IDashboard; + + // Angular gridster requires a configuration object even if it's empty. + // Pass this to the dashboard component's gridsterConfig input in the template. + public gridsterConfig: GridsterConfig = {}; + + // Boolean passed as an input to the dashboard. When true, widgets can be moved, resized, removed, or edited + public editMode: boolean = false; + + constructor( + // WidgetTypesService provides the widget's necessary structure information + private widgetTypesService: WidgetTypesService, + + // In general, the ProviderRegistryService is used for making entities available for injection into dynamically loaded components. + private providerRegistry: ProviderRegistryService + ) {} + + public ngOnInit(): void { + // Grabbing the widget's default template which will be needed as a parameter for setNode + const widgetTemplate = this.widgetTypesService.getWidgetType("kpi", 1); + // Registering our data sources as dropdown options in the widget editor/configurator + // Note: This could also be done in the parent module's constructor so that + // multiple dashboards could have access to the same widget template modification. + this.widgetTypesService.setNode( + // This is the template we grabbed above with getWidgetType + widgetTemplate, + // We are setting the editor/configurator part of the widget template + "configurator", + // This indicates which node you are changing and we want to change + // the data source providers available for selection in the editor. + WellKnownPathKey.DataSourceProviders, + // We are setting the data sources available for selection in the editor + [AverageRatingKpiDataSource.providerId] + ); + + // Registering the data source for injection into the KPI tile. + // Note: Each tile of a KPI widget is assigned its own instance of the data source + this.providerRegistry.setProviders({ + [AverageRatingKpiDataSource.providerId]: { + provide: DATA_SOURCE, + useClass: AverageRatingKpiDataSource, + // Any dependencies that need to be injected into the provider must be listed here + deps: [HttpClient], + }, + }); + + this.initializeDashboard(); + } + + public initializeDashboard(): void { + // We're using a static configuration object for this example, but this is where + // the widget's configuration could potentially be populated from a database + const kpiWidget = widgetConfig; + const widgetIndex: IWidgets = { + // Complete the KPI widget with information coming from its type definition + [kpiWidget.id]: + this.widgetTypesService.mergeWithWidgetType(kpiWidget), + }; + + // Setting the widget dimensions and position (this is for gridster) + const positions: Record = { + [kpiWidget.id]: { + cols: 4, + rows: 6, + y: 0, + x: 0, + }, + }; + + // Finally, assigning the variables we created above to the dashboard + this.dashboard = { + positions, + widgets: widgetIndex, + }; + } +} + +const widgetConfig: IWidget = { + id: "kpiWidgetId", + type: "kpi", + pizzagna: { + [PizzagnaLayer.Configuration]: { + [DEFAULT_PIZZAGNA_ROOT]: { + providers: { + [WellKnownProviders.Refresher]: { + properties: { + // Configuring the refresher interval so that our data source is invoked every ten minutes + interval: 60 * 10, + enabled: true, + } as IRefresherProperties, + } as Partial, + }, + }, + header: { + properties: { + title: "Harry Potter and the Sorcerer's Stone", + subtitle: "By J. K. Rowling", + }, + }, + tiles: { + properties: { + nodes: ["kpi1"], + }, + }, + kpi1: { + id: "kpi1", + componentType: KpiComponent.lateLoadKey, + properties: { + widgetData: { + units: \\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\`out of 5 Stars\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\`, + label: \\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\`Average Rating\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\`, + }, + }, + providers: { + [WellKnownProviders.DataSource]: { + // Setting the data source providerId for the tile with id "kpi1" + providerId: AverageRatingKpiDataSource.providerId, + } as IProviderConfiguration, + [WellKnownProviders.Adapter]: { + providerId: NOVA_KPI_DATASOURCE_ADAPTER, + properties: { + componentId: "kpi1", + propertyPath: "widgetData", + }, + } as IProviderConfiguration, + }, + }, + }, + }, +}; +\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\`, + "widget-types/kpi/kpi-widget-background-color/kpi-widget-background-color-example.component.ts": \\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\`// © 2022 SolarWinds Worldwide, LLC. All rights reserved. +// +// Permission is hereby granted, free of charge, to any person obtaining a copy +// of this software and associated documentation files (the "Software"), to +// deal in the Software without restriction, including without limitation the +// rights to use, copy, modify, merge, publish, distribute, sublicense, and/or +// sell copies of the Software, and to permit persons to whom the Software is +// furnished to do so, subject to the following conditions: +// +// The above copyright notice and this permission notice shall be included in +// all copies or substantial portions of the Software. +// +// THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR +// IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, +// FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE +// AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER +// LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, +// OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN +// THE SOFTWARE. + +import { HttpClient, HttpErrorResponse } from "@angular/common/http"; +import { + ChangeDetectorRef, + Component, + Injectable, + OnDestroy, + OnInit, +} from "@angular/core"; +import { GridsterConfig, GridsterItem } from "angular-gridster2"; +import { BehaviorSubject } from "rxjs"; +import { finalize } from "rxjs/operators"; + +import { DataSourceService, IFilteringOutputs } from "@nova-ui/bits"; +import { + DATA_SOURCE, + DEFAULT_KPI_BACKGROUND_COLORS, + IDashboard, + IKpiColorRules, + IKpiData, + IProviderConfiguration, + IWidget, + IWidgets, + KpiComponent, + NOVA_KPI_COLOR_PRIORITIZER, + NOVA_KPI_DATASOURCE_ADAPTER, + PizzagnaLayer, + ProviderRegistryService, + WellKnownPathKey, + WellKnownProviders, + WidgetTypesService, +} from "@nova-ui/dashboards"; + +/** + * A simple KPI data source to retrieve the average rating of Harry Potter and the Sorcerer's Stone (book) via googleapis + */ +@Injectable() +export class AverageRatingKpiDataSource + extends DataSourceService + implements OnDestroy +{ + public static providerId = "AverageRatingKpiDataSource"; + public busy = new BehaviorSubject(false); + + constructor(private http: HttpClient) { + super(); + } + + public async getFilteredData(): Promise { + this.busy.next(true); + return new Promise((resolve) => { + // *** Make a resource request to an external API (if needed) + this.http + .get("https://www.googleapis.com/books/v1/volumes/5MQFrgEACAAJ") + .pipe(finalize(() => this.busy.next(false))) + .subscribe({ + next: (data: any) => { + resolve({ + result: { + value: data.volumeInfo.averageRating, + // setting the color on the dataSource "Sea Green", + // uncomment to get the background color update from the "Data" layer + // backgroundColor: "var(--nui-color-chart-three)", + }, + }); + }, + error: (error: HttpErrorResponse) => { + resolve({ + result: null, + error: { + type: error.status, + }, + }); + }, + }); + }); + } + + public ngOnDestroy(): void { + this.outputsSubject.complete(); + } +} + +/** + * A component that instantiates the dashboard + */ +@Component({ + selector: "kpi-widget-background-color-example", + templateUrl: "./kpi-widget-background-color-example.component.html", + styleUrls: ["./kpi-widget-background-color-example.component.less"], + standalone: false, +}) +export class KpiWidgetBackgroundColorExampleComponent implements OnInit { + public dashboard: IDashboard | undefined; + public gridsterConfig: GridsterConfig = {}; + public editMode: boolean = false; + + constructor( + private widgetTypesService: WidgetTypesService, + private providerRegistry: ProviderRegistryService, + private changeDetectorRef: ChangeDetectorRef + ) {} + + public ngOnInit(): void { + this.setupDashboard(); + + // KPI tile default color setup + this.setupDefaultColorStructure(); + + // Sets the custom pallette to the 'Description' section + this.setupCustomPalletteDescription(); + + // Sets the custom pallette to the 'Background color rules' section + this.setupCustomPalletteRules(); + + this.initializeDashboard(); + } + + /** Used for restoring widgets state */ + public reInitializeDashboard(): void { + // destroys the components and their providers so the dashboard can re init data + this.dashboard = undefined; + this.changeDetectorRef.detectChanges(); + + this.initializeDashboard(); + } + + private setupCustomPalletteDescription() { + const kpiWidgetTemplate = this.widgetTypesService.getWidgetType( + "kpi", + 1 + ); + this.widgetTypesService.setNode( + kpiWidgetTemplate, + "configurator", + WellKnownPathKey.TileDescriptionBackgroundColors, + [ + { color: "var(--nui-color-chart-one)", label: "Blue" }, + { + color: "var(--nui-color-chart-one-light)", + label: "Blue Light", + }, + { + color: "var(--nui-color-chart-one-dark)", + label: "Blue Dark", + }, + ] + ); + } + + private setupCustomPalletteRules() { + const kpiWidgetTemplate = this.widgetTypesService.getWidgetType( + "kpi", + 1 + ); + this.widgetTypesService.setNode( + kpiWidgetTemplate, + "configurator", + WellKnownPathKey.TileBackgroundColorRulesBackgroundColors, + [ + { color: "red", label: "Native Red" }, + ...DEFAULT_KPI_BACKGROUND_COLORS, + ] + ); + } + + private setupDefaultColorStructure() { + const widgetTemplate = this.widgetTypesService.getWidgetType("kpi", 1); + this.widgetTypesService.setNode( + widgetTemplate, + "widget", + "tiles.properties.template.properties.widgetData.backgroundColor", + "red" + ); + } + + private setupDashboard() { + const widgetTemplate = this.widgetTypesService.getWidgetType("kpi", 1); + this.widgetTypesService.setNode( + widgetTemplate, + "configurator", + WellKnownPathKey.DataSourceProviders, + [AverageRatingKpiDataSource.providerId] + ); + + this.providerRegistry.setProviders({ + [AverageRatingKpiDataSource.providerId]: { + provide: DATA_SOURCE, + useClass: AverageRatingKpiDataSource, + deps: [HttpClient], + }, + }); + } + + private initializeDashboard(): void { + const kpiWidget = widgetConfig; + const widgetIndex: IWidgets = { + [kpiWidget.id]: + this.widgetTypesService.mergeWithWidgetType(kpiWidget), + }; + + const positions: Record = { + [kpiWidget.id]: { + cols: 4, + rows: 6, + y: 0, + x: 0, + }, + }; + + this.dashboard = { + positions, + widgets: widgetIndex, + }; + } +} + +const widgetConfig: IWidget = { + id: "kpiWidgetId", + type: "kpi", + pizzagna: { + [PizzagnaLayer.Configuration]: { + header: { + properties: { + title: "Harry Potter and the Sorcerer's Stone", + subtitle: "By J. K. Rowling", + }, + }, + tiles: { + properties: { + nodes: ["kpi1"], + }, + }, + kpi1: { + id: "kpi1", + componentType: KpiComponent.lateLoadKey, + properties: { + widgetData: { + units: \\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\`out of 5 Stars\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\`, + label: \\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\`Average Rating\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\`, + // Configuration color "Blue" + backgroundColor: "var(--nui-color-chart-one)", + }, + }, + providers: { + [WellKnownProviders.DataSource]: { + providerId: AverageRatingKpiDataSource.providerId, + } as IProviderConfiguration, + [WellKnownProviders.Adapter]: { + providerId: NOVA_KPI_DATASOURCE_ADAPTER, + properties: { + componentId: "kpi1", + propertyPath: "widgetData", + }, + } as IProviderConfiguration, + [WellKnownProviders.KpiColorPrioritizer]: { + providerId: NOVA_KPI_COLOR_PRIORITIZER, + properties: { + // Color Prioritizer Rules + // settings rules - if the value is more than "2" display "Violet" color + rules: [ + { + comparisonType: ">", + value: 2, + color: "var(--nui-color-chart-four)", + }, + ] as IKpiColorRules[], + }, + } as IProviderConfiguration, + }, + }, + }, + }, +}; +\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\`, + "widget-types/kpi/kpi-widget-background-color-docs.component.ts": \\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\`// © 2022 SolarWinds Worldwide, LLC. All rights reserved. +// +// Permission is hereby granted, free of charge, to any person obtaining a copy +// of this software and associated documentation files (the "Software"), to +// deal in the Software without restriction, including without limitation the +// rights to use, copy, modify, merge, publish, distribute, sublicense, and/or +// sell copies of the Software, and to permit persons to whom the Software is +// furnished to do so, subject to the following conditions: +// +// The above copyright notice and this permission notice shall be included in +// all copies or substantial portions of the Software. +// +// THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR +// IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, +// FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE +// AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER +// LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, +// OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN +// THE SOFTWARE. + +import { Component } from "@angular/core"; + +@Component({ + selector: "nui-kpi-background-color-docs", + templateUrl: "./kpi-widget-background-color-docs.component.html", + standalone: false, +}) +export class KpiWidgetBackgroundColorDocsComponent { + public comparatorsRegistryCode = \\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\` + this.comparatorsRegistry.registerComparators({ + "!=": { + comparatorFn: (actual: any, reference: any) => actual != reference, + label: "Not equal", + }, + }); + \\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\`; +} +\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\`, + "widget-types/kpi/kpi-widget-interactive/kpi-widget-interactive-example.component.ts": \\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\`// © 2022 SolarWinds Worldwide, LLC. All rights reserved. +// +// Permission is hereby granted, free of charge, to any person obtaining a copy +// of this software and associated documentation files (the "Software"), to +// deal in the Software without restriction, including without limitation the +// rights to use, copy, modify, merge, publish, distribute, sublicense, and/or +// sell copies of the Software, and to permit persons to whom the Software is +// furnished to do so, subject to the following conditions: +// +// The above copyright notice and this permission notice shall be included in +// all copies or substantial portions of the Software. +// +// THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR +// IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, +// FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE +// AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER +// LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, +// OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN +// THE SOFTWARE. + +import { HttpClient, HttpErrorResponse } from "@angular/common/http"; +import { Component, Injectable, OnDestroy, OnInit } from "@angular/core"; +import { GridsterConfig, GridsterItem } from "angular-gridster2"; +import { BehaviorSubject } from "rxjs"; +import { finalize } from "rxjs/operators"; + +import { DataSourceService, IFilteringOutputs } from "@nova-ui/bits"; +import { + DATA_SOURCE, + IDashboard, + IKpiData, + IProviderConfiguration, + IWidget, + IWidgets, + KpiComponent, + NOVA_KPI_DATASOURCE_ADAPTER, + NOVA_URL_INTERACTION_HANDLER, + PizzagnaLayer, + ProviderRegistryService, + WellKnownPathKey, + WellKnownProviders, + WidgetTypesService, +} from "@nova-ui/dashboards"; + +/** + * A simple KPI data source to retrieve the average rating of Harry Potter and the Sorcerer's Stone (book) via googleapis + */ +@Injectable() +export class BookRatingDataSource + extends DataSourceService + implements OnDestroy +{ + // This is the ID we'll use to identify the provider + public static providerId = "BookRatingDataSource"; + + // Use this subject to communicate the data source's busy state + public busy = new BehaviorSubject(false); + + constructor(private http: HttpClient) { + super(); + } + + // In this example, getFilteredData is invoked every 10 minutes (Take a look at the refresher + // provider definition in the widget configuration below to see how the interval is set) + public async getFilteredData(): Promise { + this.busy.next(true); + return new Promise((resolve) => { + // *** Make a resource request to an external API (if needed) + this.http + .get("https://www.googleapis.com/books/v1/volumes/zpvysRGsBlwC") + .pipe(finalize(() => this.busy.next(false))) + .subscribe({ + next: (data: any) => { + resolve({ + result: { + value: data.volumeInfo.averageRating, + link: data.volumeInfo.infoLink, + }, + }); + }, + error: (error: HttpErrorResponse) => { + resolve({ + result: null, + error: { + type: error.status, + }, + }); + }, + }); + }); + } + + public ngOnDestroy(): void { + this.outputsSubject.complete(); + } +} + +/** + * A component that instantiates the dashboard + */ +@Component({ + selector: "kpi-widget-interactive-example", + templateUrl: "./kpi-widget-interactive-example.component.html", + styleUrls: ["./kpi-widget-interactive-example.component.less"], + standalone: false, +}) +export class KpiWidgetInteractiveExampleComponent implements OnInit { + // This variable will hold all the data needed to define the layout and behavior of the widgets. + // Pass this to the dashboard component's dashboard input in the template. + public dashboard: IDashboard; + + // Angular gridster requires a configuration object even if it's empty. + // Pass this to the dashboard component's gridsterConfig input in the template. + public gridsterConfig: GridsterConfig = {}; + + // Boolean passed as an input to the dashboard. When true, widgets can be moved, resized, removed, or edited + public editMode: boolean = false; + + constructor( + // WidgetTypesService provides the widget's necessary structure information + private widgetTypesService: WidgetTypesService, + + // In general, the ProviderRegistryService is used for making entities available for injection into dynamically loaded components. + private providerRegistry: ProviderRegistryService + ) {} + + public ngOnInit(): void { + // Grabbing the widget's default template which will be needed as a parameter for setNode + const widgetTemplate = this.widgetTypesService.getWidgetType("kpi", 1); + // Registering our data sources as dropdown options in the widget editor/configurator + // Note: This could also be done in the parent module's constructor so that + // multiple dashboards could have access to the same widget template modification. + this.widgetTypesService.setNode( + // This is the template we grabbed above with getWidgetType + widgetTemplate, + // We are setting the editor/configurator part of the widget template + "configurator", + // This indicates which node you are changing and we want to change + // the data source providers available for selection in the editor. + WellKnownPathKey.DataSourceProviders, + // We are setting the data sources available for selection in the editor + [BookRatingDataSource.providerId] + ); + + // Registering the data source for injection into the KPI tile. + // Note: Each tile of a KPI widget is assigned its own instance of the data source + this.providerRegistry.setProviders({ + [BookRatingDataSource.providerId]: { + provide: DATA_SOURCE, + useClass: BookRatingDataSource, + // Any dependencies that need to be injected into the provider must be listed here + deps: [HttpClient], + }, + }); + + this.initializeDashboard(); + } + + public initializeDashboard(): void { + // We're using a static configuration object for this example, but this is where + // the widget's configuration could potentially be populated from a database + const kpiWidget = widgetConfig; + const widgetIndex: IWidgets = { + // Complete the KPI widget with information coming from its type definition + [kpiWidget.id]: + this.widgetTypesService.mergeWithWidgetType(kpiWidget), + }; + + // Setting the widget dimensions and position (this is for gridster) + const positions: Record = { + [kpiWidget.id]: { + cols: 4, + rows: 6, + y: 0, + x: 0, + }, + }; + + // Finally, assigning the variables we created above to the dashboard + this.dashboard = { + positions, + widgets: widgetIndex, + }; + } +} + +const widgetConfig: IWidget = { + id: "kpiWidgetId", + type: "kpi", + pizzagna: { + [PizzagnaLayer.Configuration]: { + header: { + properties: { + title: "Harry Potter and the Order of the Phoenix", + subtitle: "By: J. K. Rowling", + }, + }, + tiles: { + providers: { + interaction: { + // Configuring the UrlInteractionHandler for interactions on the tiles + providerId: NOVA_URL_INTERACTION_HANDLER, + properties: { + // the 'url' property tells the handler what link to use when interaction occurs on the series + url: "\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\${data.link}", + }, + }, + }, + properties: { + nodes: ["kpi1"], + }, + }, + kpi1: { + id: "kpi1", + componentType: KpiComponent.lateLoadKey, + properties: { + widgetData: { + units: \\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\`out of 5 stars\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\`, + label: \\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\`Average Rating\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\`, + value: 0, + // the link property that is passed to the UrlInteractionHandler when the title is clicked + // this will be updated in BookRatingDataSource's 'getFilteredData' call. + link: "http://www.google.com", + }, + }, + providers: { + [WellKnownProviders.DataSource]: { + // Setting the data source providerId for the tile with id "kpi1" + providerId: BookRatingDataSource.providerId, + } as IProviderConfiguration, + [WellKnownProviders.Adapter]: { + providerId: NOVA_KPI_DATASOURCE_ADAPTER, + properties: { + componentId: "kpi1", + propertyPath: "widgetData", + }, + } as IProviderConfiguration, + }, + }, + }, + }, +}; +\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\`, + "widget-types/proportional/models.ts": \\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\`export interface IMockBeerReview { + id: string; + name: string; + data: number[]; + icon: string; + link?: string; + value: string; + color?: string; +} +\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\`, + "widget-types/proportional/proportional-docs.component.ts": \\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\`// © 2022 SolarWinds Worldwide, LLC. All rights reserved. +// +// Permission is hereby granted, free of charge, to any person obtaining a copy +// of this software and associated documentation files (the "Software"), to +// deal in the Software without restriction, including without limitation the +// rights to use, copy, modify, merge, publish, distribute, sublicense, and/or +// sell copies of the Software, and to permit persons to whom the Software is +// furnished to do so, subject to the following conditions: +// +// The above copyright notice and this permission notice shall be included in +// all copies or substantial portions of the Software. +// +// THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR +// IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, +// FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE +// AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER +// LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, +// OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN +// THE SOFTWARE. + +import { Component, OnInit } from "@angular/core"; + +import { mapContentFile } from "../../../../demo-files-factory"; + +@Component({ + selector: "nui-proportional-docs", + templateUrl: "./proportional-docs.component.html", + standalone: false, +}) +export class ProportionalDocsComponent implements OnInit { + public proportionalWidgetFileText = ""; + public proportionalConfiguratorFileText = ""; + + public async ngOnInit(): Promise { + this.proportionalWidgetFileText = await import( + "./../../../../../../src/lib/widget-types/proportional/proportional-widget" + ).then(mapContentFile); + this.proportionalConfiguratorFileText = await import( + "./../../../../../../src/lib/widget-types/proportional/proportional-configurator" + ).then(mapContentFile); + } +} +\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\`, + "widget-types/proportional/proportional-docs.module.ts": \\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\`// © 2022 SolarWinds Worldwide, LLC. All rights reserved. +// +// Permission is hereby granted, free of charge, to any person obtaining a copy +// of this software and associated documentation files (the "Software"), to +// deal in the Software without restriction, including without limitation the +// rights to use, copy, modify, merge, publish, distribute, sublicense, and/or +// sell copies of the Software, and to permit persons to whom the Software is +// furnished to do so, subject to the following conditions: +// +// The above copyright notice and this permission notice shall be included in +// all copies or substantial portions of the Software. +// +// THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR +// IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, +// FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE +// AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER +// LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, +// OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN +// THE SOFTWARE. + +import { NgModule } from "@angular/core"; +import { RouterModule, Routes } from "@angular/router"; + +import { + NuiButtonModule, + NuiDocsModule, + NuiMessageModule, + NuiSwitchModule, + DEMO_PATH_TOKEN, +} from "@nova-ui/bits"; +import { NuiDashboardsModule } from "@nova-ui/dashboards"; + +import { ProportionalDocsComponent } from "./proportional-docs.component"; +import { ProportionalDonutContentDocsComponent } from "./proportional-donut-content-docs.component"; +import { ProportionalWidgetDonutContentFormattersExampleComponent } from "./proportional-donut-content-formatters/proportional-donut-content-formatters-example.component"; +import { ProportionalWidgetExampleComponent } from "./proportional-widget/proportional-widget-example.component"; +import { ProportionalWidgetInteractiveExampleComponent } from "./proportional-widget-interactive/proportional-widget-interactive-example.component"; +import { getDemoFiles } from "../../../../demo-files-factory"; + +const routes: Routes = [ + { + path: "", + component: ProportionalDocsComponent, + data: { + srlc: { + hideIndicator: true, + }, + showThemeSwitcher: true, + }, + }, + { + path: "example", + component: ProportionalWidgetExampleComponent, + data: { + srlc: { + hideIndicator: true, + }, + }, + }, + { + path: "donut-content-formatters", + component: ProportionalDonutContentDocsComponent, + data: { + srlc: { + hideIndicator: true, + }, + }, + }, + { + path: "donut-content-formatters-example", + component: ProportionalWidgetDonutContentFormattersExampleComponent, + data: { + srlc: { + hideIndicator: true, + }, + }, + }, + { + path: "proportional-widget-interactive-example", + component: ProportionalWidgetInteractiveExampleComponent, + data: { + srlc: { + hideIndicator: true, + }, + }, + }, +]; + +@NgModule({ + imports: [ + RouterModule.forChild(routes), + NuiButtonModule, + NuiDocsModule, + NuiDashboardsModule, + NuiMessageModule, + NuiSwitchModule, + ], + declarations: [ + ProportionalDocsComponent, + ProportionalWidgetExampleComponent, + ProportionalWidgetInteractiveExampleComponent, + ProportionalWidgetDonutContentFormattersExampleComponent, + ProportionalDonutContentDocsComponent, + ], + providers: [ + { + provide: DEMO_PATH_TOKEN, + useValue: getDemoFiles("proportional"), + }, + ], +}) +export default class ProportionalDocsModule {} +\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\`, + "widget-types/proportional/proportional-donut-content-docs.component.ts": \\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\`// © 2022 SolarWinds Worldwide, LLC. All rights reserved. +// +// Permission is hereby granted, free of charge, to any person obtaining a copy +// of this software and associated documentation files (the "Software"), to +// deal in the Software without restriction, including without limitation the +// rights to use, copy, modify, merge, publish, distribute, sublicense, and/or +// sell copies of the Software, and to permit persons to whom the Software is +// furnished to do so, subject to the following conditions: +// +// The above copyright notice and this permission notice shall be included in +// all copies or substantial portions of the Software. +// +// THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR +// IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, +// FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE +// AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER +// LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, +// OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN +// THE SOFTWARE. + +import { Component } from "@angular/core"; + +@Component({ + selector: "nui-proportional-donut-content-docs", + templateUrl: "./proportional-donut-content-docs.component.html", + standalone: false, +}) +export class ProportionalDonutContentDocsComponent { + public dataSourceDataFieldsConfig = \\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\` +public dataFieldsConfig: IProportionalDataFieldsConfig = { + dataFields$: new BehaviorSubject(this.dataFields), + chartSeriesDataFields$: new BehaviorSubject(this.chartSeriesDataFields), +}; + \\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\`; + + public widgetConfigSlice = \\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\` +"properties": { + "configuration": { + "chartOptions": { + donutContentConfig: { + formatter: { + componentType: SiUnitsFormatterComponent.lateLoadKey, + }, + aggregator: { + aggregatorType: sumAggregator.aggregatorType, + }, + }, + } + } +} + + \\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\`; +} +\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\`, + "widget-types/proportional/proportional-donut-content-formatters/proportional-donut-content-formatters-example.component.ts": \\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\`// © 2022 SolarWinds Worldwide, LLC. All rights reserved. +// +// Permission is hereby granted, free of charge, to any person obtaining a copy +// of this software and associated documentation files (the "Software"), to +// deal in the Software without restriction, including without limitation the +// rights to use, copy, modify, merge, publish, distribute, sublicense, and/or +// sell copies of the Software, and to permit persons to whom the Software is +// furnished to do so, subject to the following conditions: +// +// The above copyright notice and this permission notice shall be included in +// all copies or substantial portions of the Software. +// +// THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR +// IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, +// FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE +// AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER +// LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, +// OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN +// THE SOFTWARE. + +import { + ChangeDetectorRef, + Component, + Injectable, + OnDestroy, + OnInit, +} from "@angular/core"; +import { GridsterConfig, GridsterItem } from "angular-gridster2"; +import { BehaviorSubject } from "rxjs"; + +import { + DataSourceService, + IDataField, + IDataSource, + IFilteringOutputs, +} from "@nova-ui/bits"; +import { IAccessors, IChartAssistSeries } from "@nova-ui/charts"; +import { + DATA_SOURCE, + DEFAULT_LEGEND_FORMATTERS, + DEFAULT_PIZZAGNA_ROOT, + DEFAULT_PROPORTIONAL_CONTENT_AGGREGATORS, + DEFAULT_PROPORTIONAL_CONTENT_FORMATTERS, + DONUT_CONTENT_CONFIGURATION_SLICE, + IDashboard, + IDonutContentConfig, + IProportionalDataFieldsConfig, + IProportionalWidgetChartOptions, + IProportionalWidgetConfig, + IProviderConfiguration, + IWidget, + IWidgets, + LegendPlacement, + PizzagnaLayer, + ProportionalContentAggregatorsRegistryService, + ProportionalDonutContentFormattersRegistryService, + ProportionalLegendFormattersRegistryService, + ProportionalWidgetChartTypes, + ProviderRegistryService, + SiUnitsFormatterComponent, + sumAggregator, + WellKnownPathKey, + WellKnownProviders, + WidgetTypesService, +} from "@nova-ui/dashboards"; + +import { IMockBeerReview } from "../models"; + +/** + * A simple proportional data source to retrieve beer review counts by city + */ +@Injectable() +export class BeerReviewCountsByCityMockDataSource + extends DataSourceService> + implements IDataSource>, OnDestroy +{ + public static providerId = "BeerReviewCountsByCityMockDataSource"; + public busy = new BehaviorSubject(false); + + protected dataFields: IDataField[] = [ + { + id: "Brno", + label: "Brno", + // @ts-ignore + dataType: null, + }, + { + id: "kyiv", + label: "Kyiv", + // @ts-ignore + dataType: null, + }, + { + id: "austin", + label: "Austin", + // @ts-ignore + dataType: null, + }, + { + id: "lisbon", + label: "Lisbon", + // @ts-ignore + dataType: null, + }, + { + id: "sydney", + label: "Sydney", + // @ts-ignore + dataType: null, + }, + { + id: "nur-sultan", + label: "Nur-Sultan", + // @ts-ignore + dataType: null, + }, + ]; + protected chartSeriesDataFields: IDataField[] = [ + // default field in the chart series that is used for the aggregation + { + id: "data[0]", + label: "data", + // @ts-ignore + dataType: null, + }, + // any custom field in the chart series that is used for the aggregation + { + id: "customDonutContent", + label: "Custom Donut Content", + // @ts-ignore + dataType: null, + }, + ]; + + /** + * DataSource needs to implement the "IDataFieldsConfig" for this scenario. + * + * It's necessary to provide the "chartSeriesDataFields", + * that's why proportional widget dataSource has it's own interface for that - IProportionalDataFieldsConfig. + * + * dataFields$ - stands for possible series fields + * chartSeriesDataFields$ - stands for the fields IN the series + * + * see declaration of "dataFields" and "chartSeriesDataFields" for the example. + */ + public dataFieldsConfig: IProportionalDataFieldsConfig = { + dataFields$: new BehaviorSubject(this.dataFields), + chartSeriesDataFields$: new BehaviorSubject( + this.chartSeriesDataFields + ), + }; + + public async getFilteredData(): Promise { + this.busy.next(true); + return new Promise((resolve) => { + setTimeout(() => { + this.outputsSubject.next({ + result: getMockBeerReviewCountsByCity(), + }); + this.busy.next(false); + }, 300); + }); + } + + public ngOnDestroy(): void { + this.outputsSubject.complete(); + } +} + +/** + * A component that instantiates the dashboard + */ +@Component({ + selector: "proportional-widget-donut-content-formatters-example", + templateUrl: "./proportional-donut-content-formatters-example.component.html", + styleUrls: [ + "./proportional-donut-content-formatters-example.component.less", + ], + standalone: false, +}) +export class ProportionalWidgetDonutContentFormattersExampleComponent + implements OnInit +{ + public dashboard: IDashboard | undefined; + + // Angular gridster requires a configuration object even if it's empty. + // Pass this to the dashboard component's gridsterConfig input in the template. + public gridsterConfig: GridsterConfig = {}; + + // Boolean passed as an input to the dashboard. When true, widgets can be moved, resized, removed, or edited + public editMode: boolean = false; + + constructor( + // WidgetTypesService provides the widget's necessary structure information + private widgetTypesService: WidgetTypesService, + // In general, the ProviderRegistryService is used for making entities available for injection into dynamically loaded components. + private providerRegistry: ProviderRegistryService, + // registry for adding the formatter for donut content + contentFormattersRegistry: ProportionalDonutContentFormattersRegistryService, + // registry for adding the formatter for proportional legend + legendFormattersRegistry: ProportionalLegendFormattersRegistryService, + // registry for adding the aggregators for donut content + aggregatorRegistry: ProportionalContentAggregatorsRegistryService, + private changeDetectorRef: ChangeDetectorRef + ) { + // on the dashboard startup, it's necessary to add possible content formatters, legend formatters and content aggregators to the registry. + // using registry is a way for setting the available formatters. + legendFormattersRegistry.addItems(DEFAULT_LEGEND_FORMATTERS); + contentFormattersRegistry.addItems( + DEFAULT_PROPORTIONAL_CONTENT_FORMATTERS + ); + aggregatorRegistry.addItems(DEFAULT_PROPORTIONAL_CONTENT_AGGREGATORS); + } + + public ngOnInit(): void { + // Grabbing the widget's default template which will be needed as a parameter for setNode + const widgetTemplate = this.widgetTypesService.getWidgetType( + "proportional", + 1 + ); + + // Registering our data sources as dropdown options in the widget editor/configurator + // Note: This could also be done in the parent module's constructor so that + // multiple dashboards could have access to the same widget template modification. + this.widgetTypesService.setNode( + // This is the template we grabbed above with getWidgetType + widgetTemplate, + // We are setting the editor/configurator part of the widget template + "configurator", + // This indicates which node you are changing and we want to change + // the data source providers available for selection in the editor. + WellKnownPathKey.DataSourceProviders, + // We are setting the data sources available for selection in the editor + [BeerReviewCountsByCityMockDataSource.providerId] + ); + + // Setup of the configurator is done here + this.setupConfigurator(); + + // Registering the data source for injection into the Proportional widget. + this.providerRegistry.setProviders({ + [BeerReviewCountsByCityMockDataSource.providerId]: { + provide: DATA_SOURCE, + useClass: BeerReviewCountsByCityMockDataSource, + deps: [], + }, + }); + + this.initializeDashboard(); + } + + /** Used for restoring widgets state */ + public reInitializeDashboard(): void { + // destroys the components and their providers so the dashboard can re init data + this.dashboard = undefined; + this.changeDetectorRef.detectChanges(); + + this.initializeDashboard(); + } + + private initializeDashboard(): void { + // We're using a static configuration object for this example, but this is where + // the widget's configuration could potentially be populated from a database + const widgetIndex: IWidgets = { + // Complete the proportional widget with information coming from its type definition + [widgetConfig.id]: + this.widgetTypesService.mergeWithWidgetType(widgetConfig), + }; + + // Setting the widget dimensions and position (this is for gridster) + const positions: Record = { + [widgetConfig.id]: { + cols: 6, + rows: 6, + y: 0, + x: 0, + }, + }; + + // Finally, assigning the variables we created above to the dashboard + this.dashboard = { + positions, + widgets: widgetIndex, + }; + } + + /** + * Sets up the configurator sections for proportional donut + */ + private setupConfigurator() { + const widgetTemplate = this.widgetTypesService.getWidgetType( + "proportional", + 1 + ); + + // remove old "presentation", "chartOptionsEditor" and "donutContentConfiguration" sections from the configurator + delete widgetTemplate.configurator?.structure?.presentation; + delete widgetTemplate.configurator?.structure?.chartOptionsEditor; + delete widgetTemplate.configurator?.structure + ?.donutContentConfiguration; + + // add new "presentation" section + this.widgetTypesService.setNode( + widgetTemplate, + "configurator", + "presentation", + DONUT_CONTENT_CONFIGURATION_SLICE.presentation + ); + // add new "chartOptionsEditor" section + this.widgetTypesService.setNode( + widgetTemplate, + "configurator", + "chartOptionsEditor", + DONUT_CONTENT_CONFIGURATION_SLICE.chartOptionsEditor + ); + // add new "donutContentConfiguration" section + this.widgetTypesService.setNode( + widgetTemplate, + "configurator", + "donutContentConfiguration", + DONUT_CONTENT_CONFIGURATION_SLICE.donutContentConfiguration + ); + } +} + +const widgetConfig: IWidget = { + id: "proportionalWidgetId", + type: "proportional", + pizzagna: { + [PizzagnaLayer.Configuration]: { + [DEFAULT_PIZZAGNA_ROOT]: { + providers: {}, + }, + header: { + properties: { + title: "Beer Review Tally by City", + subtitle: "These People Love Beer", + }, + }, + chart: { + providers: { + [WellKnownProviders.DataSource]: { + // Setting the data source providerId for the chart + providerId: + BeerReviewCountsByCityMockDataSource.providerId, + } as IProviderConfiguration, + }, + properties: { + configuration: { + chartOptions: { + type: ProportionalWidgetChartTypes.DonutChart, + legendPlacement: LegendPlacement.Right, + // old configuration looks like this + // contentFormatter: { + // componentType: DonutContentSumFormatterComponent.lateLoadKey, + // }, + + // NEW configuration looks like this + donutContentConfig: { + formatter: { + componentType: + SiUnitsFormatterComponent.lateLoadKey, + }, + aggregator: { + aggregatorType: + sumAggregator.aggregatorType, + properties: { + // example of a default metric to be used for the percentage calculation + // activeMetricId: "austin", + }, + }, + } as IDonutContentConfig, + } as IProportionalWidgetChartOptions, + } as IProportionalWidgetConfig, + }, + }, + }, + }, +}; + +export function getMockBeerReviewCountsByCity(): IMockBeerReview[] { + return [ + { + id: "Brno", + name: "Brno", + data: [Math.round(Math.random() * 1000000)], + icon: "status_down", + link: "https://en.wikipedia.org/wiki/Brno", + value: "Brno", + customDonutContent: "Custom Brno", + }, + { + id: "kyiv", + name: "Kyiv", + data: [Math.round(Math.random() * 1000000)], + icon: "status_critical", + link: "https://en.wikipedia.org/wiki/Kyiv", + value: "Kyiv", + customDonutContent: "Custom Kyiv", + }, + { + id: "austin", + name: "Austin", + data: [Math.round(Math.random() * 1000000)], + icon: "status_warning", + link: "https://en.wikipedia.org/wiki/Austin", + value: "Austin", + customDonutContent: "Custom Austin", + }, + { + id: "lisbon", + name: "Lisbon", + data: [Math.round(Math.random() * 1000000)], + icon: "status_unknown", + link: "https://en.wikipedia.org/wiki/Lisbon", + value: "Lisbon", + customDonutContent: "Custom Lisbon", + }, + { + id: "sydney", + name: "Sydney", + data: [Math.round(Math.random() * 1000000)], + icon: "status_up", + link: "https://en.wikipedia.org/wiki/Sydney", + value: "Sydney", + customDonutContent: "Custom Sydney", + }, + { + id: "nur-sultan", + name: "Nur-Sultan", + data: [Math.round(Math.random() * 1000000)], + icon: "status_unmanaged", + link: "https://en.wikipedia.org/wiki/Nur-Sultan", + value: "Nur-Sultan", + customDonutContent: "Custom Nur-Sultan", + }, + ].sort((a, b) => a.data[0] - b.data[0]); +} +\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\`, + "widget-types/proportional/proportional-widget/proportional-widget-example.component.ts": \\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\`// © 2022 SolarWinds Worldwide, LLC. All rights reserved. +// +// Permission is hereby granted, free of charge, to any person obtaining a copy +// of this software and associated documentation files (the "Software"), to +// deal in the Software without restriction, including without limitation the +// rights to use, copy, modify, merge, publish, distribute, sublicense, and/or +// sell copies of the Software, and to permit persons to whom the Software is +// furnished to do so, subject to the following conditions: +// +// The above copyright notice and this permission notice shall be included in +// all copies or substantial portions of the Software. +// +// THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR +// IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, +// FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE +// AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER +// LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, +// OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN +// THE SOFTWARE. + +import { + ChangeDetectorRef, + Component, + Injectable, + OnDestroy, + OnInit, +} from "@angular/core"; +import { GridsterConfig, GridsterItem } from "angular-gridster2"; +import { BehaviorSubject } from "rxjs"; + +import { + DataSourceService, + IDataSource, + IFilteringOutputs, +} from "@nova-ui/bits"; +import { + DATA_SOURCE, + DEFAULT_PIZZAGNA_ROOT, + IDashboard, + IProportionalWidgetChartOptions, + IProportionalWidgetConfig, + IProportionalWidgetData, + IProviderConfiguration, + IRefresherProperties, + IWidget, + IWidgets, + LegendPlacement, + PizzagnaLayer, + ProportionalWidgetChartTypes, + ProviderRegistryService, + WellKnownPathKey, + WellKnownProviders, + WidgetTypesService, +} from "@nova-ui/dashboards"; + +import { IMockBeerReview } from "../models"; + +/** + * A simple proportional data source to retrieve beer review counts by city + */ +@Injectable() +export class BeerReviewCountsByCityMockDataSource + extends DataSourceService + implements IDataSource, OnDestroy +{ + // This is the ID we'll use to identify the provider + public static providerId = "BeerReviewCountsByCityMockDataSource"; + public busy = new BehaviorSubject(false); + + public async getFilteredData(): Promise { + this.busy.next(true); + return new Promise((resolve) => { + setTimeout(() => { + this.outputsSubject.next({ + result: getMockBeerReviewCountsByCity(), + }); + this.busy.next(false); + }, 300); + }); + } + + public ngOnDestroy(): void { + this.outputsSubject.complete(); + } +} + +/** + * A component that instantiates the dashboard + */ +@Component({ + selector: "proportional-widget-example", + templateUrl: "./proportional-widget-example.component.html", + styleUrls: ["./proportional-widget-example.component.less"], + standalone: false, +}) +export class ProportionalWidgetExampleComponent implements OnInit { + // This variable will hold all the data needed to define the layout and behavior of the widgets. + // Pass this to the dashboard component's dashboard input in the template. + public dashboard: IDashboard | undefined; + + // Angular gridster requires a configuration object even if it's empty. + // Pass this to the dashboard component's gridsterConfig input in the template. + public gridsterConfig: GridsterConfig = {}; + + // Boolean passed as an input to the dashboard. When true, widgets can be moved, resized, removed, or edited + public editMode: boolean = false; + + constructor( + // WidgetTypesService provides the widget's necessary structure information + private widgetTypesService: WidgetTypesService, + // In general, the ProviderRegistryService is used for making entities available for injection into dynamically loaded components. + private providerRegistry: ProviderRegistryService, + private changeDetectorRef: ChangeDetectorRef + ) {} + + public ngOnInit(): void { + // Grabbing the widget's default template which will be needed as a parameter for setNode + const widgetTemplate = this.widgetTypesService.getWidgetType( + "proportional", + 1 + ); + + // Registering our data sources as dropdown options in the widget editor/configurator + // Note: This could also be done in the parent module's constructor so that + // multiple dashboards could have access to the same widget template modification. + this.widgetTypesService.setNode( + // This is the template we grabbed above with getWidgetType + widgetTemplate, + // We are setting the editor/configurator part of the widget template + "configurator", + // This indicates which node you are changing and we want to change + // the data source providers available for selection in the editor. + WellKnownPathKey.DataSourceProviders, + // We are setting the data sources available for selection in the editor + [BeerReviewCountsByCityMockDataSource.providerId] + ); + + // Registering the data source for injection into the Proportional widget. + this.providerRegistry.setProviders({ + [BeerReviewCountsByCityMockDataSource.providerId]: { + provide: DATA_SOURCE, + useClass: BeerReviewCountsByCityMockDataSource, + deps: [], + }, + }); + + this.initializeDashboard(); + } + + /** Used for restoring widgets state */ + public reInitializeDashboard(): void { + // destroys the components and their providers so the dashboard can re init data + this.dashboard = undefined; + this.changeDetectorRef.detectChanges(); + + this.initializeDashboard(); + } + + public initializeDashboard(): void { + // We're using a static configuration object for this example, but this is where + // the widget's configuration could potentially be populated from a database + const widgetIndex: IWidgets = { + // Complete the proportional widget with information coming from its type definition + [widgetConfig.id]: + this.widgetTypesService.mergeWithWidgetType(widgetConfig), + }; + + // Setting the widget dimensions and position (this is for gridster) + const positions: Record = { + [widgetConfig.id]: { + cols: 5, + rows: 6, + y: 0, + x: 0, + }, + }; + + // Finally, assigning the variables we created above to the dashboard + this.dashboard = { + positions, + widgets: widgetIndex, + }; + } +} + +const widgetConfig: IWidget = { + id: "proportionalWidgetId", + type: "proportional", + pizzagna: { + [PizzagnaLayer.Configuration]: { + [DEFAULT_PIZZAGNA_ROOT]: { + providers: { + [WellKnownProviders.Refresher]: { + properties: { + // Configuring the refresher interval so that our data source is invoked every ten minutes + interval: 60 * 10, + enabled: true, + } as IRefresherProperties, + } as Partial, + }, + }, + header: { + properties: { + title: "Beer Review Tally by City", + subtitle: "These People Love Beer", + }, + }, + chart: { + providers: { + [WellKnownProviders.DataSource]: { + // Setting the data source providerId for the chart + providerId: + BeerReviewCountsByCityMockDataSource.providerId, + } as IProviderConfiguration, + }, + properties: { + configuration: { + chartOptions: { + type: ProportionalWidgetChartTypes.DonutChart, + legendPlacement: LegendPlacement.Right, + } as IProportionalWidgetChartOptions, + // You can optionally define custom colors for the chart by setting the 'chartColors' configuration property + // "chartColors": [ + // "var(--nui-color-chart-five)", + // "var(--nui-color-chart-six)", + // "var(--nui-color-chart-seven)", + // "var(--nui-color-chart-eight)", + // "var(--nui-color-chart-nine)", + // "var(--nui-color-chart-ten)", + // ], + // or use-mapped structure + chartColors: { + Brno: "var(--nui-color-chart-five)", + kyiv: "var(--nui-color-chart-six)", + austin: "var(--nui-color-chart-seven)", + lisbon: "var(--nui-color-chart-eight)", + sydney: "var(--nui-color-chart-nine)", + "nur-sultan": "var(--nui-color-chart-ten)", + }, + prioritizeWidgetColors: false, + } as IProportionalWidgetConfig, + }, + }, + }, + }, +}; + +export function getMockBeerReviewCountsByCity(): IMockBeerReview[] { + return [ + { + id: "Brno", + name: "Brno", + data: [Math.round(Math.random() * 100000)], + icon: "status_down", + link: "https://en.wikipedia.org/wiki/Brno", + value: "Brno", + color: "var(--nui-color-chart-one)", + }, + { + id: "kyiv", + name: "Kyiv", + data: [Math.round(Math.random() * 100000)], + icon: "status_critical", + link: "https://en.wikipedia.org/wiki/Kyiv", + value: "Kyiv", + color: "var(--nui-color-chart-two)", + }, + { + id: "austin", + name: "Austin", + data: [Math.round(Math.random() * 100000)], + icon: "status_warning", + link: "https://en.wikipedia.org/wiki/Austin", + value: "Austin", + color: "var(--nui-color-chart-three)", + }, + { + id: "lisbon", + name: "Lisbon", + data: [Math.round(Math.random() * 100000)], + icon: "status_unknown", + link: "https://en.wikipedia.org/wiki/Lisbon", + value: "Lisbon", + color: "var(--nui-color-chart-four)", + }, + { + id: "sydney", + name: "Sydney", + data: [Math.round(Math.random() * 100000)], + icon: "status_up", + link: "https://en.wikipedia.org/wiki/Sydney", + value: "Sydney", + color: "var(--nui-color-chart-five)", + }, + { + id: "nur-sultan", + name: "Nur-Sultan", + data: [Math.round(Math.random() * 100000)], + icon: "status_unmanaged", + link: "https://en.wikipedia.org/wiki/Nur-Sultan", + value: "Nur-Sultan", + color: "var(--nui-color-chart-six)", + }, + ].sort((a, b) => a.data[0] - b.data[0]); +} +\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\`, + "widget-types/proportional/proportional-widget-interactive/proportional-widget-interactive-example.component.ts": \\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\`// © 2022 SolarWinds Worldwide, LLC. All rights reserved. +// +// Permission is hereby granted, free of charge, to any person obtaining a copy +// of this software and associated documentation files (the "Software"), to +// deal in the Software without restriction, including without limitation the +// rights to use, copy, modify, merge, publish, distribute, sublicense, and/or +// sell copies of the Software, and to permit persons to whom the Software is +// furnished to do so, subject to the following conditions: +// +// The above copyright notice and this permission notice shall be included in +// all copies or substantial portions of the Software. +// +// THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR +// IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, +// FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE +// AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER +// LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, +// OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN +// THE SOFTWARE. + +import { + ChangeDetectorRef, + Component, + Injectable, + OnDestroy, + OnInit, +} from "@angular/core"; +import { GridsterConfig, GridsterItem } from "angular-gridster2"; +import keyBy from "lodash/keyBy"; +import { BehaviorSubject } from "rxjs"; + +import { + DataSourceService, + IDataSource, + IFilteringOutputs, +} from "@nova-ui/bits"; +import { + DATA_SOURCE, + DEFAULT_PIZZAGNA_ROOT, + IDashboard, + IProportionalWidgetChartOptions, + IProportionalWidgetConfig, + IProportionalWidgetData, + IProviderConfiguration, + IWidget, + LegendPlacement, + NOVA_URL_INTERACTION_HANDLER, + PizzagnaLayer, + ProportionalWidgetChartTypes, + ProviderRegistryService, + WellKnownPathKey, + WellKnownProviders, + WidgetTypesService, +} from "@nova-ui/dashboards"; + +import { IMockBeerReview } from "../models"; + +/** + * A simple proportional data source to retrieve beer review counts by city + */ +@Injectable() +export class ReviewCountsByCityMockDataSource + extends DataSourceService + implements IDataSource, OnDestroy +{ + // This is the ID we'll use to identify the provider + public static providerId = "ReviewCountsByCityMockDataSource"; + public busy = new BehaviorSubject(false); + + public async getFilteredData(): Promise { + this.busy.next(true); + return new Promise((resolve) => { + setTimeout(() => { + this.outputsSubject.next({ + result: getMockBeerReviewCountsByCity(), + }); + this.busy.next(false); + }, 300); + }); + } + + public ngOnDestroy(): void { + this.outputsSubject.complete(); + } +} + +/** + * A component that instantiates the dashboard + */ +@Component({ + selector: "proportional-widget-interactive-example", + templateUrl: "./proportional-widget-interactive-example.component.html", + styleUrls: ["./proportional-widget-interactive-example.component.less"], + standalone: false, +}) +export class ProportionalWidgetInteractiveExampleComponent implements OnInit { + // This variable will hold all the data needed to define the layout and behavior of the widgets. + // Pass this to the dashboard component's dashboard input in the template. + public dashboard: IDashboard | undefined; + + // Angular gridster requires a configuration object even if it's empty. + // Pass this to the dashboard component's gridsterConfig input in the template. + public gridsterConfig: GridsterConfig = {}; + + // Boolean passed as an input to the dashboard. When true, widgets can be moved, resized, removed, or edited + public editMode: boolean = false; + + constructor( + // WidgetTypesService provides the widget's necessary structure information + private widgetTypesService: WidgetTypesService, + // In general, the ProviderRegistryService is used for making entities available for injection into dynamically loaded components. + private providerRegistry: ProviderRegistryService, + private changeDetectorRef: ChangeDetectorRef + ) {} + + public ngOnInit(): void { + // Grabbing the widget's default template which will be needed as a parameter for setNode + const widgetTemplate = this.widgetTypesService.getWidgetType( + "proportional", + 1 + ); + + // Registering our data sources as dropdown options in the widget editor/configurator + // Note: This could also be done in the parent module's constructor so that + // multiple dashboards could have access to the same widget template modification. + this.widgetTypesService.setNode( + // This is the template we grabbed above with getWidgetType + widgetTemplate, + // We are setting the editor/configurator part of the widget template + "configurator", + // This indicates which node you are changing and we want to change + // the data source providers available for selection in the editor. + WellKnownPathKey.DataSourceProviders, + // We are setting the data sources available for selection in the editor + [ReviewCountsByCityMockDataSource.providerId] + ); + + // Registering the data source for injection into the Proportional widget. + this.providerRegistry.setProviders({ + [ReviewCountsByCityMockDataSource.providerId]: { + provide: DATA_SOURCE, + useClass: ReviewCountsByCityMockDataSource, + deps: [], + }, + }); + + this.initializeDashboard(); + } + + /** Used for restoring widgets state */ + public reInitializeDashboard(): void { + // destroys the components and their providers so the dashboard can re init data + this.dashboard = undefined; + this.changeDetectorRef.detectChanges(); + + this.initializeDashboard(); + } + + public initializeDashboard(): void { + // We're using a static configuration object for this example, but this is where + // the widget's configuration could potentially be populated from a database + const widgetsWithStructure = widgetConfigs.map((w) => + this.widgetTypesService.mergeWithWidgetType(w) + ); + const widgetsIndex = keyBy(widgetsWithStructure, (w: IWidget) => w.id); + + // Setting the widget dimensions and position (this is for gridster) + const positions: Record = { + [widgetConfigs[0].id]: { + cols: 6, + rows: 6, + y: 0, + x: 0, + }, + [widgetConfigs[1].id]: { + cols: 6, + rows: 6, + y: 0, + x: 6, + }, + }; + + // Finally, assigning the variables we created above to the dashboard + this.dashboard = { + positions, + widgets: widgetsIndex, + }; + } +} + +const widgetConfigs: IWidget[] = [ + { + id: "widget1", + type: "proportional", + pizzagna: { + [PizzagnaLayer.Configuration]: { + [DEFAULT_PIZZAGNA_ROOT]: { + providers: { + // Configuring the UrlInteractionHandler to handle interactions + [WellKnownProviders.InteractionHandler]: { + providerId: NOVA_URL_INTERACTION_HANDLER, + properties: { + // the 'url' property tells the handler what link to use when interaction occurs on the series + // if the series does not have a link we are passing one to the handler + url: "\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\${data.link || 'https://en.wikipedia.org/wiki/'+data.id}", + // by default the link is opened in the current window, set 'newWindow' to true to open in a new tab instead + // newWindow: true, + }, + }, + }, + }, + header: { + properties: { + title: "Proportional Widget", + subtitle: "With interaction handler", + }, + }, + chart: { + providers: { + [WellKnownProviders.DataSource]: { + // Setting the data source providerId for the chart + providerId: + ReviewCountsByCityMockDataSource.providerId, + } as IProviderConfiguration, + }, + properties: { + configuration: { + // Setting the interactive to true + interactive: true, + chartOptions: { + type: ProportionalWidgetChartTypes.VerticalBarChart, + legendPlacement: LegendPlacement.Bottom, + } as IProportionalWidgetChartOptions, + prioritizeWidgetColors: false, + } as IProportionalWidgetConfig, + }, + }, + }, + }, + }, + { + id: "widget2", + type: "proportional", + pizzagna: { + [PizzagnaLayer.Configuration]: { + header: { + properties: { + title: "Proportional Widget", + subtitle: "Without interaction handler", + }, + }, + chart: { + providers: { + [WellKnownProviders.DataSource]: { + // Setting the data source providerId for the chart + providerId: + ReviewCountsByCityMockDataSource.providerId, + } as IProviderConfiguration, + }, + properties: { + configuration: { + // interactive set to false so series without links are not styled like a link + interactive: false, + chartOptions: { + type: ProportionalWidgetChartTypes.HorizontalBarChart, + legendPlacement: LegendPlacement.Bottom, + } as IProportionalWidgetChartOptions, + prioritizeWidgetColors: false, + } as IProportionalWidgetConfig, + }, + }, + }, + }, + }, +]; + +export function getMockBeerReviewCountsByCity(): IMockBeerReview[] { + return [ + { + id: "Brno", + name: "Brno", + data: [Math.round(Math.random() * 100000)], + icon: "status_down", + link: "https://en.wikipedia.org/wiki/Brno", + value: "Brno", + color: "var(--nui-color-chart-one)", + }, + { + id: "kyiv", + name: "Kyiv", + data: [Math.round(Math.random() * 100000)], + icon: "status_critical", + link: "https://en.wikipedia.org/wiki/Kyiv", + value: "Kyiv", + color: "var(--nui-color-chart-two)", + }, + { + id: "austin", + name: "Austin", + data: [Math.round(Math.random() * 100000)], + icon: "status_warning", + value: "Austin", + color: "var(--nui-color-chart-three)", + }, + { + id: "lisbon", + name: "Lisbon", + data: [Math.round(Math.random() * 100000)], + icon: "status_unknown", + link: "https://en.wikipedia.org/wiki/Lisbon", + value: "Lisbon", + color: "var(--nui-color-chart-four)", + }, + { + id: "sydney", + name: "Sydney", + data: [Math.round(Math.random() * 100000)], + icon: "status_up", + value: "Sydney", + color: "var(--nui-color-chart-five)", + }, + { + id: "nur-sultan", + name: "Nur-Sultan", + data: [Math.round(Math.random() * 100000)], + icon: "status_unmanaged", + value: "Nur-Sultan", + color: "var(--nui-color-chart-six)", + }, + ].sort((a, b) => a.data[0] - b.data[0]); +} +\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\`, + "widget-types/risk-score/risk-score-docs.component.ts": \\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\`// © 2023 SolarWinds Worldwide, LLC. All rights reserved. +// +// Permission is hereby granted, free of charge, to any person obtaining a copy +// of this software and associated documentation files (the "Software"), to +// deal in the Software without restriction, including without limitation the +// rights to use, copy, modify, merge, publish, distribute, sublicense, and/or +// sell copies of the Software, and to permit persons to whom the Software is +// furnished to do so, subject to the following conditions: +// +// The above copyright notice and this permission notice shall be included in +// all copies or substantial portions of the Software. +// +// THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR +// IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, +// FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE +// AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER +// LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, +// OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN +// THE SOFTWARE. + +import { Component, OnInit } from "@angular/core"; + +import { mapContentFile } from "../../../../demo-files-factory"; + +@Component({ + selector: "nui-risk-score-docs", + templateUrl: "./risk-score-docs.component.html", + standalone: false, +}) +export class RiskScoreDocsComponent implements OnInit { + public riskScoreWidgetFileText = ""; + public riskScoreConfiguratorFileText = ""; + + public async ngOnInit(): Promise { + this.riskScoreWidgetFileText = await import( + "./../../../../../../src/lib/widget-types/risk-score/risk-score-widget" + ).then(mapContentFile); + this.riskScoreConfiguratorFileText = await import( + "./../../../../../../src/lib/widget-types/risk-score/risk-score-configurator" + ).then(mapContentFile); + } +} +\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\`, + "widget-types/risk-score/risk-score-docs.module.ts": \\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\`// © 2023 SolarWinds Worldwide, LLC. All rights reserved. +// +// Permission is hereby granted, free of charge, to any person obtaining a copy +// of this software and associated documentation files (the "Software"), to +// deal in the Software without restriction, including without limitation the +// rights to use, copy, modify, merge, publish, distribute, sublicense, and/or +// sell copies of the Software, and to permit persons to whom the Software is +// furnished to do so, subject to the following conditions: +// +// The above copyright notice and this permission notice shall be included in +// all copies or substantial portions of the Software. +// +// THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR +// IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, +// FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE +// AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER +// LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, +// OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN +// THE SOFTWARE. + +import { NgModule } from "@angular/core"; +import { RouterModule, Routes } from "@angular/router"; + +import { + DEMO_PATH_TOKEN, + NuiButtonModule, + NuiDocsModule, + NuiMessageModule, + NuiSwitchModule, +} from "@nova-ui/bits"; +import { NuiDashboardsModule } from "@nova-ui/dashboards"; + +import { RiskScoreDocsComponent } from "./risk-score-docs.component"; +import { RiskScoreWidgetExampleComponent } from "./risk-score-widget-example/risk-score-widget-example.component"; +import { getDemoFiles } from "../../../../demo-files-factory"; + +const routes: Routes = [ + { + path: "", + component: RiskScoreDocsComponent, + data: { + srlc: { + hideIndicator: true, + }, + showThemeSwitcher: true, + }, + }, + { + path: "example", + component: RiskScoreWidgetExampleComponent, + data: { + srlc: { + hideIndicator: true, + }, + }, + }, +]; + +@NgModule({ + imports: [ + RouterModule.forChild(routes), + NuiButtonModule, + NuiDocsModule, + NuiMessageModule, + NuiDashboardsModule, + NuiSwitchModule, + ], + declarations: [RiskScoreDocsComponent, RiskScoreWidgetExampleComponent], + providers: [ + { + provide: DEMO_PATH_TOKEN, + useValue: getDemoFiles("risk-score"), + }, + ], +}) +export default class RiskScoreDocsModule {} +\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\`, + "widget-types/risk-score/risk-score-widget-example/risk-score-widget-example.component.ts": \\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\`// © 2023 SolarWinds Worldwide, LLC. All rights reserved. +// +// Permission is hereby granted, free of charge, to any person obtaining a copy +// of this software and associated documentation files (the "Software"), to +// deal in the Software without restriction, including without limitation the +// rights to use, copy, modify, merge, publish, distribute, sublicense, and/or +// sell copies of the Software, and to permit persons to whom the Software is +// furnished to do so, subject to the following conditions: +// +// The above copyright notice and this permission notice shall be included in +// all copies or substantial portions of the Software. +// +// THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR +// IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, +// FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE +// AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER +// LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, +// OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN +// THE SOFTWARE. + +import { HttpClient, HttpErrorResponse } from "@angular/common/http"; +import { Component, Injectable, OnDestroy, OnInit } from "@angular/core"; +import { GridsterConfig, GridsterItem } from "angular-gridster2"; +import { BehaviorSubject } from "rxjs"; +import { finalize } from "rxjs/operators"; + +import { DataSourceService, IFilteringOutputs } from "@nova-ui/bits"; +import { + DATA_SOURCE, + DEFAULT_PIZZAGNA_ROOT, + IDashboard, + IRiskScoreData, + IProviderConfiguration, + IRefresherProperties, + IWidget, + IWidgets, + RiskScoreTileComponent, + NOVA_KPI_DATASOURCE_ADAPTER, + PizzagnaLayer, + ProviderRegistryService, + WellKnownPathKey, + WellKnownProviders, + WidgetTypesService, +} from "@nova-ui/dashboards"; + +/** + * A simple KPI data source to retrieve the average rating of Harry Potter and the Sorcerer's Stone (book) via googleapis + */ +@Injectable() +export class AverageRatingRiskScoreDataSource + extends DataSourceService + implements OnDestroy +{ + // This is the ID we'll use to identify the provider + public static providerId = "AverageRatingRiskScoreDataSource"; + + // Use this subject to communicate the data source's busy state + public busy = new BehaviorSubject(false); + + constructor(private http: HttpClient) { + super(); + } + + // In this example, getFilteredData is invoked every 10 minutes (Take a look at the refresher + // provider definition in the widget configuration below to see how the interval is set) + public async getFilteredData(): Promise { + this.busy.next(true); + return new Promise((resolve) => { + // *** Make a resource request to an external API (if needed) + this.http + .get("https://www.googleapis.com/books/v1/volumes/5MQFrgEACAAJ") + .pipe(finalize(() => this.busy.next(false))) + .subscribe({ + next: (data: any) => { + resolve({ + result: { + value: data.volumeInfo.averageRating, + }, + }); + }, + error: (error: HttpErrorResponse) => { + resolve({ + result: null, + error: { + type: error.status, + }, + }); + }, + }); + }); + } + + public ngOnDestroy(): void { + this.outputsSubject.complete(); + } +} + +/** + * A component that instantiates the dashboard + */ +@Component({ + selector: "risk-score-widget-example", + templateUrl: "./risk-score-widget-example.component.html", + styleUrls: ["./risk-score-widget-example.component.less"], + standalone: false, +}) +export class RiskScoreWidgetExampleComponent implements OnInit { + // This variable will hold all the data needed to define the layout and behavior of the widgets. + // Pass this to the dashboard component's dashboard input in the template. + public dashboard: IDashboard; + + // Angular gridster requires a configuration object even if it's empty. + // Pass this to the dashboard component's gridsterConfig input in the template. + public gridsterConfig: GridsterConfig = {}; + + // Boolean passed as an input to the dashboard. When true, widgets can be moved, resized, removed, or edited + public editMode: boolean = false; + + constructor( + // WidgetTypesService provides the widget's necessary structure information + private widgetTypesService: WidgetTypesService, + + // In general, the ProviderRegistryService is used for making entities available for injection into dynamically loaded components. + private providerRegistry: ProviderRegistryService + ) {} + + public ngOnInit(): void { + // Grabbing the widget's default template which will be needed as a parameter for setNode + const widgetTemplate = this.widgetTypesService.getWidgetType( + "risk-score", + 1 + ); + // Registering our data sources as dropdown options in the widget editor/configurator + // Note: This could also be done in the parent module's constructor so that + // multiple dashboards could have access to the same widget template modification. + this.widgetTypesService.setNode( + // This is the template we grabbed above with getWidgetType + widgetTemplate, + // We are setting the editor/configurator part of the widget template + "configurator", + // This indicates which node you are changing and we want to change + // the data source providers available for selection in the editor. + WellKnownPathKey.DataSourceProviders, + // We are setting the data sources available for selection in the editor + [AverageRatingRiskScoreDataSource.providerId] + ); + + // Registering the data source for injection into the KPI tile. + // Note: Each tile of a KPI widget is assigned its own instance of the data source + this.providerRegistry.setProviders({ + [AverageRatingRiskScoreDataSource.providerId]: { + provide: DATA_SOURCE, + useClass: AverageRatingRiskScoreDataSource, + // Any dependencies that need to be injected into the provider must be listed here + deps: [HttpClient], + }, + }); + + this.initializeDashboard(); + } + + public initializeDashboard(): void { + // We're using a static configuration object for this example, but this is where + // the widget's configuration could potentially be populated from a database + const riskScoreWidget = widgetConfig; + const widgetIndex: IWidgets = { + // Complete the KPI widget with information coming from its type definition + [riskScoreWidget.id]: + this.widgetTypesService.mergeWithWidgetType(riskScoreWidget), + }; + + // Setting the widget dimensions and position (this is for gridster) + const positions: Record = { + [riskScoreWidget.id]: { + cols: 4, + rows: 6, + y: 0, + x: 0, + }, + }; + + // Finally, assigning the variables we created above to the dashboard + this.dashboard = { + positions, + widgets: widgetIndex, + }; + } +} + +const widgetConfig: IWidget = { + id: "riskScoreWidgetId", + type: "risk-score", + pizzagna: { + [PizzagnaLayer.Configuration]: { + [DEFAULT_PIZZAGNA_ROOT]: { + providers: { + [WellKnownProviders.Refresher]: { + properties: { + // Configuring the refresher interval so that our data source is invoked every ten minutes + interval: 60 * 10, + enabled: true, + } as IRefresherProperties, + } as Partial, + }, + }, + header: { + properties: { + title: "Harry Potter and the Sorcerer's Stone", + subtitle: "By J. K. Rowling", + }, + }, + tiles: { + properties: { + nodes: ["riskScore1"], + }, + }, + riskScore1: { + id: "riskScore1", + componentType: RiskScoreTileComponent.lateLoadKey, + properties: { + widgetData: { + minValue: 0, + maxValue: 5, + useStaticLabel: false, + staticLabel: undefined, + label: \\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\`Average Rating\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\`, + description: \\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\`Harry Potter and the Sorcerer's Stone By J. K. Rowling Average Rating Risk Score\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\`, + }, + }, + providers: { + [WellKnownProviders.DataSource]: { + // Setting the data source providerId for the tile with id "kpi1" + providerId: AverageRatingRiskScoreDataSource.providerId, + } as IProviderConfiguration, + [WellKnownProviders.Adapter]: { + providerId: NOVA_KPI_DATASOURCE_ADAPTER, + properties: { + componentId: "riskScore1", + propertyPath: "widgetData", + }, + } as IProviderConfiguration, + }, + }, + }, + }, +}; +\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\`, + "widget-types/table/table-docs.component.ts": \\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\`// © 2022 SolarWinds Worldwide, LLC. All rights reserved. +// +// Permission is hereby granted, free of charge, to any person obtaining a copy +// of this software and associated documentation files (the "Software"), to +// deal in the Software without restriction, including without limitation the +// rights to use, copy, modify, merge, publish, distribute, sublicense, and/or +// sell copies of the Software, and to permit persons to whom the Software is +// furnished to do so, subject to the following conditions: +// +// The above copyright notice and this permission notice shall be included in +// all copies or substantial portions of the Software. +// +// THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR +// IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, +// FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE +// AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER +// LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, +// OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN +// THE SOFTWARE. + +import { Component, OnInit } from "@angular/core"; + +import { mapContentFile } from "../../../../demo-files-factory"; + +@Component({ + selector: "nui-table-docs", + templateUrl: "./table-docs.component.html", + standalone: false, +}) +export class TableDocsComponent implements OnInit { + public widgetFileText = ""; + public configuratorFileText = ""; + + public async ngOnInit(): Promise { + this.widgetFileText = await import( + "./../../../../../../src/lib/widget-types/table/table-widget" + ).then(mapContentFile); + this.configuratorFileText = await import( + "./../../../../../../src/lib/widget-types/table/table-configurator" + ).then(mapContentFile); + } +} +\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\`, + "widget-types/table/table-docs.module.ts": \\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\`// © 2022 SolarWinds Worldwide, LLC. All rights reserved. +// +// Permission is hereby granted, free of charge, to any person obtaining a copy +// of this software and associated documentation files (the "Software"), to +// deal in the Software without restriction, including without limitation the +// rights to use, copy, modify, merge, publish, distribute, sublicense, and/or +// sell copies of the Software, and to permit persons to whom the Software is +// furnished to do so, subject to the following conditions: +// +// The above copyright notice and this permission notice shall be included in +// all copies or substantial portions of the Software. +// +// THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR +// IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, +// FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE +// AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER +// LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, +// OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN +// THE SOFTWARE. + +import { NgModule } from "@angular/core"; +import { RouterModule, Routes } from "@angular/router"; + +import { DEMO_PATH_TOKEN } from "@nova-ui/bits"; +import { + NuiButtonModule, + NuiDocsModule, + NuiMessageModule, + NuiSwitchModule, +} from "@nova-ui/bits"; +import { + NuiDashboardsModule, + TableFormatterRegistryService, +} from "@nova-ui/dashboards"; + +import { TableDocsComponent } from "./table-docs.component"; +import { TablePaginatorDocsComponent } from "./table-paginator-docs.component"; +import { TableSelectableDocsComponent } from "./table-selectable-docs.component"; +import { TableWidgetExampleComponent } from "./table-widget/table-widget-example.component"; +import { TableWidgetInteractiveExampleComponent } from "./table-widget-interactive/table-widget-interactive-example.component"; +import { TableWidgetPaginatorExampleComponent } from "./table-widget-paginator/table-widget-paginator-example.component"; +import { TableWidgetSearchExampleComponent } from "./table-widget-search/table-widget-search-example.component"; +import { TableSearchDocsComponent } from "./table-widget-search-docs.component"; +import { TableWidgetSelectableMultiExampleComponent } from "./table-widget-selectable/table-widget-selectable-multi/table-widget-selectable-multi.example.component"; +import { TableWidgetSelectableRadioExampleComponent } from "./table-widget-selectable/table-widget-selectable-radio/table-widget-selectable-radio.example.component"; +import { TableWidgetSelectableSingleExampleComponent } from "./table-widget-selectable/table-widget-selectable-single/table-widget-selectable-single.example.component"; +import { TableWidgetSelectableExampleComponent } from "./table-widget-selectable/table-widget-selectable.example.component"; +import { DEFAULT_TABLE_FORMATTERS } from "../../../../../../src/lib/widget-types/table/default-table-formatters"; +import { getDemoFiles } from "../../../../demo-files-factory"; + +const routes: Routes = [ + { + path: "", + component: TableDocsComponent, + data: { + srlc: { + hideIndicator: true, + }, + showThemeSwitcher: true, + }, + }, + { + path: "example", + component: TableWidgetExampleComponent, + data: { + srlc: { + hideIndicator: true, + }, + }, + }, + { + path: "table-search", + component: TableSearchDocsComponent, + data: { + srlc: { + hideIndicator: true, + }, + showThemeSwitcher: true, + }, + }, + { + path: "table-paginator", + component: TablePaginatorDocsComponent, + data: { + srlc: { + hideIndicator: true, + }, + showThemeSwitcher: true, + }, + }, + { + path: "table-select", + component: TableSelectableDocsComponent, + data: { + srlc: { + hideIndicator: true, + }, + showThemeSwitcher: true, + }, + }, +]; + +@NgModule({ + imports: [ + RouterModule.forChild(routes), + NuiButtonModule, + NuiDocsModule, + NuiMessageModule, + NuiSwitchModule, + NuiDashboardsModule, + ], + declarations: [ + TableDocsComponent, + TableSearchDocsComponent, + TablePaginatorDocsComponent, + TableWidgetPaginatorExampleComponent, + TableSelectableDocsComponent, + TableWidgetInteractiveExampleComponent, + TableWidgetExampleComponent, + TableWidgetSearchExampleComponent, + TableWidgetSelectableExampleComponent, + TableWidgetSelectableMultiExampleComponent, + TableWidgetSelectableSingleExampleComponent, + TableWidgetSelectableRadioExampleComponent, + ], + providers: [ + { + provide: DEMO_PATH_TOKEN, + useValue: getDemoFiles("table"), + }, + ], +}) +export default class TableDocsModule { + constructor(tableFormattersRegistryService: TableFormatterRegistryService) { + tableFormattersRegistryService.addItems(DEFAULT_TABLE_FORMATTERS); + } +} +\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\`, + "widget-types/table/table-paginator-docs.component.ts": \\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\`// © 2022 SolarWinds Worldwide, LLC. All rights reserved. +// +// Permission is hereby granted, free of charge, to any person obtaining a copy +// of this software and associated documentation files (the "Software"), to +// deal in the Software without restriction, including without limitation the +// rights to use, copy, modify, merge, publish, distribute, sublicense, and/or +// sell copies of the Software, and to permit persons to whom the Software is +// furnished to do so, subject to the following conditions: +// +// The above copyright notice and this permission notice shall be included in +// all copies or substantial portions of the Software. +// +// THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR +// IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, +// FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE +// AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER +// LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, +// OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN +// THE SOFTWARE. + +import { Component } from "@angular/core"; + +@Component({ + selector: "nui-table-paginator-docs", + templateUrl: "./table-paginator-docs.component.html", + standalone: false, +}) +export class TablePaginatorDocsComponent { + public tableConfigurationText = \\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\` + "table": { + ... + properties: { + configuration: { + // define paginator configuration here + scrollType: ScrollType.paginator, + paginatorConfiguration: { + pageSize: 10, // Value have to be one of pageSizeSet values + pageSizeSet: [10, 20, 30], + }, + // If not specified, default is set to + // pageSize: 10, + // pageSizeSet: [10, 20, 50], + hasVirtualScroll: false, // Has to be speciefied because of backward compatibility + } as ITableWidgetConfig, + }, + }, + \\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\`; +} +\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\`, + "widget-types/table/table-selectable-docs.component.ts": \\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\`// © 2022 SolarWinds Worldwide, LLC. All rights reserved. +// +// Permission is hereby granted, free of charge, to any person obtaining a copy +// of this software and associated documentation files (the "Software"), to +// deal in the Software without restriction, including without limitation the +// rights to use, copy, modify, merge, publish, distribute, sublicense, and/or +// sell copies of the Software, and to permit persons to whom the Software is +// furnished to do so, subject to the following conditions: +// +// The above copyright notice and this permission notice shall be included in +// all copies or substantial portions of the Software. +// +// THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR +// IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, +// FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE +// AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER +// LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, +// OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN +// THE SOFTWARE. + +import { Component } from "@angular/core"; + +@Component({ + selector: "nui-table-selectable-docs", + templateUrl: "./table-selectable-docs.component.html", + standalone: false, +}) +export class TableSelectableDocsComponent { + public tableConfigurationText = \\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\` + "table": { + ... + properties: { + // enabling selection here + selectionConfiguration: { + // whether the selection is enabled or disabled + enabled: true, + // can be Multi | Radio | Single + selectionMode: TableSelectionMode.Multi, + // property that uniquely identifies row in a table + trackByProperty: "id", + // whether clicking on row should select it + clickableRow: true, + }, + }, + }, + \\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\`; + + public eventSubscriptionText = \\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\` +... +constructor(Inject(PIZZAGNA_EVENT_BUS) eventBus: EventBus) { + eventBus + .getStream(SELECTION) + // don't forget to unsubscribe! + .subscribe((selection: ISelection) => ...) +} +... + \\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\`; +} +\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\`, + "widget-types/table/table-widget/table-widget-example.component.ts": \\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\`// © 2022 SolarWinds Worldwide, LLC. All rights reserved. +// +// Permission is hereby granted, free of charge, to any person obtaining a copy +// of this software and associated documentation files (the "Software"), to +// deal in the Software without restriction, including without limitation the +// rights to use, copy, modify, merge, publish, distribute, sublicense, and/or +// sell copies of the Software, and to permit persons to whom the Software is +// furnished to do so, subject to the following conditions: +// +// The above copyright notice and this permission notice shall be included in +// all copies or substantial portions of the Software. +// +// THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR +// IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, +// FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE +// AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER +// LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, +// OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN +// THE SOFTWARE. + +import { ChangeDetectorRef, Component, OnInit } from "@angular/core"; +import { GridsterConfig, GridsterItem } from "angular-gridster2"; +import orderBy from "lodash/orderBy"; +import { BehaviorSubject, firstValueFrom, from } from "rxjs"; +import { map, tap } from "rxjs/operators"; + +import { + DataSourceService, + IDataField, + INovaFilteringOutputs, + INovaFilters, + nameof, +} from "@nova-ui/bits"; +import { + DATA_SOURCE, + IDashboard, + ITableWidgetColumnConfig, + IWidget, + IWidgets, + ProviderRegistryService, + WellKnownPathKey, + WellKnownProviders, + WidgetTypesService, +} from "@nova-ui/dashboards"; + +export const BREW_API_URL = "https://api.punkapi.com/v2/beers"; + +export interface IBrewInfo { + id: string; + name: string; + tagline: string; + first_brewed: string; + description: string; + brewers_tips: string; +} + +export interface IBrewDatasourceResponse { + brewInfo: IBrewInfo[]; + total: number; +} + +export class BeerDataSource extends DataSourceService { + public static providerId = "BeerDataSource"; + + private cache: IBrewInfo[] = []; + + public busy = new BehaviorSubject(false); + + public dataFields: Array = [ + { + id: nameof("id"), + label: "No", + dataType: "number", + sortable: true, + }, + // To indicate that a column should not be sortable, set the optional IDataField 'sortable' property to false + { + id: nameof("name"), + label: "Name", + dataType: "string", + sortable: true, + }, + { + id: nameof("tagline"), + label: "Tagline", + dataType: "string", + sortable: true, + }, + { + id: nameof("first_brewed"), + label: "First Brewed", + dataType: "string", + sortable: true, + }, + { + id: nameof("description"), + label: "Description", + dataType: "string", + sortable: false, + }, + { + id: nameof("brewers_tips"), + label: "Brewer's Tips", + dataType: "string", + sortable: false, + }, + ]; + + public async getFilteredData( + filters: INovaFilters + ): Promise { + const start = filters.virtualScroll?.value?.start ?? 0; + const end = filters.virtualScroll?.value?.end ?? 0; + + // Resetting cache on first page request + if (start === 0) { + this.cache = []; + } + + // extract sorter settings to send to the backend + // filters.sorterValue.sortBy; filters.sorterValue.direction + return firstValueFrom( + from(this.fetch(start, end)).pipe( + tap((response: IBrewDatasourceResponse | undefined) => { + if (!response) { + return; + } + this.cache = this.sortData( + this.cache.concat(response.brewInfo), + filters + ); + this.dataSubject.next(this.cache); + }), + map(() => ({ + repeat: { itemsSource: this.cache }, + dataFields: this.dataFields, + })) + ) + ); + } + + public async fetch( + start: number, + end: number + ): Promise { + const delta: number = end - start; + const currentPage: number = end / delta || 0; + const response: object | Array = await ( + await fetch( + \\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\`\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\${BREW_API_URL}/?page=\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\${currentPage}&per_page=\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\${delta}\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\` + ) + ).json(); + + // Note: In case request fails we should not proceed with mapping + if (!Array.isArray(response)) { + return undefined; + } + + return { + brewInfo: response.map((result: IBrewInfo) => ({ + id: result.id, + name: result.name, + tagline: result.tagline, + first_brewed: result.first_brewed, + description: result.description, + brewers_tips: result.brewers_tips, + })), + total: response.length, + }; + } + + private sortData(data: IBrewInfo[], filters: INovaFilters): IBrewInfo[] { + return orderBy( + data, + filters.sorter?.value?.sortBy, + filters.sorter?.value?.direction as "desc" | "asc" + ); + } +} + +/** + * A component that instantiates the dashboard + */ +@Component({ + selector: "table-widget-example", + templateUrl: "./table-widget-example.component.html", + styleUrls: ["./table-widget-example.component.less"], + standalone: false, +}) +export class TableWidgetExampleComponent implements OnInit { + // This variable will hold all the data needed to define the layout and behavior of the widgets. + // Pass this to the dashboard component's dashboard input in the template. + public dashboard: IDashboard | undefined; + + // Angular gridster requires a configuration object even if it's empty. + // Pass this to the dashboard component's gridsterConfig input in the template. + public gridsterConfig: GridsterConfig = {}; + + // Boolean passed as an input to the dashboard. When true, widgets can be moved, resized, removed, or edited + public editMode: boolean = false; + + constructor( + // WidgetTypesService provides the widget's necessary structure information + private widgetTypesService: WidgetTypesService, + // In general, the ProviderRegistryService is used for making entities available for injection into dynamically loaded components. + private providerRegistry: ProviderRegistryService, + private changeDetectorRef: ChangeDetectorRef + ) {} + + public ngOnInit(): void { + // Grabbing the widget's default template which will be needed as a parameter for setNode + const widgetTemplate = this.widgetTypesService.getWidgetType( + "table", + 1 + ); + + // Registering our data sources as dropdown options in the widget editor/configurator + // Note: This could also be done in the parent module's constructor so that + // multiple dashboards could have access to the same widget template modification. + this.widgetTypesService.setNode( + // This is the template we grabbed above with getWidgetType + widgetTemplate, + // We are setting the editor/configurator part of the widget template + "configurator", + // This indicates which node you are changing and we want to change + // the data source providers available for selection in the editor. + WellKnownPathKey.DataSourceProviders, + // We are setting the data sources available for selection in the editor + [BeerDataSource.providerId] + ); + + // Registering the data source for injection into the widget. + this.providerRegistry.setProviders({ + [BeerDataSource.providerId]: { + provide: DATA_SOURCE, + useClass: BeerDataSource, + // Any dependencies that need to be injected into the provider must be listed here + deps: [], + }, + }); + + this.initializeDashboard(); + } + + /** Used for restoring widgets state */ + public reInitializeDashboard(): void { + // destroys the components and their providers so the dashboard can re init data + this.dashboard = undefined; + this.changeDetectorRef.detectChanges(); + + this.initializeDashboard(); + } + + public initializeDashboard(): void { + // We're using a static configuration object for this example, but this is where + // the widget's configuration could potentially be populated from a database + const tableWidget = widgetConfig; + const widgetIndex: IWidgets = { + // Enhance the widget with information coming from it's type definition + [tableWidget.id]: + this.widgetTypesService.mergeWithWidgetType(tableWidget), + }; + + // Setting the widget dimensions and position (this is for gridster) + const positions: Record = { + [tableWidget.id]: { + cols: 12, + rows: 6, + y: 0, + x: 0, + }, + }; + + // Finally, assigning the variables we created above to the dashboard + this.dashboard = { + positions, + widgets: widgetIndex, + }; + } +} + +const TABLE_COLUMNS: ITableWidgetColumnConfig[] = [ + { + id: "column1", + label: $localize\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\`Beer Name\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\`, + isActive: true, + width: 185, + formatter: { + componentType: "RawFormatterComponent", + properties: { + dataFieldIds: { + value: "name", + }, + }, + }, + }, + { + id: "column2", + label: $localize\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\`Tagline\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\`, + isActive: true, + width: 250, + formatter: { + componentType: "RawFormatterComponent", + properties: { + dataFieldIds: { + value: "tagline", + }, + }, + }, + }, + { + id: "column3", + label: $localize\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\`First Brewed\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\`, + isActive: true, + width: 100, + formatter: { + componentType: "RawFormatterComponent", + properties: { + dataFieldIds: { + value: "first_brewed", + }, + }, + }, + }, + { + id: "column4", + label: $localize\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\`Description\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\`, + isActive: true, + formatter: { + componentType: "RawFormatterComponent", + properties: { + dataFieldIds: { + value: "description", + }, + }, + }, + }, +]; + +export const widgetConfig: IWidget = { + id: "tableWidgetId", + type: "table", + pizzagna: { + configuration: { + header: { + properties: { + title: "Stupendous Suds", + subtitle: "Try These Brilliant Brews", + }, + }, + table: { + providers: { + [WellKnownProviders.DataSource]: { + providerId: BeerDataSource.providerId, + }, + }, + properties: { + configuration: { + columns: TABLE_COLUMNS, + sortable: true, + sorterConfiguration: { + descendantSorting: false, + sortBy: "", + }, + hasVirtualScroll: true, + }, + }, + }, + }, + }, +}; +\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\`, + "widget-types/table/table-widget-interactive/table-widget-interactive-example.component.ts": \\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\`// © 2022 SolarWinds Worldwide, LLC. All rights reserved. +// +// Permission is hereby granted, free of charge, to any person obtaining a copy +// of this software and associated documentation files (the "Software"), to +// deal in the Software without restriction, including without limitation the +// rights to use, copy, modify, merge, publish, distribute, sublicense, and/or +// sell copies of the Software, and to permit persons to whom the Software is +// furnished to do so, subject to the following conditions: +// +// The above copyright notice and this permission notice shall be included in +// all copies or substantial portions of the Software. +// +// THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR +// IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, +// FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE +// AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER +// LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, +// OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN +// THE SOFTWARE. + +import { ChangeDetectorRef, Component, OnInit } from "@angular/core"; +import { GridsterConfig, GridsterItem } from "angular-gridster2"; +import orderBy from "lodash/orderBy"; +import { BehaviorSubject, firstValueFrom, from } from "rxjs"; +import { map, tap } from "rxjs/operators"; + +import { + DataSourceService, + IDataField, + INovaFilteringOutputs, + INovaFilters, + nameof, +} from "@nova-ui/bits"; +import { + DATA_SOURCE, + DEFAULT_PIZZAGNA_ROOT, + IDashboard, + ITableWidgetColumnConfig, + IWidget, + IWidgets, + NOVA_URL_INTERACTION_HANDLER, + ProviderRegistryService, + WellKnownPathKey, + WellKnownProviders, + WidgetTypesService, +} from "@nova-ui/dashboards"; + +export const BREW_API_URL = "https://api.punkapi.com/v2/beers"; + +export interface IBrewInfo { + id: string; + name: string; + tagline: string; + first_brewed: string; + description: string; + brewers_tips: string; +} + +export interface IBrewDatasourceResponse { + brewInfo: IBrewInfo[]; + total: number; +} + +export class MockBeerDataSource extends DataSourceService { + public static providerId = "MockBeerDataSource"; + + private cache: IBrewInfo[] = []; + + public busy = new BehaviorSubject(false); + + public dataFields: Array = [ + { + id: nameof("id"), + label: "No", + dataType: "number", + sortable: true, + }, + // To indicate that a column should not be sortable, set the optional IDataField 'sortable' property to false + { + id: nameof("name"), + label: "Name", + dataType: "string", + sortable: true, + }, + { + id: nameof("tagline"), + label: "Tagline", + dataType: "string", + sortable: true, + }, + { + id: nameof("first_brewed"), + label: "First Brewed", + dataType: "string", + sortable: true, + }, + { + id: nameof("description"), + label: "Description", + dataType: "string", + sortable: false, + }, + { + id: nameof("brewers_tips"), + label: "Brewer's Tips", + dataType: "string", + sortable: false, + }, + ]; + + public async getFilteredData( + filters: INovaFilters + ): Promise { + const start = filters.virtualScroll?.value?.start ?? 0; + const end = filters.virtualScroll?.value?.end ?? 0; + + // Resetting cache on first page request + if (start === 0) { + this.cache = []; + } + + // extract sorter settings to send to the backend + // filters.sorterValue.sortBy; filters.sorterValue.direction + return firstValueFrom( + from(this.fetch(start, end)).pipe( + tap((response) => { + if (!response) { + return; + } + this.cache = this.sortData( + this.cache.concat(response.brewInfo), + filters + ); + this.dataSubject.next(this.cache); + }), + map(() => ({ + repeat: { itemsSource: this.cache }, + dataFields: this.dataFields, + })) + ) + ); + } + + public async fetch( + start: number, + end: number + ): Promise { + const delta: number = end - start; + const currentPage: number = end / delta || 0; + const response: object | Array = await ( + await fetch( + \\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\`\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\${BREW_API_URL}/?page=\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\${currentPage}&per_page=\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\${delta}\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\` + ) + ).json(); + console.log( + "📘 table-widget-interactive-example.component: 85# -> response:", + response + ); + + // Note: In case request fails we should not proceed with mapping + if (!Array.isArray(response)) { + return undefined; + } + + return { + brewInfo: response.map((result: IBrewInfo) => ({ + id: result.id, + name: result.name, + tagline: result.tagline, + first_brewed: result.first_brewed, + description: result.description, + brewers_tips: result.brewers_tips, + })), + total: response.length, + }; + } + + private sortData(data: IBrewInfo[], filters: INovaFilters): IBrewInfo[] { + return orderBy( + data, + filters.sorter?.value?.sortBy, + filters.sorter?.value?.direction as "desc" | "asc" + ); + } +} + +/** + * A component that instantiates the dashboard + */ +@Component({ + selector: "table-widget-interactive-example", + templateUrl: "./table-widget-interactive-example.component.html", + styleUrls: ["./table-widget-interactive-example.component.less"], + standalone: false, +}) +export class TableWidgetInteractiveExampleComponent implements OnInit { + // This variable will hold all the data needed to define the layout and behavior of the widgets. + // Pass this to the dashboard component's dashboard input in the template. + public dashboard: IDashboard | undefined; + + // Angular gridster requires a configuration object even if it's empty. + // Pass this to the dashboard component's gridsterConfig input in the template. + public gridsterConfig: GridsterConfig = {}; + + // Boolean passed as an input to the dashboard. When true, widgets can be moved, resized, removed, or edited + public editMode: boolean = false; + + constructor( + // WidgetTypesService provides the widget's necessary structure information + private widgetTypesService: WidgetTypesService, + // In general, the ProviderRegistryService is used for making entities available for injection into dynamically loaded components. + private providerRegistry: ProviderRegistryService, + private changeDetectorRef: ChangeDetectorRef + ) {} + + public ngOnInit(): void { + // Grabbing the widget's default template which will be needed as a parameter for setNode + const widgetTemplate = this.widgetTypesService.getWidgetType( + "table", + 1 + ); + + // Registering our data sources as dropdown options in the widget editor/configurator + // Note: This could also be done in the parent module's constructor so that + // multiple dashboards could have access to the same widget template modification. + this.widgetTypesService.setNode( + // This is the template we grabbed above with getWidgetType + widgetTemplate, + // We are setting the editor/configurator part of the widget template + "configurator", + // This indicates which node you are changing and we want to change + // the data source providers available for selection in the editor. + WellKnownPathKey.DataSourceProviders, + // We are setting the data sources available for selection in the editor + [MockBeerDataSource.providerId] + ); + + // Registering the data source for injection into the widget. + this.providerRegistry.setProviders({ + [MockBeerDataSource.providerId]: { + provide: DATA_SOURCE, + useClass: MockBeerDataSource, + // Any dependencies that need to be injected into the provider must be listed here + deps: [], + }, + }); + + this.initializeDashboard(); + } + + /** Used for restoring widgets state */ + public reInitializeDashboard(): void { + // destroys the components and their providers so the dashboard can re init data + this.dashboard = undefined; + this.changeDetectorRef.detectChanges(); + + this.initializeDashboard(); + } + + public initializeDashboard(): void { + // We're using a static configuration object for this example, but this is where + // the widget's configuration could potentially be populated from a database + const tableWidget = widgetConfig; + const widgetIndex: IWidgets = { + // Enhance the widget with information coming from it's type definition + [tableWidget.id]: + this.widgetTypesService.mergeWithWidgetType(tableWidget), + }; + + // Setting the widget dimensions and position (this is for gridster) + const positions: Record = { + [tableWidget.id]: { + cols: 12, + rows: 6, + y: 0, + x: 0, + }, + }; + + // Finally, assigning the variables we created above to the dashboard + this.dashboard = { + positions, + widgets: widgetIndex, + }; + } +} + +const TABLE_COLUMNS: ITableWidgetColumnConfig[] = [ + { + id: "column1", + label: $localize\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\`Beer Name\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\`, + isActive: true, + width: 185, + formatter: { + componentType: "RawFormatterComponent", + properties: { + dataFieldIds: { + value: "name", + }, + }, + }, + }, + { + id: "column2", + label: $localize\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\`Tagline\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\`, + isActive: true, + width: 250, + formatter: { + componentType: "RawFormatterComponent", + properties: { + dataFieldIds: { + value: "tagline", + }, + }, + }, + }, + { + id: "column3", + label: $localize\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\`First Brewed\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\`, + isActive: true, + width: 100, + formatter: { + componentType: "RawFormatterComponent", + properties: { + dataFieldIds: { + value: "first_brewed", + }, + }, + }, + }, + { + id: "column4", + label: $localize\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\`Description\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\`, + isActive: true, + formatter: { + componentType: "RawFormatterComponent", + properties: { + dataFieldIds: { + value: "description", + }, + }, + }, + }, +]; + +export const widgetConfig: IWidget = { + id: "tableWidgetId", + type: "table", + pizzagna: { + configuration: { + [DEFAULT_PIZZAGNA_ROOT]: { + providers: { + [WellKnownProviders.InteractionHandler]: { + // Configuring the UrlInteractionHandler to handle interactions + providerId: NOVA_URL_INTERACTION_HANDLER, + properties: { + // the 'url' property tells the handler what link to use when interaction occurs on the series + url: "\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\${'https://untappd.com/search?q='+data.name}", + // by default the link is opened in the current window, set 'newWindow' to true to open in a new tab instead + newWindow: true, + }, + }, + }, + }, + header: { + properties: { + title: "Stupendous Suds", + subtitle: "Try These Brilliant Brews", + }, + }, + table: { + providers: { + [WellKnownProviders.DataSource]: { + providerId: MockBeerDataSource.providerId, + }, + }, + properties: { + configuration: { + // set interactions to true on the table + interactive: true, + columns: TABLE_COLUMNS, + sortable: true, + sorterConfiguration: { + descendantSorting: false, + sortBy: "", + }, + hasVirtualScroll: true, + }, + }, + }, + }, + }, +}; +\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\`, + "widget-types/table/table-widget-paginator/table-widget-paginator-example.component.ts": \\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\`// © 2022 SolarWinds Worldwide, LLC. All rights reserved. +// +// Permission is hereby granted, free of charge, to any person obtaining a copy +// of this software and associated documentation files (the "Software"), to +// deal in the Software without restriction, including without limitation the +// rights to use, copy, modify, merge, publish, distribute, sublicense, and/or +// sell copies of the Software, and to permit persons to whom the Software is +// furnished to do so, subject to the following conditions: +// +// The above copyright notice and this permission notice shall be included in +// all copies or substantial portions of the Software. +// +// THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR +// IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, +// FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE +// AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER +// LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, +// OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN +// THE SOFTWARE. + +import { HttpClient } from "@angular/common/http"; +import { ChangeDetectorRef, Component, OnInit } from "@angular/core"; +import { GridsterConfig, GridsterItem } from "angular-gridster2"; + +import { LoggerService } from "@nova-ui/bits"; +import { + DATA_SOURCE, + DEFAULT_PIZZAGNA_ROOT, + IDashboard, + IProviderConfiguration, + ITableWidgetConfig, + IWidget, + IWidgets, + NOVA_URL_INTERACTION_HANDLER, + PizzagnaLayer, + ProviderRegistryService, + RawFormatterComponent, + ScrollType, + WellKnownPathKey, + WellKnownProviders, + WidgetTypesService, +} from "@nova-ui/dashboards"; + +import { AcmeTableMockDataSource } from "../../../../prototypes/data/table/acme-table-mock-data-source.service"; + +/** + * A component that instantiates the dashboard + */ +@Component({ + selector: "table-widget-paginator-example", + templateUrl: "./table-widget-paginator-example.component.html", + styleUrls: ["./table-widget-paginator-example.component.less"], + standalone: false, +}) +export class TableWidgetPaginatorExampleComponent implements OnInit { + public dashboard: IDashboard | undefined; + public gridsterConfig: GridsterConfig = {}; + public editMode: boolean = false; + + constructor( + private widgetTypesService: WidgetTypesService, + private providerRegistry: ProviderRegistryService, + private changeDetectorRef: ChangeDetectorRef + ) {} + + public ngOnInit(): void { + const widgetTemplate = this.widgetTypesService.getWidgetType( + "table", + 1 + ); + this.widgetTypesService.setNode( + widgetTemplate, + "configurator", + WellKnownPathKey.DataSourceProviders, + [AcmeTableMockDataSource.providerId] + ); + + this.providerRegistry.setProviders({ + [AcmeTableMockDataSource.providerId]: { + provide: DATA_SOURCE, + useClass: AcmeTableMockDataSource, + deps: [LoggerService, HttpClient], + }, + }); + + this.initializeDashboard(); + } + + /** Used for restoring widgets state */ + public reInitializeDashboard(): void { + // destroys the components and their providers so the dashboard can re init data + this.dashboard = undefined; + this.changeDetectorRef.detectChanges(); + + this.initializeDashboard(); + } + + public initializeDashboard(): void { + const tableWithPaginator = tableWidgetWithPaginator; + const tableWithVirtualScroll = tableWidgetWithVirtualScroll; + + const widgetIndex: IWidgets = { + [tableWithPaginator.id]: + this.widgetTypesService.mergeWithWidgetType(tableWithPaginator), + [tableWithVirtualScroll.id]: + this.widgetTypesService.mergeWithWidgetType( + tableWithVirtualScroll + ), + }; + + const positions: Record = { + [tableWithPaginator.id]: { + cols: 6, + rows: 6, + y: 0, + x: 0, + }, + [tableWithVirtualScroll.id]: { + cols: 6, + rows: 6, + y: 0, + x: 0, + }, + }; + + this.dashboard = { + positions, + widgets: widgetIndex, + }; + } +} + +export const tableWidgetWithPaginator: IWidget = { + id: "widget1", + type: "table", + pizzagna: { + [PizzagnaLayer.Configuration]: { + [DEFAULT_PIZZAGNA_ROOT]: { + providers: { + [WellKnownProviders.InteractionHandler]: { + providerId: NOVA_URL_INTERACTION_HANDLER, + }, + }, + }, + header: { + properties: { + title: "Table Widget with paginator!", + subtitle: "Basic table widget", + collapsible: true, + }, + }, + table: { + providers: { + [WellKnownProviders.DataSource]: { + providerId: AcmeTableMockDataSource.providerId, + } as IProviderConfiguration, + }, + properties: { + configuration: { + interactive: true, + columns: [ + { + id: "column1", + label: "No.", + isActive: true, + formatter: { + componentType: + RawFormatterComponent.lateLoadKey, + properties: { + dataFieldIds: { + value: "position", + }, + }, + }, + }, + { + id: "column2", + label: "Name", + isActive: true, + formatter: { + componentType: + RawFormatterComponent.lateLoadKey, + properties: { + dataFieldIds: { + value: "name", + }, + }, + }, + }, + { + id: "column3", + label: "Status", + isActive: true, + formatter: { + componentType: + RawFormatterComponent.lateLoadKey, + properties: { + dataFieldIds: { + value: "status", + }, + }, + }, + }, + ], + sorterConfiguration: { + descendantSorting: false, + sortBy: "column1", + }, + scrollType: ScrollType.paginator, + paginatorConfiguration: { + pageSize: 5, + pageSizeSet: [5, 10, 20, 30], + }, + hasVirtualScroll: false, + searchConfiguration: { + enabled: true, + }, + } as ITableWidgetConfig, + }, + }, + }, + }, +}; + +export const tableWidgetWithVirtualScroll: IWidget = { + id: "widget2", + type: "table", + pizzagna: { + [PizzagnaLayer.Configuration]: { + [DEFAULT_PIZZAGNA_ROOT]: { + providers: { + [WellKnownProviders.InteractionHandler]: { + providerId: NOVA_URL_INTERACTION_HANDLER, + }, + }, + }, + header: { + properties: { + title: "Table Widget with virtual scroll!", + subtitle: "Basic table widget", + collapsible: true, + }, + }, + table: { + providers: { + [WellKnownProviders.DataSource]: { + providerId: AcmeTableMockDataSource.providerId, + } as IProviderConfiguration, + }, + properties: { + configuration: { + interactive: true, + columns: [ + { + id: "column1", + label: "No.", + isActive: true, + formatter: { + componentType: + RawFormatterComponent.lateLoadKey, + properties: { + dataFieldIds: { + value: "position", + }, + }, + }, + }, + { + id: "column2", + label: "Name", + isActive: true, + formatter: { + componentType: + RawFormatterComponent.lateLoadKey, + properties: { + dataFieldIds: { + value: "name", + }, + }, + }, + }, + { + id: "column3", + label: "Status", + isActive: true, + formatter: { + componentType: + RawFormatterComponent.lateLoadKey, + properties: { + dataFieldIds: { + value: "status", + }, + }, + }, + }, + ], + sorterConfiguration: { + descendantSorting: false, + sortBy: "column1", + }, + hasVirtualScroll: true, + searchConfiguration: { + enabled: true, + }, + } as ITableWidgetConfig, + }, + }, + }, + }, +}; +\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\`, + "widget-types/table/table-widget-search/table-widget-search-example.component.ts": \\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\`// © 2022 SolarWinds Worldwide, LLC. All rights reserved. +// +// Permission is hereby granted, free of charge, to any person obtaining a copy +// of this software and associated documentation files (the "Software"), to +// deal in the Software without restriction, including without limitation the +// rights to use, copy, modify, merge, publish, distribute, sublicense, and/or +// sell copies of the Software, and to permit persons to whom the Software is +// furnished to do so, subject to the following conditions: +// +// The above copyright notice and this permission notice shall be included in +// all copies or substantial portions of the Software. +// +// THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR +// IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, +// FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE +// AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER +// LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, +// OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN +// THE SOFTWARE. + +import { HttpClient } from "@angular/common/http"; +import { + ChangeDetectorRef, + Component, + Injectable, + OnInit, +} from "@angular/core"; +import { GridsterConfig, GridsterItem } from "angular-gridster2"; +import isEqual from "lodash/isEqual"; +import isNil from "lodash/isNil"; +import { BehaviorSubject, firstValueFrom, Observable, of, Subject } from "rxjs"; +import { + catchError, + delay, + finalize, + map, + // eslint-disable-next-line import/no-deprecated + switchMap, + tap, +} from "rxjs/operators"; + +import { + DataSourceFeatures, + DataSourceService, + IDataField, + IDataSource, + IDataSourceFeatures, + IDataSourceFeaturesConfiguration, + IDataSourceOutput, + IFilter, + IFilters, + INovaFilteringOutputs, + INovaFilters, + LoggerService, +} from "@nova-ui/bits"; +import { + DATA_SOURCE, + IDashboard, + ITableWidgetConfig, + IWidget, + IWidgets, + ProviderRegistryService, + WellKnownPathKey, + WellKnownProviders, + WidgetTypesService, +} from "@nova-ui/dashboards"; + +import { GBOOKS_API_URL } from "../../../../prototypes/data/table/constants"; + +interface IGBooksApiResponse { + kind: string; + totalItems: number; + items: IGBooksItemModel[]; + [key: string]: any; +} + +interface IGBooksItemModel { + id: string; + volumeInfo: { + title: string; + subtitle: string; + authors: string[]; + [key: string]: any; + }; + accessInfo: { [key: string]: any }; + saleInfo: { [key: string]: any }; +} + +interface IGBooksData { + books: IGBooksVolume[]; + totalItems: number; +} + +interface IGBooksVolume { + title: string; + authors: string; +} + +type searchableColumnType = "title" | "authors"; + +@Injectable() +export class AcmeTableGBooksDataSource + extends DataSourceService + implements IDataSource +{ + public static providerId = "AcmeTableGBooksDataSource"; + public static mockError = false; + + public searchableColumn: searchableColumnType = "title"; + + public page: number = 1; + public busy = new BehaviorSubject(false); + public features: IDataSourceFeaturesConfiguration; + + private cache = Array.from({ length: 0 }); + private previousFilters: INovaFilters; + // DataSource Features declared + private supportedFeatures: IDataSourceFeatures = { + search: { enabled: true }, + pagination: { enabled: true }, + }; + private columnToQueryParamMap: { [k in searchableColumnType]: string } = { + title: "intitle", + authors: "inauthor", + }; + + private applyFilters$ = new Subject(); + + public dataFields: Array = [ + { + id: "title", + label: $localize\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\`Title\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\`, + dataType: "string", + sortable: false, + }, + { + id: "authors", + label: $localize\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\`Authors\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\`, + dataType: "string", + sortable: false, + }, + ]; + + constructor(private logger: LoggerService, private http: HttpClient) { + super(); + // Using Nova DataSourceFeatures implementation for the features + this.features = new DataSourceFeatures(this.supportedFeatures); + + this.applyFilters$ + // eslint-disable-next-line import/no-deprecated + .pipe(switchMap((filters) => this.getData(filters))) + .subscribe(async (res) => { + this.outputsSubject.next(await this.getFilteredData(res)); + }); + } + + public async getFilteredData( + booksData: IGBooksData + ): Promise> { + return firstValueFrom( + of(booksData).pipe( + tap((response) => { + this.cache = this.cache.concat(response.books); + }), + map((response) => ({ + result: { + repeat: { itemsSource: this.cache }, + paginator: { total: response.totalItems }, + dataFields: this.dataFields, + }, + })) + ) + ); + } + + private getData(filters: INovaFilters): Observable { + if ( + this.isNewSearchTerm(filters.search) && + filters.virtualScroll?.value.start === 0 + ) { + this.cache = []; + } + + return this.http + .get(this.getComposedUrl(filters)) + .pipe( + tap(() => this.busy.next(true)), + delay(300), // mock + map((response) => ({ + books: + response.items?.map((volume) => ({ + title: volume.volumeInfo.title, + authors: + volume.volumeInfo.authors?.join(", ") || "", + })) || [], + totalItems: response.totalItems, + })), + catchError((e) => { + this.logger.error(e); + return of({ + books: [], + totalItems: 0, + }); + }), + finalize(() => { + this.busy.next(false); + this.previousFilters = filters; + }) + ); + } + + private getComposedUrl(filters: INovaFilters) { + const initialUrl = \\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\`\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\${GBOOKS_API_URL}?q=\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\`; + const maxResults = \\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\`maxResults=\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\${ + (filters.virtualScroll?.value.end || 0) - + (filters.virtualScroll?.value.start || 0) + }\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\`; + + const virtualScrollPart = filters.virtualScroll + ? \\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\`startIndex=\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\${filters.virtualScroll.value.start}\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\` + : ""; + + const searchQueryParam = + this.columnToQueryParamMap[this.searchableColumn]; + const searchPart = filters.search + ? \\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\`\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\${searchQueryParam}:\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\${filters.search.value}\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\` + : "_"; // google books api requires some criteria to do the search + + return \\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\`\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\${initialUrl}\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\${searchPart}&\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\${maxResults}&\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\${virtualScrollPart}&filter=full\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\`; + } + + private isNewSearchTerm(search: IFilter | undefined) { + return ( + !isNil(search?.value) && + !isEqual(search?.value, this.previousFilters?.search?.value) + ); + } + + // redefine parent method + public async applyFilters(): Promise { + this.applyFilters$.next(this.getFilters()); + } +} + +/** + * A component that instantiates the dashboard + */ +@Component({ + selector: "table-widget-search-example", + templateUrl: "./table-widget-search-example.component.html", + styleUrls: ["./table-widget-search-example.component.less"], + standalone: false, +}) +export class TableWidgetSearchExampleComponent implements OnInit { + public dashboard: IDashboard | undefined; + public gridsterConfig: GridsterConfig = {}; + public editMode: boolean = false; + + constructor( + private widgetTypesService: WidgetTypesService, + private providerRegistry: ProviderRegistryService, + private changeDetectorRef: ChangeDetectorRef + ) {} + + public ngOnInit(): void { + const widgetTemplate = this.widgetTypesService.getWidgetType( + "table", + 1 + ); + this.widgetTypesService.setNode( + widgetTemplate, + "configurator", + WellKnownPathKey.DataSourceProviders, + [AcmeTableGBooksDataSource.providerId] + ); + + this.providerRegistry.setProviders({ + [AcmeTableGBooksDataSource.providerId]: { + provide: DATA_SOURCE, + useClass: AcmeTableGBooksDataSource, + deps: [LoggerService, HttpClient], + }, + }); + + this.initializeDashboard(); + } + + /** Used for restoring widgets state */ + public reInitializeDashboard(): void { + // destroys the components and their providers so the dashboard can re init data + this.dashboard = undefined; + this.changeDetectorRef.detectChanges(); + + this.initializeDashboard(); + } + + public initializeDashboard(): void { + const tableWidget = widgetConfig; + const widgetIndex: IWidgets = { + [tableWidget.id]: + this.widgetTypesService.mergeWithWidgetType(tableWidget), + }; + + const positions: Record = { + [tableWidget.id]: { + cols: 12, + rows: 6, + y: 0, + x: 0, + }, + }; + + this.dashboard = { + positions, + widgets: widgetIndex, + }; + } +} + +export const widgetConfig: IWidget = { + id: "tableWidgetId", + type: "table", + pizzagna: { + configuration: { + header: { + properties: { + title: "Google Books", + }, + }, + table: { + providers: { + [WellKnownProviders.DataSource]: { + providerId: AcmeTableGBooksDataSource.providerId, + }, + }, + properties: { + configuration: { + columns: [ + { + id: "column1", + label: $localize\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\`Title\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\`, + isActive: true, + formatter: { + componentType: "RawFormatterComponent", + properties: { + dataFieldIds: { + value: "title", + }, + }, + }, + }, + { + id: "column2", + label: $localize\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\`Author\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\`, + isActive: true, + formatter: { + componentType: "RawFormatterComponent", + properties: { + dataFieldIds: { + value: "authors", + }, + }, + }, + }, + ], + sortable: false, + // define search configuration here + searchConfiguration: { + enabled: true, + // following properties below can be configured as well + // searchTerm: "search criteria here", + // searchDebounce: 300, + }, + hasVirtualScroll: true, + } as ITableWidgetConfig, + }, + }, + }, + }, +}; +\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\`, + "widget-types/table/table-widget-search-docs.component.ts": \\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\`// © 2022 SolarWinds Worldwide, LLC. All rights reserved. +// +// Permission is hereby granted, free of charge, to any person obtaining a copy +// of this software and associated documentation files (the "Software"), to +// deal in the Software without restriction, including without limitation the +// rights to use, copy, modify, merge, publish, distribute, sublicense, and/or +// sell copies of the Software, and to permit persons to whom the Software is +// furnished to do so, subject to the following conditions: +// +// The above copyright notice and this permission notice shall be included in +// all copies or substantial portions of the Software. +// +// THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR +// IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, +// FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE +// AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER +// LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, +// OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN +// THE SOFTWARE. + +import { Component } from "@angular/core"; + +@Component({ + selector: "nui-table-search-docs", + templateUrl: "./table-widget-search-docs.component.html", + standalone: false, +}) +export class TableSearchDocsComponent { + public featuredDeclaredText = \\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\` + private supportedFeatures: IDataSourceFeatures = { + search: { enabled: true }, + pagination: { enabled: true }, + };\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\`; + public featuresUsedText = \\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\` + this.features = new DataSourceFeatures(this.supportedFeatures); + \\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\`; + public tableConfigurationText = \\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\` + "table": { + ... + properties: { + configuration: { + // define search configuration here + searchConfiguration: { + enabled: true, + // following optional properties below can be configured as well + // searchTerm: "search criteria here", + // searchDebounce: 300, + }, + } as ITableWidgetConfig, + }, + }, + \\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\`; +} +\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\`, + "widget-types/table/table-widget-selectable/table-widget-selectable-multi/table-widget-selectable-multi.example.component.ts": \\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\`// © 2022 SolarWinds Worldwide, LLC. All rights reserved. +// +// Permission is hereby granted, free of charge, to any person obtaining a copy +// of this software and associated documentation files (the "Software"), to +// deal in the Software without restriction, including without limitation the +// rights to use, copy, modify, merge, publish, distribute, sublicense, and/or +// sell copies of the Software, and to permit persons to whom the Software is +// furnished to do so, subject to the following conditions: +// +// The above copyright notice and this permission notice shall be included in +// all copies or substantial portions of the Software. +// +// THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR +// IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, +// FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE +// AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER +// LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, +// OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN +// THE SOFTWARE. + +import { Component } from "@angular/core"; + +import { TableSelectionMode } from "@nova-ui/bits"; +import { TableWidgetSelectionConfig } from "@nova-ui/dashboards"; + +/** + * A component that instantiates the dashboard + */ +@Component({ + selector: "table-widget-selectable-multi-example", + templateUrl: "./table-widget-selectable-multi.example.component.html", + styleUrls: ["./table-widget-selectable-multi.example.component.less"], + standalone: false, +}) +export class TableWidgetSelectableMultiExampleComponent { + public selectionConfiguration: TableWidgetSelectionConfig = { + enabled: true, + selectionMode: TableSelectionMode.Multi, + trackByProperty: "id", + clickableRow: true, + }; +} +\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\`, + "widget-types/table/table-widget-selectable/table-widget-selectable-radio/table-widget-selectable-radio.example.component.ts": \\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\`// © 2022 SolarWinds Worldwide, LLC. All rights reserved. +// +// Permission is hereby granted, free of charge, to any person obtaining a copy +// of this software and associated documentation files (the "Software"), to +// deal in the Software without restriction, including without limitation the +// rights to use, copy, modify, merge, publish, distribute, sublicense, and/or +// sell copies of the Software, and to permit persons to whom the Software is +// furnished to do so, subject to the following conditions: +// +// The above copyright notice and this permission notice shall be included in +// all copies or substantial portions of the Software. +// +// THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR +// IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, +// FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE +// AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER +// LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, +// OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN +// THE SOFTWARE. + +import { Component } from "@angular/core"; + +import { TableSelectionMode } from "@nova-ui/bits"; +import { TableWidgetSelectionConfig } from "@nova-ui/dashboards"; + +/** + * A component that instantiates the dashboard + */ +@Component({ + selector: "table-widget-selectable-radio-example", + templateUrl: "./table-widget-selectable-radio.example.component.html", + styleUrls: ["./table-widget-selectable-radio.example.component.less"], + standalone: false, +}) +export class TableWidgetSelectableRadioExampleComponent { + public selectionConfiguration: TableWidgetSelectionConfig = { + enabled: true, + selectionMode: TableSelectionMode.Radio, + trackByProperty: "id", + clickableRow: true, + }; +} +\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\`, + "widget-types/table/table-widget-selectable/table-widget-selectable-single/table-widget-selectable-single.example.component.ts": \\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\`// © 2022 SolarWinds Worldwide, LLC. All rights reserved. +// +// Permission is hereby granted, free of charge, to any person obtaining a copy +// of this software and associated documentation files (the "Software"), to +// deal in the Software without restriction, including without limitation the +// rights to use, copy, modify, merge, publish, distribute, sublicense, and/or +// sell copies of the Software, and to permit persons to whom the Software is +// furnished to do so, subject to the following conditions: +// +// The above copyright notice and this permission notice shall be included in +// all copies or substantial portions of the Software. +// +// THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR +// IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, +// FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE +// AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER +// LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, +// OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN +// THE SOFTWARE. + +import { Component } from "@angular/core"; + +import { TableSelectionMode } from "@nova-ui/bits"; +import { TableWidgetSelectionConfig } from "@nova-ui/dashboards"; + +/** + * A component that instantiates the dashboard + */ +@Component({ + selector: "table-widget-selectable-single-example", + templateUrl: "./table-widget-selectable-single.example.component.html", + styleUrls: ["./table-widget-selectable-single.example.component.less"], + standalone: false, +}) +export class TableWidgetSelectableSingleExampleComponent { + public selectionConfiguration: TableWidgetSelectionConfig = { + enabled: true, + selectionMode: TableSelectionMode.Single, + trackByProperty: "id", + }; +} +\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\`, + "widget-types/table/table-widget-selectable/table-widget-selectable.example.component.ts": \\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\`// © 2022 SolarWinds Worldwide, LLC. All rights reserved. +// +// Permission is hereby granted, free of charge, to any person obtaining a copy +// of this software and associated documentation files (the "Software"), to +// deal in the Software without restriction, including without limitation the +// rights to use, copy, modify, merge, publish, distribute, sublicense, and/or +// sell copies of the Software, and to permit persons to whom the Software is +// furnished to do so, subject to the following conditions: +// +// The above copyright notice and this permission notice shall be included in +// all copies or substantial portions of the Software. +// +// THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR +// IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, +// FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE +// AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER +// LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, +// OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN +// THE SOFTWARE. + +import { HttpClient } from "@angular/common/http"; +import { ChangeDetectorRef, Component, Input, OnInit } from "@angular/core"; +import { GridsterConfig, GridsterItem } from "angular-gridster2"; + +import { LoggerService, TableSelectionMode } from "@nova-ui/bits"; +import { + DATA_SOURCE, + DEFAULT_PIZZAGNA_ROOT, + IDashboard, + IProviderConfiguration, + ITableWidgetConfig, + IWidget, + IWidgets, + NOVA_URL_INTERACTION_HANDLER, + PizzagnaLayer, + ProviderRegistryService, + RawFormatterComponent, + TableWidgetSelectionConfig, + WellKnownPathKey, + WellKnownProviders, + WidgetTypesService, +} from "@nova-ui/dashboards"; + +import { AcmeTableMockDataSource } from "../../../../prototypes/data/table/acme-table-mock-data-source.service"; + +/** + * A component that instantiates the dashboard + */ +@Component({ + selector: "table-widget-selectable-example", + templateUrl: "./table-widget-selectable.example.component.html", + styleUrls: ["./table-widget-selectable.example.component.less"], + standalone: false, +}) +export class TableWidgetSelectableExampleComponent implements OnInit { + public dashboard: IDashboard | undefined; + public gridsterConfig: GridsterConfig = {}; + public editMode: boolean = false; + + @Input() public selectionConfiguration: TableWidgetSelectionConfig = { + enabled: false, + selectionMode: TableSelectionMode.None, + }; + + constructor( + private widgetTypesService: WidgetTypesService, + private providerRegistry: ProviderRegistryService, + private changeDetectorRef: ChangeDetectorRef + ) {} + + public ngOnInit(): void { + const widgetTemplate = this.widgetTypesService.getWidgetType( + "table", + 1 + ); + this.widgetTypesService.setNode( + widgetTemplate, + "configurator", + WellKnownPathKey.DataSourceProviders, + [AcmeTableMockDataSource.providerId] + ); + + this.providerRegistry.setProviders({ + [AcmeTableMockDataSource.providerId]: { + provide: DATA_SOURCE, + useClass: AcmeTableMockDataSource, + deps: [LoggerService, HttpClient], + }, + }); + + this.initializeDashboard(); + } + + /** Used for restoring widgets state */ + public reInitializeDashboard(): void { + // destroys the components and their providers so the dashboard can re init data + this.dashboard = undefined; + this.changeDetectorRef.detectChanges(); + + this.initializeDashboard(); + } + + public initializeDashboard(): void { + const tableWidget = this.widgetConfig; + const widgetIndex: IWidgets = { + [tableWidget.id]: + this.widgetTypesService.mergeWithWidgetType(tableWidget), + }; + + const positions: Record = { + [tableWidget.id]: { + cols: 12, + rows: 6, + y: 0, + x: 0, + }, + }; + + this.dashboard = { + positions, + widgets: widgetIndex, + }; + } + + private get widgetConfig(): IWidget { + return { + id: "widget1", + type: "table", + pizzagna: { + [PizzagnaLayer.Configuration]: { + [DEFAULT_PIZZAGNA_ROOT]: { + providers: { + [WellKnownProviders.InteractionHandler]: { + providerId: NOVA_URL_INTERACTION_HANDLER, + }, + }, + }, + header: { + properties: { + title: "Table Widget with Selection!", + subtitle: "Basic table widget", + collapsible: true, + }, + }, + table: { + providers: { + [WellKnownProviders.DataSource]: { + providerId: AcmeTableMockDataSource.providerId, + } as IProviderConfiguration, + }, + properties: { + configuration: { + // enabling selection here + selectionConfiguration: + this.selectionConfiguration, + columns: [ + { + id: "column1", + label: "No.", + isActive: true, + formatter: { + componentType: + RawFormatterComponent.lateLoadKey, + properties: { + dataFieldIds: { + value: "position", + }, + }, + }, + }, + { + id: "column2", + label: "Name", + isActive: true, + formatter: { + componentType: + RawFormatterComponent.lateLoadKey, + properties: { + dataFieldIds: { + value: "name", + }, + }, + }, + }, + { + id: "column3", + label: "Status", + isActive: true, + formatter: { + componentType: + RawFormatterComponent.lateLoadKey, + properties: { + dataFieldIds: { + value: "status", + }, + }, + }, + }, + ], + } as ITableWidgetConfig, + }, + }, + }, + }, + }; + } +} +\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\`, + "widget-types/timeseries/timeseries-docs.component.ts": \\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\`// © 2022 SolarWinds Worldwide, LLC. All rights reserved. +// +// Permission is hereby granted, free of charge, to any person obtaining a copy +// of this software and associated documentation files (the "Software"), to +// deal in the Software without restriction, including without limitation the +// rights to use, copy, modify, merge, publish, distribute, sublicense, and/or +// sell copies of the Software, and to permit persons to whom the Software is +// furnished to do so, subject to the following conditions: +// +// The above copyright notice and this permission notice shall be included in +// all copies or substantial portions of the Software. +// +// THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR +// IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, +// FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE +// AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER +// LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, +// OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN +// THE SOFTWARE. + +import { Component, OnInit } from "@angular/core"; + +import { mapContentFile } from "../../../../demo-files-factory"; + +@Component({ + selector: "nui-timeseries-docs", + templateUrl: "./timeseries-docs.component.html", + standalone: false, +}) +export class TimeseriesDocsComponent implements OnInit { + public timeseriesWidgetFileText = ""; + public timeseriesConfiguratorFileText = ""; + + async ngOnInit(): Promise { + this.timeseriesWidgetFileText = await import( + "./../../../../../../src/lib/widget-types/timeseries/timeseries-widget" + ).then(mapContentFile); + this.timeseriesConfiguratorFileText = await import( + "./../../../../../../src/lib/widget-types/timeseries/timeseries-configurator" + ).then(mapContentFile); + } +} +\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\`, + "widget-types/timeseries/timeseries-docs.module.ts": \\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\`// © 2022 SolarWinds Worldwide, LLC. All rights reserved. +// +// Permission is hereby granted, free of charge, to any person obtaining a copy +// of this software and associated documentation files (the "Software"), to +// deal in the Software without restriction, including without limitation the +// rights to use, copy, modify, merge, publish, distribute, sublicense, and/or +// sell copies of the Software, and to permit persons to whom the Software is +// furnished to do so, subject to the following conditions: +// +// The above copyright notice and this permission notice shall be included in +// all copies or substantial portions of the Software. +// +// THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR +// IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, +// FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE +// AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER +// LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, +// OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN +// THE SOFTWARE. + +import { NgModule } from "@angular/core"; +import { RouterModule, Routes } from "@angular/router"; + +import { DEMO_PATH_TOKEN } from "@nova-ui/bits"; +import { + NuiButtonModule, + NuiDocsModule, + NuiMessageModule, + NuiSwitchModule, +} from "@nova-ui/bits"; +import { NuiDashboardsModule } from "@nova-ui/dashboards"; + +import { TimeseriesDocsComponent } from "./timeseries-docs.component"; +import { TimeseriesWidgetExampleComponent } from "./timeseries-widget-example/timeseries-widget-example.component"; +import { TimeseriesWidgetInteractiveExampleComponent } from "./timeseries-widget-interactive-example/timeseries-widget-interactive-example.component"; +import { TimeseriesWidgetStatusBarExampleComponent } from "./timeseries-widget-status-bar-example/timeseries-widget-status-bar-example.component"; +import { getDemoFiles } from "../../../../demo-files-factory"; + +const routes: Routes = [ + { + path: "", + component: TimeseriesDocsComponent, + data: { + srlc: { + hideIndicator: true, + }, + showThemeSwitcher: true, + }, + }, + { + path: "example", + component: TimeseriesWidgetExampleComponent, + data: { + srlc: { + hideIndicator: true, + }, + }, + }, +]; + +@NgModule({ + imports: [ + RouterModule.forChild(routes), + NuiButtonModule, + NuiDocsModule, + NuiMessageModule, + NuiSwitchModule, + NuiDashboardsModule, + ], + declarations: [ + TimeseriesDocsComponent, + TimeseriesWidgetExampleComponent, + TimeseriesWidgetInteractiveExampleComponent, + TimeseriesWidgetStatusBarExampleComponent, + ], + providers: [ + { + provide: DEMO_PATH_TOKEN, + useValue: getDemoFiles("timeseries"), + }, + ], +}) +export default class TimeseriesDocsModule {} +\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\`, + "widget-types/timeseries/timeseries-widget-example/timeseries-widget-example.component.ts": \\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\`// © 2022 SolarWinds Worldwide, LLC. All rights reserved. +// +// Permission is hereby granted, free of charge, to any person obtaining a copy +// of this software and associated documentation files (the "Software"), to +// deal in the Software without restriction, including without limitation the +// rights to use, copy, modify, merge, publish, distribute, sublicense, and/or +// sell copies of the Software, and to permit persons to whom the Software is +// furnished to do so, subject to the following conditions: +// +// The above copyright notice and this permission notice shall be included in +// all copies or substantial portions of the Software. +// +// THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR +// IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, +// FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE +// AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER +// LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, +// OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN +// THE SOFTWARE. + +import { + ChangeDetectorRef, + Component, + Injectable, + OnInit, +} from "@angular/core"; +import { GridsterConfig, GridsterItem } from "angular-gridster2"; +import cloneDeep from "lodash/cloneDeep"; +import keyBy from "lodash/keyBy"; +import moment, { Moment } from "moment/moment"; +import { BehaviorSubject } from "rxjs"; + +import { + DataSourceService, + IDataSource, + INovaFilters, + ITimeframe, +} from "@nova-ui/bits"; +import { + DATA_SOURCE, + DEFAULT_PIZZAGNA_ROOT, + IDashboard, + IDataSourceOutput, + IProviderConfiguration, + ISerializableTimeframe, + ITimeseriesItemConfiguration, + ITimeseriesOutput, + ITimeseriesScaleConfig, + ITimeseriesWidgetConfig, + ITimeseriesWidgetData, + ITimeseriesWidgetSeriesData, + IWidget, + LegendPlacement, + PizzagnaLayer, + ProviderRegistryService, + TimeseriesChartPreset, + TimeseriesScaleType, + WellKnownPathKey, + WellKnownProviders, + WidgetTypesService, +} from "@nova-ui/dashboards"; + +/** + * A simple Timeseries data source implementation + */ +@Injectable() +export class BeerVsReadingMockDataSource + extends DataSourceService + implements IDataSource +{ + public static providerId = "BeerVsReadingMockDataSource"; + + public busy = new BehaviorSubject(false); + + public async getFilteredData( + filters: INovaFilters + ): Promise> { + // In this example we're using some static mock data located at the bottom of this file. In a real-world + // scenario, the data for the chart would likely be retrieved via an asynchronous backend call. + let filteredData = getData(); + + this.busy.next(true); + + // Filtering using the filter registered by the TimeFrameBar + const timeframeFilter = filters.timeframe?.value as ITimeframe; + if (timeframeFilter) { + filteredData = filteredData.map((item: ITimeseriesWidgetData) => ({ + id: item.id, + name: item.name, + description: item.description, + data: item.data.filter( + (seriesData: ITimeseriesWidgetSeriesData) => + filterDates( + seriesData.x, + timeframeFilter.startDatetime, + timeframeFilter.endDatetime + ) + ), + })); + } + + this.busy.next(false); + + return { result: { series: filteredData } }; + } +} + +function filterDates(dateToCheck: Date, startDate: Moment, endDate: Moment) { + const mom = moment(dateToCheck); + return ( + mom.isBetween(startDate, endDate) || + mom.isSame(startDate) || + mom.isSame(endDate) + ); +} + +/** + * A component that instantiates the dashboard + */ +@Component({ + selector: "timeseries-widget-example", + templateUrl: "./timeseries-widget-example.component.html", + styleUrls: ["./timeseries-widget-example.component.less"], + standalone: false, +}) +export class TimeseriesWidgetExampleComponent implements OnInit { + // This variable will hold all the data needed to define the layout and behavior of the widgets. + // Pass this to the dashboard component's dashboard input in the template. + public dashboard: IDashboard | undefined; + + // Angular gridster requires a configuration object even if it's empty. + // Pass this to the dashboard component's gridsterConfig input in the template. + public gridsterConfig: GridsterConfig = {}; + + // Boolean passed as an input to the dashboard. When true, widgets can be moved, resized, removed, or edited + public editMode: boolean = false; + + constructor( + // WidgetTypesService provides the widget's necessary structure information + private widgetTypesService: WidgetTypesService, + + // In general, the ProviderRegistryService is used for making entities available for injection into dynamically loaded components. + private providerRegistry: ProviderRegistryService, + + // Angular's ChangeDetectorRef + private changeDetectorRef: ChangeDetectorRef + ) {} + + public ngOnInit(): void { + // Grabbing the widget's default template which will be needed as a parameter for setNode + const widgetTemplate = this.widgetTypesService.getWidgetType( + "timeseries", + 1 + ); + // Registering our data sources as dropdown options in the widget editor/configurator + // Note: This could also be done in the parent module's constructor so that + // multiple dashboards could have access to the same widget template modification. + this.widgetTypesService.setNode( + // This is the template we grabbed above with getWidgetType + widgetTemplate, + // We are setting the editor/configurator part of the widget template + "configurator", + // This indicates which node you are changing and we want to change + // the data source providers available for selection in the editor. + WellKnownPathKey.DataSourceProviders, + // We are setting the data sources available for selection in the editor + [BeerVsReadingMockDataSource.providerId] + ); + + // Registering the data source for injection into the widget. + this.providerRegistry.setProviders({ + [BeerVsReadingMockDataSource.providerId]: { + provide: DATA_SOURCE, + useClass: BeerVsReadingMockDataSource, + deps: [], + }, + }); + + this.initializeDashboard(); + } + + public initializeDashboard(): void { + // We're using a static configuration object for this example, but this is where + // the widget's configuration could potentially be populated from a database + const widgetsWithStructure = widgetConfigs.map((w) => + this.widgetTypesService.mergeWithWidgetType(w) + ); + const widgetsIndex = keyBy(widgetsWithStructure, (w: IWidget) => w.id); + + // Finally, assigning the variables we created above to the dashboard + this.dashboard = { + positions: cloneDeep(positions), + widgets: widgetsIndex, + }; + } + + /** Used for restoring widgets state */ + public reInitializeDashboard(): void { + // destroys the components and their providers so the dashboard can re init data + this.dashboard = undefined; + this.changeDetectorRef.detectChanges(); + + this.initializeDashboard(); + } +} + +const widgetConfigs: IWidget[] = [ + { + id: "lineWidgetId", + type: "timeseries", + pizzagna: { + [PizzagnaLayer.Configuration]: { + [DEFAULT_PIZZAGNA_ROOT]: { + providers: { + [WellKnownProviders.DataSource]: { + // Setting the initially selected data source providerId + providerId: BeerVsReadingMockDataSource.providerId, + } as IProviderConfiguration, + }, + }, + header: { + properties: { + title: "Line Chart", + subtitle: "Survey of 1000 Solarians", + }, + }, + chart: { + providers: { + [WellKnownProviders.Adapter]: { + properties: { + // Setting the series and corresponding labels to initially display on the chart + series: [ + { + id: "series-1", + label: "Beer Tasting", + selectedSeriesId: "series-1", + }, + { + id: "series-2", + label: "Reading", + selectedSeriesId: "series-2", + }, + ] as ITimeseriesItemConfiguration[], + }, + } as Partial, + }, + properties: { + // Setting the general chart configuration + configuration: { + legendPlacement: LegendPlacement.Right, + enableZoom: true, + leftAxisLabel: "Solarians (%)", + // You can optionally define custom colors for the chart by setting the 'chartColors' configuration property + // "chartColors": [ + // "var(--nui-color-chart-eight)", + // "var(--nui-color-chart-nine)", + // "var(--nui-color-chart-ten)", + // ], + } as ITimeseriesWidgetConfig, + }, + }, + timeframeSelection: { + properties: { + // Setting the initial timeframe selected in the timeframe bar + timeframe: { + selectedPresetId: "last7Days", + } as ISerializableTimeframe, + minDate: moment().subtract(60, "days").format(), + maxDate: moment().format(), + }, + }, + }, + }, + }, + { + id: "stackedAreaWidgetId", + type: "timeseries", + pizzagna: { + [PizzagnaLayer.Configuration]: { + [DEFAULT_PIZZAGNA_ROOT]: { + providers: { + [WellKnownProviders.DataSource]: { + // Setting the initially selected data source providerId + providerId: BeerVsReadingMockDataSource.providerId, + } as IProviderConfiguration, + }, + }, + header: { + properties: { + title: "Stacked Area Chart", + subtitle: "Survey of 1000 Solarians", + }, + }, + chart: { + providers: { + [WellKnownProviders.Adapter]: { + properties: { + // Setting the series and corresponding labels to initially display on the chart + series: [ + { + id: "series-1", + label: "Beer Tasting", + selectedSeriesId: "series-1", + }, + { + id: "series-2", + label: "Reading", + selectedSeriesId: "series-2", + }, + ] as ITimeseriesItemConfiguration[], + }, + } as Partial, + }, + properties: { + // Setting the general chart configuration + configuration: { + legendPlacement: LegendPlacement.Right, + enableZoom: true, + // Setting the preset to stacked area + preset: TimeseriesChartPreset.StackedArea, + leftAxisLabel: "Solarians (%)", + // You can optionally define custom colors for the chart by setting the 'chartColors' configuration property + // "chartColors": [ + // "var(--nui-color-chart-eight)", + // "var(--nui-color-chart-nine)", + // "var(--nui-color-chart-ten)", + // ], + } as ITimeseriesWidgetConfig, + }, + }, + timeframeSelection: { + properties: { + // Setting the initial timeframe selected in the timeframe bar + timeframe: { + selectedPresetId: "last7Days", + } as ISerializableTimeframe, + minDate: moment().subtract(60, "days").format(), + maxDate: moment().format(), + }, + }, + }, + }, + }, + { + id: "stackedPercentageAreaWidgetId", + type: "timeseries", + pizzagna: { + [PizzagnaLayer.Configuration]: { + [DEFAULT_PIZZAGNA_ROOT]: { + providers: { + [WellKnownProviders.DataSource]: { + // Setting the initially selected data source providerId + providerId: BeerVsReadingMockDataSource.providerId, + } as IProviderConfiguration, + }, + }, + header: { + properties: { + title: "Stacked Percentage Area Chart", + subtitle: "Survey of 1000 Solarians", + }, + }, + chart: { + providers: { + [WellKnownProviders.Adapter]: { + properties: { + // Setting the series and corresponding labels to initially display on the chart + series: [ + { + id: "series-1", + label: "Beer Tasting", + selectedSeriesId: "series-1", + }, + { + id: "series-2", + label: "Reading", + selectedSeriesId: "series-2", + }, + ] as ITimeseriesItemConfiguration[], + }, + } as Partial, + }, + properties: { + // Setting the general chart configuration + configuration: { + legendPlacement: LegendPlacement.Right, + enableZoom: true, + // Setting the preset to stacked percentage area + preset: TimeseriesChartPreset.StackedPercentageArea, + leftAxisLabel: "Solarians (%)", + // You can optionally define custom colors for the chart by setting the 'chartColors' configuration property + // "chartColors": [ + // "var(--nui-color-chart-eight)", + // "var(--nui-color-chart-nine)", + // "var(--nui-color-chart-ten)", + // ], + } as ITimeseriesWidgetConfig, + }, + }, + timeframeSelection: { + properties: { + // Setting the initial timeframe selected in the timeframe bar + timeframe: { + selectedPresetId: "last7Days", + } as ISerializableTimeframe, + minDate: moment().subtract(60, "days").format(), + maxDate: moment().format(), + }, + }, + }, + }, + }, + { + id: "stackedBarWidgetId", + type: "timeseries", + pizzagna: { + [PizzagnaLayer.Configuration]: { + [DEFAULT_PIZZAGNA_ROOT]: { + providers: { + [WellKnownProviders.DataSource]: { + // Setting the initially selected data source providerId + providerId: BeerVsReadingMockDataSource.providerId, + } as IProviderConfiguration, + }, + }, + header: { + properties: { + title: "Stacked Bar Chart", + subtitle: "Survey of 1000 Solarians", + }, + }, + chart: { + providers: { + [WellKnownProviders.Adapter]: { + properties: { + // Setting the series and corresponding labels to initially display on the chart + series: [ + { + id: "series-1", + label: "Beer Tasting", + selectedSeriesId: "series-1", + }, + { + id: "series-2", + label: "Reading", + selectedSeriesId: "series-2", + }, + ] as ITimeseriesItemConfiguration[], + }, + } as Partial, + }, + properties: { + configuration: { + legendPlacement: LegendPlacement.Right, + enableZoom: true, + leftAxisLabel: "Solarians (%)", + // Setting the preset to stacked bar + preset: TimeseriesChartPreset.StackedBar, + scales: { + x: { + type: TimeseriesScaleType.TimeInterval, + properties: { + interval: 24 * 60 * 60, + }, + } as ITimeseriesScaleConfig, + }, + } as ITimeseriesWidgetConfig, + }, + }, + timeframeSelection: { + properties: { + // Setting the initial timeframe selected in the timeframe bar + timeframe: { + selectedPresetId: "last7Days", + } as ISerializableTimeframe, + minDate: moment().subtract(60, "days").format(), + maxDate: moment().format(), + }, + }, + }, + }, + }, +]; + +// using startOf("day") so that each band for the bar chart corresponds to a calendar day +const now = moment().startOf("day"); + +export const getData = (): ITimeseriesWidgetData[] => [ + { + id: "series-1", + name: "Beer Tasting", + description: "Havin' some suds", + data: [ + { x: now.clone().subtract(20, "day").toDate(), y: 30 }, + { x: now.clone().subtract(19, "day").toDate(), y: 35 }, + { x: now.clone().subtract(18, "day").toDate(), y: 33 }, + { x: now.clone().subtract(17, "day").toDate(), y: 40 }, + { x: now.clone().subtract(16, "day").toDate(), y: 35 }, + { x: now.clone().subtract(15, "day").toDate(), y: 30 }, + { x: now.clone().subtract(14, "day").toDate(), y: 35 }, + { x: now.clone().subtract(13, "day").toDate(), y: 15 }, + { x: now.clone().subtract(12, "day").toDate(), y: 30 }, + { x: now.clone().subtract(11, "day").toDate(), y: 45 }, + { x: now.clone().subtract(10, "day").toDate(), y: 60 }, + { x: now.clone().subtract(9, "day").toDate(), y: 54 }, + { x: now.clone().subtract(8, "day").toDate(), y: 42 }, + { x: now.clone().subtract(7, "day").toDate(), y: 44 }, + { x: now.clone().subtract(6, "day").toDate(), y: 54 }, + { x: now.clone().subtract(5, "day").toDate(), y: 43 }, + { x: now.clone().subtract(4, "day").toDate(), y: 76 }, + { x: now.clone().subtract(3, "day").toDate(), y: 54 }, + { x: now.clone().subtract(2, "day").toDate(), y: 42 }, + { x: now.clone().subtract(1, "day").toDate(), y: 34 }, + ], + }, + { + id: "series-2", + name: "Reading", + description: "Hittin' the books", + data: [ + { x: now.clone().subtract(20, "day").toDate(), y: 60 }, + { x: now.clone().subtract(19, "day").toDate(), y: 64 }, + { x: now.clone().subtract(18, "day").toDate(), y: 70 }, + { x: now.clone().subtract(17, "day").toDate(), y: 55 }, + { x: now.clone().subtract(16, "day").toDate(), y: 55 }, + { x: now.clone().subtract(15, "day").toDate(), y: 45 }, + { x: now.clone().subtract(14, "day").toDate(), y: 60 }, + { x: now.clone().subtract(13, "day").toDate(), y: 65 }, + { x: now.clone().subtract(12, "day").toDate(), y: 63 }, + { x: now.clone().subtract(11, "day").toDate(), y: 60 }, + { x: now.clone().subtract(10, "day").toDate(), y: 61 }, + { x: now.clone().subtract(9, "day").toDate(), y: 65 }, + { x: now.clone().subtract(8, "day").toDate(), y: 63 }, + { x: now.clone().subtract(7, "day").toDate(), y: 58 }, + { x: now.clone().subtract(6, "day").toDate(), y: 64 }, + { x: now.clone().subtract(5, "day").toDate(), y: 63 }, + { x: now.clone().subtract(4, "day").toDate(), y: 60 }, + { x: now.clone().subtract(3, "day").toDate(), y: 62 }, + { x: now.clone().subtract(2, "day").toDate(), y: 61 }, + { x: now.clone().subtract(1, "day").toDate(), y: 62 }, + ], + }, +]; + +// Setting the widget dimensions and position (this is for gridster) +const positions: Record = { + [widgetConfigs[0].id]: { + cols: 6, + rows: 6, + y: 0, + x: 0, + }, + [widgetConfigs[1].id]: { + cols: 6, + rows: 6, + y: 0, + x: 6, + }, + [widgetConfigs[3].id]: { + cols: 6, + rows: 6, + y: 6, + x: 0, + }, + [widgetConfigs[2].id]: { + cols: 6, + rows: 6, + y: 6, + x: 6, + }, +}; +\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\`, + "widget-types/timeseries/timeseries-widget-interactive-example/timeseries-widget-interactive-example.component.ts": \\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\`// © 2022 SolarWinds Worldwide, LLC. All rights reserved. +// +// Permission is hereby granted, free of charge, to any person obtaining a copy +// of this software and associated documentation files (the "Software"), to +// deal in the Software without restriction, including without limitation the +// rights to use, copy, modify, merge, publish, distribute, sublicense, and/or +// sell copies of the Software, and to permit persons to whom the Software is +// furnished to do so, subject to the following conditions: +// +// The above copyright notice and this permission notice shall be included in +// all copies or substantial portions of the Software. +// +// THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR +// IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, +// FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE +// AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER +// LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, +// OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN +// THE SOFTWARE. + +import { + ChangeDetectorRef, + Component, + Injectable, + OnInit, +} from "@angular/core"; +import { GridsterConfig, GridsterItem } from "angular-gridster2"; +import cloneDeep from "lodash/cloneDeep"; +import keyBy from "lodash/keyBy"; +import moment, { Moment } from "moment/moment"; +import { BehaviorSubject } from "rxjs"; + +import { + DataSourceService, + IDataSource, + INovaFilters, + ITimeframe, +} from "@nova-ui/bits"; +import { + DATA_SOURCE, + DEFAULT_PIZZAGNA_ROOT, + IDashboard, + IDataSourceOutput, + IProviderConfiguration, + ISerializableTimeframe, + ITimeseriesItemConfiguration, + ITimeseriesOutput, + ITimeseriesScaleConfig, + ITimeseriesWidgetConfig, + ITimeseriesWidgetData, + ITimeseriesWidgetSeriesData, + IWidget, + NOVA_URL_INTERACTION_HANDLER, + LegendPlacement, + PizzagnaLayer, + ProviderRegistryService, + TimeseriesChartPreset, + TimeseriesScaleType, + WellKnownPathKey, + WellKnownProviders, + WidgetTypesService, +} from "@nova-ui/dashboards"; + +/** + * A simple Timeseries data source implementation + */ +@Injectable() +export class TimeseriesMockDataSource + extends DataSourceService + implements IDataSource +{ + public static providerId = "TimeseriesMockDataSource"; + + public busy = new BehaviorSubject(false); + + public async getFilteredData( + filters: INovaFilters + ): Promise> { + // In this example we're using some static mock data located at the bottom of this file. In a real-world + // scenario, the data for the chart would likely be retrieved via an asynchronous backend call. + let filteredData = getData(); + + this.busy.next(true); + + // Filtering using the filter registered by the TimeFrameBar + const timeframeFilter = filters.timeframe?.value as ITimeframe; + if (timeframeFilter) { + filteredData = filteredData.map((item: ITimeseriesWidgetData) => ({ + id: item.id, + name: item.name, + description: item.description, + // the filtered data should return the provided links if they are set. + link: item?.link, + secondaryLink: item?.secondaryLink, + data: item.data.filter( + (seriesData: ITimeseriesWidgetSeriesData) => + filterDates( + seriesData.x, + timeframeFilter.startDatetime, + timeframeFilter.endDatetime + ) + ), + })); + } + + this.busy.next(false); + + return { result: { series: filteredData } }; + } +} + +function filterDates(dateToCheck: Date, startDate: Moment, endDate: Moment) { + const mom = moment(dateToCheck); + return ( + mom.isBetween(startDate, endDate) || + mom.isSame(startDate) || + mom.isSame(endDate) + ); +} + +/** + * A component that instantiates the dashboard + */ +@Component({ + selector: "timeseries-widget-interactive-example", + templateUrl: "./timeseries-widget-interactive-example.component.html", + styleUrls: ["./timeseries-widget-interactive-example.component.less"], + standalone: false, +}) +export class TimeseriesWidgetInteractiveExampleComponent implements OnInit { + // This variable will hold all the data needed to define the layout and behavior of the widgets. + // Pass this to the dashboard component's dashboard input in the template. + public dashboard: IDashboard | undefined; + + // Angular gridster requires a configuration object even if it's empty. + // Pass this to the dashboard component's gridsterConfig input in the template. + public gridsterConfig: GridsterConfig = {}; + + // Boolean passed as an input to the dashboard. When true, widgets can be moved, resized, removed, or edited + public editMode: boolean = false; + + constructor( + // WidgetTypesService provides the widget's necessary structure information + private widgetTypesService: WidgetTypesService, + + // In general, the ProviderRegistryService is used for making entities available for injection into dynamically loaded components. + private providerRegistry: ProviderRegistryService, + + // Angular's ChangeDetectorRef + private changeDetectorRef: ChangeDetectorRef + ) {} + + public ngOnInit(): void { + // Grabbing the widget's default template which will be needed as a parameter for setNode + const widgetTemplate = this.widgetTypesService.getWidgetType( + "timeseries", + 1 + ); + // Registering our data sources as dropdown options in the widget editor/configurator + // Note: This could also be done in the parent module's constructor so that + // multiple dashboards could have access to the same widget template modification. + this.widgetTypesService.setNode( + // This is the template we grabbed above with getWidgetType + widgetTemplate, + // We are setting the editor/configurator part of the widget template + "configurator", + // This indicates which node you are changing and we want to change + // the data source providers available for selection in the editor. + WellKnownPathKey.DataSourceProviders, + // We are setting the data sources available for selection in the editor + [TimeseriesMockDataSource.providerId] + ); + + // Registering the data source for injection into the widget. + this.providerRegistry.setProviders({ + [TimeseriesMockDataSource.providerId]: { + provide: DATA_SOURCE, + useClass: TimeseriesMockDataSource, + deps: [], + }, + }); + + this.initializeDashboard(); + } + + public initializeDashboard(): void { + // We're using a static configuration object for this example, but this is where + // the widget's configuration could potentially be populated from a database + const widgetsWithStructure = widgetConfigs.map((w) => + this.widgetTypesService.mergeWithWidgetType(w) + ); + const widgetsIndex = keyBy(widgetsWithStructure, (w: IWidget) => w.id); + + // Finally, assigning the variables we created above to the dashboard + this.dashboard = { + positions: cloneDeep(positions), + widgets: widgetsIndex, + }; + } + + /** Used for restoring widgets state */ + public reInitializeDashboard(): void { + // destroys the components and their providers so the dashboard can re init data + this.dashboard = undefined; + this.changeDetectorRef.detectChanges(); + + this.initializeDashboard(); + } +} + +const widgetConfigs: IWidget[] = [ + { + id: "lineWidgetId", + type: "timeseries", + pizzagna: { + [PizzagnaLayer.Configuration]: { + [DEFAULT_PIZZAGNA_ROOT]: { + providers: { + [WellKnownProviders.DataSource]: { + // Setting the initially selected data source providerId + providerId: TimeseriesMockDataSource.providerId, + } as IProviderConfiguration, + [WellKnownProviders.InteractionHandler]: { + // Setting the UrlInteractionHandler as an interactionHandler + providerId: NOVA_URL_INTERACTION_HANDLER, + properties: { + // the 'url' property tells the handler what link to use when interaction occurs on the series + url: "\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\${data.link || 'https://en.wikipedia.org/wiki/'+data.legendDescriptionPrimary}", + // by default the link is opened in the current window, set 'newWindow' to true to open in a new tab instead + // newWindow: true, + }, + }, + }, + }, + header: { + properties: { + title: "Line Chart", + subtitle: "Basic Timeseries with Interaction", + }, + }, + chart: { + providers: { + [WellKnownProviders.Adapter]: { + properties: { + // Setting the series and corresponding labels to initially display on the chart + series: [ + { + id: "series-1", + label: "Nur-Sultan", + selectedSeriesId: "series-1", + }, + { + id: "series-2", + label: "Brno", + selectedSeriesId: "series-2", + }, + { + id: "series-3", + label: "Lisbon", + selectedSeriesId: "series-3", + }, + { + id: "series-4", + label: "Austin", + selectedSeriesId: "series-4", + }, + ] as ITimeseriesItemConfiguration[], + }, + } as Partial, + }, + properties: { + // Setting the general chart configuration + configuration: { + // setting interaction to 'series' will make all series in the chart interactable + interaction: "series", + legendPlacement: LegendPlacement.Right, + enableZoom: true, + } as ITimeseriesWidgetConfig, + }, + }, + timeframeSelection: { + properties: { + timeframe: { + selectedPresetId: "last7Days", + } as ISerializableTimeframe, + minDate: moment().subtract(60, "days").format(), + maxDate: moment().format(), + }, + }, + }, + }, + }, + { + id: "stackedBarWidgetId", + type: "timeseries", + pizzagna: { + [PizzagnaLayer.Configuration]: { + [DEFAULT_PIZZAGNA_ROOT]: { + providers: { + [WellKnownProviders.DataSource]: { + // Setting the initially selected data source providerId + providerId: TimeseriesMockDataSource.providerId, + } as IProviderConfiguration, + }, + }, + header: { + properties: { + title: "Stacked Bar Chart", + subtitle: + "Basic Timeseries without Interaction Handler", + }, + }, + chart: { + providers: { + [WellKnownProviders.Adapter]: { + properties: { + // Setting the series and corresponding labels to initially display on the chart + series: [ + { + id: "series-1", + label: "Nur-Sultan", + selectedSeriesId: "series-1", + }, + { + id: "series-2", + label: "Brno", + selectedSeriesId: "series-2", + }, + { + id: "series-3", + label: "Lisbon", + selectedSeriesId: "series-3", + }, + { + id: "series-4", + label: "Austin", + selectedSeriesId: "series-4", + }, + ] as ITimeseriesItemConfiguration[], + }, + } as Partial, + }, + properties: { + // Setting the general chart configuration + configuration: { + legendPlacement: LegendPlacement.Right, + enableZoom: true, + // Setting the preset to stacked bar + preset: TimeseriesChartPreset.StackedBar, + scales: { + x: { + type: TimeseriesScaleType.TimeInterval, + properties: { + interval: 24 * 60 * 60, + }, + } as ITimeseriesScaleConfig, + }, + } as ITimeseriesWidgetConfig, + }, + }, + timeframeSelection: { + properties: { + // Setting the initial timeframe selected in the timeframe bar + timeframe: { + selectedPresetId: "last7Days", + } as ISerializableTimeframe, + minDate: moment().subtract(60, "days").format(), + maxDate: moment().format(), + }, + }, + }, + }, + }, +]; + +// using startOf("day") so that each band for the bar chart corresponds to a calendar day +const startOfToday = moment().startOf("day").toDate(); + +export const getData = (): ITimeseriesWidgetData[] => [ + { + id: "series-1", + name: "Nur-Sultan", + description: "'link' only", + link: "https://en.wikipedia.org/wiki/Nur-Sultan", + data: [ + { x: moment(startOfToday).subtract(59, "day").toDate(), y: 35 }, + { x: moment(startOfToday).subtract(58, "day").toDate(), y: 33 }, + { x: moment(startOfToday).subtract(57, "day").toDate(), y: 40 }, + { x: moment(startOfToday).subtract(56, "day").toDate(), y: 35 }, + { x: moment(startOfToday).subtract(55, "day").toDate(), y: 30 }, + { x: moment(startOfToday).subtract(54, "day").toDate(), y: 35 }, + { x: moment(startOfToday).subtract(53, "day").toDate(), y: 15 }, + { x: moment(startOfToday).subtract(52, "day").toDate(), y: 30 }, + { x: moment(startOfToday).subtract(51, "day").toDate(), y: 35 }, + { x: moment(startOfToday).subtract(50, "day").toDate(), y: 34 }, + { x: moment(startOfToday).subtract(49, "day").toDate(), y: 35 }, + { x: moment(startOfToday).subtract(48, "day").toDate(), y: 33 }, + { x: moment(startOfToday).subtract(47, "day").toDate(), y: 40 }, + { x: moment(startOfToday).subtract(46, "day").toDate(), y: 35 }, + { x: moment(startOfToday).subtract(45, "day").toDate(), y: 30 }, + { x: moment(startOfToday).subtract(44, "day").toDate(), y: 35 }, + { x: moment(startOfToday).subtract(43, "day").toDate(), y: 15 }, + { x: moment(startOfToday).subtract(42, "day").toDate(), y: 30 }, + { x: moment(startOfToday).subtract(41, "day").toDate(), y: 35 }, + { x: moment(startOfToday).subtract(40, "day").toDate(), y: 34 }, + { x: moment(startOfToday).subtract(39, "day").toDate(), y: 35 }, + { x: moment(startOfToday).subtract(38, "day").toDate(), y: 33 }, + { x: moment(startOfToday).subtract(37, "day").toDate(), y: 40 }, + { x: moment(startOfToday).subtract(36, "day").toDate(), y: 35 }, + { x: moment(startOfToday).subtract(35, "day").toDate(), y: 30 }, + { x: moment(startOfToday).subtract(34, "day").toDate(), y: 35 }, + { x: moment(startOfToday).subtract(33, "day").toDate(), y: 15 }, + { x: moment(startOfToday).subtract(32, "day").toDate(), y: 30 }, + { x: moment(startOfToday).subtract(31, "day").toDate(), y: 35 }, + { x: moment(startOfToday).subtract(30, "day").toDate(), y: 34 }, + { x: moment(startOfToday).subtract(29, "day").toDate(), y: 35 }, + { x: moment(startOfToday).subtract(28, "day").toDate(), y: 33 }, + { x: moment(startOfToday).subtract(27, "day").toDate(), y: 40 }, + { x: moment(startOfToday).subtract(26, "day").toDate(), y: 35 }, + { x: moment(startOfToday).subtract(25, "day").toDate(), y: 30 }, + { x: moment(startOfToday).subtract(24, "day").toDate(), y: 35 }, + { x: moment(startOfToday).subtract(23, "day").toDate(), y: 15 }, + { x: moment(startOfToday).subtract(22, "day").toDate(), y: 30 }, + { x: moment(startOfToday).subtract(21, "day").toDate(), y: 35 }, + { x: moment(startOfToday).subtract(20, "day").toDate(), y: 34 }, + { x: moment(startOfToday).subtract(19, "day").toDate(), y: 35 }, + { x: moment(startOfToday).subtract(18, "day").toDate(), y: 33 }, + { x: moment(startOfToday).subtract(17, "day").toDate(), y: 40 }, + { x: moment(startOfToday).subtract(16, "day").toDate(), y: 35 }, + { x: moment(startOfToday).subtract(15, "day").toDate(), y: 30 }, + { x: moment(startOfToday).subtract(14, "day").toDate(), y: 35 }, + { x: moment(startOfToday).subtract(13, "day").toDate(), y: 15 }, + { x: moment(startOfToday).subtract(12, "day").toDate(), y: 30 }, + { x: moment(startOfToday).subtract(11, "day").toDate(), y: 35 }, + { x: moment(startOfToday).subtract(10, "day").toDate(), y: 34 }, + { x: moment(startOfToday).subtract(9, "day").toDate(), y: 33 }, + { x: moment(startOfToday).subtract(8, "day").toDate(), y: 35 }, + { x: moment(startOfToday).subtract(7, "day").toDate(), y: 36 }, + { x: moment(startOfToday).subtract(6, "day").toDate(), y: 34 }, + { x: moment(startOfToday).subtract(5, "day").toDate(), y: 33 }, + { x: moment(startOfToday).subtract(4, "day").toDate(), y: 30 }, + { x: moment(startOfToday).subtract(3, "day").toDate(), y: 32 }, + { x: moment(startOfToday).subtract(2, "day").toDate(), y: 31 }, + { x: moment(startOfToday).subtract(1, "day").toDate(), y: 34 }, + { x: moment(startOfToday).toDate(), y: 25 }, + ], + }, + { + id: "series-2", + name: "Brno", + description: "'link' and 'secondaryLink'", + link: "https://en.wikipedia.org/wiki/Brno", + secondaryLink: "https://en.wikipedia.org/wiki/Europe", + data: [ + { x: moment(startOfToday).subtract(59, "day").toDate(), y: 35 }, + { x: moment(startOfToday).subtract(58, "day").toDate(), y: 33 }, + { x: moment(startOfToday).subtract(57, "day").toDate(), y: 40 }, + { x: moment(startOfToday).subtract(56, "day").toDate(), y: 35 }, + { x: moment(startOfToday).subtract(55, "day").toDate(), y: 30 }, + { x: moment(startOfToday).subtract(54, "day").toDate(), y: 35 }, + { x: moment(startOfToday).subtract(53, "day").toDate(), y: 15 }, + { x: moment(startOfToday).subtract(52, "day").toDate(), y: 30 }, + { x: moment(startOfToday).subtract(51, "day").toDate(), y: 35 }, + { x: moment(startOfToday).subtract(50, "day").toDate(), y: 34 }, + { x: moment(startOfToday).subtract(49, "day").toDate(), y: 35 }, + { x: moment(startOfToday).subtract(48, "day").toDate(), y: 33 }, + { x: moment(startOfToday).subtract(47, "day").toDate(), y: 40 }, + { x: moment(startOfToday).subtract(46, "day").toDate(), y: 35 }, + { x: moment(startOfToday).subtract(45, "day").toDate(), y: 30 }, + { x: moment(startOfToday).subtract(44, "day").toDate(), y: 35 }, + { x: moment(startOfToday).subtract(43, "day").toDate(), y: 15 }, + { x: moment(startOfToday).subtract(42, "day").toDate(), y: 30 }, + { x: moment(startOfToday).subtract(41, "day").toDate(), y: 35 }, + { x: moment(startOfToday).subtract(40, "day").toDate(), y: 34 }, + { x: moment(startOfToday).subtract(39, "day").toDate(), y: 35 }, + { x: moment(startOfToday).subtract(38, "day").toDate(), y: 33 }, + { x: moment(startOfToday).subtract(37, "day").toDate(), y: 40 }, + { x: moment(startOfToday).subtract(36, "day").toDate(), y: 35 }, + { x: moment(startOfToday).subtract(35, "day").toDate(), y: 30 }, + { x: moment(startOfToday).subtract(34, "day").toDate(), y: 35 }, + { x: moment(startOfToday).subtract(33, "day").toDate(), y: 15 }, + { x: moment(startOfToday).subtract(32, "day").toDate(), y: 30 }, + { x: moment(startOfToday).subtract(31, "day").toDate(), y: 35 }, + { x: moment(startOfToday).subtract(30, "day").toDate(), y: 34 }, + { x: moment(startOfToday).subtract(29, "day").toDate(), y: 35 }, + { x: moment(startOfToday).subtract(28, "day").toDate(), y: 33 }, + { x: moment(startOfToday).subtract(27, "day").toDate(), y: 40 }, + { x: moment(startOfToday).subtract(26, "day").toDate(), y: 35 }, + { x: moment(startOfToday).subtract(25, "day").toDate(), y: 30 }, + { x: moment(startOfToday).subtract(24, "day").toDate(), y: 35 }, + { x: moment(startOfToday).subtract(23, "day").toDate(), y: 15 }, + { x: moment(startOfToday).subtract(22, "day").toDate(), y: 30 }, + { x: moment(startOfToday).subtract(21, "day").toDate(), y: 35 }, + { x: moment(startOfToday).subtract(20, "day").toDate(), y: 34 }, + { x: moment(startOfToday).subtract(19, "day").toDate(), y: 64 }, + { x: moment(startOfToday).subtract(18, "day").toDate(), y: 70 }, + { x: moment(startOfToday).subtract(17, "day").toDate(), y: 55 }, + { x: moment(startOfToday).subtract(16, "day").toDate(), y: 55 }, + { x: moment(startOfToday).subtract(15, "day").toDate(), y: 45 }, + { x: moment(startOfToday).subtract(14, "day").toDate(), y: 10 }, + { x: moment(startOfToday).subtract(13, "day").toDate(), y: 65 }, + { x: moment(startOfToday).subtract(12, "day").toDate(), y: 35 }, + { x: moment(startOfToday).subtract(11, "day").toDate(), y: 60 }, + { x: moment(startOfToday).subtract(10, "day").toDate(), y: 61 }, + { x: moment(startOfToday).subtract(9, "day").toDate(), y: 65 }, + { x: moment(startOfToday).subtract(8, "day").toDate(), y: 63 }, + { x: moment(startOfToday).subtract(7, "day").toDate(), y: 58 }, + { x: moment(startOfToday).subtract(6, "day").toDate(), y: 64 }, + { x: moment(startOfToday).subtract(5, "day").toDate(), y: 63 }, + { x: moment(startOfToday).subtract(4, "day").toDate(), y: 60 }, + { x: moment(startOfToday).subtract(3, "day").toDate(), y: 62 }, + { x: moment(startOfToday).subtract(2, "day").toDate(), y: 61 }, + { x: moment(startOfToday).subtract(1, "day").toDate(), y: 62 }, + { x: moment(startOfToday).toDate(), y: 55 }, + ], + }, + { + id: "series-3", + name: "Lisbon", + description: "'secondaryLink' only", + secondaryLink: "https://en.wikipedia.org/wiki/Lisbon", + data: [ + { x: moment(startOfToday).subtract(59, "day").toDate(), y: 35 }, + { x: moment(startOfToday).subtract(58, "day").toDate(), y: 33 }, + { x: moment(startOfToday).subtract(57, "day").toDate(), y: 40 }, + { x: moment(startOfToday).subtract(56, "day").toDate(), y: 35 }, + { x: moment(startOfToday).subtract(55, "day").toDate(), y: 30 }, + { x: moment(startOfToday).subtract(54, "day").toDate(), y: 35 }, + { x: moment(startOfToday).subtract(53, "day").toDate(), y: 15 }, + { x: moment(startOfToday).subtract(52, "day").toDate(), y: 30 }, + { x: moment(startOfToday).subtract(51, "day").toDate(), y: 35 }, + { x: moment(startOfToday).subtract(50, "day").toDate(), y: 34 }, + { x: moment(startOfToday).subtract(49, "day").toDate(), y: 35 }, + { x: moment(startOfToday).subtract(48, "day").toDate(), y: 33 }, + { x: moment(startOfToday).subtract(47, "day").toDate(), y: 40 }, + { x: moment(startOfToday).subtract(46, "day").toDate(), y: 35 }, + { x: moment(startOfToday).subtract(45, "day").toDate(), y: 30 }, + { x: moment(startOfToday).subtract(44, "day").toDate(), y: 35 }, + { x: moment(startOfToday).subtract(43, "day").toDate(), y: 15 }, + { x: moment(startOfToday).subtract(42, "day").toDate(), y: 30 }, + { x: moment(startOfToday).subtract(41, "day").toDate(), y: 35 }, + { x: moment(startOfToday).subtract(40, "day").toDate(), y: 34 }, + { x: moment(startOfToday).subtract(39, "day").toDate(), y: 35 }, + { x: moment(startOfToday).subtract(38, "day").toDate(), y: 33 }, + { x: moment(startOfToday).subtract(37, "day").toDate(), y: 40 }, + { x: moment(startOfToday).subtract(36, "day").toDate(), y: 35 }, + { x: moment(startOfToday).subtract(35, "day").toDate(), y: 30 }, + { x: moment(startOfToday).subtract(34, "day").toDate(), y: 35 }, + { x: moment(startOfToday).subtract(33, "day").toDate(), y: 15 }, + { x: moment(startOfToday).subtract(32, "day").toDate(), y: 30 }, + { x: moment(startOfToday).subtract(31, "day").toDate(), y: 35 }, + { x: moment(startOfToday).subtract(30, "day").toDate(), y: 34 }, + { x: moment(startOfToday).subtract(29, "day").toDate(), y: 35 }, + { x: moment(startOfToday).subtract(28, "day").toDate(), y: 33 }, + { x: moment(startOfToday).subtract(27, "day").toDate(), y: 40 }, + { x: moment(startOfToday).subtract(26, "day").toDate(), y: 35 }, + { x: moment(startOfToday).subtract(25, "day").toDate(), y: 30 }, + { x: moment(startOfToday).subtract(24, "day").toDate(), y: 35 }, + { x: moment(startOfToday).subtract(23, "day").toDate(), y: 15 }, + { x: moment(startOfToday).subtract(22, "day").toDate(), y: 30 }, + { x: moment(startOfToday).subtract(21, "day").toDate(), y: 35 }, + { x: moment(startOfToday).subtract(20, "day").toDate(), y: 34 }, + { x: moment(startOfToday).subtract(19, "day").toDate(), y: 80 }, + { x: moment(startOfToday).subtract(18, "day").toDate(), y: 70 }, + { x: moment(startOfToday).subtract(17, "day").toDate(), y: 95 }, + { x: moment(startOfToday).subtract(16, "day").toDate(), y: 90 }, + { x: moment(startOfToday).subtract(15, "day").toDate(), y: 85 }, + { x: moment(startOfToday).subtract(14, "day").toDate(), y: 70 }, + { x: moment(startOfToday).subtract(13, "day").toDate(), y: 75 }, + { x: moment(startOfToday).subtract(12, "day").toDate(), y: 69 }, + { x: moment(startOfToday).subtract(11, "day").toDate(), y: 75 }, + { x: moment(startOfToday).subtract(10, "day").toDate(), y: 81 }, + { x: moment(startOfToday).subtract(9, "day").toDate(), y: 93 }, + { x: moment(startOfToday).subtract(8, "day").toDate(), y: 83 }, + { x: moment(startOfToday).subtract(7, "day").toDate(), y: 70 }, + { x: moment(startOfToday).subtract(6, "day").toDate(), y: 74 }, + { x: moment(startOfToday).subtract(5, "day").toDate(), y: 73 }, + { x: moment(startOfToday).subtract(4, "day").toDate(), y: 68 }, + { x: moment(startOfToday).subtract(3, "day").toDate(), y: 72 }, + { x: moment(startOfToday).subtract(2, "day").toDate(), y: 61 }, + { x: moment(startOfToday).subtract(1, "day").toDate(), y: 69 }, + { x: moment(startOfToday).toDate(), y: 60 }, + ], + }, + { + id: "series-4", + name: "Austin", + description: "No links", + data: [ + { x: moment(startOfToday).subtract(59, "day").toDate(), y: 25 }, + { x: moment(startOfToday).subtract(58, "day").toDate(), y: 43 }, + { x: moment(startOfToday).subtract(57, "day").toDate(), y: 40 }, + { x: moment(startOfToday).subtract(56, "day").toDate(), y: 65 }, + { x: moment(startOfToday).subtract(55, "day").toDate(), y: 30 }, + { x: moment(startOfToday).subtract(54, "day").toDate(), y: 25 }, + { x: moment(startOfToday).subtract(53, "day").toDate(), y: 45 }, + { x: moment(startOfToday).subtract(52, "day").toDate(), y: 30 }, + { x: moment(startOfToday).subtract(51, "day").toDate(), y: 85 }, + { x: moment(startOfToday).subtract(50, "day").toDate(), y: 74 }, + { x: moment(startOfToday).subtract(49, "day").toDate(), y: 55 }, + { x: moment(startOfToday).subtract(48, "day").toDate(), y: 23 }, + { x: moment(startOfToday).subtract(47, "day").toDate(), y: 40 }, + { x: moment(startOfToday).subtract(46, "day").toDate(), y: 15 }, + { x: moment(startOfToday).subtract(45, "day").toDate(), y: 20 }, + { x: moment(startOfToday).subtract(44, "day").toDate(), y: 65 }, + { x: moment(startOfToday).subtract(43, "day").toDate(), y: 25 }, + { x: moment(startOfToday).subtract(42, "day").toDate(), y: 40 }, + { x: moment(startOfToday).subtract(41, "day").toDate(), y: 25 }, + { x: moment(startOfToday).subtract(40, "day").toDate(), y: 54 }, + { x: moment(startOfToday).subtract(39, "day").toDate(), y: 65 }, + { x: moment(startOfToday).subtract(38, "day").toDate(), y: 33 }, + { x: moment(startOfToday).subtract(37, "day").toDate(), y: 50 }, + { x: moment(startOfToday).subtract(36, "day").toDate(), y: 45 }, + { x: moment(startOfToday).subtract(35, "day").toDate(), y: 20 }, + { x: moment(startOfToday).subtract(34, "day").toDate(), y: 25 }, + { x: moment(startOfToday).subtract(33, "day").toDate(), y: 35 }, + { x: moment(startOfToday).subtract(32, "day").toDate(), y: 20 }, + { x: moment(startOfToday).subtract(31, "day").toDate(), y: 35 }, + { x: moment(startOfToday).subtract(30, "day").toDate(), y: 14 }, + { x: moment(startOfToday).subtract(29, "day").toDate(), y: 55 }, + { x: moment(startOfToday).subtract(28, "day").toDate(), y: 23 }, + { x: moment(startOfToday).subtract(27, "day").toDate(), y: 10 }, + { x: moment(startOfToday).subtract(26, "day").toDate(), y: 5 }, + { x: moment(startOfToday).subtract(25, "day").toDate(), y: 20 }, + { x: moment(startOfToday).subtract(24, "day").toDate(), y: 35 }, + { x: moment(startOfToday).subtract(23, "day").toDate(), y: 15 }, + { x: moment(startOfToday).subtract(22, "day").toDate(), y: 30 }, + { x: moment(startOfToday).subtract(21, "day").toDate(), y: 35 }, + { x: moment(startOfToday).subtract(20, "day").toDate(), y: 34 }, + { x: moment(startOfToday).subtract(19, "day").toDate(), y: 50 }, + { x: moment(startOfToday).subtract(18, "day").toDate(), y: 60 }, + { x: moment(startOfToday).subtract(17, "day").toDate(), y: 95 }, + { x: moment(startOfToday).subtract(16, "day").toDate(), y: 80 }, + { x: moment(startOfToday).subtract(15, "day").toDate(), y: 65 }, + { x: moment(startOfToday).subtract(14, "day").toDate(), y: 80 }, + { x: moment(startOfToday).subtract(13, "day").toDate(), y: 85 }, + { x: moment(startOfToday).subtract(12, "day").toDate(), y: 69 }, + { x: moment(startOfToday).subtract(11, "day").toDate(), y: 65 }, + { x: moment(startOfToday).subtract(10, "day").toDate(), y: 71 }, + { x: moment(startOfToday).subtract(9, "day").toDate(), y: 73 }, + { x: moment(startOfToday).subtract(8, "day").toDate(), y: 43 }, + { x: moment(startOfToday).subtract(7, "day").toDate(), y: 70 }, + { x: moment(startOfToday).subtract(6, "day").toDate(), y: 84 }, + { x: moment(startOfToday).subtract(5, "day").toDate(), y: 73 }, + { x: moment(startOfToday).subtract(4, "day").toDate(), y: 38 }, + { x: moment(startOfToday).subtract(3, "day").toDate(), y: 72 }, + { x: moment(startOfToday).subtract(2, "day").toDate(), y: 81 }, + { x: moment(startOfToday).subtract(1, "day").toDate(), y: 59 }, + { x: moment(startOfToday).toDate(), y: 60 }, + ], + }, +]; +// Setting the widget dimensions and position (this is for gridster) +const positions: Record = { + [widgetConfigs[0].id]: { + cols: 6, + rows: 6, + y: 0, + x: 0, + }, + [widgetConfigs[1].id]: { + cols: 6, + rows: 6, + y: 0, + x: 6, + }, +}; +\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\`, + "widget-types/timeseries/timeseries-widget-status-bar-example/timeseries-widget-status-bar-example.component.ts": \\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\`// © 2022 SolarWinds Worldwide, LLC. All rights reserved. +// +// Permission is hereby granted, free of charge, to any person obtaining a copy +// of this software and associated documentation files (the "Software"), to +// deal in the Software without restriction, including without limitation the +// rights to use, copy, modify, merge, publish, distribute, sublicense, and/or +// sell copies of the Software, and to permit persons to whom the Software is +// furnished to do so, subject to the following conditions: +// +// The above copyright notice and this permission notice shall be included in +// all copies or substantial portions of the Software. +// +// THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR +// IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, +// FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE +// AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER +// LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, +// OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN +// THE SOFTWARE. + +import { + ChangeDetectorRef, + Component, + Injectable, + OnInit, +} from "@angular/core"; +import { GridsterConfig, GridsterItem } from "angular-gridster2"; +import keyBy from "lodash/keyBy"; +import moment, { Moment } from "moment/moment"; +import { BehaviorSubject } from "rxjs"; + +import { + DataSourceService, + IDataSource, + IDataSourceOutput, + INovaFilters, + ITimeframe, +} from "@nova-ui/bits"; +import { CHART_PALETTE_CS_S_EXTENDED } from "@nova-ui/charts"; +import { + applyStatusEndpoints, + DATA_SOURCE, + DEFAULT_PIZZAGNA_ROOT, + IDashboard, + IProviderConfiguration, + ISerializableTimeframe, + ITimeseriesItemConfiguration, + ITimeseriesOutput, + ITimeseriesScaleConfig, + ITimeseriesWidgetConfig, + ITimeseriesWidgetData, + ITimeseriesWidgetSeriesData, + ITimeseriesWidgetStatusData, + IWidget, + LegendPlacement, + PizzagnaLayer, + ProviderRegistryService, + TimeseriesChartPreset, + TimeseriesScaleType, + WellKnownPathKey, + WellKnownProviders, + WidgetTypesService, +} from "@nova-ui/dashboards"; + +/** + * A simple Timeseries data source implementation with continuous (non-interval-based) output + */ +@Injectable() +export class TimeseriesStatusContinuousDataSource + extends DataSourceService + implements IDataSource> +{ + public static providerId = "TimeseriesStatusContinuousDataSource"; + + public busy = new BehaviorSubject(false); + + public async getFilteredData( + filters: INovaFilters + ): Promise< + IDataSourceOutput> + > { + // In this example we're using some static mock data located at the bottom of this file. In a real-world + // scenario, the data for the chart would likely be retrieved via an asynchronous backend call. + const data = getContinuousData(); + let filteredData = data; + + this.busy.next(true); + + // Filtering using the filter registered by the TimeFrameBar + const timeframeFilter = filters.timeframe?.value as ITimeframe; + if (timeframeFilter) { + filteredData = filteredData.map((item: ITimeseriesWidgetData) => ({ + id: item.id, + name: item.name, + description: item.description, + data: item.data.filter( + (seriesData: ITimeseriesWidgetSeriesData) => + filterDates( + seriesData.x, + timeframeFilter.startDatetime, + timeframeFilter.endDatetime + ) + ), + })); + + // apply endpoints on the filtered status data so that when the status chart is zoomed (filtered), + // each status visualizations is ensured to have valid start and end values + filteredData = applyStatusEndpoints( + timeframeFilter, + filteredData, + data + ); + } + + this.busy.next(false); + return { result: { series: filteredData } }; + } +} + +/** + * A simple Timeseries data source implementation with interval-based output + */ +@Injectable() +export class TimeseriesStatusIntervalDataSource + extends DataSourceService + implements IDataSource> +{ + public static providerId = "TimeseriesStatusIntervalDataSource"; + + public busy = new BehaviorSubject(false); + + public async getFilteredData( + filters: INovaFilters + ): Promise< + IDataSourceOutput> + > { + // In this example we're using some static mock data located at the bottom of this file. In a real-world + // scenario, the data for the chart would likely be retrieved via an asynchronous backend call. + const data = getIntervalData(); + let filteredData = data; + + this.busy.next(true); + + // Filtering using the filter registered by the TimeFrameBar + const timeframeFilter = filters.timeframe?.value as ITimeframe; + if (timeframeFilter) { + filteredData = filteredData.map((item: ITimeseriesWidgetData) => ({ + id: item.id, + name: item.name, + description: item.description, + data: item.data.filter( + (seriesData: ITimeseriesWidgetSeriesData) => + filterDates( + seriesData.x, + timeframeFilter.startDatetime, + timeframeFilter.endDatetime + ) + ), + })); + + // Note: There's no need to apply filter endpoints to the status data in this case since we know it's visualized in regular intervals + } + + this.busy.next(false); + return { result: { series: filteredData } }; + } +} + +function filterDates(dateToCheck: Date, startDate: Moment, endDate: Moment) { + const mom = moment(dateToCheck); + return ( + mom.isBetween(startDate, endDate) || + mom.isSame(startDate) || + mom.isSame(endDate) + ); +} + +/** + * A component that instantiates the dashboard + */ +@Component({ + selector: "timeseries-widget-status-bar-example", + templateUrl: "./timeseries-widget-status-bar-example.component.html", + styleUrls: ["./timeseries-widget-status-bar-example.component.less"], + standalone: false, +}) +export class TimeseriesWidgetStatusBarExampleComponent implements OnInit { + // This variable will hold all the data needed to define the layout and behavior of the widgets. + // Pass this to the dashboard component's dashboard input in the template. + public dashboard: IDashboard | undefined; + + // Angular gridster requires a configuration object even if it's empty. + // Pass this to the dashboard component's gridsterConfig input in the template. + public gridsterConfig: GridsterConfig = {}; + + // Boolean passed as an input to the dashboard. When true, widgets can be moved, resized, removed, or edited + public editMode: boolean = false; + + constructor( + // WidgetTypesService provides the widget's necessary structure information + private widgetTypesService: WidgetTypesService, + + // In general, the ProviderRegistryService is used for making entities available for injection into dynamically loaded components. + private providerRegistry: ProviderRegistryService, + private changeDetectorRef: ChangeDetectorRef + ) {} + + public ngOnInit(): void { + // Grabbing the widget's default template which will be needed as a parameter for setNode + const widgetTemplate = this.widgetTypesService.getWidgetType( + "timeseries", + 1 + ); + // Registering our data sources as dropdown options in the widget editor/configurator + // Note: This could also be done in the parent module's constructor so that + // multiple dashboards could have access to the same widget template modification. + this.widgetTypesService.setNode( + // This is the template we grabbed above with getWidgetType + widgetTemplate, + // We are setting the editor/configurator part of the widget template + "configurator", + // This indicates which node you are changing and we want to change + // the data source providers available for selection in the editor. + WellKnownPathKey.DataSourceProviders, + // We are setting the data sources available for selection in the editor + [ + TimeseriesStatusContinuousDataSource.providerId, + TimeseriesStatusIntervalDataSource.providerId, + ] + ); + + // Registering the data source for injection into the widget. + this.providerRegistry.setProviders({ + [TimeseriesStatusContinuousDataSource.providerId]: { + provide: DATA_SOURCE, + useClass: TimeseriesStatusContinuousDataSource, + deps: [], + }, + [TimeseriesStatusIntervalDataSource.providerId]: { + provide: DATA_SOURCE, + useClass: TimeseriesStatusIntervalDataSource, + deps: [], + }, + }); + + this.initializeDashboard(); + } + + /** Used for restoring widgets state */ + public reInitializeDashboard(): void { + // destroys the components and their providers so the dashboard can re init data + this.dashboard = undefined; + this.changeDetectorRef.detectChanges(); + + this.initializeDashboard(); + } + + public initializeDashboard(): void { + // We're using a static configuration object for this example, but this is where + // the widget's configuration could potentially be populated from a database + const widgetsWithStructure = widgetConfigs.map((w) => + this.widgetTypesService.mergeWithWidgetType(w) + ); + const widgetsIndex = keyBy(widgetsWithStructure, (w: IWidget) => w.id); + + // Finally, assigning the variables we created above to the dashboard + this.dashboard = { + positions, + widgets: widgetsIndex, + }; + } +} + +const widgetConfigs: IWidget[] = [ + { + id: "statusChartWidgetId", + type: "timeseries", + pizzagna: { + [PizzagnaLayer.Configuration]: { + [DEFAULT_PIZZAGNA_ROOT]: { + providers: { + [WellKnownProviders.DataSource]: { + // Setting the initially selected data source providerId + providerId: + TimeseriesStatusContinuousDataSource.providerId, + } as IProviderConfiguration, + }, + }, + header: { + properties: { + title: "Status Bar Chart with Continuous (Non-Interval) Scale", + subtitle: "Basic Timeseries Widget", + }, + }, + chart: { + providers: { + [WellKnownProviders.Adapter]: { + properties: { + // Setting the series and corresponding labels to initially display on the chart + series: [ + { + id: "series-1", + label: "Node Status", + selectedSeriesId: "series-1", + }, + { + id: "series-2", + label: "Node Status", + selectedSeriesId: "series-2", + }, + ] as ITimeseriesItemConfiguration[], + }, + } as Partial, + }, + properties: { + configuration: { + legendPlacement: LegendPlacement.Right, + enableZoom: true, + // Setting the preset to status bar + preset: TimeseriesChartPreset.StatusBar, + } as ITimeseriesWidgetConfig, + }, + }, + timeframeSelection: { + properties: { + // Setting the initial timeframe selected in the timeframe bar + timeframe: { + selectedPresetId: "last7Days", + } as ISerializableTimeframe, + maxDate: moment().format(), + }, + }, + }, + }, + }, + { + id: "statusIntervalChartWidgetId", + type: "timeseries", + pizzagna: { + [PizzagnaLayer.Configuration]: { + [DEFAULT_PIZZAGNA_ROOT]: { + providers: { + [WellKnownProviders.DataSource]: { + // Setting the initially selected data source providerId + providerId: + TimeseriesStatusIntervalDataSource.providerId, + } as IProviderConfiguration, + }, + }, + header: { + properties: { + title: "Status Bar Chart with Interval Scale", + subtitle: "Basic Timeseries Widget", + }, + }, + chart: { + providers: { + [WellKnownProviders.Adapter]: { + properties: { + // Setting the series and corresponding labels to initially display on the chart + series: [ + { + id: "series-1", + label: "Node Status", + selectedSeriesId: "series-1", + }, + { + id: "series-2", + label: "Node Status", + selectedSeriesId: "series-2", + }, + ] as ITimeseriesItemConfiguration[], + }, + } as Partial, + }, + properties: { + configuration: { + legendPlacement: LegendPlacement.Right, + enableZoom: true, + // Setting the preset to status bar + preset: TimeseriesChartPreset.StatusBar, + scales: { + x: { + type: TimeseriesScaleType.TimeInterval, + properties: { + // one-day interval in seconds + interval: 24 * 60 * 60, + }, + } as ITimeseriesScaleConfig, + }, + } as ITimeseriesWidgetConfig, + }, + }, + timeframeSelection: { + properties: { + // Setting the initial timeframe selected in the timeframe bar + timeframe: { + selectedPresetId: "last7Days", + } as ISerializableTimeframe, + maxDate: moment().format(), + }, + }, + }, + }, + }, +]; + +export const startOfToday = (): Moment => moment().startOf("day"); + +export const getContinuousData = + (): ITimeseriesWidgetData[] => { + const series: ITimeseriesWidgetData[] = [ + { + id: "series-1", + name: "Node Status", + description: "lastchance.demo.lab", + data: [ + // the 'x' value is set to the time and 'y' to the status at that given time + { + x: startOfToday().subtract(20, "day").toDate(), + y: Status.Warning, + }, + { + x: startOfToday().subtract(19, "day").toDate(), + y: Status.Down, + }, + { + x: startOfToday().subtract(17, "day").toDate(), + y: Status.Critical, + }, + { + x: startOfToday().subtract(16, "day").toDate(), + y: Status.Warning, + }, + { + x: startOfToday().subtract(15, "day").toDate(), + y: Status.Down, + }, + { + x: startOfToday().subtract(14, "day").toDate(), + y: Status.Critical, + }, + { + x: startOfToday().subtract(12, "day").toDate(), + y: Status.Unknown, + }, + { + x: startOfToday().subtract(10, "day").toDate(), + y: Status.Up, + }, + { + x: startOfToday().subtract(9, "day").toDate(), + y: Status.Critical, + }, + { + x: startOfToday().subtract(6, "day").toDate(), + y: Status.Up, + }, + { + x: startOfToday().subtract(3, "day").toDate(), + y: Status.Warning, + }, + { + x: startOfToday().subtract(2, "day").toDate(), + y: Status.Critical, + }, + { + x: startOfToday().subtract(1, "day").toDate(), + y: Status.Up, + }, + // This data point will be ignored and is only here to provide an endpoint for the previous status. + { x: moment().toDate(), y: Status.Up }, + ], + }, + { + id: "series-2", + name: "Node Status", + description: "newhope.demo.lab", + data: [ + { + x: startOfToday().subtract(19, "day").toDate(), + y: Status.Critical, + }, + { + x: startOfToday().subtract(18, "day").toDate(), + y: Status.Unknown, + }, + { + x: startOfToday().subtract(17, "day").toDate(), + y: Status.Warning, + }, + { + x: startOfToday().subtract(15, "day").toDate(), + y: Status.Down, + }, + { + x: startOfToday().subtract(8, "day").toDate(), + y: Status.Critical, + }, + { + x: startOfToday().subtract(7, "day").toDate(), + y: Status.Down, + }, + { + x: startOfToday().subtract(6, "day").toDate(), + y: Status.Up, + }, + { + x: startOfToday().subtract(5, "day").toDate(), + y: Status.Critical, + }, + { + x: startOfToday().subtract(4, "day").toDate(), + y: Status.Up, + }, + { + x: startOfToday().subtract(3, "day").toDate(), + y: Status.Warning, + }, + { + x: startOfToday().subtract(2, "day").toDate(), + y: Status.Up, + }, + { + x: startOfToday().subtract(1, "day").toDate(), + y: Status.Down, + }, + // This data point will be ignored and is only here to provide an endpoint for the previous status. + { x: moment().toDate(), y: Status.Down }, + ], + }, + ]; + + for (const s of series) { + // here are we setting the color and icon associated to the status for each data point + s.data = s.data.map((d: any, i: number) => ({ + ...d, + color: statusColors[d.y as Status], + // The thickness of the line is dependant on the status. If the status equals 'Up' then 'thick' is set to false. + thick: d.y !== Status.Up, + icon: "status_" + d.y, + })); + } + + return series; + }; + +// Note that the output of this function is spaced evenly at one-day intervals +export const getIntervalData = + (): ITimeseriesWidgetData[] => { + const series: ITimeseriesWidgetData[] = [ + { + id: "series-1", + name: "Node Status", + description: "lastchance.demo.lab", + data: [ + // the 'x' value is set to the time and 'y' to the status at that given time + { + x: startOfToday().subtract(20, "day").toDate(), + y: Status.Up, + }, + { + x: startOfToday().subtract(19, "day").toDate(), + y: Status.Critical, + }, + { + x: startOfToday().subtract(18, "day").toDate(), + y: Status.Warning, + }, + { + x: startOfToday().subtract(17, "day").toDate(), + y: Status.Down, + }, + { + x: startOfToday().subtract(16, "day").toDate(), + y: Status.Critical, + }, + { + x: startOfToday().subtract(15, "day").toDate(), + y: Status.Down, + }, + { + x: startOfToday().subtract(14, "day").toDate(), + y: Status.Up, + }, + { + x: startOfToday().subtract(13, "day").toDate(), + y: Status.Warning, + }, + { + x: startOfToday().subtract(12, "day").toDate(), + y: Status.Up, + }, + { + x: startOfToday().subtract(11, "day").toDate(), + y: Status.Critical, + }, + { + x: startOfToday().subtract(10, "day").toDate(), + y: Status.Up, + }, + { + x: startOfToday().subtract(9, "day").toDate(), + y: Status.Down, + }, + { + x: startOfToday().subtract(8, "day").toDate(), + y: Status.Critical, + }, + { + x: startOfToday().subtract(7, "day").toDate(), + y: Status.Warning, + }, + { + x: startOfToday().subtract(6, "day").toDate(), + y: Status.Down, + }, + { + x: startOfToday().subtract(5, "day").toDate(), + y: Status.Critical, + }, + { + x: startOfToday().subtract(4, "day").toDate(), + y: Status.Down, + }, + { + x: startOfToday().subtract(3, "day").toDate(), + y: Status.Up, + }, + { + x: startOfToday().subtract(2, "day").toDate(), + y: Status.Warning, + }, + { + x: startOfToday().subtract(1, "day").toDate(), + y: Status.Critical, + }, + { x: startOfToday().toDate(), y: Status.Up }, + ], + }, + { + id: "series-2", + name: "Node Status", + description: "newhope.demo.lab", + data: [ + { + x: startOfToday().subtract(20, "day").toDate(), + y: Status.Up, + }, + { + x: startOfToday().subtract(19, "day").toDate(), + y: Status.Up, + }, + { + x: startOfToday().subtract(18, "day").toDate(), + y: Status.Down, + }, + { + x: startOfToday().subtract(17, "day").toDate(), + y: Status.Critical, + }, + { + x: startOfToday().subtract(16, "day").toDate(), + y: Status.Down, + }, + { + x: startOfToday().subtract(15, "day").toDate(), + y: Status.Up, + }, + { + x: startOfToday().subtract(14, "day").toDate(), + y: Status.Critical, + }, + { + x: startOfToday().subtract(13, "day").toDate(), + y: Status.Up, + }, + { + x: startOfToday().subtract(12, "day").toDate(), + y: Status.Critical, + }, + { + x: startOfToday().subtract(11, "day").toDate(), + y: Status.Warning, + }, + { + x: startOfToday().subtract(10, "day").toDate(), + y: Status.Up, + }, + { + x: startOfToday().subtract(9, "day").toDate(), + y: Status.Down, + }, + { + x: startOfToday().subtract(8, "day").toDate(), + y: Status.Up, + }, + { + x: startOfToday().subtract(7, "day").toDate(), + y: Status.Down, + }, + { + x: startOfToday().subtract(6, "day").toDate(), + y: Status.Critical, + }, + { + x: startOfToday().subtract(5, "day").toDate(), + y: Status.Down, + }, + { + x: startOfToday().subtract(4, "day").toDate(), + y: Status.Up, + }, + { + x: startOfToday().subtract(3, "day").toDate(), + y: Status.Critical, + }, + { + x: startOfToday().subtract(2, "day").toDate(), + y: Status.Up, + }, + { + x: startOfToday().subtract(1, "day").toDate(), + y: Status.Warning, + }, + { x: startOfToday().toDate(), y: Status.Critical }, + ], + }, + ]; + + for (const s of series) { + // here are we setting the color and icon associated to the status for each data point + s.data = s.data.map((d: any, i: number) => ({ + ...d, + color: statusColors[d.y as Status], + // The thickness of the line is dependant on the status. If the status equals 'Up' then 'thick' is set to false. + thick: d.y !== Status.Up, + icon: "status_" + d.y, + })); + } + + return series; + }; + +// An enumeration of statuses +enum Status { + Unknown = "unknown", + Up = "up", + Warning = "warning", + Down = "down", + Critical = "critical", +} + +// This is the map used for setting the color of each status bar +const statusColors: Record = { + [Status.Unknown]: CHART_PALETTE_CS_S_EXTENDED[6], + [Status.Up]: CHART_PALETTE_CS_S_EXTENDED[8], + [Status.Warning]: CHART_PALETTE_CS_S_EXTENDED[4], + [Status.Down]: CHART_PALETTE_CS_S_EXTENDED[0], + [Status.Critical]: CHART_PALETTE_CS_S_EXTENDED[2], +}; + +// Setting the widget dimensions and position (this is for gridster) +const positions: Record = { + [widgetConfigs[0].id]: { + cols: 12, + rows: 4, + y: 0, + x: 0, + }, + [widgetConfigs[1].id]: { + cols: 12, + rows: 4, + y: 4, + x: 0, + }, +}; +\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\`, + "widget-types/view-components/kpi-tile-view-basic/kpi-tile-view-basic-example.component.ts": \\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\`// © 2022 SolarWinds Worldwide, LLC. All rights reserved. +// +// Permission is hereby granted, free of charge, to any person obtaining a copy +// of this software and associated documentation files (the "Software"), to +// deal in the Software without restriction, including without limitation the +// rights to use, copy, modify, merge, publish, distribute, sublicense, and/or +// sell copies of the Software, and to permit persons to whom the Software is +// furnished to do so, subject to the following conditions: +// +// The above copyright notice and this permission notice shall be included in +// all copies or substantial portions of the Software. +// +// THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR +// IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, +// FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE +// AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER +// LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, +// OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN +// THE SOFTWARE. + +import { Component } from "@angular/core"; +import { FormControl } from "@angular/forms"; + +type KpiTileState = "normal" | "loading" | "empty"; + +/** + * KPI Tile View - Playground example. + * Switch between all visual states (normal / loading / empty) and toggle + * interactivity to explore every variant the standalone tile supports. + */ +@Component({ + selector: "kpi-tile-view-basic-example", + templateUrl: "./kpi-tile-view-basic-example.component.html", + standalone: false, +}) +export class KpiTileViewBasicExampleComponent { + public readonly stateControl = new FormControl("normal", { + nonNullable: true, + }); + public interactive = false; + public lastClicked = ""; + + public readonly stateOptions: KpiTileState[] = ["normal", "loading", "empty"]; + + public onTileClick(label: string): void { + this.lastClicked = label; + } +} +\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\`, + "widget-types/view-components/kpi-tile-view-interactive/kpi-tile-view-interactive-example.component.ts": \\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\`// © 2022 SolarWinds Worldwide, LLC. All rights reserved. +// +// Permission is hereby granted, free of charge, to any person obtaining a copy +// of this software and associated documentation files (the "Software"), to +// deal in the Software without restriction, including without limitation the +// rights to use, copy, modify, merge, publish, distribute, sublicense, and/or +// sell copies of the Software, and to permit persons to whom the Software is +// furnished to do so, subject to the following conditions: +// +// The above copyright notice and this permission notice shall be included in +// all copies or substantial portions of the Software. +// +// THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR +// IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, +// FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE +// AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER +// LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, +// OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN +// THE SOFTWARE. + +import { Component, TemplateRef, ViewChild } from "@angular/core"; + +/** + * Interactive KPI Tile View example with custom value formatting + * and click event handling. + */ +@Component({ + selector: "kpi-tile-view-interactive-example", + templateUrl: "./kpi-tile-view-interactive-example.component.html", + standalone: false, +}) +export class KpiTileViewInteractiveExampleComponent { + public currentValue = 1_247; + public lastClickedTile = ""; + + @ViewChild("customValueTpl", { static: true }) + public customValueTpl: TemplateRef; + + public onTileClick(): void { + this.lastClickedTile = "Active Sessions"; + } + + public onUptimeClick(): void { + this.lastClickedTile = "Uptime"; + } +} +\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\`, + "widget-types/view-components/proportional-chart-view-playground/proportional-chart-view-playground-example.component.ts": \\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\`// © 2022 SolarWinds Worldwide, LLC. All rights reserved. +// +// Permission is hereby granted, free of charge, to any person obtaining a copy +// of this software and associated documentation files (the "Software"), to +// deal in the Software without restriction, including without limitation the +// rights to use, copy, modify, merge, publish, distribute, sublicense, and/or +// sell copies of the Software, and to permit persons to whom the Software is +// furnished to do so, subject to the following conditions: +// +// The above copyright notice and this permission notice shall be included in +// all copies or substantial portions of the Software. +// +// THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR +// IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, +// FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE +// AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER +// LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, +// OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN +// THE SOFTWARE. + +import { Component } from "@angular/core"; +import { FormControl } from "@angular/forms"; + +import { IProportionalDataItem } from "@nova-ui/dashboards"; + +type ProportionalChartType = "donut" | "pie" | "verticalBar" | "horizontalBar"; +type LegendPlacement = "right" | "bottom" | "none"; + +/** + * Proportional Chart View - Playground example. + * Lets you switch between all supported chart types and legend placements + * to see every visual variant the standalone view component provides. + */ +@Component({ + selector: "proportional-chart-view-playground-example", + templateUrl: "./proportional-chart-view-playground-example.component.html", + standalone: false, +}) +export class ProportionalChartViewPlaygroundExampleComponent { + public readonly chartTypeControl = new FormControl( + "donut", + { nonNullable: true } + ); + public readonly legendPlacementControl = new FormControl( + "right", + { nonNullable: true } + ); + + public readonly chartTypeOptions: ProportionalChartType[] = [ + "donut", + "pie", + "verticalBar", + "horizontalBar", + ]; + public readonly legendPlacementOptions: LegendPlacement[] = [ + "right", + "bottom", + "none", + ]; + + public colors: Record = { + down: "#dc3545", + up: "#2cc079", + warning: "#f3a002", + unknown: "#707070", + }; + + public chartData: Array = [ + { id: "up", name: "Up", value: 78 }, + { id: "down", name: "Down", value: 8 }, + { id: "warning", name: "Warning", value: 12 }, + { id: "unknown", name: "Unknown", value: 2 }, + ]; +} +\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\`, + "widget-types/view-components/view-components-docs.component.ts": \\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\`// © 2022 SolarWinds Worldwide, LLC. All rights reserved. +// +// Permission is hereby granted, free of charge, to any person obtaining a copy +// of this software and associated documentation files (the "Software"), to +// deal in the Software without restriction, including without limitation the +// rights to use, copy, modify, merge, publish, distribute, sublicense, and/or +// sell copies of the Software, and to permit persons to whom the Software is +// furnished to do so, subject to the following conditions: +// +// The above copyright notice and this permission notice shall be included in +// all copies or substantial portions of the Software. +// +// THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR +// IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, +// FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE +// AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER +// LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, +// OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN +// THE SOFTWARE. + +import { Component } from "@angular/core"; + +@Component({ + selector: "nui-view-components-docs", + templateUrl: "./view-components-docs.component.html", + standalone: false, +}) +export class ViewComponentsDocsComponent { + public readonly installationSnippet = \\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\`import { NuiDashboardViewsModule } from "@nova-ui/dashboards"; + +@NgModule({ + imports: [NuiDashboardViewsModule], +}) +export class MyFeatureModule {}\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\`; + + public readonly proportionalDataItemSnippet = \\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\`interface IProportionalDataItem { + id: string; // Unique segment identifier + name: string; // Display name in legend + value: number; // Numeric value determining segment size + color?: string; // Optional CSS color (hex or token) + icon?: string; // Optional icon name for legend + link?: string; // Optional drill-down URL +}\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\`; +} +\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\`, + "widget-types/view-components/view-components-docs.module.ts": \\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\`// © 2022 SolarWinds Worldwide, LLC. All rights reserved. +// +// Permission is hereby granted, free of charge, to any person obtaining a copy +// of this software and associated documentation files (the "Software"), to +// deal in the Software without restriction, including without limitation the +// rights to use, copy, modify, merge, publish, distribute, sublicense, and/or +// sell copies of the Software, and to permit persons to whom the Software is +// furnished to do so, subject to the following conditions: +// +// The above copyright notice and this permission notice shall be included in +// all copies or substantial portions of the Software. +// +// THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR +// IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, +// FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE +// AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER +// LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, +// OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN +// THE SOFTWARE. + +import { CommonModule } from "@angular/common"; +import { NgModule } from "@angular/core"; +import { ReactiveFormsModule } from "@angular/forms"; +import { RouterModule, Routes } from "@angular/router"; + +import { + NuiDocsModule, + NuiIconModule, + NuiMessageModule, + NuiFormFieldModule, + NuiSelectV2Module, + NuiSwitchModule, + DEMO_PATH_TOKEN, +} from "@nova-ui/bits"; +import { NuiDashboardViewsModule } from "@nova-ui/dashboards"; + +import { getDemoFiles } from "../../../../demo-files-factory"; +import { KpiTileViewBasicExampleComponent } from "./kpi-tile-view-basic/kpi-tile-view-basic-example.component"; +import { KpiTileViewInteractiveExampleComponent } from "./kpi-tile-view-interactive/kpi-tile-view-interactive-example.component"; +import { ProportionalChartViewPlaygroundExampleComponent } from "./proportional-chart-view-playground/proportional-chart-view-playground-example.component"; +import { ViewComponentsDocsComponent } from "./view-components-docs.component"; + +const routes: Routes = [ + { + path: "", + component: ViewComponentsDocsComponent, + data: { + srlc: { + hideIndicator: true, + }, + showThemeSwitcher: true, + }, + }, + { + path: "kpi-tile-view-basic", + component: KpiTileViewBasicExampleComponent, + data: { + srlc: { + hideIndicator: true, + }, + }, + }, + { + path: "proportional-chart-view-playground", + component: ProportionalChartViewPlaygroundExampleComponent, + data: { + srlc: { + hideIndicator: true, + }, + }, + }, +]; + +@NgModule({ + imports: [ + CommonModule, + ReactiveFormsModule, + RouterModule.forChild(routes), + NuiDocsModule, + NuiMessageModule, + NuiIconModule, + NuiFormFieldModule, + NuiSelectV2Module, + NuiSwitchModule, + NuiDashboardViewsModule, + ], + declarations: [ + ViewComponentsDocsComponent, + KpiTileViewBasicExampleComponent, + KpiTileViewInteractiveExampleComponent, + ProportionalChartViewPlaygroundExampleComponent, + ], + providers: [ + { + provide: DEMO_PATH_TOKEN, + useValue: getDemoFiles("view-components"), + }, + ], +}) +export default class ViewComponentsDocsModule {} +\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\`, + "widget-types/widget-types.module.ts": \\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\`// © 2022 SolarWinds Worldwide, LLC. All rights reserved. +// +// Permission is hereby granted, free of charge, to any person obtaining a copy +// of this software and associated documentation files (the "Software"), to +// deal in the Software without restriction, including without limitation the +// rights to use, copy, modify, merge, publish, distribute, sublicense, and/or +// sell copies of the Software, and to permit persons to whom the Software is +// furnished to do so, subject to the following conditions: +// +// The above copyright notice and this permission notice shall be included in +// all copies or substantial portions of the Software. +// +// THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR +// IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, +// FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE +// AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER +// LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, +// OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN +// THE SOFTWARE. + +import { NgModule, Type } from "@angular/core"; +import { RouterModule, Routes } from "@angular/router"; + +import { NuiDocsModule } from "@nova-ui/bits"; +import { + ConfiguratorHeadingService, + NuiDashboardsModule, +} from "@nova-ui/dashboards"; + +export enum WidgetTypesRoute { + kpi = "kpi", + riskScore = "risk-score", + timeseries = "timeseries", + table = "table", + proportional = "proportional", + embedded = "embedded", + drilldown = "drilldown", + viewComponents = "view-components", +} + +const routes: Routes = [ + { + path: WidgetTypesRoute.kpi, + loadChildren: async () => + import("./kpi/kpi-docs.module") as object as Promise>, + data: { + srlc: { + hideIndicator: true, + }, + }, + }, + { + path: WidgetTypesRoute.riskScore, + loadChildren: async () => + import("./risk-score/risk-score-docs.module") as object as Promise< + Type + >, + data: { + srlc: { + hideIndicator: true, + }, + }, + }, + { + path: WidgetTypesRoute.timeseries, + loadChildren: async () => + import("./timeseries/timeseries-docs.module") as object as Promise< + Type + >, + data: { + srlc: { + hideIndicator: true, + }, + }, + }, + { + path: WidgetTypesRoute.table, + loadChildren: async () => + import("./table/table-docs.module") as object as Promise>, + data: { + srlc: { + hideIndicator: true, + }, + }, + }, + { + path: WidgetTypesRoute.proportional, + loadChildren: async () => + import( + "./proportional/proportional-docs.module" + ) as object as Promise>, + data: { + srlc: { + hideIndicator: true, + }, + }, + }, + { + path: WidgetTypesRoute.embedded, + loadChildren: async () => + import( + "./embedded-content/embedded-content-docs.module" + ) as object as Promise>, + data: { + srlc: { + hideIndicator: true, + }, + }, + }, + { + path: WidgetTypesRoute.drilldown, + loadChildren: async () => + import( + "./drilldown/drilldown-widget-docs.module" + ) as object as Promise>, + data: { + srlc: { + hideIndicator: true, + }, + }, + }, + { + path: WidgetTypesRoute.viewComponents, + loadChildren: async () => + import( + "./view-components/view-components-docs.module" + ) as object as Promise>, + data: { + srlc: { + hideIndicator: true, + }, + }, + }, +]; + +@NgModule({ + imports: [ + RouterModule.forChild(routes), + NuiDocsModule, + NuiDashboardsModule, + ], + providers: [ConfiguratorHeadingService], +}) +export default class WidgetTypesModule {} +\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\`, +}; +\\\\\\\\\\\\\\\`, + "overview/hero/dashboard/hero-dashboard.component.ts": \\\\\\\\\\\\\\\`// © 2022 SolarWinds Worldwide, LLC. All rights reserved. +// +// Permission is hereby granted, free of charge, to any person obtaining a copy +// of this software and associated documentation files (the "Software"), to +// deal in the Software without restriction, including without limitation the +// rights to use, copy, modify, merge, publish, distribute, sublicense, and/or +// sell copies of the Software, and to permit persons to whom the Software is +// furnished to do so, subject to the following conditions: +// +// The above copyright notice and this permission notice shall be included in +// all copies or substantial portions of the Software. +// +// THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR +// IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, +// FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE +// AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER +// LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, +// OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN +// THE SOFTWARE. + +import { HttpClient } from "@angular/common/http"; +import { + ChangeDetectionStrategy, + Component, + OnInit, + ViewEncapsulation, +} from "@angular/core"; +import keyBy from "lodash/keyBy"; + +import { LoggerService, ThemeSwitchService } from "@nova-ui/bits"; +import { + DATA_SOURCE, + IDashboard, + IWidget, + ProviderRegistryService, + WidgetTypesService, +} from "@nova-ui/dashboards"; + +import { positions, widgets } from "./widget-configs"; +import { + HarryPotterAverageRatingDataSource, + HarryPotterRatingsCountDataSource, +} from "../data/kpi-datasources"; +import { + BeerReviewCountsByCityMockDataSource, + BeerReviewCountsByCityMockDataSource2, +} from "../data/proportional-datasources"; +import { BeerDataSource } from "../data/table/beer-data-source"; +import { RandomUserDataSource } from "../data/table/random-user-data-source"; +import { + BeerVsReadingMockDataSource, + LoungingVsFrisbeeGolfMockDataSource, +} from "../data/timeseries-data-sources"; + +/** + * A component that instantiates the dashboard + */ +@Component({ + selector: "hero-dashboard", + templateUrl: "./hero-dashboard.component.html", + styleUrls: ["./hero-dashboard.component.less"], + encapsulation: ViewEncapsulation.Emulated, + changeDetection: ChangeDetectionStrategy.Default, + standalone: false, +}) +export class HeroDashboardComponent implements OnInit { + public dashboard: IDashboard = { + positions: {}, + widgets: {}, + }; + + public gridsterConfig = {}; + public editMode = false; + + constructor( + private providerRegistry: ProviderRegistryService, + public themeSwitcherService: ThemeSwitchService, + private widgetTypesService: WidgetTypesService + ) { + this.providerRegistry.setProviders({ + [HarryPotterAverageRatingDataSource.providerId]: { + provide: DATA_SOURCE, + useClass: HarryPotterAverageRatingDataSource, + deps: [HttpClient], + }, + [HarryPotterRatingsCountDataSource.providerId]: { + provide: DATA_SOURCE, + useClass: HarryPotterRatingsCountDataSource, + deps: [HttpClient], + }, + [BeerReviewCountsByCityMockDataSource.providerId]: { + provide: DATA_SOURCE, + useClass: BeerReviewCountsByCityMockDataSource, + deps: [], + }, + [BeerReviewCountsByCityMockDataSource2.providerId]: { + provide: DATA_SOURCE, + useClass: BeerReviewCountsByCityMockDataSource2, + deps: [], + }, + [RandomUserDataSource.providerId]: { + provide: DATA_SOURCE, + useClass: RandomUserDataSource, + deps: [LoggerService], + }, + [BeerDataSource.providerId]: { + provide: DATA_SOURCE, + useClass: BeerDataSource, + deps: [LoggerService], + }, + [BeerVsReadingMockDataSource.providerId]: { + provide: DATA_SOURCE, + useClass: BeerVsReadingMockDataSource, + deps: [], + }, + [LoungingVsFrisbeeGolfMockDataSource.providerId]: { + provide: DATA_SOURCE, + useClass: LoungingVsFrisbeeGolfMockDataSource, + deps: [], + }, + }); + } + + public ngOnInit(): void { + const widgetsWithStructure = widgets.map((w) => ({ + ...w, + pizzagna: { + ...this.widgetTypesService.getWidgetType(w.type, w.version) + .widget, + ...w.pizzagna, + }, + })); + const widgetsIndex = keyBy(widgetsWithStructure, (w: IWidget) => w.id); + + this.dashboard = { + positions: positions, + widgets: widgetsIndex, + }; + } +} +\\\\\\\\\\\\\\\`, + "overview/hero/dashboard/widget-configs.ts": \\\\\\\\\\\\\\\`// © 2022 SolarWinds Worldwide, LLC. All rights reserved. +// +// Permission is hereby granted, free of charge, to any person obtaining a copy +// of this software and associated documentation files (the "Software"), to +// deal in the Software without restriction, including without limitation the +// rights to use, copy, modify, merge, publish, distribute, sublicense, and/or +// sell copies of the Software, and to permit persons to whom the Software is +// furnished to do so, subject to the following conditions: +// +// The above copyright notice and this permission notice shall be included in +// all copies or substantial portions of the Software. +// +// THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR +// IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, +// FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE +// AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER +// LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, +// OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN +// THE SOFTWARE. + +import { GridsterItem } from "angular-gridster2"; + +import { IWidget } from "@nova-ui/dashboards"; + +import { kpiConfig } from "../widget-configs/kpi"; +import { proportionalConfig } from "../widget-configs/proportional"; +import { tableConfig } from "../widget-configs/table"; +import { timeseriesConfig } from "../widget-configs/timeseries"; + +export const positions: Record = { + [tableConfig.id]: { + cols: 7, + rows: 7, + y: 0, + x: 0, + }, + [proportionalConfig.id]: { + cols: 5, + rows: 7, + y: 0, + x: 7, + }, + [kpiConfig.id]: { + cols: 6, + rows: 7, + y: 7, + x: 0, + }, + [timeseriesConfig.id]: { + cols: 6, + rows: 7, + y: 7, + x: 6, + }, +}; + +export const widgets: IWidget[] = [ + { + ...tableConfig, + }, + { + ...proportionalConfig, + }, + { + ...kpiConfig, + }, + { + ...timeseriesConfig, + }, +]; +\\\\\\\\\\\\\\\`, + "overview/hero/data/kpi-datasources.ts": \\\\\\\\\\\\\\\`// © 2022 SolarWinds Worldwide, LLC. All rights reserved. +// +// Permission is hereby granted, free of charge, to any person obtaining a copy +// of this software and associated documentation files (the "Software"), to +// deal in the Software without restriction, including without limitation the +// rights to use, copy, modify, merge, publish, distribute, sublicense, and/or +// sell copies of the Software, and to permit persons to whom the Software is +// furnished to do so, subject to the following conditions: +// +// The above copyright notice and this permission notice shall be included in +// all copies or substantial portions of the Software. +// +// THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR +// IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, +// FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE +// AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER +// LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, +// OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN +// THE SOFTWARE. + +import { HttpClient, HttpErrorResponse } from "@angular/common/http"; +import { Injectable, OnDestroy } from "@angular/core"; +import { BehaviorSubject } from "rxjs"; +import { finalize } from "rxjs/operators"; + +import { DataSourceService, IFilteringOutputs } from "@nova-ui/bits"; +import { IKpiData } from "@nova-ui/dashboards"; + +import { GOOGLE_BOOKS_URL } from "./table/constants"; + +@Injectable() +export class HarryPotterAverageRatingDataSource + extends DataSourceService + implements OnDestroy +{ + public static providerId = "HarryPotterAverageRatingDataSource"; + + public busy = new BehaviorSubject(false); + + constructor(private http: HttpClient) { + super(); + } + + public async getFilteredData(): Promise { + this.busy.next(true); + return new Promise((resolve) => { + // *** Make a resource request to an external API (if needed) + this.http + .get(\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\`\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\${GOOGLE_BOOKS_URL}/5MQFrgEACAAJ\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\`) + .pipe(finalize(() => this.busy.next(false))) + .subscribe({ + next: (data: any) => { + resolve({ + result: { + value: data.volumeInfo.averageRating, + }, + }); + }, + error: (error: HttpErrorResponse) => { + resolve({ + result: null, + error: { + type: error.status, + }, + }); + }, + }); + }); + } + + public ngOnDestroy(): void { + this.outputsSubject.complete(); + } +} + +@Injectable() +export class HarryPotterRatingsCountDataSource + extends DataSourceService + implements OnDestroy +{ + public static providerId = "HarryPotterRatingsCountDataSource"; + + public busy = new BehaviorSubject(false); + + constructor(private http: HttpClient) { + super(); + } + + public async getFilteredData(): Promise { + this.busy.next(true); + return new Promise((resolve) => { + this.http + .get(\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\`\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\${GOOGLE_BOOKS_URL}/5MQFrgEACAAJ\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\`) + .pipe(finalize(() => this.busy.next(false))) + .subscribe({ + next: (data: any) => { + resolve({ + result: { + value: data.volumeInfo.ratingsCount, + }, + }); + }, + error: (error: HttpErrorResponse) => { + resolve({ + result: null, + error: { + type: error.status, + }, + }); + }, + }); + }); + } + + public ngOnDestroy(): void { + this.outputsSubject.complete(); + } +} +\\\\\\\\\\\\\\\`, + "overview/hero/data/proportional-datasources.ts": \\\\\\\\\\\\\\\`// © 2022 SolarWinds Worldwide, LLC. All rights reserved. +// +// Permission is hereby granted, free of charge, to any person obtaining a copy +// of this software and associated documentation files (the "Software"), to +// deal in the Software without restriction, including without limitation the +// rights to use, copy, modify, merge, publish, distribute, sublicense, and/or +// sell copies of the Software, and to permit persons to whom the Software is +// furnished to do so, subject to the following conditions: +// +// The above copyright notice and this permission notice shall be included in +// all copies or substantial portions of the Software. +// +// THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR +// IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, +// FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE +// AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER +// LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, +// OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN +// THE SOFTWARE. + +import { Injectable, OnDestroy } from "@angular/core"; +import { BehaviorSubject } from "rxjs"; + +import { + DataSourceService, + IDataSource, + IFilteringOutputs, +} from "@nova-ui/bits"; + +import { + getMockBeerReviewCountsByCity, + getMockBeerReviewCountsByCity2, + IProportionalWidgetData, +} from "./widget-data"; + +@Injectable() +export class BeerReviewCountsByCityMockDataSource + extends DataSourceService + implements IDataSource, OnDestroy +{ + // This is the ID we'll use to identify the provider + public static providerId = "BeerReviewCountsByCityMockDataSource"; + public busy = new BehaviorSubject(false); + + public async getFilteredData(): Promise { + this.busy.next(true); + return new Promise((resolve) => { + setTimeout(() => { + this.outputsSubject.next({ + result: getMockBeerReviewCountsByCity(), + }); + this.busy.next(false); + }, 300); + }); + } + + public ngOnDestroy(): void { + this.outputsSubject.complete(); + } +} + +@Injectable() +export class BeerReviewCountsByCityMockDataSource2 + extends DataSourceService + implements IDataSource, OnDestroy +{ + // This is the ID we'll use to identify the provider + public static providerId = "BeerReviewCountsByCityMockDataSource2"; + public busy = new BehaviorSubject(false); + + public async getFilteredData(): Promise { + this.busy.next(true); + return new Promise((resolve) => { + setTimeout(() => { + this.outputsSubject.next({ + result: getMockBeerReviewCountsByCity2(), + }); + this.busy.next(false); + }, 1500); + }); + } + + public ngOnDestroy(): void { + this.outputsSubject.complete(); + } +} +\\\\\\\\\\\\\\\`, + "overview/hero/data/table/beer-data-source.ts": \\\\\\\\\\\\\\\`// © 2022 SolarWinds Worldwide, LLC. All rights reserved. +// +// Permission is hereby granted, free of charge, to any person obtaining a copy +// of this software and associated documentation files (the "Software"), to +// deal in the Software without restriction, including without limitation the +// rights to use, copy, modify, merge, publish, distribute, sublicense, and/or +// sell copies of the Software, and to permit persons to whom the Software is +// furnished to do so, subject to the following conditions: +// +// The above copyright notice and this permission notice shall be included in +// all copies or substantial portions of the Software. +// +// THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR +// IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, +// FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE +// AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER +// LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, +// OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN +// THE SOFTWARE. + +import { ListRange } from "@angular/cdk/collections"; +import { Injectable } from "@angular/core"; +import isEqual from "lodash/isEqual"; +import orderBy from "lodash/orderBy"; +import { BehaviorSubject } from "rxjs"; + +import { + DataSourceService, + IDataField, + INovaFilteringOutputs, + INovaFilters, + ISorterFilter, + LoggerService, +} from "@nova-ui/bits"; + +import { IBrewDatasourceResponse, IBrewInfo } from "../types"; +import { BREW_API_URL } from "./constants"; + +@Injectable() +export class BeerDataSource extends DataSourceService { + public static providerId = "BeerDataSource"; + + private cache = Array.from({ length: 0 }); + private lastSortValue?: ISorterFilter; + private lastVirtualScroll?: ListRange; + private totalItems: number = 325; + + public page: number = 1; + public busy = new BehaviorSubject(false); + + public dataFields: Array = [ + { id: "id", label: "No", dataType: "number" }, + { id: "name", label: "Name", dataType: "string" }, + { id: "tagline", label: "Tagline", dataType: "string" }, + { id: "first_brewed", label: "First Brewed", dataType: "string" }, + { id: "description", label: "Description", dataType: "string" }, + { id: "brewers_tips", label: "Brewer's Tips", dataType: "string" }, + ]; + + constructor(private logger: LoggerService) { + super(); + } + + public async getFilteredData( + filters: INovaFilters + ): Promise { + const start = filters.virtualScroll?.value?.start ?? 0; + const end = filters.virtualScroll?.value?.end ?? 0; + const delta = end - start; + + // This condition handles sorting. We want to sort columns without fetching another chunk of data. + // Since the data is being fetched when scrolled, we compare virtual scroll indexes here in the condition as well. + if (filters.sorter?.value) { + if ( + !isEqual(this.lastSortValue, filters.sorter.value) && + isEqual(this.lastVirtualScroll, filters.virtualScroll?.value) + ) { + const totalPages = Math.ceil( + delta ? this.totalItems / delta : 1 + ); + const itemsPerPage: number = Math.max( + delta < 80 ? delta : 80, + 1 + ); + let response: Array | null = null; + let map: IBrewDatasourceResponse; + + if (filters.sorter?.value?.direction === "desc") { + this.cache = []; + for (let i = 0; i < this.page; ++i) { + response = await ( + await fetch( + \\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\`\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\${BREW_API_URL}/?page=\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\${ + totalPages - i || 1 + }&per_page=\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\${itemsPerPage}\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\` + ) + ).json(); + + // since the last page contains only 5 items we need to fetch another page to give virtual scroll enough space to work + if (response && response.length < itemsPerPage) { + this.page++; + } + map = { + brewInfo: response?.map((result: IBrewInfo) => ({ + id: result.id, + name: result.name, + tagline: result.tagline, + first_brewed: result.first_brewed, + description: result.description, + brewers_tips: result.brewers_tips, + })), + total: response?.length, + } as IBrewDatasourceResponse; + this.cache = + totalPages - i !== 0 + ? this.cache.concat(map.brewInfo) + : this.cache; + } + } + + if (filters.sorter?.value?.direction === "asc") { + this.cache = []; + for (let i = 0; i < this.page; i++) { + response = await ( + await fetch( + \\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\`\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\${BREW_API_URL}/?page=\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\${ + i + 1 + }&per_page=\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\${itemsPerPage}\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\` + ) + ).json(); + map = { + brewInfo: response?.map((result: IBrewInfo) => ({ + id: result.id, + name: result.name, + tagline: result.tagline, + first_brewed: result.first_brewed, + description: result.description, + brewers_tips: result.brewers_tips, + })), + total: response?.length, + } as IBrewDatasourceResponse; + this.cache = this.cache.concat(map.brewInfo); + } + } + + this.lastSortValue = filters.sorter?.value; + this.lastVirtualScroll = filters.virtualScroll?.value; + + return { + repeat: { itemsSource: this.sortData(this.cache, filters) }, + paginator: { total: this.totalItems }, + dataFields: this.dataFields, + }; + } + } + + this.busy.next(true); + return new Promise((resolve) => { + setTimeout(() => { + this.getData(start, end, filters).then( + (response: INovaFilteringOutputs) => { + if (!response) { + return; + } + + this.cache = this.cache.concat(response.brewInfo); + + this.dataSubject.next(this.cache); + resolve({ + repeat: { + itemsSource: this.sortData(this.cache, filters), + }, + paginator: { total: this.totalItems }, + dataFields: this.dataFields, + }); + + this.lastSortValue = filters.sorter?.value; + this.lastVirtualScroll = filters.virtualScroll?.value; + this.busy.next(false); + } + ); + }, 500); + }); + } + + public async getData( + start: number = 0, + end: number = 20, + filters: INovaFilters + ): Promise { + const delta = end - start; + const totalPages = Math.ceil(delta ? this.totalItems / delta : 1); + let response: Array | null = null; + // The api.punk.com is able to return only 80 items per page + const itemsPerPage: number = Math.max(delta < 80 ? delta : 80, 1); + + if (filters.sorter?.value?.direction === "asc") { + response = await ( + await fetch( + \\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\`\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\${BREW_API_URL}/?page=\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\${this.page}&per_page=\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\${itemsPerPage}\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\` + ) + ).json(); + } + + if (filters.sorter?.value?.direction === "desc") { + response = await ( + await fetch( + \\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\`\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\${BREW_API_URL}/?page=\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\${ + totalPages - this.page + }&per_page=\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\${itemsPerPage}\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\` + ) + ).json(); + } + + if (!filters.sorter) { + response = await ( + await fetch( + \\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\`\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\${BREW_API_URL}/?page=\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\${this.page}&per_page=\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\${itemsPerPage}\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\` + ) + ).json(); + } + return { + brewInfo: response?.map((result: IBrewInfo, i: number) => ({ + id: result.id, + name: result.name, + tagline: result.tagline, + first_brewed: result.first_brewed, + description: result.description, + brewers_tips: result.brewers_tips, + })), + total: response?.length, + } as IBrewDatasourceResponse; + } + + private sortData(data: IBrewInfo[], filters: INovaFilters) { + return orderBy( + data, + filters.sorter?.value?.sortBy, + filters.sorter?.value?.direction as "desc" | "asc" + ); + } +} +\\\\\\\\\\\\\\\`, + "overview/hero/data/table/constants.ts": \\\\\\\\\\\\\\\`// © 2022 SolarWinds Worldwide, LLC. All rights reserved. +// +// Permission is hereby granted, free of charge, to any person obtaining a copy +// of this software and associated documentation files (the "Software"), to +// deal in the Software without restriction, including without limitation the +// rights to use, copy, modify, merge, publish, distribute, sublicense, and/or +// sell copies of the Software, and to permit persons to whom the Software is +// furnished to do so, subject to the following conditions: +// +// The above copyright notice and this permission notice shall be included in +// all copies or substantial portions of the Software. +// +// THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR +// IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, +// FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE +// AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER +// LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, +// OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN +// THE SOFTWARE. + +export const corsProxy = "https://cors-anywhere.herokuapp.com"; +export const RANDOMUSER_API_URL = "https://randomuser.me"; +export const BREW_API_URL = "https://api.punkapi.com/v2/beers"; +export const GOOGLE_BOOKS_URL = "https://www.googleapis.com/books/v1/volumes"; +export const apiRoute = "api/1.3"; +export const responseError = \\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\`Error responding from server. Please visit \\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\${RANDOMUSER_API_URL} and \\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\${corsProxy} to see if they're available\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\`; +\\\\\\\\\\\\\\\`, + "overview/hero/data/table/random-user-data-source.ts": \\\\\\\\\\\\\\\`// © 2022 SolarWinds Worldwide, LLC. All rights reserved. +// +// Permission is hereby granted, free of charge, to any person obtaining a copy +// of this software and associated documentation files (the "Software"), to +// deal in the Software without restriction, including without limitation the +// rights to use, copy, modify, merge, publish, distribute, sublicense, and/or +// sell copies of the Software, and to permit persons to whom the Software is +// furnished to do so, subject to the following conditions: +// +// The above copyright notice and this permission notice shall be included in +// all copies or substantial portions of the Software. +// +// THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR +// IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, +// FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE +// AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER +// LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, +// OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN +// THE SOFTWARE. + +import { ListRange } from "@angular/cdk/collections"; +import { Injectable } from "@angular/core"; +import isEqual from "lodash/isEqual"; +import orderBy from "lodash/orderBy"; +import { BehaviorSubject } from "rxjs"; + +import { + DataSourceService, + IDataField, + INovaFilteringOutputs, + INovaFilters, + ISorterFilter, + LoggerService, +} from "@nova-ui/bits"; + +import { + IRandomUserResponse, + IRandomUserResults, + IRandomUserTableModel, + UsersQueryResponse, +} from "../types"; +import { + apiRoute, + corsProxy, + RANDOMUSER_API_URL, + responseError, +} from "./constants"; + +@Injectable() +export class RandomUserDataSource extends DataSourceService { + public static providerId = "RandomUserDataSource"; + + private readonly seed = "sw"; + + private cache = Array.from({ length: 0 }); + private lastSortValue?: ISorterFilter; + private lastVirtualScroll?: ListRange; + + public page: number = 1; + public busy = new BehaviorSubject(false); + + public dataFields: Array = [ + { id: "no", label: $localize\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\`No\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\`, dataType: "number" }, + { id: "nameTitle", label: $localize\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\`Title\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\`, dataType: "string" }, + { id: "nameFirst", label: $localize\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\`First\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\`, dataType: "string" }, + { id: "nameLast", label: $localize\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\`Last\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\`, dataType: "string" }, + { id: "gender", label: $localize\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\`Gender\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\`, dataType: "string" }, + { id: "country", label: $localize\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\`Country\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\`, dataType: "string" }, + { id: "city", label: $localize\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\`City\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\`, dataType: "string" }, + { id: "postcode", label: $localize\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\`Postcode\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\`, dataType: "number" }, + { id: "email", label: $localize\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\`E-Mail\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\`, dataType: "string" }, + { id: "cell", label: $localize\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\`Cell\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\`, dataType: "string" }, + ]; + + constructor(private logger: LoggerService) { + super(); + } + + public async getFilteredData( + filters: INovaFilters + ): Promise { + // This condition handles sorting. We want to sort columns without fetching another chunk of data. + // Since the data is being fetched when scrolled, we compare virtual scroll indexes here in the condition as well. + if (filters.sorter?.value) { + if ( + !isEqual(this.lastSortValue, filters.sorter.value) && + isEqual(this.lastVirtualScroll, filters.virtualScroll?.value) + ) { + this.lastSortValue = filters.sorter?.value; + this.lastVirtualScroll = filters.virtualScroll?.value; + + return { + repeat: { itemsSource: this.sortData(this.cache, filters) }, + paginator: { total: 200 }, + dataFields: this.dataFields, + }; + } + } + this.busy.next(true); + + const virtualScrollFilter = + filters.virtualScroll && filters.virtualScroll.value; + const start = virtualScrollFilter + ? filters.virtualScroll?.value.start + : 0; + const end = virtualScrollFilter ? filters.virtualScroll?.value.end : 0; + + // We're returning Promise with setTimeout here to make the response from the server longer, as the API being used sends responses + // almost immediately. We need it longer to be able the show the spinner component on data load + return new Promise((resolve) => { + setTimeout(() => { + this.getData(start, end).then( + (response: INovaFilteringOutputs | undefined) => { + if (!response) { + return; + } + + this.cache = this.cache.concat(response.users); + + this.dataSubject.next(this.cache); + resolve({ + repeat: { + itemsSource: this.sortData(this.cache, filters), + }, + // This API can return thousands of results, however doesn't return the max number of results, + // so we set the max number of result manually here. + paginator: { total: 200 }, + dataFields: this.dataFields, + }); + + this.lastSortValue = filters.sorter?.value; + this.lastVirtualScroll = filters.virtualScroll?.value; + this.busy.next(false); + } + ); + }, 300); + }); + } + + public async getData( + start: number = 0, + end: number = 20 + ): Promise { + let response: IRandomUserResponse | null = null; + try { + response = await ( + await fetch( + \\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\`\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\${corsProxy}/\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\${RANDOMUSER_API_URL}/\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\${apiRoute}/?page=\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\${ + this.page + }&results=\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\${end - start}&seed=\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\${this.seed}\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\` + ) + ).json(); + return { + users: response?.results.map( + (result: IRandomUserResults, i: number) => ({ + no: this.cache.length + i + 1, + nameTitle: result.name.title, + nameFirst: result.name.first, + nameLast: result.name.last, + gender: result.gender, + country: result.location.country, + city: result.location.city, + postcode: result.location.postcode, + email: result.email, + cell: result.cell, + }) + ), + total: response?.results.length, + start: start, + } as UsersQueryResponse; + } catch (e) { + this.logger.error(responseError); + } + } + + private sortData(data: IRandomUserTableModel[], filters: INovaFilters) { + return orderBy( + data, + filters.sorter?.value?.sortBy, + filters.sorter?.value?.direction as "desc" | "asc" + ); + } +} +\\\\\\\\\\\\\\\`, + "overview/hero/data/table/types.ts": \\\\\\\\\\\\\\\`// © 2022 SolarWinds Worldwide, LLC. All rights reserved. +// +// Permission is hereby granted, free of charge, to any person obtaining a copy +// of this software and associated documentation files (the "Software"), to +// deal in the Software without restriction, including without limitation the +// rights to use, copy, modify, merge, publish, distribute, sublicense, and/or +// sell copies of the Software, and to permit persons to whom the Software is +// furnished to do so, subject to the following conditions: +// +// The above copyright notice and this permission notice shall be included in +// all copies or substantial portions of the Software. +// +// THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR +// IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, +// FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE +// AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER +// LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, +// OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN +// THE SOFTWARE. + +import { IDataField, INovaFilteringOutputs } from "@nova-ui/bits"; +export interface BasicTableModel { + position: number; + name: string; + features: any; + status: string; + checks: any; + "cpu-load": number; + firstUrl: string; + firstUrlLabel: string; + secondUrl: string; + secondUrlLabel: string; +} + +export interface ITableDataSourceOutput extends INovaFilteringOutputs { + dataFields: IDataField[]; +} +\\\\\\\\\\\\\\\`, + "overview/hero/data/timeseries-data-sources.ts": \\\\\\\\\\\\\\\`// © 2022 SolarWinds Worldwide, LLC. All rights reserved. +// +// Permission is hereby granted, free of charge, to any person obtaining a copy +// of this software and associated documentation files (the "Software"), to +// deal in the Software without restriction, including without limitation the +// rights to use, copy, modify, merge, publish, distribute, sublicense, and/or +// sell copies of the Software, and to permit persons to whom the Software is +// furnished to do so, subject to the following conditions: +// +// The above copyright notice and this permission notice shall be included in +// all copies or substantial portions of the Software. +// +// THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR +// IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, +// FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE +// AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER +// LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, +// OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN +// THE SOFTWARE. + +import { Injectable } from "@angular/core"; +import { Moment } from "moment/moment"; +import { BehaviorSubject } from "rxjs"; + +import { DataSourceService, IDataSource, INovaFilters } from "@nova-ui/bits"; +import { + ITimeseriesOutput, + ITimeseriesWidgetData, + ITimeseriesWidgetSeriesData, +} from "@nova-ui/dashboards"; + +import { + BEER_VS_READING_DATA, + LOUNGING_VS_ULTIMATE_FRISBEE_DATA, +} from "./widget-data"; + +@Injectable() +export class BeerVsReadingMockDataSource + extends DataSourceService + implements IDataSource +{ + public static providerId = "BeerVsReadingMockDataSource"; + + public busy = new BehaviorSubject(false); + + constructor() { + super(); + } + + public async getFilteredData( + filters: INovaFilters + ): Promise { + this.busy.next(true); + const result = await delay( + { series: getData(filters, BEER_VS_READING_DATA) }, + 1000 + ); + this.busy.next(false); + return result; + } +} + +@Injectable() +export class LoungingVsFrisbeeGolfMockDataSource + extends DataSourceService + implements IDataSource +{ + public static providerId = "LoungingVsFrisbeeGolfMockDataSource"; + + public busy = new BehaviorSubject(false); + + constructor() { + super(); + } + + public async getFilteredData( + filters: INovaFilters + ): Promise { + this.busy.next(true); + const result = await delay( + { series: getData(filters, LOUNGING_VS_ULTIMATE_FRISBEE_DATA) }, + 1000 + ); + this.busy.next(false); + return result; + } +} + +function getData( + filters: INovaFilters, + data: ITimeseriesWidgetData[] +): ITimeseriesWidgetData[] { + const timeframeFilter = filters.timeframe; + let filteredData = data; + // TIME FRAME PICKER FILTERING + if (timeframeFilter) { + filteredData = filteredData.map((item: ITimeseriesWidgetData) => ({ + id: item.id, + name: item.name, + description: item.description, + data: item.data.filter((seriesData: ITimeseriesWidgetSeriesData) => + filterDates( + seriesData.x, + timeframeFilter.value.startDatetime, + timeframeFilter.value.endDatetime + ) + ), + })); + } + + return filteredData; +} + +function filterDates(dateToCheck: Moment, startDate: Moment, endDate: Moment) { + return ( + dateToCheck.isBetween(startDate, endDate) || + dateToCheck.isSame(startDate) || + dateToCheck.isSame(endDate) + ); +} + +async function delay( + value: ITimeseriesOutput, + ms: number +): Promise { + return new Promise((resolve) => setTimeout(() => resolve(value), ms)); +} +\\\\\\\\\\\\\\\`, + "overview/hero/data/types.ts": \\\\\\\\\\\\\\\`// © 2022 SolarWinds Worldwide, LLC. All rights reserved. +// +// Permission is hereby granted, free of charge, to any person obtaining a copy +// of this software and associated documentation files (the "Software"), to +// deal in the Software without restriction, including without limitation the +// rights to use, copy, modify, merge, publish, distribute, sublicense, and/or +// sell copies of the Software, and to permit persons to whom the Software is +// furnished to do so, subject to the following conditions: +// +// The above copyright notice and this permission notice shall be included in +// all copies or substantial portions of the Software. +// +// THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR +// IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, +// FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE +// AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER +// LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, +// OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN +// THE SOFTWARE. + +export interface UsersQueryResponse { + users: IRandomUserTableModel[]; + total: number; + start: number; +} + +export interface IRandomUserResponse { + info: Array; + results: Array; +} + +export interface IRandomUserInfo { + page: number; + results: number; + seed: string; + version: string; +} + +export interface IRandomUserResults { + cell: string; + dob: { + age: number; + date: string; + }; + email: string; + gender: string; + id: any; + location: IRandomUserLocation; + login: { + md5: string; + password: string; + salt: string; + sha1: string; + sha256: string; + username: string; + uuid: string; + }; + name: { + title: string; + first: string; + last: string; + }; + nat: string; + phone: string; + picture: { + large: string; + medium: string; + thumbnail: string; + }; + registered: { + date: string; + age: number; + }; +} + +export interface IRandomUserTableModel { + no: number; + nameTitle: string; + nameFirst: string; + nameLast: string; + gender: string; + country: string; + city: string; + postcode: number; + email: string; + cell: string; +} + +export interface IRandomUserLocation { + city: string; + coordinates: { latitude: string; longitude: string }; + country: string; + postcode: number; + state: string; + street: { number: number; name: string }; + timezone: any; +} + +export interface IBrewInfo { + id: number; + name: string; + tagline: string; + first_brewed: string; + description: string; + brewers_tips: string; +} + +export interface IBrewDatasourceResponse { + brewInfo: IBrewInfo[]; + total: number; +} +\\\\\\\\\\\\\\\`, + "overview/hero/data/widget-data.ts": \\\\\\\\\\\\\\\`// © 2022 SolarWinds Worldwide, LLC. All rights reserved. +// +// Permission is hereby granted, free of charge, to any person obtaining a copy +// of this software and associated documentation files (the "Software"), to +// deal in the Software without restriction, including without limitation the +// rights to use, copy, modify, merge, publish, distribute, sublicense, and/or +// sell copies of the Software, and to permit persons to whom the Software is +// furnished to do so, subject to the following conditions: +// +// The above copyright notice and this permission notice shall be included in +// all copies or substantial portions of the Software. +// +// THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR +// IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, +// FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE +// AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER +// LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, +// OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN +// THE SOFTWARE. + +import moment from "moment/moment"; + +import { ITimeseriesWidgetData } from "@nova-ui/dashboards"; + +import { BasicTableModel } from "./table/types"; + +export interface IProportionalWidgetData { + id: string; + name: string; + data: number[]; + link: string; + value: string; +} + +export function getMockBeerReviewCountsByCity(): IProportionalWidgetData[] { + return [ + { + id: "Brno", + name: "Brno", + data: [Math.round(Math.random() * 100000)], + link: "https://en.wikipedia.org/wiki/Brno", + value: "Brno", + }, + { + id: "kyiv", + name: "Kyiv", + data: [Math.round(Math.random() * 100000)], + link: "https://en.wikipedia.org/wiki/Kyiv", + value: "Kyiv", + }, + { + id: "austin", + name: "Austin", + data: [Math.round(Math.random() * 100000)], + link: "https://en.wikipedia.org/wiki/Austin", + value: "Austin", + }, + { + id: "lisbon", + name: "Lisbon", + data: [Math.round(Math.random() * 100000)], + link: "https://en.wikipedia.org/wiki/Lisbon", + value: "Lisbon", + }, + { + id: "sydney", + name: "Sydney", + data: [Math.round(Math.random() * 100000)], + link: "https://en.wikipedia.org/wiki/Sydney", + value: "Sydney", + }, + { + id: "nur-sultan", + name: "Nur-Sultan", + data: [Math.round(Math.random() * 100000)], + link: "https://en.wikipedia.org/wiki/Nur-Sultan", + value: "Nur-Sultan", + }, + ].sort((a, b) => a.data[0] - b.data[0]); +} + +export function getMockBeerReviewCountsByCity2(): IProportionalWidgetData[] { + return [ + { + id: "london", + name: "London", + data: [Math.round(Math.random() * 100000)], + link: "https://en.wikipedia.org/wiki/London", + value: "London", + }, + { + id: "paris", + name: "Paris", + data: [Math.round(Math.random() * 100000)], + link: "https://en.wikipedia.org/wiki/Paris", + value: "Paris", + }, + { + id: "rio", + name: "Rio", + data: [Math.round(Math.random() * 100000)], + link: "https://en.wikipedia.org/wiki/Rio_de_Janeiro", + value: "Rio", + }, + ].sort((a, b) => a.data[0] - b.data[0]); +} + +export const BEER_VS_READING_DATA: ITimeseriesWidgetData[] = [ + { + id: "series-1", + name: "Beer Tasting", + description: "Havin' some suds", + data: [ + { x: moment().subtract(10, "day"), y: 30 }, + { x: moment().subtract(9, "day"), y: 35 }, + { x: moment().subtract(8, "day"), y: 33 }, + { x: moment().subtract(7, "day"), y: 40 }, + { x: moment().subtract(6, "day"), y: 35 }, + { x: moment().subtract(5, "day"), y: 30 }, + { x: moment().subtract(4, "day"), y: 35 }, + { x: moment().subtract(3, "day"), y: 15 }, + { x: moment().subtract(2, "day"), y: 30 }, + { x: moment().subtract(1, "day"), y: 35 }, + { x: moment().subtract(24, "hour"), y: 34 }, + { x: moment().subtract(15, "hour"), y: 33 }, + { x: moment().subtract(10, "hour"), y: 35 }, + { x: moment().subtract(5, "hour"), y: 36 }, + { x: moment().subtract(1, "hour"), y: 34 }, + { x: moment().subtract(50, "minute"), y: 33 }, + { x: moment().subtract(40, "minute"), y: 30 }, + { x: moment().subtract(30, "minute"), y: 32 }, + { x: moment().subtract(20, "minute"), y: 31 }, + { x: moment().subtract(10, "minute"), y: 34 }, + ], + }, + { + id: "series-2", + name: "Reading", + description: "Hittin' the books", + data: [ + { x: moment().subtract(10, "day"), y: 60 }, + { x: moment().subtract(9, "day"), y: 64 }, + { x: moment().subtract(8, "day"), y: 70 }, + { x: moment().subtract(7, "day"), y: 55 }, + { x: moment().subtract(6, "day"), y: 55 }, + { x: moment().subtract(5, "day"), y: 45 }, + { x: moment().subtract(4, "day"), y: 10 }, + { x: moment().subtract(3, "day"), y: 65 }, + { x: moment().subtract(2, "day"), y: 35 }, + { x: moment().subtract(1, "day"), y: 60 }, + { x: moment().subtract(24, "hour"), y: 61 }, + { x: moment().subtract(15, "hour"), y: 65 }, + { x: moment().subtract(10, "hour"), y: 63 }, + { x: moment().subtract(5, "hour"), y: 58 }, + { x: moment().subtract(1, "hour"), y: 64 }, + { x: moment().subtract(50, "minute"), y: 63 }, + { x: moment().subtract(40, "minute"), y: 60 }, + { x: moment().subtract(30, "minute"), y: 62 }, + { x: moment().subtract(20, "minute"), y: 61 }, + { x: moment().subtract(10, "minute"), y: 62 }, + ], + }, +]; + +export const LOUNGING_VS_ULTIMATE_FRISBEE_DATA: ITimeseriesWidgetData[] = [ + { + id: "series-a", + name: "Lounging", + description: "Shootin' the Breeze", + data: [ + { x: moment().subtract(10, "day"), y: 10 }, + { x: moment().subtract(9, "day"), y: 15 }, + { x: moment().subtract(8, "day"), y: 13 }, + { x: moment().subtract(7, "day"), y: 20 }, + { x: moment().subtract(6, "day"), y: 15 }, + { x: moment().subtract(5, "day"), y: 10 }, + { x: moment().subtract(4, "day"), y: 15 }, + { x: moment().subtract(3, "day"), y: 5 }, + { x: moment().subtract(2, "day"), y: 10 }, + { x: moment().subtract(1, "day"), y: 15 }, + { x: moment().subtract(24, "hour"), y: 14 }, + { x: moment().subtract(15, "hour"), y: 13 }, + { x: moment().subtract(10, "hour"), y: 15 }, + { x: moment().subtract(5, "hour"), y: 16 }, + { x: moment().subtract(1, "hour"), y: 14 }, + { x: moment().subtract(50, "minute"), y: 13 }, + { x: moment().subtract(40, "minute"), y: 10 }, + { x: moment().subtract(30, "minute"), y: 12 }, + { x: moment().subtract(20, "minute"), y: 11 }, + { x: moment().subtract(10, "minute"), y: 14 }, + ], + }, + { + id: "series-b", + name: "Frisbee Golfing", + description: "Golfin' with a disc", + data: [ + { x: moment().subtract(10, "day"), y: 80 }, + { x: moment().subtract(9, "day"), y: 84 }, + { x: moment().subtract(8, "day"), y: 80 }, + { x: moment().subtract(7, "day"), y: 75 }, + { x: moment().subtract(6, "day"), y: 95 }, + { x: moment().subtract(5, "day"), y: 85 }, + { x: moment().subtract(4, "day"), y: 80 }, + { x: moment().subtract(3, "day"), y: 85 }, + { x: moment().subtract(2, "day"), y: 85 }, + { x: moment().subtract(1, "day"), y: 80 }, + { x: moment().subtract(24, "hour"), y: 81 }, + { x: moment().subtract(15, "hour"), y: 85 }, + { x: moment().subtract(10, "hour"), y: 83 }, + { x: moment().subtract(5, "hour"), y: 88 }, + { x: moment().subtract(1, "hour"), y: 84 }, + { x: moment().subtract(50, "minute"), y: 83 }, + { x: moment().subtract(40, "minute"), y: 80 }, + { x: moment().subtract(30, "minute"), y: 82 }, + { x: moment().subtract(20, "minute"), y: 81 }, + { x: moment().subtract(10, "minute"), y: 82 }, + ], + }, +]; + +export const TABLE_DATA: BasicTableModel[] = [ + { + position: 1, + name: "FOCUS-SVR-02258", + features: ["remote-access-vpn-tunnel", "patch-manager01"], + status: "Active", + checks: { + icon: "status_up", + num: 25, + }, + "cpu-load": 86, + firstUrl: "https://en.wikipedia.org/wiki/Brno", + firstUrlLabel: "Brno", + secondUrl: "https://en.wikipedia.org/wiki/VMware_Workstation", + secondUrlLabel: "Workstation", + }, + { + position: 2, + name: "FOCUS-SVR-03312", + features: ["tools", "database", "orion-ape-backup"], + status: "Active", + checks: { + icon: "status_critical", + num: 25, + }, + "cpu-load": 47, + firstUrl: "https://en.wikipedia.org/wiki/Brno", + firstUrlLabel: "Brno", + secondUrl: "https://en.wikipedia.org/wiki/VMware_Workstation", + secondUrlLabel: "Workstation", + }, + { + position: 3, + name: "FOCUS-SVR-02258", + features: [ + "remote-access-vpn-tunnel", + "database", + "orion-ape-backup", + "patch-manager01", + ], + status: "Active", + checks: { + icon: "status_down", + num: 25, + }, + "cpu-load": 53, + firstUrl: "https://en.wikipedia.org/wiki/Kyiv", + firstUrlLabel: "Kyiv", + secondUrl: "https://en.wikipedia.org/wiki/VMware_Workstation", + secondUrlLabel: "Workstation", + }, + { + position: 4, + name: "Man-LT-JYJ4AD5", + features: [ + "remote-access-vpn-tunnel", + "tools", + "database", + "orion-ape-backup", + ], + status: "Active", + checks: { + icon: "status_up", + num: 25, + }, + "cpu-load": 32, + firstUrl: "https://en.wikipedia.org/wiki/Kyiv", + firstUrlLabel: "Kyiv", + secondUrl: "https://en.wikipedia.org/wiki/VirtualBox", + secondUrlLabel: "VirtualBox", + }, + { + position: 5, + name: "Man-LT-JYJ425", + features: [ + "remote-access-vpn-tunnel", + "tools", + "database", + "orion-ape-backup", + ], + status: "Active", + checks: { + icon: "status_up", + num: 25, + }, + "cpu-load": 22, + firstUrl: "https://en.wikipedia.org/wiki/Kyiv", + firstUrlLabel: "Kyiv", + secondUrl: "https://en.wikipedia.org/wiki/VirtualBox", + secondUrlLabel: "VirtualBox", + }, + { + position: 6, + name: "Man-LT-JYJ4333", + features: [ + "remote-access-vpn-tunnel", + "tools", + "database", + "orion-ape-backup", + ], + status: "Active", + checks: { + icon: "status_up", + num: 25, + }, + "cpu-load": 12, + firstUrl: "https://en.wikipedia.org/wiki/Kyiv", + firstUrlLabel: "Kyiv", + secondUrl: "https://en.wikipedia.org/wiki/VirtualBox", + secondUrlLabel: "VirtualBox", + }, + { + position: 7, + name: "FOCUS-SVR-02258", + features: ["remote-access-vpn-tunnel", "patch-manager01"], + status: "Active", + checks: { + icon: "status_up", + num: 25, + }, + "cpu-load": 86, + firstUrl: "https://en.wikipedia.org/wiki/Austin", + firstUrlLabel: "Austin", + secondUrl: "https://en.wikipedia.org/wiki/VMware_Workstation", + secondUrlLabel: "Workstation", + }, + { + position: 8, + name: "Man-LT-JYJ4AD5", + features: [ + "remote-access-vpn-tunnel", + "tools", + "database", + "orion-ape-backup", + "patch-manager01", + ], + status: "Active", + checks: { + icon: "status_inactive", + num: 25, + }, + "cpu-load": 35, + firstUrl: "https://en.wikipedia.org/wiki/Austin", + firstUrlLabel: "Austin", + secondUrl: "https://en.wikipedia.org/wiki/VMware_Workstation", + secondUrlLabel: "Workstation", + }, + { + position: 9, + name: "Man-LT-JYJ4AD5", + features: [ + "remote-access-vpn-tunnel", + "tools", + "database", + "patch-manager01", + ], + status: "Active", + checks: { + icon: "status_up", + num: 25, + }, + "cpu-load": 32, + firstUrl: "https://en.wikipedia.org/wiki/Brno", + firstUrlLabel: "Brno", + secondUrl: "https://en.wikipedia.org/wiki/VMware_Workstation", + secondUrlLabel: "Workstation", + }, + { + position: 10, + name: "Man-LT-JYJ4AD5", + features: [ + "remote-access-vpn-tunnel", + "database", + "orion-ape-backup", + "patch-manager01", + ], + status: "Active", + checks: { + icon: "status_up", + num: 25, + }, + "cpu-load": 64, + firstUrl: "https://en.wikipedia.org/wiki/Kyiv", + firstUrlLabel: "Kyiv", + secondUrl: "https://en.wikipedia.org/wiki/VMware_Workstation", + secondUrlLabel: "Workstation", + }, + { + position: 11, + name: "Man-LT-111", + features: [], + status: "Active", + checks: { + icon: "status_external", + num: 25, + }, + "cpu-load": 55, + firstUrl: "https://en.wikipedia.org/wiki/Brno", + firstUrlLabel: "Brno", + secondUrl: "https://en.wikipedia.org/wiki/VMware_Workstation", + secondUrlLabel: "Workstation", + }, + { + position: 12, + name: "Man-LT-2222", + features: [ + "remote-access-vpn-tunnel", + "tools", + "database", + "orion-ape-backup", + "patch-manager01", + ], + status: "Active", + checks: { + icon: "status_inactive", + num: 25, + }, + "cpu-load": 34, + firstUrl: "https://en.wikipedia.org/wiki/Brno", + firstUrlLabel: "Brno", + secondUrl: "https://en.wikipedia.org/wiki/VMware_Workstation", + secondUrlLabel: "Workstation", + }, + { + position: 13, + name: "Man-LT-333333", + features: [ + "remote-access-vpn-tunnel", + "tools", + "database", + "patch-manager01", + ], + status: "Active", + checks: { + icon: "status_up", + num: 25, + }, + "cpu-load": 56, + firstUrl: "https://en.wikipedia.org/wiki/Kyiv", + firstUrlLabel: "Kyiv", + secondUrl: "https://en.wikipedia.org/wiki/VMware_Workstation", + secondUrlLabel: "Workstation", + }, + { + position: 14, + name: "Man-LT-444444", + features: [ + "remote-access-vpn-tunnel", + "database", + "orion-ape-backup", + "patch-manager01", + ], + status: "Active", + checks: { + icon: "status_up", + num: 25, + }, + "cpu-load": 26, + firstUrl: "https://en.wikipedia.org/wiki/Kyiv", + firstUrlLabel: "Kyiv", + secondUrl: "https://en.wikipedia.org/wiki/VMware_Workstation", + secondUrlLabel: "Workstation", + }, + { + position: 15, + name: "Man-LT-555555", + features: [ + "remote-access-vpn-tunnel", + "database", + "orion-ape-backup", + "patch-manager01", + ], + status: "Active", + checks: { + icon: "status_up", + num: 25, + }, + "cpu-load": 76, + firstUrl: "https://en.wikipedia.org/wiki/Austin", + firstUrlLabel: "Austin", + secondUrl: "https://en.wikipedia.org/wiki/VMware_Workstation", + secondUrlLabel: "Workstation", + }, + { + position: 16, + name: "FOCUS-SVR-02258", + features: ["remote-access-vpn-tunnel", "patch-manager01"], + status: "Active", + checks: { + icon: "status_up", + num: 25, + }, + "cpu-load": 86, + firstUrl: "https://en.wikipedia.org/wiki/Brno", + firstUrlLabel: "Brno", + secondUrl: "https://en.wikipedia.org/wiki/VMware_Workstation", + secondUrlLabel: "Workstation", + }, + { + position: 17, + name: "FOCUS-SVR-03312", + features: ["tools", "database", "orion-ape-backup"], + status: "Active", + checks: { + icon: "status_critical", + num: 25, + }, + "cpu-load": 47, + firstUrl: "https://en.wikipedia.org/wiki/Brno", + firstUrlLabel: "Brno", + secondUrl: "https://en.wikipedia.org/wiki/VMware_Workstation", + secondUrlLabel: "Workstation", + }, + { + position: 18, + name: "FOCUS-SVR-02258", + features: [ + "remote-access-vpn-tunnel", + "database", + "orion-ape-backup", + "patch-manager01", + ], + status: "Active", + checks: { + icon: "status_down", + num: 25, + }, + "cpu-load": 53, + firstUrl: "https://en.wikipedia.org/wiki/Kyiv", + firstUrlLabel: "Kyiv", + secondUrl: "https://en.wikipedia.org/wiki/VMware_Workstation", + secondUrlLabel: "Workstation", + }, + { + position: 19, + name: "Man-LT-JYJ4AD5", + features: [ + "remote-access-vpn-tunnel", + "tools", + "database", + "orion-ape-backup", + ], + status: "Active", + checks: { + icon: "status_up", + num: 25, + }, + "cpu-load": 32, + firstUrl: "https://en.wikipedia.org/wiki/Kyiv", + firstUrlLabel: "Kyiv", + secondUrl: "https://en.wikipedia.org/wiki/VirtualBox", + secondUrlLabel: "VirtualBox", + }, + { + position: 20, + name: "Man-LT-JYJ425", + features: [ + "remote-access-vpn-tunnel", + "tools", + "database", + "orion-ape-backup", + ], + status: "Active", + checks: { + icon: "status_up", + num: 25, + }, + "cpu-load": 22, + firstUrl: "https://en.wikipedia.org/wiki/Kyiv", + firstUrlLabel: "Kyiv", + secondUrl: "https://en.wikipedia.org/wiki/VirtualBox", + secondUrlLabel: "VirtualBox", + }, + { + position: 21, + name: "Man-LT-JYJ4333", + features: [ + "remote-access-vpn-tunnel", + "tools", + "database", + "orion-ape-backup", + ], + status: "Active", + checks: { + icon: "status_up", + num: 25, + }, + "cpu-load": 12, + firstUrl: "https://en.wikipedia.org/wiki/Kyiv", + firstUrlLabel: "Kyiv", + secondUrl: "https://en.wikipedia.org/wiki/VirtualBox", + secondUrlLabel: "VirtualBox", + }, + { + position: 22, + name: "FOCUS-SVR-02258", + features: ["remote-access-vpn-tunnel", "patch-manager01"], + status: "Active", + checks: { + icon: "status_up", + num: 25, + }, + "cpu-load": 86, + firstUrl: "https://en.wikipedia.org/wiki/Austin", + firstUrlLabel: "Austin", + secondUrl: "https://en.wikipedia.org/wiki/VMware_Workstation", + secondUrlLabel: "Workstation", + }, + { + position: 23, + name: "Man-LT-JYJ4AD5", + features: [ + "remote-access-vpn-tunnel", + "tools", + "database", + "orion-ape-backup", + "patch-manager01", + ], + status: "Active", + checks: { + icon: "status_inactive", + num: 25, + }, + "cpu-load": 35, + firstUrl: "https://en.wikipedia.org/wiki/Austin", + firstUrlLabel: "Austin", + secondUrl: "https://en.wikipedia.org/wiki/VMware_Workstation", + secondUrlLabel: "Workstation", + }, + { + position: 24, + name: "Man-LT-JYJ4AD5", + features: [ + "remote-access-vpn-tunnel", + "tools", + "database", + "patch-manager01", + ], + status: "Active", + checks: { + icon: "status_up", + num: 25, + }, + "cpu-load": 32, + firstUrl: "https://en.wikipedia.org/wiki/Brno", + firstUrlLabel: "Brno", + secondUrl: "https://en.wikipedia.org/wiki/VMware_Workstation", + secondUrlLabel: "Workstation", + }, + { + position: 25, + name: "Man-LT-JYJ4AD5", + features: [ + "remote-access-vpn-tunnel", + "database", + "orion-ape-backup", + "patch-manager01", + ], + status: "Active", + checks: { + icon: "status_up", + num: 25, + }, + "cpu-load": 64, + firstUrl: "https://en.wikipedia.org/wiki/Kyiv", + firstUrlLabel: "Kyiv", + secondUrl: "https://en.wikipedia.org/wiki/VMware_Workstation", + secondUrlLabel: "Workstation", + }, + { + position: 26, + name: "Man-LT-111", + features: [], + status: "Active", + checks: { + icon: "status_external", + num: 25, + }, + "cpu-load": 55, + firstUrl: "https://en.wikipedia.org/wiki/Brno", + firstUrlLabel: "Brno", + secondUrl: "https://en.wikipedia.org/wiki/VMware_Workstation", + secondUrlLabel: "Workstation", + }, + { + position: 27, + name: "Man-LT-2222", + features: [ + "remote-access-vpn-tunnel", + "tools", + "database", + "orion-ape-backup", + "patch-manager01", + ], + status: "Active", + checks: { + icon: "status_inactive", + num: 25, + }, + "cpu-load": 34, + firstUrl: "https://en.wikipedia.org/wiki/Brno", + firstUrlLabel: "Brno", + secondUrl: "https://en.wikipedia.org/wiki/VMware_Workstation", + secondUrlLabel: "Workstation", + }, + { + position: 28, + name: "Man-LT-333333", + features: [ + "remote-access-vpn-tunnel", + "tools", + "database", + "patch-manager01", + ], + status: "Active", + checks: { + icon: "status_up", + num: 25, + }, + "cpu-load": 56, + firstUrl: "https://en.wikipedia.org/wiki/Kyiv", + firstUrlLabel: "Kyiv", + secondUrl: "https://en.wikipedia.org/wiki/VMware_Workstation", + secondUrlLabel: "Workstation", + }, + { + position: 29, + name: "Man-LT-444444", + features: [ + "remote-access-vpn-tunnel", + "database", + "orion-ape-backup", + "patch-manager01", + ], + status: "Active", + checks: { + icon: "status_up", + num: 25, + }, + "cpu-load": 26, + firstUrl: "https://en.wikipedia.org/wiki/Kyiv", + firstUrlLabel: "Kyiv", + secondUrl: "https://en.wikipedia.org/wiki/VMware_Workstation", + secondUrlLabel: "Workstation", + }, + { + position: 30, + name: "Man-LT-555555", + features: [ + "remote-access-vpn-tunnel", + "database", + "orion-ape-backup", + "patch-manager01", + ], + status: "Active", + checks: { + icon: "status_up", + num: 25, + }, + "cpu-load": 76, + firstUrl: "https://en.wikipedia.org/wiki/Austin", + firstUrlLabel: "Austin", + secondUrl: "https://en.wikipedia.org/wiki/VMware_Workstation", + secondUrlLabel: "Workstation", + }, + { + position: 31, + name: "FOCUS-SVR-02258", + features: ["remote-access-vpn-tunnel", "patch-manager01"], + status: "Active", + checks: { + icon: "status_up", + num: 25, + }, + "cpu-load": 86, + firstUrl: "https://en.wikipedia.org/wiki/Brno", + firstUrlLabel: "Brno", + secondUrl: "https://en.wikipedia.org/wiki/VMware_Workstation", + secondUrlLabel: "Workstation", + }, + { + position: 32, + name: "FOCUS-SVR-03312", + features: ["tools", "database", "orion-ape-backup"], + status: "Active", + checks: { + icon: "status_critical", + num: 25, + }, + "cpu-load": 47, + firstUrl: "https://en.wikipedia.org/wiki/Brno", + firstUrlLabel: "Brno", + secondUrl: "https://en.wikipedia.org/wiki/VMware_Workstation", + secondUrlLabel: "Workstation", + }, + { + position: 33, + name: "FOCUS-SVR-02258", + features: [ + "remote-access-vpn-tunnel", + "database", + "orion-ape-backup", + "patch-manager01", + ], + status: "Active", + checks: { + icon: "status_down", + num: 25, + }, + "cpu-load": 53, + firstUrl: "https://en.wikipedia.org/wiki/Kyiv", + firstUrlLabel: "Kyiv", + secondUrl: "https://en.wikipedia.org/wiki/VMware_Workstation", + secondUrlLabel: "Workstation", + }, + { + position: 34, + name: "Man-LT-JYJ4AD5", + features: [ + "remote-access-vpn-tunnel", + "tools", + "database", + "orion-ape-backup", + ], + status: "Active", + checks: { + icon: "status_up", + num: 25, + }, + "cpu-load": 32, + firstUrl: "https://en.wikipedia.org/wiki/Kyiv", + firstUrlLabel: "Kyiv", + secondUrl: "https://en.wikipedia.org/wiki/VirtualBox", + secondUrlLabel: "VirtualBox", + }, + { + position: 35, + name: "Man-LT-JYJ425", + features: [ + "remote-access-vpn-tunnel", + "tools", + "database", + "orion-ape-backup", + ], + status: "Active", + checks: { + icon: "status_up", + num: 25, + }, + "cpu-load": 22, + firstUrl: "https://en.wikipedia.org/wiki/Kyiv", + firstUrlLabel: "Kyiv", + secondUrl: "https://en.wikipedia.org/wiki/VirtualBox", + secondUrlLabel: "VirtualBox", + }, + { + position: 36, + name: "Man-LT-JYJ4333", + features: [ + "remote-access-vpn-tunnel", + "tools", + "database", + "orion-ape-backup", + ], + status: "Active", + checks: { + icon: "status_up", + num: 25, + }, + "cpu-load": 12, + firstUrl: "https://en.wikipedia.org/wiki/Kyiv", + firstUrlLabel: "Kyiv", + secondUrl: "https://en.wikipedia.org/wiki/VirtualBox", + secondUrlLabel: "VirtualBox", + }, + { + position: 37, + name: "FOCUS-SVR-02258", + features: ["remote-access-vpn-tunnel", "patch-manager01"], + status: "Active", + checks: { + icon: "status_up", + num: 25, + }, + "cpu-load": 86, + firstUrl: "https://en.wikipedia.org/wiki/Austin", + firstUrlLabel: "Austin", + secondUrl: "https://en.wikipedia.org/wiki/VMware_Workstation", + secondUrlLabel: "Workstation", + }, + { + position: 38, + name: "Man-LT-JYJ4AD5", + features: [ + "remote-access-vpn-tunnel", + "tools", + "database", + "orion-ape-backup", + "patch-manager01", + ], + status: "Active", + checks: { + icon: "status_inactive", + num: 25, + }, + "cpu-load": 35, + firstUrl: "https://en.wikipedia.org/wiki/Austin", + firstUrlLabel: "Austin", + secondUrl: "https://en.wikipedia.org/wiki/VMware_Workstation", + secondUrlLabel: "Workstation", + }, + { + position: 39, + name: "Man-LT-JYJ4AD5", + features: [ + "remote-access-vpn-tunnel", + "tools", + "database", + "patch-manager01", + ], + status: "Active", + checks: { + icon: "status_up", + num: 25, + }, + "cpu-load": 32, + firstUrl: "https://en.wikipedia.org/wiki/Brno", + firstUrlLabel: "Brno", + secondUrl: "https://en.wikipedia.org/wiki/VMware_Workstation", + secondUrlLabel: "Workstation", + }, + { + position: 40, + name: "Man-LT-JYJ4AD5", + features: [ + "remote-access-vpn-tunnel", + "database", + "orion-ape-backup", + "patch-manager01", + ], + status: "Active", + checks: { + icon: "status_up", + num: 25, + }, + "cpu-load": 64, + firstUrl: "https://en.wikipedia.org/wiki/Kyiv", + firstUrlLabel: "Kyiv", + secondUrl: "https://en.wikipedia.org/wiki/VMware_Workstation", + secondUrlLabel: "Workstation", + }, + { + position: 41, + name: "Man-LT-111", + features: [], + status: "Active", + checks: { + icon: "status_external", + num: 25, + }, + "cpu-load": 55, + firstUrl: "https://en.wikipedia.org/wiki/Brno", + firstUrlLabel: "Brno", + secondUrl: "https://en.wikipedia.org/wiki/VMware_Workstation", + secondUrlLabel: "Workstation", + }, + { + position: 42, + name: "Man-LT-2222", + features: [ + "remote-access-vpn-tunnel", + "tools", + "database", + "orion-ape-backup", + "patch-manager01", + ], + status: "Active", + checks: { + icon: "status_inactive", + num: 25, + }, + "cpu-load": 34, + firstUrl: "https://en.wikipedia.org/wiki/Brno", + firstUrlLabel: "Brno", + secondUrl: "https://en.wikipedia.org/wiki/VMware_Workstation", + secondUrlLabel: "Workstation", + }, + { + position: 43, + name: "Man-LT-333333", + features: [ + "remote-access-vpn-tunnel", + "tools", + "database", + "patch-manager01", + ], + status: "Active", + checks: { + icon: "status_up", + num: 25, + }, + "cpu-load": 56, + firstUrl: "https://en.wikipedia.org/wiki/Kyiv", + firstUrlLabel: "Kyiv", + secondUrl: "https://en.wikipedia.org/wiki/VMware_Workstation", + secondUrlLabel: "Workstation", + }, + { + position: 44, + name: "Man-LT-444444", + features: [ + "remote-access-vpn-tunnel", + "database", + "orion-ape-backup", + "patch-manager01", + ], + status: "Active", + checks: { + icon: "status_up", + num: 25, + }, + "cpu-load": 26, + firstUrl: "https://en.wikipedia.org/wiki/Kyiv", + firstUrlLabel: "Kyiv", + secondUrl: "https://en.wikipedia.org/wiki/VMware_Workstation", + secondUrlLabel: "Workstation", + }, + { + position: 45, + name: "Man-LT-555555", + features: [ + "remote-access-vpn-tunnel", + "database", + "orion-ape-backup", + "patch-manager01", + ], + status: "Active", + checks: { + icon: "status_up", + num: 25, + }, + "cpu-load": 76, + firstUrl: "https://en.wikipedia.org/wiki/Austin", + firstUrlLabel: "Austin", + secondUrl: "https://en.wikipedia.org/wiki/VMware_Workstation", + secondUrlLabel: "Workstation", + }, +]; +\\\\\\\\\\\\\\\`, + "overview/hero/widget-configs/kpi.ts": \\\\\\\\\\\\\\\`// © 2022 SolarWinds Worldwide, LLC. All rights reserved. +// +// Permission is hereby granted, free of charge, to any person obtaining a copy +// of this software and associated documentation files (the "Software"), to +// deal in the Software without restriction, including without limitation the +// rights to use, copy, modify, merge, publish, distribute, sublicense, and/or +// sell copies of the Software, and to permit persons to whom the Software is +// furnished to do so, subject to the following conditions: +// +// The above copyright notice and this permission notice shall be included in +// all copies or substantial portions of the Software. +// +// THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR +// IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, +// FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE +// AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER +// LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, +// OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN +// THE SOFTWARE. + +import { + DEFAULT_PIZZAGNA_ROOT, + IProviderConfiguration, + IRefresherProperties, + IWidget, + KpiComponent, + NOVA_KPI_DATASOURCE_ADAPTER, + PizzagnaLayer, + WellKnownProviders, +} from "@nova-ui/dashboards"; + +import { + HarryPotterAverageRatingDataSource, + HarryPotterRatingsCountDataSource, +} from "../data/kpi-datasources"; + +export const kpiConfig: IWidget = { + id: "kpiWidgetId", + type: "kpi", + pizzagna: { + [PizzagnaLayer.Configuration]: { + [DEFAULT_PIZZAGNA_ROOT]: { + providers: { + [WellKnownProviders.Refresher]: { + properties: { + // Configuring the refresher interval so that our data source is invoked every ten minutes + interval: 60 * 10, + enabled: true, + } as IRefresherProperties, + } as Partial, + }, + }, + header: { + properties: { + title: "Harry Potter and the Sorcerer's Stone", + subtitle: "By J. K. Rowling", + }, + }, + tiles: { + properties: { + nodes: ["kpi1", "kpi2"], + }, + }, + kpi1: { + id: "kpi1", + componentType: KpiComponent.lateLoadKey, + properties: { + widgetData: { + label: "Average Rating", + backgroundColor: "var(--nui-color-chart-three)", + units: "out of 5 Stars", + }, + }, + providers: { + [WellKnownProviders.DataSource]: { + // Setting the data source providerId for the tile with id "kpi1" + providerId: + HarryPotterAverageRatingDataSource.providerId, + } as IProviderConfiguration, + [WellKnownProviders.Adapter]: { + providerId: NOVA_KPI_DATASOURCE_ADAPTER, + properties: { + componentId: "kpi1", + propertyPath: "widgetData", + }, + } as IProviderConfiguration, + }, + }, + kpi2: { + id: "kpi2", + componentType: KpiComponent.lateLoadKey, + properties: { + widgetData: { + label: "Reader Feedback", + units: "Ratings", + }, + }, + providers: { + [WellKnownProviders.DataSource]: { + // Setting the data source providerId for the tile with id "kpi" + providerId: + HarryPotterRatingsCountDataSource.providerId, + } as IProviderConfiguration, + [WellKnownProviders.Adapter]: { + providerId: NOVA_KPI_DATASOURCE_ADAPTER, + properties: { + componentId: "kpi2", + propertyPath: "widgetData", + }, + } as IProviderConfiguration, + }, + }, + }, + }, +}; +\\\\\\\\\\\\\\\`, + "overview/hero/widget-configs/proportional.ts": \\\\\\\\\\\\\\\`// © 2022 SolarWinds Worldwide, LLC. All rights reserved. +// +// Permission is hereby granted, free of charge, to any person obtaining a copy +// of this software and associated documentation files (the "Software"), to +// deal in the Software without restriction, including without limitation the +// rights to use, copy, modify, merge, publish, distribute, sublicense, and/or +// sell copies of the Software, and to permit persons to whom the Software is +// furnished to do so, subject to the following conditions: +// +// The above copyright notice and this permission notice shall be included in +// all copies or substantial portions of the Software. +// +// THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR +// IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, +// FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE +// AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER +// LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, +// OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN +// THE SOFTWARE. + +import { + DEFAULT_PIZZAGNA_ROOT, + IProportionalWidgetChartOptions, + IProviderConfiguration, + IWidget, + LegendPlacement, + PizzagnaLayer, + ProportionalWidgetChartTypes, + WellKnownProviders, +} from "@nova-ui/dashboards"; + +import { BeerReviewCountsByCityMockDataSource } from "../data/proportional-datasources"; + +export const proportionalConfig: IWidget = { + id: "proportionalWidgetId", + type: "proportional", + pizzagna: { + [PizzagnaLayer.Configuration]: { + [DEFAULT_PIZZAGNA_ROOT]: { + providers: { + [WellKnownProviders.Refresher]: { + properties: { + interval: 0, + }, + }, + }, + }, + header: { + properties: { + title: "Beer Review Tally by City", + subtitle: "These People Love Beer", + }, + }, + chart: { + providers: { + [WellKnownProviders.DataSource]: { + providerId: + BeerReviewCountsByCityMockDataSource.providerId, + } as IProviderConfiguration, + }, + properties: { + configuration: { + chartOptions: { + type: ProportionalWidgetChartTypes.DonutChart, + legendPlacement: LegendPlacement.Right, + } as IProportionalWidgetChartOptions, + }, + }, + }, + }, + }, +}; +\\\\\\\\\\\\\\\`, + "overview/hero/widget-configs/risk-score.ts": \\\\\\\\\\\\\\\`// © 2023 SolarWinds Worldwide, LLC. All rights reserved. +// +// Permission is hereby granted, free of charge, to any person obtaining a copy +// of this software and associated documentation files (the "Software"), to +// deal in the Software without restriction, including without limitation the +// rights to use, copy, modify, merge, publish, distribute, sublicense, and/or +// sell copies of the Software, and to permit persons to whom the Software is +// furnished to do so, subject to the following conditions: +// +// The above copyright notice and this permission notice shall be included in +// all copies or substantial portions of the Software. +// +// THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR +// IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, +// FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE +// AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER +// LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, +// OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN +// THE SOFTWARE. + +import { + DEFAULT_PIZZAGNA_ROOT, + IProviderConfiguration, + IRefresherProperties, + IWidget, + RiskScoreTileComponent, + NOVA_KPI_DATASOURCE_ADAPTER, + PizzagnaLayer, + WellKnownProviders, +} from "@nova-ui/dashboards"; + +import { HarryPotterAverageRatingDataSource } from "../data/kpi-datasources"; + +export const riskScoreConfig: IWidget = { + id: "riskScoreWidgetId", + type: "risk-score", + pizzagna: { + [PizzagnaLayer.Configuration]: { + [DEFAULT_PIZZAGNA_ROOT]: { + providers: { + [WellKnownProviders.Refresher]: { + properties: { + // Configuring the refresher interval so that our data source is invoked every ten minutes + interval: 60 * 10, + enabled: true, + } as IRefresherProperties, + } as Partial, + }, + }, + header: { + properties: { + title: "Harry Potter and the Sorcerer's Stone", + subtitle: "By J. K. Rowling", + }, + }, + tiles: { + properties: { + nodes: ["riskScore1"], + }, + }, + riskScore1: { + id: "riskScore1", + componentType: RiskScoreTileComponent.lateLoadKey, + properties: { + widgetData: { + minValue: 0, + maxValue: 5, + useStaticLabel: false, + staticLabel: undefined, + label: \\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\`Average Rating\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\`, + description: \\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\`Harry Potter and the Sorcerer's Stone By J. K. Rowling Average Rating Risk Score\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\`, + }, + }, + providers: { + [WellKnownProviders.DataSource]: { + // Setting the data source providerId for the tile with id "riskScore1" + providerId: + HarryPotterAverageRatingDataSource.providerId, + } as IProviderConfiguration, + [WellKnownProviders.Adapter]: { + providerId: NOVA_KPI_DATASOURCE_ADAPTER, + properties: { + componentId: "riskScore1", + propertyPath: "widgetData", + }, + } as IProviderConfiguration, + }, + }, + }, + }, +}; +\\\\\\\\\\\\\\\`, + "overview/hero/widget-configs/table.ts": \\\\\\\\\\\\\\\`// © 2022 SolarWinds Worldwide, LLC. All rights reserved. +// +// Permission is hereby granted, free of charge, to any person obtaining a copy +// of this software and associated documentation files (the "Software"), to +// deal in the Software without restriction, including without limitation the +// rights to use, copy, modify, merge, publish, distribute, sublicense, and/or +// sell copies of the Software, and to permit persons to whom the Software is +// furnished to do so, subject to the following conditions: +// +// The above copyright notice and this permission notice shall be included in +// all copies or substantial portions of the Software. +// +// THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR +// IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, +// FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE +// AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER +// LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, +// OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN +// THE SOFTWARE. + +import { + ITableWidgetColumnConfig, + ITableWidgetSorterConfig, + IWidget, + PizzagnaLayer, + RawFormatterComponent, + WellKnownProviders, +} from "@nova-ui/dashboards"; + +import { BeerDataSource } from "../data/table/beer-data-source"; + +export const tableConfig: IWidget = { + id: "tableWidgetId", + type: "table", + pizzagna: { + [PizzagnaLayer.Configuration]: { + header: { + properties: { + title: "Stupendous Suds", + subtitle: "Try These Brilliant Brews", + }, + }, + table: { + providers: { + [WellKnownProviders.DataSource]: { + providerId: BeerDataSource.providerId, + }, + }, + properties: { + configuration: { + columns: [ + { + id: "column1", + label: "Beer Name", + isActive: true, + width: 185, + formatter: { + componentType: + RawFormatterComponent.lateLoadKey, + properties: { + dataFieldIds: { + value: "name", + }, + }, + }, + }, + { + id: "column2", + label: "Tagline", + isActive: true, + width: 250, + formatter: { + componentType: + RawFormatterComponent.lateLoadKey, + properties: { + dataFieldIds: { + value: "tagline", + }, + }, + }, + }, + { + id: "column3", + label: "First Brewed", + isActive: true, + formatter: { + componentType: + RawFormatterComponent.lateLoadKey, + properties: { + dataFieldIds: { + value: "first_brewed", + }, + }, + }, + }, + { + id: "column4", + label: "Description", + isActive: true, + width: 275, + formatter: { + componentType: + RawFormatterComponent.lateLoadKey, + properties: { + dataFieldIds: { + value: "description", + }, + }, + }, + }, + ] as ITableWidgetColumnConfig[], + sorterConfiguration: { + descendantSorting: false, + sortBy: "", + } as ITableWidgetSorterConfig, + hasVirtualScroll: true, + }, + }, + }, + }, + }, +}; +\\\\\\\\\\\\\\\`, + "overview/hero/widget-configs/timeseries.ts": \\\\\\\\\\\\\\\`// © 2022 SolarWinds Worldwide, LLC. All rights reserved. +// +// Permission is hereby granted, free of charge, to any person obtaining a copy +// of this software and associated documentation files (the "Software"), to +// deal in the Software without restriction, including without limitation the +// rights to use, copy, modify, merge, publish, distribute, sublicense, and/or +// sell copies of the Software, and to permit persons to whom the Software is +// furnished to do so, subject to the following conditions: +// +// The above copyright notice and this permission notice shall be included in +// all copies or substantial portions of the Software. +// +// THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR +// IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, +// FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE +// AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER +// LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, +// OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN +// THE SOFTWARE. + +import moment from "moment/moment"; + +import { + DEFAULT_PIZZAGNA_ROOT, + IProviderConfiguration, + ISerializableTimeframe, + ITimeseriesItemConfiguration, + IWidget, + LegendPlacement, + WellKnownProviders, +} from "@nova-ui/dashboards"; + +import { BeerVsReadingMockDataSource } from "../data/timeseries-data-sources"; + +export const timeseriesConfig: IWidget = { + id: "timeseriesWidgetId", + type: "timeseries", + pizzagna: { + configuration: { + [DEFAULT_PIZZAGNA_ROOT]: { + providers: { + [WellKnownProviders.DataSource]: { + providerId: BeerVsReadingMockDataSource.providerId, + } as IProviderConfiguration, + }, + }, + header: { + properties: { + title: "Primary Leisure Activity Over Time", + subtitle: "Survey of 1000 Solarians", + }, + }, + chart: { + providers: { + [WellKnownProviders.Adapter]: { + properties: { + series: [ + { + id: "series-1", + label: "Beer Tasting", + selectedSeriesId: "series-1", + }, + { + id: "series-2", + label: "Reading", + selectedSeriesId: "series-2", + }, + ] as ITimeseriesItemConfiguration[], + }, + } as Partial, + }, + properties: { + configuration: { + legendPlacement: LegendPlacement.Right, + enableZoom: true, + leftAxisLabel: "Solarians (%)", + }, + }, + }, + timeframeSelection: { + properties: { + timeframe: { + selectedPresetId: "last7Days", + } as ISerializableTimeframe, + minDate: moment().subtract(10, "days").format(), + maxDate: moment().format(), + }, + }, + }, + }, +}; +\\\\\\\\\\\\\\\`, + "overview/overview-docs.component.ts": \\\\\\\\\\\\\\\`// © 2022 SolarWinds Worldwide, LLC. All rights reserved. +// +// Permission is hereby granted, free of charge, to any person obtaining a copy +// of this software and associated documentation files (the "Software"), to +// deal in the Software without restriction, including without limitation the +// rights to use, copy, modify, merge, publish, distribute, sublicense, and/or +// sell copies of the Software, and to permit persons to whom the Software is +// furnished to do so, subject to the following conditions: +// +// The above copyright notice and this permission notice shall be included in +// all copies or substantial portions of the Software. +// +// THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR +// IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, +// FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE +// AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER +// LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, +// OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN +// THE SOFTWARE. + +import { Component } from "@angular/core"; + +@Component({ + selector: "nui-dashboard-overview-docs", + templateUrl: "./overview-docs.component.html", + standalone: false, +}) +export class OverviewDocsComponent {} +\\\\\\\\\\\\\\\`, + "overview/overview.module.ts": \\\\\\\\\\\\\\\`// © 2022 SolarWinds Worldwide, LLC. All rights reserved. +// +// Permission is hereby granted, free of charge, to any person obtaining a copy +// of this software and associated documentation files (the "Software"), to +// deal in the Software without restriction, including without limitation the +// rights to use, copy, modify, merge, publish, distribute, sublicense, and/or +// sell copies of the Software, and to permit persons to whom the Software is +// furnished to do so, subject to the following conditions: +// +// The above copyright notice and this permission notice shall be included in +// all copies or substantial portions of the Software. +// +// THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR +// IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, +// FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE +// AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER +// LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, +// OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN +// THE SOFTWARE. + +import { CommonModule } from "@angular/common"; +import { NgModule } from "@angular/core"; +import { RouterModule } from "@angular/router"; + +import { + NuiBusyModule, + NuiButtonModule, + NuiDocsModule, + NuiIconModule, + NuiMessageModule, + NuiSwitchModule, +} from "@nova-ui/bits"; +import { + ConfiguratorHeadingService, + IFormatterDefinition, + LinkFormatterComponent, + NuiDashboardsModule, + WellKnownPathKey, + WidgetTypesService, +} from "@nova-ui/dashboards"; + +import { HeroDashboardComponent } from "./hero/dashboard/hero-dashboard.component"; +import { + HarryPotterAverageRatingDataSource, + HarryPotterRatingsCountDataSource, +} from "./hero/data/kpi-datasources"; +import { + BeerReviewCountsByCityMockDataSource, + BeerReviewCountsByCityMockDataSource2, +} from "./hero/data/proportional-datasources"; +import { BeerDataSource } from "./hero/data/table/beer-data-source"; +import { RandomUserDataSource } from "./hero/data/table/random-user-data-source"; +import { + BeerVsReadingMockDataSource, + LoungingVsFrisbeeGolfMockDataSource, +} from "./hero/data/timeseries-data-sources"; +import { OverviewDocsComponent } from "./overview-docs.component"; + +const routes = [ + { + path: "", + component: OverviewDocsComponent, + data: { + srlc: { + hideIndicator: true, + }, + showThemeSwitcher: true, + }, + }, + { + path: "hero", + component: HeroDashboardComponent, + data: { + srlc: { + hideIndicator: true, + }, + }, + }, +]; + +@NgModule({ + imports: [ + CommonModule, + NuiDashboardsModule, + NuiBusyModule, + NuiButtonModule, + NuiDocsModule, + NuiMessageModule, + NuiSwitchModule, + NuiIconModule, + RouterModule.forChild(routes), + ], + declarations: [OverviewDocsComponent, HeroDashboardComponent], + providers: [ConfiguratorHeadingService], +}) +export default class OverviewModule { + constructor(private widgetTypesService: WidgetTypesService) { + this.setupDataSourceProviders(); + this.setupProportionalLegendFormatters(); + } + + private setupDataSourceProviders() { + this.setDataSourceProviders("table", [ + RandomUserDataSource.providerId, + BeerDataSource.providerId, + ]); + this.setDataSourceProviders("kpi", [ + HarryPotterAverageRatingDataSource.providerId, + HarryPotterRatingsCountDataSource.providerId, + ]); + this.setDataSourceProviders("risk-score", [ + HarryPotterAverageRatingDataSource.providerId, + HarryPotterRatingsCountDataSource.providerId, + ]); + this.setDataSourceProviders("proportional", [ + BeerReviewCountsByCityMockDataSource.providerId, + BeerReviewCountsByCityMockDataSource2.providerId, + ]); + this.setDataSourceProviders("timeseries", [ + BeerVsReadingMockDataSource.providerId, + LoungingVsFrisbeeGolfMockDataSource.providerId, + ]); + } + + private setDataSourceProviders(type: string, providers: string[]) { + const widgetTemplate = this.widgetTypesService.getWidgetType(type, 1); + this.widgetTypesService.setNode( + widgetTemplate, + "configurator", + WellKnownPathKey.DataSourceProviders, + providers + ); + } + + private setupProportionalLegendFormatters() { + const formatters: IFormatterDefinition[] = [ + { + componentType: LinkFormatterComponent.lateLoadKey, + label: $localize\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\`Link\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\`, + dataTypes: { + value: "label", + link: "link", + }, + }, + ]; + + const widgetTemplate = this.widgetTypesService.getWidgetType( + "proportional", + 1 + ); + this.widgetTypesService.setNode( + widgetTemplate, + "configurator", + WellKnownPathKey.Formatters, + formatters + ); + } +} +\\\\\\\\\\\\\\\`, + "tutorials/customization/configurator-section/custom-configurator-section/custom-configurator-section.example.component.ts": \\\\\\\\\\\\\\\`// © 2022 SolarWinds Worldwide, LLC. All rights reserved. +// +// Permission is hereby granted, free of charge, to any person obtaining a copy +// of this software and associated documentation files (the "Software"), to +// deal in the Software without restriction, including without limitation the +// rights to use, copy, modify, merge, publish, distribute, sublicense, and/or +// sell copies of the Software, and to permit persons to whom the Software is +// furnished to do so, subject to the following conditions: +// +// The above copyright notice and this permission notice shall be included in +// all copies or substantial portions of the Software. +// +// THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR +// IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, +// FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE +// AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER +// LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, +// OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN +// THE SOFTWARE. + +import { HttpClient, HttpErrorResponse } from "@angular/common/http"; +import { + ChangeDetectionStrategy, + ChangeDetectorRef, + Component, + EventEmitter, + Injectable, + Input, + OnChanges, + OnDestroy, + OnInit, + Output, + SimpleChanges, +} from "@angular/core"; +import { FormBuilder, FormGroup, Validators } from "@angular/forms"; +import { GridsterConfig, GridsterItem } from "angular-gridster2"; +// eslint-disable-next-line import/no-deprecated +import { BehaviorSubject, combineLatest, Observable } from "rxjs"; +// eslint-disable-next-line import/no-deprecated +import { finalize, map, startWith } from "rxjs/operators"; + +import { DataSourceService, IFilteringOutputs } from "@nova-ui/bits"; +import { + ComponentRegistryService, + DATA_SOURCE, + DEFAULT_PIZZAGNA_ROOT, + IDashboard, + IHasChangeDetector, + IHasForm, + IKpiData, + IProviderConfiguration, + IRefresherProperties, + IWidget, + IWidgets, + KpiComponent, + NOVA_KPI_DATASOURCE_ADAPTER, + PizzagnaLayer, + ProviderRegistryService, + WellKnownPathKey, + WellKnownProviders, + WidgetTypesService, +} from "@nova-ui/dashboards"; + +/** + * A custom version of the KpiDescriptionConfigurationComponent provided by the dashboards framework. + * --- + * For this example, the existing background color selection functionality has been replaced by custom + * template content. + */ +@Component({ + selector: "custom-kpi-description-configuration", + template: \\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\` + + +
+
+ + + +
+ + +
+
+ Custom Content +
+
+ The default version of this configurator section + displays a background color selector here. +
+
+ + +
+ + + +
+
+
+ \\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\`, + styleUrls: ["./custom-configurator-section.example.component.less"], + changeDetection: ChangeDetectionStrategy.OnPush, + standalone: false, +}) +// Remember to declare this class in the parent module +export class CustomKpiDescriptionConfigurationComponent + implements OnInit, OnChanges, IHasChangeDetector, IHasForm +{ + // Ensure that the lateLoadKey value matches class name + public static lateLoadKey = "CustomKpiDescriptionConfigurationComponent"; + + @Input() componentId: string; + @Input() configurableUnits: boolean; + + @Input() label: string = ""; + @Input() units: string = ""; + + @Output() formReady = new EventEmitter(); + + public form: FormGroup; + public subtitle$: Observable; + + constructor( + public changeDetector: ChangeDetectorRef, + private formBuilder: FormBuilder + ) {} + + public ngOnInit(): void { + this.form = this.formBuilder.group({ + label: [this.label, [Validators.required]], + }); + + if (this.configurableUnits) { + this.form.addControl("units", this.formBuilder.control(this.units)); + } + + const label = this.form.get("label"); + // eslint-disable-next-line import/no-deprecated + const labelValue = label?.valueChanges.pipe(startWith(label?.value)); + + // eslint-disable-next-line import/no-deprecated + this.subtitle$ = combineLatest([ + labelValue?.pipe(map((t) => t || $localize\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\`no label\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\`)), + ]).pipe(map((labels) => labels.join(", "))); + + this.formReady.emit(this.form); + } + + public ngOnChanges(changes: SimpleChanges): void { + if (changes.label) { + this.form.patchValue({ label: changes.label.currentValue }); + } + if (changes.units) { + this.form.patchValue({ units: changes.units.currentValue }); + } + } +} + +/** + * A simple KPI data source to retrieve the average rating of Harry Potter and the Sorcerer's Stone (book) via googleapis + */ +@Injectable() +export class AverageRatingKpiDataSource + extends DataSourceService + implements OnDestroy +{ + // This is the ID we'll use to identify the provider + public static providerId = "AverageRatingKpiDataSource"; + + // Use this subject to communicate the data source's busy state + public busy = new BehaviorSubject(false); + + constructor(private http: HttpClient) { + super(); + } + + // In this example, getFilteredData is invoked every 10 minutes (Take a look at the refresher + // provider definition in the widget configuration below to see how the interval is set) + public async getFilteredData(): Promise { + this.busy.next(true); + return new Promise((resolve) => { + // *** Make a resource request to an external API (if needed) + this.http + .get("https://www.googleapis.com/books/v1/volumes/5MQFrgEACAAJ") + .pipe(finalize(() => this.busy.next(false))) + .subscribe({ + next: (data: any) => { + resolve({ + result: { + value: data.volumeInfo.averageRating, + }, + }); + }, + error: (error: HttpErrorResponse) => { + resolve({ + result: null, + error: { + type: error.status, + }, + }); + }, + }); + }); + } + + public ngOnDestroy(): void { + this.outputsSubject.complete(); + } +} + +/** + * A simple KPI data source to retrieve the ratings count of Harry Potter and the Sorcerer's Stone (book) via googleapis + */ +@Injectable() +export class RatingsCountKpiDataSource + extends DataSourceService + implements OnDestroy +{ + public static providerId = "RatingsCountKpiDataSource"; + + // Use this subject to communicate the data source's busy state + public busy = new BehaviorSubject(false); + + constructor(private http: HttpClient) { + super(); + } + + public async getFilteredData(): Promise { + this.busy.next(true); + return new Promise((resolve) => { + this.http + .get("https://www.googleapis.com/books/v1/volumes/5MQFrgEACAAJ") + .pipe(finalize(() => this.busy.next(false))) + .subscribe({ + next: (data: any) => { + resolve({ + result: { + value: data.volumeInfo.ratingsCount, + }, + }); + }, + error: (error: HttpErrorResponse) => { + resolve({ + result: null, + error: { + type: error.status, + }, + }); + }, + }); + }); + } + + public ngOnDestroy(): void { + this.outputsSubject.complete(); + } +} + +/** + * A component that instantiates the dashboard + */ +@Component({ + selector: "custom-configurator-section-example", + templateUrl: "./custom-configurator-section.example.component.html", + styleUrls: ["./custom-configurator-section.example.component.less"], + standalone: false, +}) +export class CustomConfiguratorSectionExampleComponent implements OnInit { + // This variable will hold all the data needed to define the layout and behavior of the widgets. + // Pass this to the dashboard component's dashboard input in the template. + public dashboard: IDashboard | undefined; + + // Angular gridster requires a configuration object even if it's empty. + // Pass this to the dashboard component's gridsterConfig input in the template. + public gridsterConfig: GridsterConfig = {}; + + // Boolean which dashboard takes in as an input if its true it allows you to move widgets around. + public editMode: boolean = false; + + constructor( + // WidgetTypesService provides the widget's necessary structure information + private widgetTypesService: WidgetTypesService, + + // In general, the ProviderRegistryService is used for making entities available for injection into dynamically loaded components. + private providerRegistry: ProviderRegistryService, + + // Inject the ComponentRegistryService to make our custom component available for late loading by the dashboards framework + private componentRegistry: ComponentRegistryService, + + private changeDetectorRef: ChangeDetectorRef + ) {} + + public ngOnInit(): void { + // Grab the widget's default template which will be needed as a parameter for setNode. + const widgetTemplate = this.widgetTypesService.getWidgetType("kpi", 1); + + // Replace the default KPI description configuration component with our custom one. + // Note: This could also be done in the parent module's constructor to give + // multiple dashboards access to the same custom configurator section. + this.widgetTypesService.setNode( + widgetTemplate, + "configurator", + WellKnownPathKey.TileDescriptionConfigComponentType, + CustomKpiDescriptionConfigurationComponent.lateLoadKey + ); + + // Register the custom configurator section with the component registry to make it available + // for late loading by the dashboards framework. + this.componentRegistry.registerByLateLoadKey( + CustomKpiDescriptionConfigurationComponent + ); + + // Register our data sources as dropdown options in the widget editor/configurator + // Note: This could also be done in the parent module's constructor so that + // multiple dashboards could have access to the same widget template modification. + this.widgetTypesService.setNode( + // This is the template we grabbed above with getWidgetType + widgetTemplate, + // We are setting the editor/configurator part of the widget template + "configurator", + // This indicates which node you are changing and we want to change + // the data source providers available for selection in the editor. + WellKnownPathKey.DataSourceProviders, + // We are setting the data sources available for selection in the editor + [ + AverageRatingKpiDataSource.providerId, + RatingsCountKpiDataSource.providerId, + ] + ); + + // Register the data sources available for injection into the KPI tiles. + // Note: Each tile of a KPI widget is assigned its own instance of a data source + this.providerRegistry.setProviders({ + [AverageRatingKpiDataSource.providerId]: { + provide: DATA_SOURCE, + useClass: AverageRatingKpiDataSource, + // Any dependencies that need to be injected into the provider must be listed here + deps: [HttpClient], + }, + [RatingsCountKpiDataSource.providerId]: { + provide: DATA_SOURCE, + useClass: RatingsCountKpiDataSource, + deps: [HttpClient], + }, + }); + + this.initializeDashboard(); + } + + public initializeDashboard(): void { + // We're using a static configuration object for this example (see widgetConfig at the bottom of the file), + // but this is where the widget's configuration could potentially be populated from a database + const kpiWidget = widgetConfig; + const widgetIndex: IWidgets = { + // Complete the KPI widget with information coming from its type definition + [kpiWidget.id]: + this.widgetTypesService.mergeWithWidgetType(kpiWidget), + }; + + // Setting the widget dimensions and position (this is for gridster) + const positions: Record = { + [kpiWidget.id]: { + cols: 4, + rows: 6, + y: 0, + x: 0, + }, + }; + + // Finally, assigning the variables we created above to the dashboard + this.dashboard = { + positions, + widgets: widgetIndex, + }; + } + + /** Used for restoring widgets state */ + public reInitializeDashboard(): void { + // destroys the components and their providers so the dashboard can re init data + this.dashboard = undefined; + this.changeDetectorRef.detectChanges(); + + this.initializeDashboard(); + } +} + +const widgetConfig: IWidget = { + id: "widget1", + type: "kpi", + pizzagna: { + [PizzagnaLayer.Configuration]: { + [DEFAULT_PIZZAGNA_ROOT]: { + providers: { + [WellKnownProviders.Refresher]: { + properties: { + // Configuring the refresher interval so that our data source is invoked every ten minutes + interval: 60 * 10, + enabled: true, + } as IRefresherProperties, + } as Partial, + }, + }, + header: { + properties: { + title: "Harry Potter and the Sorcerer's Stone", + subtitle: "By J. K. Rowling", + }, + }, + tiles: { + properties: { + nodes: ["kpi1"], + }, + }, + kpi1: { + id: "kpi1", + componentType: KpiComponent.lateLoadKey, + properties: { + widgetData: { + units: "out of 5 Stars", + label: "Average Rating", + }, + }, + providers: { + [WellKnownProviders.DataSource]: { + // Setting the data source providerId for the tile with id "kpi1" + providerId: AverageRatingKpiDataSource.providerId, + } as IProviderConfiguration, + [WellKnownProviders.Adapter]: { + providerId: NOVA_KPI_DATASOURCE_ADAPTER, + properties: { + componentId: "kpi1", + propertyPath: "widgetData", + }, + } as IProviderConfiguration, + }, + }, + }, + }, +}; +\\\\\\\\\\\\\\\`, + "tutorials/customization/configurator-section/custom-configurator-section-docs.component.ts": \\\\\\\\\\\\\\\`// © 2022 SolarWinds Worldwide, LLC. All rights reserved. +// +// Permission is hereby granted, free of charge, to any person obtaining a copy +// of this software and associated documentation files (the "Software"), to +// deal in the Software without restriction, including without limitation the +// rights to use, copy, modify, merge, publish, distribute, sublicense, and/or +// sell copies of the Software, and to permit persons to whom the Software is +// furnished to do so, subject to the following conditions: +// +// The above copyright notice and this permission notice shall be included in +// all copies or substantial portions of the Software. +// +// THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR +// IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, +// FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE +// AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER +// LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, +// OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN +// THE SOFTWARE. + +import { Component } from "@angular/core"; + +@Component({ + selector: "custom-configurator-section-docs", + templateUrl: "./custom-configurator-section-docs.component.html", + standalone: false, +}) +export class CustomConfiguratorSectionDocsComponent {} +\\\\\\\\\\\\\\\`, + "tutorials/customization/configurator-section/custom-configurator-section.module.ts": \\\\\\\\\\\\\\\`// © 2022 SolarWinds Worldwide, LLC. All rights reserved. +// +// Permission is hereby granted, free of charge, to any person obtaining a copy +// of this software and associated documentation files (the "Software"), to +// deal in the Software without restriction, including without limitation the +// rights to use, copy, modify, merge, publish, distribute, sublicense, and/or +// sell copies of the Software, and to permit persons to whom the Software is +// furnished to do so, subject to the following conditions: +// +// The above copyright notice and this permission notice shall be included in +// all copies or substantial portions of the Software. +// +// THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR +// IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, +// FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE +// AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER +// LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, +// OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN +// THE SOFTWARE. + +import { CommonModule } from "@angular/common"; +import { HttpClientModule } from "@angular/common/http"; +import { NgModule } from "@angular/core"; +import { ReactiveFormsModule } from "@angular/forms"; +import { RouterModule } from "@angular/router"; + +import { + NuiButtonModule, + NuiDocsModule, + NuiFormFieldModule, + NuiIconModule, + NuiMessageModule, + NuiSwitchModule, + NuiTextboxModule, + DEMO_PATH_TOKEN, +} from "@nova-ui/bits"; +import { + NuiDashboardConfiguratorModule, + NuiDashboardsModule, +} from "@nova-ui/dashboards"; + +import { + CustomConfiguratorSectionExampleComponent, + CustomKpiDescriptionConfigurationComponent, +} from "./custom-configurator-section/custom-configurator-section.example.component"; +import { CustomConfiguratorSectionDocsComponent } from "./custom-configurator-section-docs.component"; +import { getDemoFiles } from "../../../../../demo-files-factory"; + +const routes = [ + { + path: "", + component: CustomConfiguratorSectionDocsComponent, + data: { + srlc: { + hideIndicator: true, + }, + showThemeSwitcher: true, + }, + }, + { + path: "example", + component: CustomConfiguratorSectionExampleComponent, + data: { + srlc: { + hideIndicator: true, + }, + }, + }, +]; + +@NgModule({ + imports: [ + CommonModule, + ReactiveFormsModule, + HttpClientModule, + NuiDashboardsModule, + NuiDashboardConfiguratorModule, + NuiDocsModule, + NuiFormFieldModule, + NuiIconModule, + NuiMessageModule, + NuiSwitchModule, + NuiTextboxModule, + NuiButtonModule, + RouterModule.forChild(routes), + ], + declarations: [ + CustomConfiguratorSectionDocsComponent, + CustomKpiDescriptionConfigurationComponent, + CustomConfiguratorSectionExampleComponent, + ], + providers: [ + { + provide: DEMO_PATH_TOKEN, + useValue: getDemoFiles("configurator-section"), + }, + ], +}) +export default class CustomConfiguratorSectionModule {} +\\\\\\\\\\\\\\\`, + "tutorials/customization/customization.module.ts": \\\\\\\\\\\\\\\`// © 2022 SolarWinds Worldwide, LLC. All rights reserved. +// +// Permission is hereby granted, free of charge, to any person obtaining a copy +// of this software and associated documentation files (the "Software"), to +// deal in the Software without restriction, including without limitation the +// rights to use, copy, modify, merge, publish, distribute, sublicense, and/or +// sell copies of the Software, and to permit persons to whom the Software is +// furnished to do so, subject to the following conditions: +// +// The above copyright notice and this permission notice shall be included in +// all copies or substantial portions of the Software. +// +// THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR +// IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, +// FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE +// AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER +// LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, +// OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN +// THE SOFTWARE. + +import { NgModule, Type } from "@angular/core"; +import { RouterModule, Routes } from "@angular/router"; + +import { ConfiguratorHeadingService } from "@nova-ui/dashboards"; + +enum CustomizationModuleRoute { + ConfiguratorSection = "configurator-section", + Widget = "widget", + Formatter = "formatter", + DataSourceConfigurator = "data-source-configurator", +} + +const routes: Routes = [ + { + path: CustomizationModuleRoute.ConfiguratorSection, + loadChildren: async () => + import( + "./configurator-section/custom-configurator-section.module" + ) as object as Promise>, + }, + { + path: CustomizationModuleRoute.Widget, + loadChildren: async () => + import("./widget/custom-widget.module") as object as Promise< + Type + >, + }, + { + path: CustomizationModuleRoute.Formatter, + loadChildren: async () => + import("./formatter/custom-formatter.module") as object as Promise< + Type + >, + }, + { + path: CustomizationModuleRoute.DataSourceConfigurator, + loadChildren: async () => + import( + "./data-source-configurator/custom-data-source-configurator.module" + ) as object as Promise>, + }, +]; + +@NgModule({ + imports: [RouterModule.forChild(routes)], + providers: [ConfiguratorHeadingService], +}) +export default class CustomizationModule {} +\\\\\\\\\\\\\\\`, + "tutorials/customization/data-source-configurator/custom-data-source-configurator-docs.component.ts": \\\\\\\\\\\\\\\`// © 2022 SolarWinds Worldwide, LLC. All rights reserved. +// +// Permission is hereby granted, free of charge, to any person obtaining a copy +// of this software and associated documentation files (the "Software"), to +// deal in the Software without restriction, including without limitation the +// rights to use, copy, modify, merge, publish, distribute, sublicense, and/or +// sell copies of the Software, and to permit persons to whom the Software is +// furnished to do so, subject to the following conditions: +// +// The above copyright notice and this permission notice shall be included in +// all copies or substantial portions of the Software. +// +// THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR +// IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, +// FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE +// AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER +// LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, +// OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN +// THE SOFTWARE. + +import { Component } from "@angular/core"; + +@Component({ + selector: "nui-custom-data-source-configurator-docs", + templateUrl: "./custom-data-source-configurator-docs.component.html", + standalone: false, +}) +export class CustomDataSourceConfiguratorDocComponent {} +\\\\\\\\\\\\\\\`, + "tutorials/customization/data-source-configurator/custom-data-source-configurator.module.ts": \\\\\\\\\\\\\\\`// © 2022 SolarWinds Worldwide, LLC. All rights reserved. +// +// Permission is hereby granted, free of charge, to any person obtaining a copy +// of this software and associated documentation files (the "Software"), to +// deal in the Software without restriction, including without limitation the +// rights to use, copy, modify, merge, publish, distribute, sublicense, and/or +// sell copies of the Software, and to permit persons to whom the Software is +// furnished to do so, subject to the following conditions: +// +// The above copyright notice and this permission notice shall be included in +// all copies or substantial portions of the Software. +// +// THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR +// IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, +// FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE +// AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER +// LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, +// OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN +// THE SOFTWARE. + +import { NgModule } from "@angular/core"; +import { ReactiveFormsModule } from "@angular/forms"; +import { RouterModule, Routes } from "@angular/router"; + +// eslint-disable-next-line max-len +import { + NuiButtonModule, + NuiDocsModule, + NuiFormFieldModule, + NuiIconModule, + NuiMessageModule, + NuiSelectV2Module, + NuiSwitchModule, + NuiTextboxModule, + NuiValidationMessageModule, + DEMO_PATH_TOKEN, +} from "@nova-ui/bits"; +import { + NuiDashboardConfiguratorModule, + NuiDashboardsModule, +} from "@nova-ui/dashboards"; + +import { CustomDataSourceConfiguratorDocComponent } from "./custom-data-source-configurator-docs.component"; +import { + CustomDataSourceConfiguratorExampleComponent, + HarryPotterDataSourceConfiguratorComponent, +} from "./example/custom-data-source-configurator-example.component"; +import { getDemoFiles } from "../../../../../demo-files-factory"; + +const routes: Routes = [ + { + path: "", + component: CustomDataSourceConfiguratorDocComponent, + data: { + srlc: { + hideIndicator: true, + }, + showThemeSwitcher: true, + }, + }, +]; + +@NgModule({ + imports: [ + RouterModule.forChild(routes), + NuiDocsModule, + NuiButtonModule, + NuiMessageModule, + NuiDashboardConfiguratorModule, + NuiDashboardsModule, + NuiFormFieldModule, + NuiTextboxModule, + NuiSwitchModule, + NuiSelectV2Module, + NuiValidationMessageModule, + NuiIconModule, + ReactiveFormsModule, + ], + declarations: [ + CustomDataSourceConfiguratorDocComponent, + CustomDataSourceConfiguratorExampleComponent, + HarryPotterDataSourceConfiguratorComponent, + ], + providers: [ + { + provide: DEMO_PATH_TOKEN, + useValue: getDemoFiles("data-source-configurator"), + }, + ], +}) +export default class CustomDataSourceConfiguratorModuleRoute {} +\\\\\\\\\\\\\\\`, + "tutorials/customization/data-source-configurator/example/custom-data-source-configurator-example.component.ts": \\\\\\\\\\\\\\\`// © 2022 SolarWinds Worldwide, LLC. All rights reserved. +// +// Permission is hereby granted, free of charge, to any person obtaining a copy +// of this software and associated documentation files (the "Software"), to +// deal in the Software without restriction, including without limitation the +// rights to use, copy, modify, merge, publish, distribute, sublicense, and/or +// sell copies of the Software, and to permit persons to whom the Software is +// furnished to do so, subject to the following conditions: +// +// The above copyright notice and this permission notice shall be included in +// all copies or substantial portions of the Software. +// +// THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR +// IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, +// FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE +// AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER +// LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, +// OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN +// THE SOFTWARE. + +import { HttpClient, HttpErrorResponse } from "@angular/common/http"; +import { + ChangeDetectorRef, + Component, + Inject, + Injectable, + Injector, + OnDestroy, + OnInit, +} from "@angular/core"; +import { FormBuilder, Validators } from "@angular/forms"; +import { GridsterConfig, GridsterItem } from "angular-gridster2"; +import { BehaviorSubject } from "rxjs"; +import { finalize } from "rxjs/operators"; + +import { + DataSourceService, + EventBus, + IEvent, + IFilteringOutputs, + LoggerService, +} from "@nova-ui/bits"; +import { + ComponentRegistryService, + ConfiguratorHeadingService, + DataSourceConfigurationV2Component, + DATA_SOURCE, + DEFAULT_PIZZAGNA_ROOT, + IConfigurable, + IDashboard, + IKpiData, + IProperties, + IProviderConfiguration, + IRefresherProperties, + IWidget, + IWidgets, + KpiComponent, + NOVA_KPI_DATASOURCE_ADAPTER, + PizzagnaLayer, + PIZZAGNA_EVENT_BUS, + ProviderRegistryService, + WellKnownPathKey, + WellKnownProviders, + WidgetTypesService, +} from "@nova-ui/dashboards"; + +/** + * This component will serve as the data source accordion in the configurator. + */ +@Component({ + selector: "harry-potter-data-source-configurator", + styleUrls: ["./custom-data-source-configurator-example.component.less"], + template: \\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\` + +
+ +
+ Data Source +
+ Harry Potter Books +
+
+
+
+ + + + {{ book.title }} + + + +
+
+ + + + {{ metric.label }} + + + +
+
+ \\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\`, + standalone: false, +}) +@Injectable() +export class HarryPotterDataSourceConfiguratorComponent + extends DataSourceConfigurationV2Component + implements OnInit +{ + // This lateLoadKey allows the component to be able to be registered by the componentRegistry + public static lateLoadKey = "HarryPotterDataSourceConfiguratorComponent"; + + // Array of books that will populate the book select + public books = [ + { + id: "5MQFrgEACAAJ", + title: $localize\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\`Harry Potter and the Sorcerer's Stone\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\`, + }, + { + id: "5iTebBW-w7QC", + title: $localize\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\`Harry Potter and the Chamber of Secrets\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\`, + }, + ]; + + // Array of metrics that will populate the metric select + public metrics = [ + { + id: "averageRating", + label: $localize\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\`Average Rating\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\`, + }, + { + id: "ratingsCount", + label: $localize\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\`Ratings Count\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\`, + }, + ]; + + // These need to be injected because DataSourceConfigurationV2Component uses them + constructor( + changeDetector: ChangeDetectorRef, + configuratorHeading: ConfiguratorHeadingService, + formBuilder: FormBuilder, + providerRegistryService: ProviderRegistryService, + @Inject(PIZZAGNA_EVENT_BUS) eventBus: EventBus, + injector: Injector, + logger: LoggerService + ) { + super( + changeDetector, + configuratorHeading, + formBuilder, + providerRegistryService, + eventBus, + injector, + logger + ); + } + + // Overriding 'ngOnInit' to add custom controls to the 'properties' form group + public ngOnInit(): void { + super.ngOnInit(); + + // Overriding the 'properties' control on the form to create a form group that accommodates our custom properties + this.form.setControl( + "properties", + this.formBuilder.group({ + bookId: [this.properties?.bookId ?? "", Validators.required], + metric: [this.properties?.metric ?? "", Validators.required], + }) + ); + // The default data source control has a required validator we're removing that validator here since we aren't using it. + this.form.setControl("dataSource", this.formBuilder.control(null)); + // Here we set the providerId to our only data source so when a new tile gets created it will default to it. + this.form.get("providerId")?.setValue(AcmeKpiDataSource.providerId); + // Here we subscribe to the form and if there are any changes we invoke the data source + this.form.valueChanges.subscribe((value) => { + if (!value.providerId) { + return; + } + this.invokeDataSource(value); + }); + } +} + +/** + * A simple KPI data source to retrieve the average rating of Harry Potter and the Sorcerer's Stone (book) via googleapis + */ +@Injectable() +export class AcmeKpiDataSource + extends DataSourceService + implements OnDestroy, IConfigurable +{ + // This is the ID we'll use to identify the provider + public static providerId = "AcmeKpiDataSource"; + + // Use this subject to communicate the data source's busy state + public busy = new BehaviorSubject(false); + + public properties: IProperties; + + constructor(private http: HttpClient) { + super(); + } + + // This function MUST be implemented in order to receive property updates from our configurator + public updateConfiguration(properties: IProperties): void { + // Saving the properties because we will need it for this data source. + this.properties = properties; + } + + // In this example, getFilteredData is invoked every 10 minutes (Take a look at the refresher + // provider definition in the widget configuration below to see how the interval is set) + public async getFilteredData(): Promise { + // For loading indicator to show + this.busy.next(true); + return new Promise((resolve) => { + // *** Make a resource request to an external API (if needed) + this.http + .get( + \\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\`https://www.googleapis.com/books/v1/volumes/\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\${this.properties?.bookId}\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\` + ) + // For loading indicator to be hidden + .pipe(finalize(() => this.busy.next(false))) + .subscribe({ + next: (data: any) => { + resolve({ + result: { + value: data.volumeInfo[this.properties?.metric], + }, + }); + }, + error: (error: HttpErrorResponse) => { + resolve({ + result: null, + error: { + type: error.status, + }, + }); + }, + }); + }); + } + + public ngOnDestroy(): void { + this.outputsSubject.complete(); + } +} + +/** + * A component that instantiates the dashboard + */ +@Component({ + selector: "custom-data-source-configurator-example", + templateUrl: "./custom-data-source-configurator-example.component.html", + styleUrls: ["./custom-data-source-configurator-example.component.less"], + standalone: false, +}) +export class CustomDataSourceConfiguratorExampleComponent implements OnInit { + // This variable will hold all the data needed to define the layout and behavior of the widgets. + // Pass this to the dashboard component's dashboard input in the template. + public dashboard: IDashboard; + + // Angular gridster requires a configuration object even if it's empty. + // Pass this to the dashboard component's gridsterConfig input in the template. + public gridsterConfig: GridsterConfig = {}; + + // Boolean which dashboard takes in as an input if its true it allows you to move widgets around. + public editMode: boolean = false; + + constructor( + // WidgetTypesService provides the widget's necessary structure information + private widgetTypesService: WidgetTypesService, + + // In general, the ProviderRegistryService is used for making entities available for injection into dynamically loaded components. + private providerRegistry: ProviderRegistryService, + + // Inject the ComponentRegistryService to make our custom component available for late loading by the dashboards framework + private componentRegistry: ComponentRegistryService + ) {} + + public ngOnInit(): void { + // Registering the new data source configurator so it can be used. + this.componentRegistry.registerByLateLoadKey( + HarryPotterDataSourceConfiguratorComponent + ); + // Registering the data source for injection into the KPI tile. + // Note: Each tile of a KPI widget is assigned its own instance of the data source + this.providerRegistry.setProviders({ + [AcmeKpiDataSource.providerId]: { + provide: DATA_SOURCE, + useClass: AcmeKpiDataSource, + // Any dependencies that need to be injected into the provider must be listed here + deps: [HttpClient], + }, + }); + + const kpiWidgetTemplate = this.widgetTypesService.getWidgetType( + "kpi", + 1 + ); + + this.widgetTypesService.setNode( + // This is the template we grabbed above with getWidgetType + kpiWidgetTemplate, + // We are setting the editor/configurator part of the widget template + "configurator", + // This is the path to go to the data source config component type. + WellKnownPathKey.DataSourceConfigComponentType, + // We are changing it to use the component we just created above instead of the default. + HarryPotterDataSourceConfiguratorComponent.lateLoadKey + ); + + // We're using a static configuration object for this example, but this is where + // the widget's configuration could potentially be populated from a database + const kpiWidget = widgetConfig; + const widgetIndex: IWidgets = { + // Complete the KPI widget with information coming from its type definition + [kpiWidget.id]: + this.widgetTypesService.mergeWithWidgetType(kpiWidget), + }; + + // Setting the widget dimensions and position (this is for gridster) + const positions: Record = { + [kpiWidget.id]: { + cols: 4, + rows: 6, + y: 0, + x: 0, + }, + }; + + // Finally, assigning the variables we created above to the dashboard + this.dashboard = { + positions, + widgets: widgetIndex, + }; + } +} + +const widgetConfig: IWidget = { + id: "widget1", + type: "kpi", + pizzagna: { + [PizzagnaLayer.Configuration]: { + [DEFAULT_PIZZAGNA_ROOT]: { + providers: { + [WellKnownProviders.Refresher]: { + properties: { + // Configuring the refresher interval so that our data source is invoked every ten minutes + interval: 60 * 10, + enabled: true, + } as IRefresherProperties, + } as Partial, + }, + }, + header: { + properties: { + title: "Harry Potter and the Sorcerer's Stone", + subtitle: "By J. K. Rowling", + }, + }, + tiles: { + properties: { + nodes: ["kpi1"], + }, + }, + kpi1: { + id: "kpi1", + componentType: KpiComponent.lateLoadKey, + properties: { + widgetData: { + units: "out of 5 Stars", + label: "Average Rating", + }, + }, + providers: { + [WellKnownProviders.DataSource]: { + // Setting the data source providerId for the tile with id "kpi1" + providerId: AcmeKpiDataSource.providerId, + properties: { + bookId: "5MQFrgEACAAJ", + metric: "averageRating", + }, + } as IProviderConfiguration, + [WellKnownProviders.Adapter]: { + providerId: NOVA_KPI_DATASOURCE_ADAPTER, + properties: { + componentId: "kpi1", + propertyPath: "widgetData", + }, + } as IProviderConfiguration, + }, + }, + }, + }, +}; +\\\\\\\\\\\\\\\`, + "tutorials/customization/formatter/custom-formatter.module.ts": \\\\\\\\\\\\\\\`// © 2022 SolarWinds Worldwide, LLC. All rights reserved. +// +// Permission is hereby granted, free of charge, to any person obtaining a copy +// of this software and associated documentation files (the "Software"), to +// deal in the Software without restriction, including without limitation the +// rights to use, copy, modify, merge, publish, distribute, sublicense, and/or +// sell copies of the Software, and to permit persons to whom the Software is +// furnished to do so, subject to the following conditions: +// +// The above copyright notice and this permission notice shall be included in +// all copies or substantial portions of the Software. +// +// THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR +// IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, +// FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE +// AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER +// LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, +// OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN +// THE SOFTWARE. + +import { NgModule } from "@angular/core"; +import { ReactiveFormsModule } from "@angular/forms"; +import { RouterModule, Routes } from "@angular/router"; + +// eslint-disable-next-line max-len +import { + NuiButtonModule, + NuiDocsModule, + NuiFormFieldModule, + NuiIconModule, + NuiMessageModule, + NuiSelectV2Module, + NuiSwitchModule, + NuiTextboxModule, + NuiValidationMessageModule, + DEMO_PATH_TOKEN, +} from "@nova-ui/bits"; +import { NuiDashboardsModule } from "@nova-ui/dashboards"; + +import { CustomDonutContentFormatterDocComponent } from "./donut-content-formatter-example/custom-donut-content-formatter-docs.component"; +import { + CustomDonutContentFormatterComponent, + CustomDonutContentFormatterConfiguratorComponent, + CustomDonutContentFormatterExampleComponent, +} from "./donut-content-formatter-example/custom-donut-content-formatter-example.component"; +import { CustomFormatterDocComponent } from "./formatter-example/custom-formatter-docs.component"; +import { + CustomFormatterComponent, + CustomFormatterConfiguratorComponent, + CustomFormatterExampleComponent, +} from "./formatter-example/custom-formatter-example.component"; +import { getDemoFiles } from "../../../../../demo-files-factory"; + +const routes: Routes = [ + { + path: "table-formatter", + component: CustomFormatterDocComponent, + data: { + srlc: { + hideIndicator: true, + }, + showThemeSwitcher: true, + }, + }, + { + path: "donut-content-formatter", + component: CustomDonutContentFormatterDocComponent, + data: { + srlc: { + hideIndicator: true, + }, + showThemeSwitcher: true, + }, + }, +]; + +@NgModule({ + imports: [ + RouterModule.forChild(routes), + NuiDocsModule, + NuiButtonModule, + NuiMessageModule, + NuiDashboardsModule, + NuiFormFieldModule, + NuiTextboxModule, + NuiSwitchModule, + NuiSelectV2Module, + NuiValidationMessageModule, + NuiIconModule, + ReactiveFormsModule, + ], + declarations: [ + CustomDonutContentFormatterComponent, + CustomDonutContentFormatterExampleComponent, + CustomDonutContentFormatterConfiguratorComponent, + CustomDonutContentFormatterDocComponent, + CustomFormatterDocComponent, + CustomFormatterExampleComponent, + CustomFormatterConfiguratorComponent, + CustomFormatterComponent, + ], + providers: [ + { + provide: DEMO_PATH_TOKEN, + useValue: getDemoFiles("formatter"), + }, + ], +}) +export default class CustomFormatterModuleRoute {} +\\\\\\\\\\\\\\\`, + "tutorials/customization/formatter/donut-content-formatter-example/custom-donut-content-formatter-docs.component.ts": \\\\\\\\\\\\\\\`// © 2022 SolarWinds Worldwide, LLC. All rights reserved. +// +// Permission is hereby granted, free of charge, to any person obtaining a copy +// of this software and associated documentation files (the "Software"), to +// deal in the Software without restriction, including without limitation the +// rights to use, copy, modify, merge, publish, distribute, sublicense, and/or +// sell copies of the Software, and to permit persons to whom the Software is +// furnished to do so, subject to the following conditions: +// +// The above copyright notice and this permission notice shall be included in +// all copies or substantial portions of the Software. +// +// THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR +// IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, +// FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE +// AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER +// LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, +// OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN +// THE SOFTWARE. + +import { Component } from "@angular/core"; + +@Component({ + selector: "nui-custom-donut-content-formatter-docs", + templateUrl: "./custom-donut-content-formatter-docs.component.html", + standalone: false, +}) +export class CustomDonutContentFormatterDocComponent {} +\\\\\\\\\\\\\\\`, + "tutorials/customization/formatter/donut-content-formatter-example/custom-donut-content-formatter-example.component.ts": \\\\\\\\\\\\\\\`// © 2022 SolarWinds Worldwide, LLC. All rights reserved. +// +// Permission is hereby granted, free of charge, to any person obtaining a copy +// of this software and associated documentation files (the "Software"), to +// deal in the Software without restriction, including without limitation the +// rights to use, copy, modify, merge, publish, distribute, sublicense, and/or +// sell copies of the Software, and to permit persons to whom the Software is +// furnished to do so, subject to the following conditions: +// +// The above copyright notice and this permission notice shall be included in +// all copies or substantial portions of the Software. +// +// THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR +// IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, +// FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE +// AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER +// LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, +// OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN +// THE SOFTWARE. + +import { HttpClient } from "@angular/common/http"; +import { + ChangeDetectorRef, + Component, + Injectable, + Input, + OnChanges, + OnDestroy, + OnInit, + SimpleChanges, +} from "@angular/core"; +import { FormBuilder, FormGroup, Validators } from "@angular/forms"; +import { GridsterConfig, GridsterItem } from "angular-gridster2"; +import { Subject } from "rxjs"; +import { takeUntil, tap } from "rxjs/operators"; + +import { + DataSourceService, + IconService, + IDataSource, + IFilteringOutputs, + LoggerService, +} from "@nova-ui/bits"; +import { + ChartAssist, + IAccessors, + IChartAssistEvent, + IChartAssistSeries, +} from "@nova-ui/charts"; +import { + ComponentRegistryService, + ConfiguratorHeadingService, + DATA_SOURCE, + DonutChartFormatterConfiguratorComponent, + DonutContentPercentageConfigurationComponent, + DonutContentPercentageFormatterComponent, + DonutContentSumFormatterComponent, + IDashboard, + IFormatterDefinition, + IHasChangeDetector, + IProperties, + IProportionalWidgetChartOptions, + IProportionalWidgetConfig, + IProviderConfiguration, + IWidget, + IWidgets, + LegendPlacement, + PizzagnaLayer, + ProportionalWidgetChartTypes, + ProviderRegistryService, + WellKnownPathKey, + WellKnownProviders, + WidgetTypesService, +} from "@nova-ui/dashboards"; + +export enum Units { + Days = "Day(s)", + Weeks = "Week(s)", + Hours = "Hour(s)", +} + +@Component({ + selector: "custom-donut-content-formatter", + host: { class: "d-flex flex-column align-items-center" }, + template: \\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\` +
+ + {{ chartMetric || properties?.currentMetric || data[0].id }} +
+
+ {{ chartContent }} +
+
+ {{ units }} +
+
\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\`, + styleUrls: ["./custom-donut-content-formatter-example.component.less"], + standalone: false, +}) +export class CustomDonutContentFormatterComponent + implements IHasChangeDetector, OnInit, OnChanges +{ + public static lateLoadKey = "CustomDonutContentFormatterComponent"; + + // Used to emphasize the chart series when user interacts either with the chart legend, or chart segments. + public emphasizedSeriesData: IChartAssistSeries | undefined; + + // Current raw value of the metric to display + public currentMetricData: number; + + // Metric value rendered inside the template, when user selects a metric, and gets automatically recalculated depending on selected units + public chartContent: number; + + // Metric value rendered inside the template, when user interacts with either chart legend, or chart segments + public chartMetric: number; + + // Units which user can select from the configuration + public units: Units = Units.Days; + + private readonly destroy$ = new Subject(); + + constructor(public changeDetector: ChangeDetectorRef) {} + + // The data we receive from the chart, including metrics names and their values + @Input() data: IChartAssistSeries[]; + + // We use this chart assist instance to subscribe to the events triggered when an interaction with the chart occurs + @Input() chartAssist: ChartAssist; + + // These are the current properties from pizzagna. Used to use data set at the configuration layer + @Input() properties: IProperties; + + public ngOnChanges(changes: SimpleChanges): void { + if (changes.properties || !this.properties) { + // If current metric is not in the list of metrics any more we fall back to the very first one from the list we get from the datasource + this.currentMetricData = + this.data.find( + (item) => item.id === this.properties?.currentMetric + )?.data[0] || this.data[0].data[0]; + + // We either take the selected value, or fall back to the preselected default one + this.units = this.properties?.units || this.units; + } + + this.setContentValue(); + } + + public ngOnInit(): void { + // Here 'chartAssistSubject' is the entity that emits events every time user interacts with either chart legend, or chart segments. + // Subscribing to properly react on these kind of events + this.chartAssist.chartAssistSubject + .pipe( + tap( + (data: IChartAssistEvent) => + (this.emphasizedSeriesData = this.data.find( + (item) => item.id === data.payload.seriesId + )) + ), + tap(() => this.setContentValue()), + tap(() => this.setMetricValue()), + takeUntil(this.destroy$) + ) + .subscribe(); + } + + public getConvertedData(emphData: number): number { + // Recalculating data depending on the units user selected from the configuration view + switch (this.units) { + case Units.Weeks: + return this.emphasizedSeriesData + ? this.convertToWeeks(emphData) + : this.convertToWeeks(this.currentMetricData); + + case Units.Hours: + return this.emphasizedSeriesData + ? this.convertToHours(emphData) + : this.convertToHours(this.currentMetricData); + + default: + return this.emphasizedSeriesData + ? emphData + : this.currentMetricData; + } + } + + public setContentValue(): void { + this.chartContent = this.getConvertedData( + this.emphasizedSeriesData?.data[0] + ); + } + + public setMetricValue(): void { + this.chartMetric = this.emphasizedSeriesData + ? this.data.find( + (item) => + this.getConvertedData(item.data[0]) === + this.getConvertedData(this.emphasizedSeriesData?.data[0]) + )?.id + : // if metric was not initially selected we fall back to the very first one + this.properties?.currentMetric || this.data[0].id; + } + + private convertToWeeks(days: number | undefined): number { + return days ? Number((days / 7).toFixed(2)) : 0; + } + + private convertToHours(days: number | undefined): number { + return days ? Number((days * 24).toFixed(2)) : 0; + } +} + +@Component({ + selector: "custom-donut-content-formatter-configurator", + styleUrls: ["./custom-donut-content-formatter-example.component.less"], + template: \\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\` +
+
+ + + + {{ itemValue?.name }} + + + + This field is required + + +
+
+ + + + {{ itemValue }} + + + + This field is required + + +
+
+ \\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\`, + standalone: false, +}) +export class CustomDonutContentFormatterConfiguratorComponent + extends DonutChartFormatterConfiguratorComponent + implements OnChanges, OnInit, IHasChangeDetector +{ + public static lateLoadKey = "CustomFormatterConfiguratorComponent"; + + constructor( + changeDetector: ChangeDetectorRef, + formBuilder: FormBuilder, + logger: LoggerService, + public iconService: IconService, + public configuratorHeading: ConfiguratorHeadingService + ) { + super(changeDetector, formBuilder, logger); + } + + public availableUnits: Units[] = [Units.Days, Units.Hours, Units.Weeks]; + + protected addCustomFormControls(form: FormGroup): void { + form.addControl( + "units", + this.formBuilder.control(Units.Days, Validators.required) + ); + } +} + +/** + * A component that instantiates the dashboard + */ +@Component({ + selector: "custom-donut-content-formatter-example", + templateUrl: "./custom-donut-content-formatter-example.component.html", + styleUrls: ["./custom-donut-content-formatter-example.component.less"], + standalone: false, +}) +export class CustomDonutContentFormatterExampleComponent implements OnInit { + public editMode: boolean = false; + // This variable will hold all the data needed to define the layout and behavior of the widgets. + // Pass this to the dashboard component's dashboard input in the template. + public dashboard: IDashboard | undefined; + + // Angular gridster requires a configuration object even if it's empty. + // Pass this to the dashboard component's gridsterConfig input in the template. + public gridsterConfig: GridsterConfig = {}; + + constructor( + // WidgetTypesService provides the widget's necessary structure information + private widgetTypesService: WidgetTypesService, + // In general, the ProviderRegistryService is used for making entities available for injection into dynamically loaded components. + private providerRegistry: ProviderRegistryService, + // Inject the ComponentRegistryService to make our custom component available for late loading by the dashboards framework + private componentRegistry: ComponentRegistryService, + private changeDetectorRef: ChangeDetectorRef + ) { + // Register the custom configurator component with the component registry to make it available + // for late loading by the dashboard framework. + this.componentRegistry.registerByLateLoadKey( + CustomDonutContentFormatterConfiguratorComponent + ); + // Register the custom formatter component with the component registry to make it available + // for late loading by the dashboard framework. + this.componentRegistry.registerByLateLoadKey( + CustomDonutContentFormatterComponent + ); + + // Grab the widget's default template which will be needed as a parameter for setNode below. + const proportional = this.widgetTypesService.getWidgetType( + "proportional", + 1 + ); + + const donutFormatters: IFormatterDefinition[] = [ + { + componentType: DonutContentSumFormatterComponent.lateLoadKey, + label: $localize\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\`Sum\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\`, + } as IFormatterDefinition, + { + componentType: + DonutContentPercentageFormatterComponent.lateLoadKey, + label: $localize\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\`Percentage\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\`, + configurationComponent: + DonutContentPercentageConfigurationComponent.lateLoadKey, + } as IFormatterDefinition, + { + componentType: CustomDonutContentFormatterComponent.lateLoadKey, + label: $localize\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\`Custom\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\`, + // This is a custom configurator that will pop up below the formatter once it gets selected + configurationComponent: + CustomDonutContentFormatterConfiguratorComponent.lateLoadKey, + } as IFormatterDefinition, + ]; + + this.widgetTypesService.setNode( + // This is the template we grabbed above with getWidgetType + proportional, + // We are setting the editor/configurator part of the widget template + "configurator", + // This indicates which node you are changing and we want to change the formatters available for selection in the editor. + WellKnownPathKey.Formatters, + // We are setting the available formatters with the array we created above. + donutFormatters + ); + + // This sets the donut chart's datasource to have the StatusesExampleDatasource so the drop down is filled similar to the line above. + this.widgetTypesService.setNode( + proportional, + "configurator", + WellKnownPathKey.DataSourceProviders, + [StatusesExampleDatasource.providerId] + ); + + // Registering the data source for injection into the widget. + this.providerRegistry.setProviders({ + [StatusesExampleDatasource.providerId]: { + provide: DATA_SOURCE, + useClass: StatusesExampleDatasource, + // Any dependencies that need to be injected into the provider must be listed here + deps: [], + }, + }); + } + + public ngOnInit(): void { + this.initializeDashboard(); + } + + /** Used for restoring widgets state */ + public reInitializeDashboard(): void { + // destroys the components and their providers so the dashboard can re init data + this.dashboard = undefined; + this.changeDetectorRef.detectChanges(); + + this.initializeDashboard(); + } + + public initializeDashboard(): void { + // We're using a static configuration object for this example, but this is where + // the widget's configuration could potentially be populated from a database + const proportionalWidget = widgetConfig; + const widgetIndex: IWidgets = { + // Enhance the widget with information coming from it's type definition + [proportionalWidget.id]: + this.widgetTypesService.mergeWithWidgetType(proportionalWidget), + }; + + // Setting the widget dimensions and position (this is for gridster) + const positions: Record = { + [proportionalWidget.id]: { + cols: 12, + rows: 6, + y: 0, + x: 0, + }, + }; + + // Finally, assigning the variables we created above to the dashboard + this.dashboard = { + positions, + widgets: widgetIndex, + }; + } +} + +export interface IStatusesWidgetData { + id: string; + name: string; + data: number[]; +} + +export const randomStatusesWidgetData: IStatusesWidgetData[] = [ + { + id: "Down", + name: "Down", + data: [Math.round(Math.random() * 100)], + }, + { + id: "Critical", + name: "Critical", + data: [Math.round(Math.random() * 100)], + }, + { + id: "Warning", + name: "Warning", + data: [Math.round(Math.random() * 100)], + }, + { + id: "Unknown", + name: "Unknown", + data: [Math.round(Math.random() * 100)], + }, + { + id: "Up", + name: "Up", + data: [Math.round(Math.random() * 100)], + }, + { + id: "Unmanaged", + name: "Unmanaged", + data: [Math.round(Math.random() * 100)], + }, +]; + +@Injectable() +export class StatusesExampleDatasource + extends DataSourceService + implements IDataSource, OnDestroy +{ + public static providerId = "StatusesExampleDatasource"; + + public busy = new Subject(); + + constructor(private http: HttpClient) { + super(); + } + + public async getFilteredData(): Promise { + this.busy.next(true); + + return new Promise((resolve) => { + setTimeout(() => { + resolve({ + result: randomStatusesWidgetData, + }); + this.busy.next(false); + }, 1000); + }); + } + + public ngOnDestroy(): void { + this.outputsSubject.complete(); + } +} + +export const widgetConfig: IWidget = { + id: "proportionalWidgetId", + type: "proportional", + pizzagna: { + [PizzagnaLayer.Configuration]: { + header: { + properties: { + title: "Proportional Widget!", + subtitle: "Proportional widget with legend formatters", + }, + }, + chart: { + providers: { + [WellKnownProviders.DataSource]: { + providerId: StatusesExampleDatasource.providerId, + } as IProviderConfiguration, + }, + properties: { + configuration: { + interactive: true, + chartOptions: { + type: ProportionalWidgetChartTypes.DonutChart, + legendPlacement: LegendPlacement.Right, + contentFormatter: { + componentType: + CustomDonutContentFormatterComponent.lateLoadKey, + properties: { + // here you can set the default value for the metric you receive. If not set the first one from the list will be taken + currentMetric: "Down", + // here you set the default value for your custom controls. If not set the first one from the list will be taken + units: Units.Weeks, + }, + }, + } as IProportionalWidgetChartOptions, + chartColors: [ + "var(--nui-color-chart-eight)", + "var(--nui-color-chart-nine)", + "var(--nui-color-chart-ten)", + ], + } as IProportionalWidgetConfig, + }, + }, + }, + }, +}; +\\\\\\\\\\\\\\\`, + "tutorials/customization/formatter/formatter-example/custom-formatter-docs.component.ts": \\\\\\\\\\\\\\\`// © 2022 SolarWinds Worldwide, LLC. All rights reserved. +// +// Permission is hereby granted, free of charge, to any person obtaining a copy +// of this software and associated documentation files (the "Software"), to +// deal in the Software without restriction, including without limitation the +// rights to use, copy, modify, merge, publish, distribute, sublicense, and/or +// sell copies of the Software, and to permit persons to whom the Software is +// furnished to do so, subject to the following conditions: +// +// The above copyright notice and this permission notice shall be included in +// all copies or substantial portions of the Software. +// +// THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR +// IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, +// FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE +// AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER +// LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, +// OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN +// THE SOFTWARE. + +import { Component } from "@angular/core"; + +@Component({ + selector: "nui-custom-formatter-docs", + templateUrl: "./custom-formatter-docs.component.html", + standalone: false, +}) +export class CustomFormatterDocComponent {} +\\\\\\\\\\\\\\\`, + "tutorials/customization/formatter/formatter-example/custom-formatter-example.component.ts": \\\\\\\\\\\\\\\`// © 2022 SolarWinds Worldwide, LLC. All rights reserved. +// +// Permission is hereby granted, free of charge, to any person obtaining a copy +// of this software and associated documentation files (the "Software"), to +// deal in the Software without restriction, including without limitation the +// rights to use, copy, modify, merge, publish, distribute, sublicense, and/or +// sell copies of the Software, and to permit persons to whom the Software is +// furnished to do so, subject to the following conditions: +// +// The above copyright notice and this permission notice shall be included in +// all copies or substantial portions of the Software. +// +// THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR +// IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, +// FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE +// AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER +// LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, +// OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN +// THE SOFTWARE. + +import { ListRange } from "@angular/cdk/collections"; +import { ChangeDetectorRef, Component, Input, OnInit } from "@angular/core"; +import { FormBuilder, FormGroup, Validators } from "@angular/forms"; +import { GridsterConfig, GridsterItem } from "angular-gridster2"; +import isEqual from "lodash/isEqual"; +import orderBy from "lodash/orderBy"; +import { BehaviorSubject } from "rxjs"; + +import { + DataSourceService, + IconService, + IDataField, + INovaFilteringOutputs, + INovaFilters, + ISorterFilter, + LoggerService, +} from "@nova-ui/bits"; +import { + ComponentRegistryService, + ConfiguratorHeadingService, + DATA_SOURCE, + FormatterConfiguratorComponent, + IDashboard, + IDataSourceOutput, + IFormatterDefinition, + IHasChangeDetector, + ITableWidgetColumnConfig, + ITableWidgetSorterConfig, + IWidget, + IWidgets, + PizzagnaLayer, + ProviderRegistryService, + RawFormatterComponent, + TableFormatterRegistryService, + WellKnownPathKey, + WellKnownProviders, + WidgetTypesService, +} from "@nova-ui/dashboards"; + +export const BREW_API_URL = "https://api.punkapi.com/v2/beers"; + +@Component({ + selector: "custom-formatter", + host: { class: "d-flex" }, + template: \\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\` +
+
+ +
+
+ {{ data.value }} +
+
+ \\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\`, + styleUrls: ["./custom-formatter-example.component.less"], + standalone: false, +}) +export class CustomFormatterComponent implements IHasChangeDetector { + public static lateLoadKey = "CustomFormatterComponent"; + + constructor(public changeDetector: ChangeDetectorRef) {} + + @Input() public data: any; + @Input() public icon: string; + @Input() public threshold: string; + + public isAboveThreshold(): boolean { + return parseFloat(this.threshold) <= this.data.value; + } +} + +@Component({ + selector: "custom-formatter-configurator", + styleUrls: ["./custom-formatter-example.component.less"], + template: \\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\` +
+
+ + + + {{ item.label }} + + + + This field is required + + +
+
+ + + + + + + + This field is required + + +
+
+ + + + + This field is required + + +
+
+ +
+
+ +
+ + +
+ + + Select Item + +
+ \\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\`, + standalone: false, +}) +export class CustomFormatterConfiguratorComponent + extends FormatterConfiguratorComponent + implements OnInit, IHasChangeDetector +{ + public static lateLoadKey = "CustomFormatterConfiguratorComponent"; + + constructor( + changeDetector: ChangeDetectorRef, + configuratorHeading: ConfiguratorHeadingService, + formBuilder: FormBuilder, + logger: LoggerService, + public iconService: IconService + ) { + super(changeDetector, configuratorHeading, formBuilder, logger); + } + + public formatterFormGroup: FormGroup; + // This array is where the icon names will be stored + public options: string[] = []; + + public ngOnInit(): void { + for (const icon of this.iconService.icons) { + if (icon.category === "severity") { + this.options.push(icon.name); + } + } + } + + protected addCustomFormControls(form: FormGroup): void { + form.addControl( + "icon", + this.formBuilder.control("", Validators.required) + ); + form.addControl( + "threshold", + this.formBuilder.control(null, Validators.required) + ); + } +} + +/** + * A component that instantiates the dashboard + */ +@Component({ + selector: "custom-formatter-example", + templateUrl: "./custom-formatter-example.component.html", + styleUrls: ["./custom-formatter-example.component.less"], + standalone: false, +}) +export class CustomFormatterExampleComponent implements OnInit { + public editMode: boolean = false; + // This variable will hold all the data needed to define the layout and behavior of the widgets. + // Pass this to the dashboard component's dashboard input in the template. + public dashboard: IDashboard | undefined; + + // Angular gridster requires a configuration object even if it's empty. + // Pass this to the dashboard component's gridsterConfig input in the template. + public gridsterConfig: GridsterConfig = {}; + + constructor( + // WidgetTypesService provides the widget's necessary structure information + private widgetTypesService: WidgetTypesService, + // In general, the ProviderRegistryService is used for making entities available for injection into dynamically loaded components. + private providerRegistry: ProviderRegistryService, + // Inject the ComponentRegistryService to make our custom component available for late loading by the dashboards framework + private componentRegistry: ComponentRegistryService, + private tableFormatterRegistryService: TableFormatterRegistryService, + private changeDetectorRef: ChangeDetectorRef + ) { + // Register the custom configurator component with the component registry to make it available + // for late loading by the dashboard framework. + this.componentRegistry.registerByLateLoadKey( + CustomFormatterConfiguratorComponent + ); + // Register the custom formatter component with the component registry to make it available + // for late loading by the dashboard framework. + this.componentRegistry.registerByLateLoadKey(CustomFormatterComponent); + + // Grab the widget's default template which will be needed as a parameter for setNode below. + const table = this.widgetTypesService.getWidgetType("table", 1); + + const tableFormatters: IFormatterDefinition[] = [ + { + // This will be the component that will format the data + componentType: RawFormatterComponent.lateLoadKey, + // This is the label for what the formatter is selected in the drop down + label: $localize\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\`:table formatter|:No formatter\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\`, + // This says what datatype the formatter supports. If the value node is null, it accepts any data type. + dataTypes: { + // @ts-ignore: Ignoring compiler error to keep the same flow + value: null, + }, + }, + { + componentType: CustomFormatterComponent.lateLoadKey, + label: $localize\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\`:table formatter|:Custom formatter\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\`, + // This is a custom configurator that will pop up below the formatter once it gets selected + configurationComponent: + CustomFormatterConfiguratorComponent.lateLoadKey, + // This says what data types the formatter supports. + // In this case, it supports abv values only. + // If you look below in the table data source you'll see where we define our column's data types. + dataTypes: { + value: ["abv"], + }, + }, + ]; + + // Registering the formatters + this.tableFormatterRegistryService.addItems(tableFormatters); + + // This sets the table's datasource to have the BeerDataSource so the drop down is filled similar to the line above. + this.widgetTypesService.setNode( + table, + "configurator", + WellKnownPathKey.DataSourceProviders, + [BeerDataSource.providerId] + ); + + // Registering the data source for injection into the widget. + this.providerRegistry.setProviders({ + [BeerDataSource.providerId]: { + provide: DATA_SOURCE, + useClass: BeerDataSource, + // Any dependencies that need to be injected into the provider must be listed here + deps: [], + }, + }); + } + + public ngOnInit(): void { + this.initializeDashboard(); + } + + /** Used for restoring widgets state */ + public reInitializeDashboard(): void { + // destroys the components and their providers so the dashboard can re init data + this.dashboard = undefined; + this.changeDetectorRef.detectChanges(); + + this.initializeDashboard(); + } + + public initializeDashboard(): void { + // We're using a static configuration object for this example, but this is where + // the widget's configuration could potentially be populated from a database + const tableWidget = widgetConfig; + const widgetIndex: IWidgets = { + // Enhance the widget with information coming from it's type definition + [tableWidget.id]: + this.widgetTypesService.mergeWithWidgetType(tableWidget), + }; + + // Setting the widget dimensions and position (this is for gridster) + const positions: Record = { + [tableWidget.id]: { + cols: 12, + rows: 6, + y: 0, + x: 0, + }, + }; + + // Finally, assigning the variables we created above to the dashboard + this.dashboard = { + positions, + widgets: widgetIndex, + }; + } +} + +export interface IBrewInfo { + id: number; + name: string; + tagline: string; + first_brewed: string; + description: string; + brewers_tips: string; + abv: number; +} + +export interface IBrewDatasourceResponse { + brewInfo: IBrewInfo[]; + total: number; +} + +export class BeerDataSource extends DataSourceService { + public static providerId = "BeerDataSource"; + + private cache = Array.from({ length: 0 }); + private lastSortValue?: ISorterFilter; + private lastVirtualScroll?: ListRange; + // For simplicity, the totalItems value is hard-coded here, but in a real-world scenario the value would likely be retrieved via an async backend call + private totalItems: number = 325; + + public page: number = 1; + public busy = new BehaviorSubject(false); + + public dataFields: Array = [ + { id: "id", label: "No", dataType: "number" }, + { id: "name", label: "Name", dataType: "string" }, + { id: "tagline", label: "Tagline", dataType: "string" }, + { id: "first_brewed", label: "First Brewed", dataType: "string" }, + { id: "description", label: "Description", dataType: "string" }, + { id: "brewers_tips", label: "Brewer's Tips", dataType: "string" }, + // We are giving this field a custom data type of 'abv' so the dropdown in the custom formatter configurator can use it to filter out other data types + { id: "abv", label: "Alcohol By Volume", dataType: "abv" }, + ]; + + constructor(private logger: LoggerService) { + super(); + } + + public async getFilteredData( + filters: INovaFilters + ): Promise> { + const start = filters.virtualScroll?.value?.start ?? 0; + const end = filters.virtualScroll?.value?.end ?? 0; + const delta = end - start; + + // Note: We should start with a clean cache every time first page is requested + if (start === 0) { + this.cache = []; + } + + // This condition handles sorting. We want to sort columns without fetching another chunk of data. + // Since the data is being fetched when scrolled, we compare virtual scroll indexes here in the condition as well. + if (filters.sorter?.value) { + if ( + !isEqual(this.lastSortValue, filters.sorter.value) && + filters.virtualScroll?.value.start === 0 && + !!this.lastVirtualScroll + ) { + const totalPages = Math.ceil( + delta ? this.totalItems / delta : 1 + ); + const itemsPerPage: number = Math.max( + delta < 80 ? delta : 80, + 1 + ); + let response: Array | null = null; + let map: IBrewDatasourceResponse; + + if (filters.sorter?.value?.direction === "desc") { + this.cache = []; + for (let i = 0; i < this.page; ++i) { + response = await ( + await fetch( + \\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\`\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\${BREW_API_URL}/?page=\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\${ + totalPages - i || 1 + }&per_page=\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\${itemsPerPage}\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\` + ) + ).json(); + + // since the last page contains only 5 items we need to fetch another page to give virtual scroll enough space to work + if (response && response.length < itemsPerPage) { + this.page++; + } + map = { + brewInfo: response?.map((result: IBrewInfo) => ({ + id: result.id, + name: result.name, + tagline: result.tagline, + first_brewed: result.first_brewed, + description: result.description, + brewers_tips: result.brewers_tips, + })), + total: response?.length, + } as IBrewDatasourceResponse; + this.cache = + totalPages - i !== 0 + ? this.cache.concat(map.brewInfo) + : this.cache; + } + } + + if (filters.sorter?.value?.direction === "asc") { + this.cache = []; + for (let i = 0; i < this.page; i++) { + response = await ( + await fetch( + \\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\`\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\${BREW_API_URL}/?page=\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\${ + i + 1 + }&per_page=\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\${itemsPerPage}\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\` + ) + ).json(); + map = { + brewInfo: response?.map((result: IBrewInfo) => ({ + id: result.id, + name: result.name, + tagline: result.tagline, + first_brewed: result.first_brewed, + description: result.description, + brewers_tips: result.brewers_tips, + })), + total: response?.length, + } as IBrewDatasourceResponse; + this.cache = this.cache.concat(map.brewInfo); + } + } + + this.lastSortValue = filters.sorter?.value; + this.lastVirtualScroll = filters.virtualScroll?.value; + + return { + result: { + repeat: { + itemsSource: this.sortData(this.cache, filters), + }, + paginator: { total: this.totalItems }, + dataFields: this.dataFields, + }, + }; + } + } + + this.busy.next(true); + return new Promise((resolve) => { + setTimeout(() => { + this.getData(start, end, filters).then( + (response: INovaFilteringOutputs) => { + if (!response) { + return; + } + + this.cache = this.cache.concat(response.brewInfo); + + this.dataSubject.next(this.cache); + resolve({ + result: { + repeat: { + itemsSource: this.sortData( + this.cache, + filters + ), + }, + paginator: { total: this.totalItems }, + dataFields: this.dataFields, + }, + }); + + this.lastSortValue = filters.sorter?.value; + this.lastVirtualScroll = filters.virtualScroll?.value; + this.busy.next(false); + } + ); + }, 500); + }); + } + + public async getData( + start: number = 0, + end: number = 20, + filters: INovaFilters + ): Promise { + const delta = end - start; + const totalPages = Math.ceil(delta ? this.totalItems / delta : 1); + let response: Array | null = null; + // The api.punk.com is able to return only 80 items per page + const itemsPerPage: number = Math.max(delta < 80 ? delta : 80, 1); + + if (filters.sorter?.value?.direction === "asc") { + response = await ( + await fetch( + \\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\`\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\${BREW_API_URL}/?page=\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\${this.page}&per_page=\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\${itemsPerPage}\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\` + ) + ).json(); + } + + if (filters.sorter?.value?.direction === "desc") { + response = await ( + await fetch( + \\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\`\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\${BREW_API_URL}/?page=\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\${ + totalPages - this.page + }&per_page=\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\${itemsPerPage}\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\` + ) + ).json(); + } + + if (!filters.sorter) { + response = await ( + await fetch( + \\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\`\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\${BREW_API_URL}/?page=\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\${this.page}&per_page=\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\${itemsPerPage}\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\` + ) + ).json(); + } + return { + brewInfo: response?.map((result: IBrewInfo, i: number) => ({ + id: result.id, + abv: result.abv, + name: result.name, + tagline: result.tagline, + first_brewed: result.first_brewed, + description: result.description, + brewers_tips: result.brewers_tips, + })), + total: response?.length, + } as IBrewDatasourceResponse; + } + + private sortData(data: IBrewInfo[], filters: INovaFilters) { + return orderBy( + data, + filters.sorter?.value?.sortBy, + filters.sorter?.value?.direction as "desc" | "asc" + ); + } +} + +export const widgetConfig: IWidget = { + id: "tableWidgetId", + type: "table", + pizzagna: { + [PizzagnaLayer.Configuration]: { + header: { + properties: { + title: "Stupendous Suds", + subtitle: "Try These Brilliant Brews", + }, + }, + table: { + providers: { + [WellKnownProviders.DataSource]: { + providerId: BeerDataSource.providerId, + }, + }, + properties: { + configuration: { + columns: [ + { + id: "column1", + label: "Beer Name", + isActive: true, + width: 185, + formatter: { + componentType: + RawFormatterComponent.lateLoadKey, + properties: { + dataFieldIds: { + value: "name", + }, + }, + }, + }, + { + id: "column2", + label: "Tagline", + isActive: true, + width: 250, + formatter: { + componentType: + RawFormatterComponent.lateLoadKey, + properties: { + dataFieldIds: { + value: "tagline", + }, + }, + }, + }, + { + id: "column3", + label: "Alcohol By Volume", + isActive: true, + width: 150, + formatter: { + componentType: + CustomFormatterComponent.lateLoadKey, + properties: { + dataFieldIds: { + value: "abv", + }, + icon: "severity_error", + threshold: "5", + }, + }, + }, + { + id: "column4", + label: "Description", + isActive: true, + formatter: { + componentType: + RawFormatterComponent.lateLoadKey, + properties: { + dataFieldIds: { + value: "description", + }, + }, + }, + }, + ] as ITableWidgetColumnConfig[], + sorterConfiguration: { + descendantSorting: false, + sortBy: "", + } as ITableWidgetSorterConfig, + hasVirtualScroll: true, + }, + }, + }, + }, + }, +}; +\\\\\\\\\\\\\\\`, + "tutorials/customization/widget/custom-widget-docs.component.ts": \\\\\\\\\\\\\\\`// © 2022 SolarWinds Worldwide, LLC. All rights reserved. +// +// Permission is hereby granted, free of charge, to any person obtaining a copy +// of this software and associated documentation files (the "Software"), to +// deal in the Software without restriction, including without limitation the +// rights to use, copy, modify, merge, publish, distribute, sublicense, and/or +// sell copies of the Software, and to permit persons to whom the Software is +// furnished to do so, subject to the following conditions: +// +// The above copyright notice and this permission notice shall be included in +// all copies or substantial portions of the Software. +// +// THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR +// IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, +// FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE +// AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER +// LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, +// OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN +// THE SOFTWARE. + +import { Component } from "@angular/core"; + +@Component({ + selector: "custom-widget-docs", + templateUrl: "./custom-widget-docs.component.html", + standalone: false, +}) +export class CustomWidgetDocsComponent {} +\\\\\\\\\\\\\\\`, + "tutorials/customization/widget/custom-widget.component.ts": \\\\\\\\\\\\\\\`// © 2022 SolarWinds Worldwide, LLC. All rights reserved. +// +// Permission is hereby granted, free of charge, to any person obtaining a copy +// of this software and associated documentation files (the "Software"), to +// deal in the Software without restriction, including without limitation the +// rights to use, copy, modify, merge, publish, distribute, sublicense, and/or +// sell copies of the Software, and to permit persons to whom the Software is +// furnished to do so, subject to the following conditions: +// +// The above copyright notice and this permission notice shall be included in +// all copies or substantial portions of the Software. +// +// THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR +// IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, +// FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE +// AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER +// LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, +// OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN +// THE SOFTWARE. + +import { + ChangeDetectionStrategy, + ChangeDetectorRef, + Component, + EventEmitter, + HostBinding, + Input, + OnChanges, + OnInit, + Output, + SimpleChanges, +} from "@angular/core"; +import { FormBuilder, FormGroup, Validators } from "@angular/forms"; +import { GridsterConfig, GridsterItem } from "angular-gridster2"; + +import { IMenuItem } from "@nova-ui/bits"; +import { + ComponentRegistryService, + ConfiguratorHeadingService, + DEFAULT_PIZZAGNA_ROOT, + EVENT_PROXY, + FormStackComponent, + IConverterFormPartsProperties, + IDashboard, + IHasChangeDetector, + IHasForm, + IProviderConfiguration, + IWidget, + IWidgets, + IWidgetTypeDefinition, + NOVA_GENERIC_CONVERTER, + NOVA_TITLE_AND_DESCRIPTION_CONVERTER, + PizzagnaLayer, + refresher, + StackComponent, + TitleAndDescriptionConfigurationComponent, + WellKnownPathKey, + WellKnownProviders, + widgetBodyContentNodes, + WidgetConfiguratorSectionComponent, + WidgetTypesService, + WIDGET_BODY, + WIDGET_HEADER, + WIDGET_LOADING, +} from "@nova-ui/dashboards"; + +// The custom widget type name we'll use +const CUSTOM_WIDGET_TYPENAME = "example-custom-widget"; +// The path key we'll use for image selection in the configurator definition +const IMAGE_SELECTION_CONFIGURATOR_PATH_KEY = "imageSelection"; + +@Component({ + selector: "custom-widget-body", + // A simple template for our custom widget + template: \\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\`\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\`, + styleUrls: ["./custom-widget.component.less"], + changeDetection: ChangeDetectionStrategy.OnPush, + standalone: false, +}) +// Remember to declare this class in the parent module +export class CustomWidgetBodyContentComponent implements IHasChangeDetector { + // Ensure that the lateLoadKey value matches class name + public static lateLoadKey = "CustomWidgetBodyContentComponent"; + + // Optionally, providing an input for styling of the host element + @Input() @HostBinding("class") public elementClass = ""; + + // We'll map this input with the configurator form using the NOVA_GENERIC_CONVERTER. + // See the customWidget definition at the bottom of the file. + @Input() public imageSource: string; + + // Injecting the ChangeDetectorRef to implement IHasChangeDetector. + // This allows the dashboard framework to reliably propagate component property changes to the DOM. + constructor(public changeDetector: ChangeDetectorRef) {} +} + +/** + * A custom configurator section component for selecting the image source for the custom widget + */ +@Component({ + selector: "custom-configurator-section", + template: \\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\` + + + + +
+ + +
+ Image Selection +
+ {{ imageDisplayValue }} +
+
+
+
+ + + + + {{ item.title }} + + + +
+
+ \\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\`, + styleUrls: ["./custom-widget.component.less"], + changeDetection: ChangeDetectionStrategy.OnPush, + standalone: false, +}) +// Remember to declare this class in the parent module +export class CustomConfiguratorSectionComponent + implements OnInit, OnChanges, IHasChangeDetector, IHasForm +{ + // Ensure that the lateLoadKey value matches the class name + public static lateLoadKey = "CustomConfiguratorSectionComponent"; + + /** + * This input serves as the itemsSource a user can select an image from. + */ + @Input() imageItems: IMenuItem[] = []; + /** + * This property holds the currently selected image source string. + */ + @Input() imageSource: string; + + /** + * An output for emitting formReady to allow the immediate parent formGroup component to register us as a form control + * in the larger form. In this case, the immediate parent would be the WidgetConfiguratorSectionComponent as specified + * in the customWidget configurator definition at the bottom of this file. + */ + @Output() formReady = new EventEmitter(); + + public form: FormGroup; + public imageDisplayValue: string; + + constructor( + public changeDetector: ChangeDetectorRef, + private formBuilder: FormBuilder, + public configuratorHeading: ConfiguratorHeadingService + ) {} + + public ngOnInit(): void { + // Initializing the form + this.form = this.formBuilder.group({ + // Note: When using the NOVA_GENERIC_CONVERTER, the form control name, in this case 'imageSource', must match the input name on + // this component as well as that of the corresponding property on the custom widget body component. + imageSource: [{}, [Validators.required]], + }); + + // Emitting the formReady as described above. + this.formReady.emit(this.form); + } + + public ngOnChanges(changes: SimpleChanges): void { + if (changes.imageSource && !changes.imageSource.isFirstChange()) { + const previousValue: string = changes.imageSource.previousValue; + if (previousValue !== this.imageSource) { + // Setting the display value according to the current imageSource value + this.imageDisplayValue = this.imageItems.find( + (item: IMenuItem) => item.url === this.imageSource + )?.title; + + // Updating the form when the imageSource input gets updated + this.form.get("imageSource")?.setValue(this.imageSource); + } + } + } + + public onChanged(newValue: string): void { + // Keeping the display value updated as the user changes the dropdown selection + this.imageDisplayValue = this.imageItems.find( + (item: IMenuItem) => item.url === newValue + )?.title; + } +} + +/** + * The component that instantiates the dashboard + */ +@Component({ + selector: "custom-widget", + templateUrl: "./custom-widget.component.html", + styleUrls: ["./custom-widget.component.less"], + standalone: false, +}) +export class CustomWidgetComponent implements OnInit { + // This variable will hold all the data needed to define the layout and behavior of the widgets. + // Pass this to the dashboard component's dashboard input in the template. + public dashboard: IDashboard | undefined; + + // Angular gridster requires a configuration object even if it's empty. + // Pass this to the dashboard component's gridsterConfig input in the template. + public gridsterConfig: GridsterConfig = {}; + + // Boolean which dashboard takes in as an input if its true it allows you to move widgets around. + public editMode: boolean = false; + + constructor( + // WidgetTypesService provides the widget's necessary structure information + private widgetTypesService: WidgetTypesService, + + // Inject the ComponentRegistryService to make our custom component available for late loading by the dashboards framework + private componentRegistry: ComponentRegistryService, + private changeDetectorRef: ChangeDetectorRef + ) {} + + public ngOnInit(): void { + // Register the custom widget type and custom components + // Note: This could also be done in the parent module's constructor so that + // multiple dashboards could have access to the same registrations. + this.prepareNovaDashboards(); + + // Register some image items as dropdown options in the widget editor/configurator + // Note: This could also be done in the parent module's constructor so that + // multiple dashboards could have access to the same dropdown options. + this.registerImageOptions(); + + // Initialize our current instance of a dashboard with an instance of our custom widget + this.initializeDashboard(); + } + + /** Used for restoring widgets state */ + public reInitializeDashboard(): void { + // destroys the components and their providers so the dashboard can re init data + this.dashboard = undefined; + this.changeDetectorRef.detectChanges(); + + this.initializeDashboard(); + } + + public initializeDashboard(): void { + // We're using a static configuration object for this example (see widgetConfig at the bottom of the file), + // but this is where the widget's configuration could potentially be populated from a database + const widget = widgetConfig; + + // Create an index of widgets complete with structure and configuration to assign to the dashboard + const widgets: IWidgets = { + // Complete the custom widget with structure information coming from its type definition + [widget.id]: this.widgetTypesService.mergeWithWidgetType(widget), + }; + + // Setting the widget dimensions and position (this is for gridster) + const positions: Record = { + [widget.id]: { + cols: 4, + rows: 11, + y: 0, + x: 0, + }, + }; + + // Finally, assigning the variables we created above to the dashboard + this.dashboard = { positions, widgets }; + } + + private prepareNovaDashboards() { + // Register the custom widget type + this.widgetTypesService.registerWidgetType( + CUSTOM_WIDGET_TYPENAME, + 1, + customWidget + ); + + // Register the custom widget body component with the component registry to make it available + // for late loading by the dashboard framework. + this.componentRegistry.registerByLateLoadKey( + CustomWidgetBodyContentComponent + ); + + // Register the custom configurator section with the component registry to make it available + // for late loading by the dashboard framework. + this.componentRegistry.registerByLateLoadKey( + CustomConfiguratorSectionComponent + ); + } + + private registerImageOptions() { + // Grab the widget's default template which will be needed as a parameter for setNode below. + const widgetTemplate = this.widgetTypesService.getWidgetType( + CUSTOM_WIDGET_TYPENAME, + 1 + ); + + // Register some image items as dropdown options in the widget editor/configurator + this.widgetTypesService.setNode( + // This is the template we grabbed above with getWidgetType + widgetTemplate, + // We are setting the editor/configurator part of the widget template + "configurator", + // This indicates which node you are changing and we want to change the image items available for selection in the editor. + // For reference, see the 'paths' property of the custom widget's IWidgetTypeDefinition at the bottom of this file. + IMAGE_SELECTION_CONFIGURATOR_PATH_KEY, + // We are setting the image items available for selection in the editor. 'imageItems' is defined + // at the bottom of this file. + imageItems + ); + } +} + +/*************************************************************************************************** + * This is the type definition of our custom widget + ***************************************************************************************************/ +const customWidget: IWidgetTypeDefinition = { + /*************************************************************************************************** + * Paths to important settings in this type definition + ***************************************************************************************************/ + paths: { + widget: { + [WellKnownPathKey.Root]: DEFAULT_PIZZAGNA_ROOT, + }, + configurator: { + [WellKnownPathKey.Root]: DEFAULT_PIZZAGNA_ROOT, + // for the custom configuration component, this is the path for the list of image items available for selection + [IMAGE_SELECTION_CONFIGURATOR_PATH_KEY]: + "imageSelection.properties.imageItems", + }, + }, + /*************************************************************************************************** + * Widget section describes the structural part of the custom widget + ***************************************************************************************************/ + widget: { + [PizzagnaLayer.Structure]: { + [DEFAULT_PIZZAGNA_ROOT]: { + id: DEFAULT_PIZZAGNA_ROOT, + // base layout of the widget - all components referenced herein will be stacked in a column + componentType: StackComponent.lateLoadKey, + providers: { + // When enabled, this provider emits the REFRESH event on the pizzagna event bus every X seconds + [WellKnownProviders.Refresher]: refresher(), + // event proxy manages the transmission of events between widget and dashboard such as the WIDGET_EDIT and WIDGET_REMOVE events + [WellKnownProviders.EventProxy]: EVENT_PROXY, + }, + properties: { + // these values reference child components in the widget structure defined below + nodes: ["header", "loading", "body"], + }, + }, + // standard widget header + header: WIDGET_HEADER, + // this is the loading bar below the header + loading: WIDGET_LOADING, + // the body node + body: WIDGET_BODY, + + // retrieving the definitions for the body content nodes. the argument corresponds to the main content node key + ...widgetBodyContentNodes("mainContent"), + + // the component that supplies the content of our custom widget + mainContent: { + id: "mainContent", + componentType: CustomWidgetBodyContentComponent.lateLoadKey, + properties: { + elementClass: "d-flex w-100 justify-content-center", + }, + }, + }, + [PizzagnaLayer.Configuration]: { + [DEFAULT_PIZZAGNA_ROOT]: { + id: DEFAULT_PIZZAGNA_ROOT, + providers: { + // default refresher configuration + [WellKnownProviders.Refresher]: refresher(false, 60), + }, + }, + // default header configuration + header: { + properties: { + title: $localize\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\`Empty Custom Widget\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\`, + }, + }, + }, + }, + /*************************************************************************************************** + * Configurator section describes the form that's used to configure the widget + ***************************************************************************************************/ + configurator: { + [PizzagnaLayer.Structure]: { + [DEFAULT_PIZZAGNA_ROOT]: { + id: DEFAULT_PIZZAGNA_ROOT, + // base layout of the configurator - all form components referenced herein will be stacked in a column + componentType: FormStackComponent.lateLoadKey, + properties: { + elementClass: + "flex-grow-1 overflow-auto nui-scroll-shadows", + // these values reference child components laid out in this form (defined below) + nodes: ["presentation", "customConfig"], + }, + }, + // /presentation + presentation: { + id: "presentation", + componentType: WidgetConfiguratorSectionComponent.lateLoadKey, + properties: { + headerText: $localize\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\`Presentation\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\`, + nodes: ["titleAndDescription"], + }, + }, + // /presentation/titleAndDescription + titleAndDescription: { + id: "titleAndDescription", + componentType: + TitleAndDescriptionConfigurationComponent.lateLoadKey, + providers: { + converter: { + providerId: NOVA_TITLE_AND_DESCRIPTION_CONVERTER, + } as IProviderConfiguration, + }, + }, + // /customConfig + customConfig: { + id: "customConfig", + componentType: WidgetConfiguratorSectionComponent.lateLoadKey, + properties: { + headerText: $localize\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\`Custom Widget Configuration\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\`, + nodes: ["imageSelection"], + }, + }, + // /customConfig/imageSelection + imageSelection: { + id: "imageSelection", + // Here's where we set the configurator to use our custom configurator section + componentType: CustomConfiguratorSectionComponent.lateLoadKey, + properties: { + // This corresponds to the 'imageItems' input on the custom configurator section component + // which defines the list of image items to pick from. The empty value shown here is overridden + // in the 'registerImageOptions' method above. + imageItems: [] as IMenuItem[], + }, + providers: { + // Using the generic converter to map the selected image source between the widget and the form + [WellKnownProviders.Converter]: { + providerId: NOVA_GENERIC_CONVERTER, + properties: { + formParts: [ + { + // Setting up the generic converter to update the 'imageSource' property of the custom widget 'mainContent' component + previewPath: "mainContent.properties", + // Note: To use the NOVA_GENERIC_CONVERTER, the linked properties must have the same name between the configurator + // section component and the widget 'mainContent' component. Additionally, the property name must match the formControl + // name used in the configurator section component. In this case, the common name among all three is 'imageSource'. + keys: ["imageSource"], + }, + ] as IConverterFormPartsProperties[], + }, + } as IProviderConfiguration, + }, + }, + }, + }, +}; + +// For this example, we're using static items for the image selection dropdown. In a more realistic scenario, +// the items available for selection might come from a backend database. +const imageItems = [ + { + title: "Harry Potter Book Cover", + url: "https://imgc.allpostersimages.com/img/print/u-g-F8PQ9I0.jpg?w=550&h=550&p=0", + }, + { + title: "Harry Potter Movie Poster", + url: "https://images-na.ssl-images-amazon.com/images/I/81gpmMdKOHL._AC_SY741_.jpg", + }, +] as IMenuItem[]; + +// We're using a static configuration object for this example. In a more realistic scenario, +// a widget's configuration would likely be stored in a database. +const widgetConfig: IWidget = { + id: "widget1", + // This custom type is registered in the 'prepareNovaDashboards' method above. + type: CUSTOM_WIDGET_TYPENAME, + pizzagna: { + [PizzagnaLayer.Configuration]: { + header: { + // Setting the initial property values for the WidgetHeaderComponent + properties: { + title: "Harry Potter and the Sorcerer's Stone", + subtitle: "By J. K. Rowling", + }, + }, + mainContent: { + properties: { + // Setting the initial value for the 'imageSource' property on our custom widget body + imageSource: imageItems[0].url, + }, + }, + }, + }, +}; +\\\\\\\\\\\\\\\`, + "tutorials/customization/widget/custom-widget.module.ts": \\\\\\\\\\\\\\\`// © 2022 SolarWinds Worldwide, LLC. All rights reserved. +// +// Permission is hereby granted, free of charge, to any person obtaining a copy +// of this software and associated documentation files (the "Software"), to +// deal in the Software without restriction, including without limitation the +// rights to use, copy, modify, merge, publish, distribute, sublicense, and/or +// sell copies of the Software, and to permit persons to whom the Software is +// furnished to do so, subject to the following conditions: +// +// The above copyright notice and this permission notice shall be included in +// all copies or substantial portions of the Software. +// +// THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR +// IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, +// FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE +// AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER +// LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, +// OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN +// THE SOFTWARE. + +import { CommonModule } from "@angular/common"; +import { HttpClientModule } from "@angular/common/http"; +import { NgModule } from "@angular/core"; +import { ReactiveFormsModule } from "@angular/forms"; +import { RouterModule } from "@angular/router"; + +import { + NuiButtonModule, + NuiDocsModule, + NuiFormFieldModule, + NuiIconModule, + NuiImageModule, + NuiMessageModule, + NuiSelectV2Module, + NuiSwitchModule, + DEMO_PATH_TOKEN, +} from "@nova-ui/bits"; +import { + NuiDashboardConfiguratorModule, + NuiDashboardsModule, +} from "@nova-ui/dashboards"; + +import { CustomWidgetDocsComponent } from "./custom-widget-docs.component"; +import { + CustomConfiguratorSectionComponent, + CustomWidgetBodyContentComponent, + CustomWidgetComponent, +} from "./custom-widget.component"; +import { getDemoFiles } from "../../../../../demo-files-factory"; + +const routes = [ + { + path: "", + component: CustomWidgetDocsComponent, + data: { + srlc: { + hideIndicator: true, + }, + showThemeSwitcher: true, + }, + }, + { + path: "example", + component: CustomWidgetComponent, + data: { + srlc: { + hideIndicator: true, + }, + }, + }, +]; + +@NgModule({ + imports: [ + CommonModule, + ReactiveFormsModule, + HttpClientModule, + NuiDashboardsModule, + NuiDashboardConfiguratorModule, + NuiDocsModule, + NuiFormFieldModule, + NuiIconModule, + NuiImageModule, + NuiMessageModule, + NuiSelectV2Module, + NuiSwitchModule, + NuiButtonModule, + RouterModule.forChild(routes), + ], + declarations: [ + CustomWidgetDocsComponent, + CustomConfiguratorSectionComponent, + CustomWidgetBodyContentComponent, + CustomWidgetComponent, + ], + providers: [ + { + provide: DEMO_PATH_TOKEN, + useValue: getDemoFiles("widget"), + }, + ], +}) +export default class CustomWidgetModule {} +\\\\\\\\\\\\\\\`, + "tutorials/data-source-setup/data-source-setup-docs.component.ts": \\\\\\\\\\\\\\\`// © 2022 SolarWinds Worldwide, LLC. All rights reserved. +// +// Permission is hereby granted, free of charge, to any person obtaining a copy +// of this software and associated documentation files (the "Software"), to +// deal in the Software without restriction, including without limitation the +// rights to use, copy, modify, merge, publish, distribute, sublicense, and/or +// sell copies of the Software, and to permit persons to whom the Software is +// furnished to do so, subject to the following conditions: +// +// The above copyright notice and this permission notice shall be included in +// all copies or substantial portions of the Software. +// +// THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR +// IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, +// FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE +// AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER +// LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, +// OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN +// THE SOFTWARE. + +import { Component } from "@angular/core"; + +@Component({ + selector: "nui-dashboard-data-source-docs", + templateUrl: "./data-source-setup-docs.component.html", + standalone: false, +}) +export class DataSourceDocsComponent {} +\\\\\\\\\\\\\\\`, + "tutorials/data-source-setup/data-source-setup.component.ts": \\\\\\\\\\\\\\\`// © 2022 SolarWinds Worldwide, LLC. All rights reserved. +// +// Permission is hereby granted, free of charge, to any person obtaining a copy +// of this software and associated documentation files (the "Software"), to +// deal in the Software without restriction, including without limitation the +// rights to use, copy, modify, merge, publish, distribute, sublicense, and/or +// sell copies of the Software, and to permit persons to whom the Software is +// furnished to do so, subject to the following conditions: +// +// The above copyright notice and this permission notice shall be included in +// all copies or substantial portions of the Software. +// +// THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR +// IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, +// FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE +// AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER +// LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, +// OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN +// THE SOFTWARE. + +import { HttpClient, HttpErrorResponse } from "@angular/common/http"; +import { Component, Injectable, OnDestroy, OnInit } from "@angular/core"; +import { GridsterConfig, GridsterItem } from "angular-gridster2"; +import { BehaviorSubject } from "rxjs"; +import { finalize } from "rxjs/operators"; + +import { DataSourceService, IFilteringOutputs } from "@nova-ui/bits"; +import { + DATA_SOURCE, + DEFAULT_PIZZAGNA_ROOT, + IDashboard, + IKpiData, + IProviderConfiguration, + IRefresherProperties, + IWidget, + IWidgets, + KpiComponent, + NOVA_KPI_DATASOURCE_ADAPTER, + PizzagnaLayer, + ProviderRegistryService, + WellKnownProviders, + WidgetTypesService, +} from "@nova-ui/dashboards"; + +/** + * A simple KPI data source to retrieve the average rating of Harry Potter and the Sorcerer's Stone (book) via googleapis + */ +@Injectable() +export class AverageRatingKpiDataSource + extends DataSourceService + implements OnDestroy +{ + // This is the ID we'll use to identify the provider + public static providerId = "AverageRatingKpiDataSource"; + + // Use this subject to communicate the data source's busy state + public busy = new BehaviorSubject(false); + + constructor(private http: HttpClient) { + super(); + } + + // In this example, getFilteredData is invoked every 10 minutes (Take a look at the refresher + // provider definition in the widget configuration below to see how the interval is set) + public async getFilteredData(): Promise { + this.busy.next(true); + return new Promise((resolve) => { + // *** Make a resource request to an external API (if needed) + this.http + .get("https://www.googleapis.com/books/v1/volumes/5MQFrgEACAAJ") + .pipe(finalize(() => this.busy.next(false))) + .subscribe({ + next: (data: any) => { + resolve({ + result: { + value: data.volumeInfo.averageRating, + }, + }); + }, + error: (error: HttpErrorResponse) => { + resolve({ + result: null, + error: { + type: error.status, + }, + }); + }, + }); + }); + } + + public ngOnDestroy(): void { + this.outputsSubject.complete(); + } +} + +/** + * A component that instantiates the dashboard + */ +@Component({ + selector: "data-source-setup", + templateUrl: "./data-source-setup.component.html", + styleUrls: ["./data-source-setup.component.less"], + standalone: false, +}) +export class DataSourceSetupComponent implements OnInit { + // This variable will hold all the data needed to define the layout and behavior of the widgets. + // Pass this to the dashboard component's dashboard input in the template. + public dashboard: IDashboard; + + // Angular gridster requires a configuration object even if it's empty. + // Pass this to the dashboard component's gridsterConfig input in the template. + public gridsterConfig: GridsterConfig = {}; + + constructor( + // WidgetTypesService provides the widget's necessary structure information + private widgetTypesService: WidgetTypesService, + + // In general, the ProviderRegistryService is used for making entities available for injection into dynamically loaded components. + private providerRegistry: ProviderRegistryService + ) {} + + public ngOnInit(): void { + // Registering the data source for injection into the KPI tile. + // Note: Each tile of a KPI widget is assigned its own instance of the data source + this.providerRegistry.setProviders({ + [AverageRatingKpiDataSource.providerId]: { + provide: DATA_SOURCE, + useClass: AverageRatingKpiDataSource, + // Any dependencies that need to be injected into the provider must be listed here + deps: [HttpClient], + }, + }); + // We're using a static configuration object for this example, but this is where + // the widget's configuration could potentially be populated from a database + const kpiWidget = widgetConfig; + const widgetIndex: IWidgets = { + // Complete the KPI widget with information coming from its type definition + [kpiWidget.id]: + this.widgetTypesService.mergeWithWidgetType(kpiWidget), + }; + + // Setting the widget dimensions and position (this is for gridster) + const positions: Record = { + [kpiWidget.id]: { + cols: 4, + rows: 6, + y: 0, + x: 0, + }, + }; + + // Finally, assigning the variables we created above to the dashboard + this.dashboard = { + positions, + widgets: widgetIndex, + }; + } +} + +const widgetConfig: IWidget = { + id: "widget1", + type: "kpi", + pizzagna: { + [PizzagnaLayer.Configuration]: { + [DEFAULT_PIZZAGNA_ROOT]: { + providers: { + [WellKnownProviders.Refresher]: { + properties: { + // Configuring the refresher interval so that our data source is invoked every ten minutes + interval: 60 * 10, + enabled: true, + } as IRefresherProperties, + } as Partial, + }, + }, + header: { + properties: { + title: "Harry Potter and the Sorcerer's Stone", + subtitle: "By J. K. Rowling", + }, + }, + tiles: { + properties: { + nodes: ["kpi1"], + }, + }, + kpi1: { + id: "kpi1", + componentType: KpiComponent.lateLoadKey, + properties: { + widgetData: { + units: "out of 5 Stars", + label: "Average Rating", + }, + }, + providers: { + [WellKnownProviders.DataSource]: { + // Setting the data source providerId for the tile with id "kpi1" + providerId: AverageRatingKpiDataSource.providerId, + } as IProviderConfiguration, + [WellKnownProviders.Adapter]: { + providerId: NOVA_KPI_DATASOURCE_ADAPTER, + properties: { + componentId: "kpi1", + propertyPath: "widgetData", + }, + } as IProviderConfiguration, + }, + }, + }, + }, +}; +\\\\\\\\\\\\\\\`, + "tutorials/data-source-setup/data-source-setup.module.ts": \\\\\\\\\\\\\\\`// © 2022 SolarWinds Worldwide, LLC. All rights reserved. +// +// Permission is hereby granted, free of charge, to any person obtaining a copy +// of this software and associated documentation files (the "Software"), to +// deal in the Software without restriction, including without limitation the +// rights to use, copy, modify, merge, publish, distribute, sublicense, and/or +// sell copies of the Software, and to permit persons to whom the Software is +// furnished to do so, subject to the following conditions: +// +// The above copyright notice and this permission notice shall be included in +// all copies or substantial portions of the Software. +// +// THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR +// IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, +// FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE +// AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER +// LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, +// OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN +// THE SOFTWARE. + +import { CommonModule } from "@angular/common"; +import { HttpClientModule } from "@angular/common/http"; +import { NgModule } from "@angular/core"; +import { RouterModule } from "@angular/router"; + +import { + NuiDocsModule, + NuiMessageModule, + DEMO_PATH_TOKEN, +} from "@nova-ui/bits"; +import { NuiDashboardsModule } from "@nova-ui/dashboards"; + +import { DataSourceDocsComponent } from "./data-source-setup-docs.component"; +import { DataSourceSetupComponent } from "./data-source-setup.component"; +import { getDemoFiles } from "../../../../demo-files-factory"; + +const routes = [ + { + path: "", + component: DataSourceDocsComponent, + data: { + srlc: { + hideIndicator: true, + }, + showThemeSwitcher: true, + }, + }, + { + path: "example", + component: DataSourceSetupComponent, + data: { + srlc: { + hideIndicator: true, + }, + }, + }, +]; + +@NgModule({ + imports: [ + CommonModule, + HttpClientModule, + NuiDashboardsModule, + NuiDocsModule, + NuiMessageModule, + RouterModule.forChild(routes), + ], + declarations: [DataSourceDocsComponent, DataSourceSetupComponent], + providers: [ + { + provide: DEMO_PATH_TOKEN, + useValue: getDemoFiles("data-source-setup"), + }, + ], +}) +export default class DataSourceSetupModule {} +\\\\\\\\\\\\\\\`, + "tutorials/dynamic-header-links/dynamic-header-links-docs.component.ts": \\\\\\\\\\\\\\\`// © 2022 SolarWinds Worldwide, LLC. All rights reserved. +// +// Permission is hereby granted, free of charge, to any person obtaining a copy +// of this software and associated documentation files (the "Software"), to +// deal in the Software without restriction, including without limitation the +// rights to use, copy, modify, merge, publish, distribute, sublicense, and/or +// sell copies of the Software, and to permit persons to whom the Software is +// furnished to do so, subject to the following conditions: +// +// The above copyright notice and this permission notice shall be included in +// all copies or substantial portions of the Software. +// +// THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR +// IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, +// FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE +// AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER +// LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, +// OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN +// THE SOFTWARE. + +import { Component } from "@angular/core"; + +@Component({ + selector: "nui-dynamic-header-links-docs", + templateUrl: "./dynamic-header-links-docs.component.html", + standalone: false, +}) +export class DynamicHeaderLinksDocsComponent {} +\\\\\\\\\\\\\\\`, + "tutorials/dynamic-header-links/dynamic-header-links-docs.module.ts": \\\\\\\\\\\\\\\`// © 2022 SolarWinds Worldwide, LLC. All rights reserved. +// +// Permission is hereby granted, free of charge, to any person obtaining a copy +// of this software and associated documentation files (the "Software"), to +// deal in the Software without restriction, including without limitation the +// rights to use, copy, modify, merge, publish, distribute, sublicense, and/or +// sell copies of the Software, and to permit persons to whom the Software is +// furnished to do so, subject to the following conditions: +// +// The above copyright notice and this permission notice shall be included in +// all copies or substantial portions of the Software. +// +// THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR +// IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, +// FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE +// AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER +// LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, +// OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN +// THE SOFTWARE. + +import { NgModule } from "@angular/core"; +import { RouterModule } from "@angular/router"; + +import { DynamicHeaderLinksDocsComponent } from "./dynamic-header-links-docs.component"; + +const routes = [ + { + path: "", + component: DynamicHeaderLinksDocsComponent, + data: { + srlc: { + hideIndicator: true, + }, + showThemeSwitcher: true, + }, + }, +]; + +@NgModule({ + imports: [RouterModule.forChild(routes)], + declarations: [DynamicHeaderLinksDocsComponent], +}) +export default class DynamicHeaderLinksDocsModule {} +\\\\\\\\\\\\\\\`, + "tutorials/hello-dashboards/hello-dashboards-docs.component.ts": \\\\\\\\\\\\\\\`// © 2022 SolarWinds Worldwide, LLC. All rights reserved. +// +// Permission is hereby granted, free of charge, to any person obtaining a copy +// of this software and associated documentation files (the "Software"), to +// deal in the Software without restriction, including without limitation the +// rights to use, copy, modify, merge, publish, distribute, sublicense, and/or +// sell copies of the Software, and to permit persons to whom the Software is +// furnished to do so, subject to the following conditions: +// +// The above copyright notice and this permission notice shall be included in +// all copies or substantial portions of the Software. +// +// THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR +// IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, +// FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE +// AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER +// LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, +// OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN +// THE SOFTWARE. + +import { Component } from "@angular/core"; + +@Component({ + selector: "nui-dashboard-hello-dashboards-docs", + templateUrl: "./hello-dashboards-docs.component.html", + standalone: false, +}) +export class HelloDashboardsDocsComponent {} +\\\\\\\\\\\\\\\`, + "tutorials/hello-dashboards/hello-dashboards-example/hello-dashboards-example.component.ts": \\\\\\\\\\\\\\\`// © 2022 SolarWinds Worldwide, LLC. All rights reserved. +// +// Permission is hereby granted, free of charge, to any person obtaining a copy +// of this software and associated documentation files (the "Software"), to +// deal in the Software without restriction, including without limitation the +// rights to use, copy, modify, merge, publish, distribute, sublicense, and/or +// sell copies of the Software, and to permit persons to whom the Software is +// furnished to do so, subject to the following conditions: +// +// The above copyright notice and this permission notice shall be included in +// all copies or substantial portions of the Software. +// +// THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR +// IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, +// FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE +// AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER +// LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, +// OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN +// THE SOFTWARE. + +import { Component, OnInit } from "@angular/core"; +import { GridsterConfig, GridsterItem } from "angular-gridster2"; + +import { + IDashboard, + IWidget, + IWidgets, + KpiComponent, + PizzagnaLayer, + WidgetTypesService, +} from "@nova-ui/dashboards"; + +/** + * A component that instantiates the dashboard + */ +@Component({ + selector: "hello-dashboards-example", + templateUrl: "./hello-dashboards-example.component.html", + styleUrls: ["./hello-dashboards-example.component.less"], + standalone: false, +}) +export class HelloDashboardsExampleComponent implements OnInit { + // This variable will have all the data needed to render the widgets widgets. + // Pass this to the dashboard component's dashboard input. + public dashboard: IDashboard; + // Angular gridster requires a configuration object even if its empty. + // Pass this to the dashboard component's gridsterConfig input. + public gridsterConfig: GridsterConfig = {}; + + // WidgetTypesService provides the widget's necessary structure information + constructor(private widgetTypesService: WidgetTypesService) {} + + public ngOnInit(): void { + // Here we are hard-coding the widget config for this example, but this is where you + // could potentially populate the widget's configuration from a database + const kpiWidget = widgetConfig; + const widgetIndex: IWidgets = { + // Complete the KPI widget with information coming from its type definition + [kpiWidget.id]: + this.widgetTypesService.mergeWithWidgetType(kpiWidget), + }; + // Setting widget position and dimensions (this is for gridster) + const positions: Record = { + [kpiWidget.id]: { + cols: 4, + rows: 6, + y: 0, + x: 0, + }, + }; + // Finally, assigning the variables we created above to the dashboard + this.dashboard = { + positions, + widgets: widgetIndex, + }; + } +} + +// In a real-world scenario, this configuration would typically be fetched from a database or at least live in another file +const widgetConfig: IWidget = { + id: "widget1", + type: "kpi", + pizzagna: { + [PizzagnaLayer.Configuration]: { + header: { + properties: { + title: "Hello, KPI Widget!", + subtitle: "A Venue for Meaningful Values", + }, + }, + tiles: { + properties: { + nodes: ["kpi1"], + }, + }, + kpi1: { + id: "kpi1", + componentType: KpiComponent.lateLoadKey, + properties: { + widgetData: { + id: "totalStorage", + value: 1, + label: "Total storage", + units: "TB", + }, + }, + }, + }, + }, +}; +\\\\\\\\\\\\\\\`, + "tutorials/hello-dashboards/hello-dashboards.module.ts": \\\\\\\\\\\\\\\`// © 2022 SolarWinds Worldwide, LLC. All rights reserved. +// +// Permission is hereby granted, free of charge, to any person obtaining a copy +// of this software and associated documentation files (the "Software"), to +// deal in the Software without restriction, including without limitation the +// rights to use, copy, modify, merge, publish, distribute, sublicense, and/or +// sell copies of the Software, and to permit persons to whom the Software is +// furnished to do so, subject to the following conditions: +// +// The above copyright notice and this permission notice shall be included in +// all copies or substantial portions of the Software. +// +// THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR +// IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, +// FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE +// AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER +// LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, +// OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN +// THE SOFTWARE. + +import { CommonModule } from "@angular/common"; +import { NgModule } from "@angular/core"; +import { RouterModule } from "@angular/router"; + +import { + NuiDocsModule, + NuiMessageModule, + DEMO_PATH_TOKEN, +} from "@nova-ui/bits"; +import { NuiDashboardsModule } from "@nova-ui/dashboards"; + +import { HelloDashboardsDocsComponent } from "./hello-dashboards-docs.component"; +import { HelloDashboardsExampleComponent } from "./hello-dashboards-example/hello-dashboards-example.component"; +import { getDemoFiles } from "../../../../demo-files-factory"; + +const routes = [ + { + path: "", + component: HelloDashboardsDocsComponent, + data: { + srlc: { + hideIndicator: true, + }, + showThemeSwitcher: true, + }, + }, + { + path: "example", + component: HelloDashboardsExampleComponent, + data: { + srlc: { + hideIndicator: true, + }, + }, + }, +]; + +@NgModule({ + imports: [ + CommonModule, + NuiDashboardsModule, + NuiDocsModule, + NuiMessageModule, + RouterModule.forChild(routes), + ], + declarations: [ + HelloDashboardsDocsComponent, + HelloDashboardsExampleComponent, + ], + providers: [ + { + provide: DEMO_PATH_TOKEN, + useValue: getDemoFiles("hello-dashboards"), + }, + ], +}) +export default class HelloDashboardsModule {} +\\\\\\\\\\\\\\\`, + "tutorials/persistence-handler-setup/persistence-handler-setup-docs.component.ts": \\\\\\\\\\\\\\\`// © 2022 SolarWinds Worldwide, LLC. All rights reserved. +// +// Permission is hereby granted, free of charge, to any person obtaining a copy +// of this software and associated documentation files (the "Software"), to +// deal in the Software without restriction, including without limitation the +// rights to use, copy, modify, merge, publish, distribute, sublicense, and/or +// sell copies of the Software, and to permit persons to whom the Software is +// furnished to do so, subject to the following conditions: +// +// The above copyright notice and this permission notice shall be included in +// all copies or substantial portions of the Software. +// +// THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR +// IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, +// FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE +// AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER +// LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, +// OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN +// THE SOFTWARE. + +import { Component } from "@angular/core"; + +@Component({ + selector: "nui-dashboard-persistence-handler-setup-docs", + templateUrl: "./persistence-handler-setup-docs.component.html", + standalone: false, +}) +export class PersistenceHandlerSetupDocsComponent {} +\\\\\\\\\\\\\\\`, + "tutorials/persistence-handler-setup/persistence-handler-setup.component.ts": \\\\\\\\\\\\\\\`// © 2022 SolarWinds Worldwide, LLC. All rights reserved. +// +// Permission is hereby granted, free of charge, to any person obtaining a copy +// of this software and associated documentation files (the "Software"), to +// deal in the Software without restriction, including without limitation the +// rights to use, copy, modify, merge, publish, distribute, sublicense, and/or +// sell copies of the Software, and to permit persons to whom the Software is +// furnished to do so, subject to the following conditions: +// +// The above copyright notice and this permission notice shall be included in +// all copies or substantial portions of the Software. +// +// THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR +// IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, +// FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE +// AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER +// LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, +// OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN +// THE SOFTWARE. + +import { HttpClient, HttpErrorResponse } from "@angular/common/http"; +import { + ChangeDetectorRef, + Component, + Injectable, + OnDestroy, + OnInit, +} from "@angular/core"; +import { GridsterConfig, GridsterItem } from "angular-gridster2"; +import { BehaviorSubject, Observable, Subject } from "rxjs"; +import { finalize } from "rxjs/operators"; + +import { + DataSourceService, + IFilteringOutputs, + ToastService, + uuid, +} from "@nova-ui/bits"; +import { + DATA_SOURCE, + DEFAULT_PIZZAGNA_ROOT, + IDashboard, + IDashboardPersistenceHandler, + IKpiData, + IProviderConfiguration, + IRefresherProperties, + IWidget, + IWidgets, + KpiComponent, + NOVA_KPI_DATASOURCE_ADAPTER, + PizzagnaLayer, + ProviderRegistryService, + WellKnownPathKey, + WellKnownProviders, + WidgetTypesService, +} from "@nova-ui/dashboards"; + +/** + * A simple persistence handler that is tied to the widget editor directive + */ +@Injectable() +// The realizer of IDashboardPersistenceHandler may implement a trySubmit and/or a tryRemove method. +export class PersistenceHandler implements IDashboardPersistenceHandler { + // This variable is just to show how to handle error handling. + private persistenceSucceeded: boolean = true; + + // The example uses the toast service to demonstrate the + // invocation of each of the persistence handler callbacks + constructor(private toastService: ToastService) { + // toastService options to let it sit on the page for 2 seconds. + this.toastService.setConfig({ + timeOut: 2000, + }); + } + + // This method will be invoked anytime the widget editor form gets submitted. + public trySubmit = (widget: IWidget): Observable => { + // Since we are working asynchronously, we'll return a subject. So, after the submit attempt + // succeeds or fails, we can let the subscriber know the result. + const subject = new Subject(); + + if (!widget.id) { + // Creates an id if the widget has no id. + // (This step will make more sense in the context of the widget cloning tutorial + // in which we handle the persistence of a newly created widget.) + widget.id = uuid(); + } + + // For this example, we're using a setTimeout to mock an asynchronous persistence request to a backend + setTimeout(() => { + if (this.persistenceSucceeded) { + // Passes along the new widget after one second. + subject.next(widget); + // Toast on the page on success. + this.toastService.success({ + title: $localize\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\`Submit succeeded.\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\`, + }); + } else { + const errorText = $localize\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\`Submit failed.\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\`; + // Toast on the page on failure. + this.toastService.error({ title: errorText }); + // Makes the subject say there is an error. + subject.error(errorText); + } + // Completes the subject so whoever subscribes to it knows its finished. + subject.complete(); + }, 1000); + + // Returns the subject as an observable. + return subject.asObservable(); + }; + + // This method will be invoked anytime there's a widget removal attempt. + public tryRemove = (widgetId: string): Observable => { + const subject = new Subject(); + + setTimeout(() => { + if (this.persistenceSucceeded) { + // Pass through the id of the widget that was removed. + subject.next(widgetId); + this.toastService.success({ + title: $localize\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\`Removal succeeded.\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\`, + }); + } else { + const errorText = $localize\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\`Removal failed.\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\`; + this.toastService.error({ title: errorText }); + subject.error(errorText); + } + subject.complete(); + }, 1000); + + return subject.asObservable(); + }; +} + +/** + * A component that instantiates the dashboard + */ +@Component({ + selector: "persistence-handler-setup", + templateUrl: "./persistence-handler-setup.component.html", + styleUrls: ["./persistence-handler-setup.component.less"], + // Here we provide our persistence handler at the component level; this can also be done in the module. + providers: [PersistenceHandler], + standalone: false, +}) +export class PersistenceHandlerSetupComponent implements OnInit { + // This variable will hold all the data needed to define the layout and behavior of the widgets. + // Pass this to the dashboard component's dashboard input in the template. + public dashboard: IDashboard | undefined; + + // Angular gridster requires a configuration object even if it's empty. + // Pass this to the dashboard component's gridsterConfig input in the template. + public gridsterConfig: GridsterConfig = {}; + + // Boolean which dashboard takes in as an input if its true it allows you to move widgets around. + public editMode: boolean = false; + + constructor( + // WidgetTypesService provides the widget's necessary structure information + private widgetTypesService: WidgetTypesService, + + // In general, the ProviderRegistryService is used for making entities available for injection into dynamically loaded components. + private providerRegistry: ProviderRegistryService, + + // We are injecting the PersistenceHandler we created and assigning it to a property we use in the template. + public persistenceHandler: PersistenceHandler, + private changeDetectorRef: ChangeDetectorRef + ) {} + + public ngOnInit(): void { + // Grabbing the widget's default template which will be needed as a parameter for setNode + const widgetTemplate = this.widgetTypesService.getWidgetType("kpi", 1); + // Registering our data sources as dropdown options in the widget editor/configurator + // Note: This could also be done in the parent module's constructor so that + // multiple dashboards could have access to the same widget template modification. + this.widgetTypesService.setNode( + // This is the template we grabbed above with getWidgetType + widgetTemplate, + // We are setting the editor/configurator part of the widget template + "configurator", + // This indicates which node you are changing and we want to change + // the data source providers available for selection in the editor. + WellKnownPathKey.DataSourceProviders, + // We are setting the data sources available for selection in the editor + [ + AverageRatingKpiDataSource.providerId, + RatingsCountKpiDataSource.providerId, + ] + ); + + // Registering the data sources available for injection into the KPI tiles. + // Note: Each tile of a KPI widget is assigned its own instance of a data source + this.providerRegistry.setProviders({ + [AverageRatingKpiDataSource.providerId]: { + provide: DATA_SOURCE, + useClass: AverageRatingKpiDataSource, + // Any dependencies that need to be injected into the provider must be listed here + deps: [HttpClient], + }, + [RatingsCountKpiDataSource.providerId]: { + provide: DATA_SOURCE, + useClass: RatingsCountKpiDataSource, + deps: [HttpClient], + }, + }); + + this.initializeDashboard(); + } + + /** Used for restoring widgets state */ + public reInitializeDashboard(): void { + // destroys the components and their providers so the dashboard can re init data + this.dashboard = undefined; + this.changeDetectorRef.detectChanges(); + + this.initializeDashboard(); + } + + public initializeDashboard(): void { + // We're using a static configuration object for this example (see widgetConfig at the bottom of the file), + // but this is where the widget's configuration could potentially be populated from a database + const kpiWidget = widgetConfig; + const widgetIndex: IWidgets = { + // Complete the KPI widget with information coming from its type definition + [kpiWidget.id]: + this.widgetTypesService.mergeWithWidgetType(kpiWidget), + }; + + // Setting the widget dimensions and position (this is for gridster) + const positions: Record = { + [kpiWidget.id]: { + cols: 4, + rows: 6, + y: 0, + x: 0, + }, + }; + + // Finally, assigning the variables we created above to the dashboard + this.dashboard = { + positions, + widgets: widgetIndex, + }; + } +} + +/** + * A simple KPI data source to retrieve the average rating of Harry Potter and the Sorcerer's Stone (book) via googleapis + */ +@Injectable() +export class AverageRatingKpiDataSource + extends DataSourceService + implements OnDestroy +{ + // This is the ID we'll use to identify the provider + public static providerId = "AverageRatingKpiDataSource"; + + // Use this subject to communicate the data source's busy state + public busy = new BehaviorSubject(false); + + constructor(private http: HttpClient) { + super(); + } + + // In this example, getFilteredData is invoked every 10 minutes (Take a look at the refresher + // provider definition in the widget configuration below to see how the interval is set) + public async getFilteredData(): Promise { + this.busy.next(true); + return new Promise((resolve) => { + // *** Make a resource request to an external API (if needed) + this.http + .get("https://www.googleapis.com/books/v1/volumes/5MQFrgEACAAJ") + .pipe(finalize(() => this.busy.next(false))) + .subscribe({ + next: (data: any) => { + resolve({ + result: { + value: data.volumeInfo.averageRating, + }, + }); + }, + error: (error: HttpErrorResponse) => { + resolve({ + result: null, + error: { + type: error.status, + }, + }); + }, + }); + }); + } + + public ngOnDestroy(): void { + this.outputsSubject.complete(); + } +} + +/** + * A simple KPI data source to retrieve the ratings count of Harry Potter and the Sorcerer's Stone (book) via googleapis + */ +@Injectable() +export class RatingsCountKpiDataSource + extends DataSourceService + implements OnDestroy +{ + public static providerId = "RatingsCountKpiDataSource"; + + // Use this subject to communicate the data source's busy state + public busy = new BehaviorSubject(false); + + constructor(private http: HttpClient) { + super(); + } + + public async getFilteredData(): Promise { + this.busy.next(true); + return new Promise((resolve) => { + this.http + .get("https://www.googleapis.com/books/v1/volumes/5MQFrgEACAAJ") + .pipe(finalize(() => this.busy.next(false))) + .subscribe({ + next: (data: any) => { + resolve({ + result: { + value: data.volumeInfo.ratingsCount, + }, + }); + }, + error: (error: HttpErrorResponse) => { + resolve({ + result: null, + error: { + type: error.status, + }, + }); + }, + }); + }); + } + + public ngOnDestroy(): void { + this.outputsSubject.complete(); + } +} + +const widgetConfig: IWidget = { + id: "widget1", + type: "kpi", + pizzagna: { + [PizzagnaLayer.Configuration]: { + [DEFAULT_PIZZAGNA_ROOT]: { + providers: { + [WellKnownProviders.Refresher]: { + properties: { + // Configuring the refresher interval so that our data source is invoked every ten minutes + interval: 60 * 10, + enabled: true, + } as IRefresherProperties, + } as Partial, + }, + }, + header: { + properties: { + title: "Harry Potter and the Sorcerer's Stone", + subtitle: "By J. K. Rowling", + }, + }, + tiles: { + properties: { + nodes: ["kpi1"], + }, + }, + kpi1: { + id: "kpi1", + componentType: KpiComponent.lateLoadKey, + properties: { + widgetData: { + units: "out of 5 Stars", + label: "Average Rating", + }, + }, + providers: { + [WellKnownProviders.DataSource]: { + // Setting the data source providerId for the tile with id "kpi1" + providerId: AverageRatingKpiDataSource.providerId, + } as IProviderConfiguration, + [WellKnownProviders.Adapter]: { + providerId: NOVA_KPI_DATASOURCE_ADAPTER, + properties: { + componentId: "kpi1", + propertyPath: "widgetData", + }, + } as IProviderConfiguration, + }, + }, + }, + }, +}; +\\\\\\\\\\\\\\\`, + "tutorials/persistence-handler-setup/persistence-handler-setup.module.ts": \\\\\\\\\\\\\\\`// © 2022 SolarWinds Worldwide, LLC. All rights reserved. +// +// Permission is hereby granted, free of charge, to any person obtaining a copy +// of this software and associated documentation files (the "Software"), to +// deal in the Software without restriction, including without limitation the +// rights to use, copy, modify, merge, publish, distribute, sublicense, and/or +// sell copies of the Software, and to permit persons to whom the Software is +// furnished to do so, subject to the following conditions: +// +// The above copyright notice and this permission notice shall be included in +// all copies or substantial portions of the Software. +// +// THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR +// IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, +// FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE +// AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER +// LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, +// OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN +// THE SOFTWARE. + +import { CommonModule } from "@angular/common"; +import { HttpClientModule } from "@angular/common/http"; +import { NgModule } from "@angular/core"; +import { RouterModule } from "@angular/router"; + +import { + NuiButtonModule, + NuiDocsModule, + NuiMessageModule, + NuiSwitchModule, + NuiToastModule, + DEMO_PATH_TOKEN, +} from "@nova-ui/bits"; +import { NuiDashboardsModule } from "@nova-ui/dashboards"; + +import { PersistenceHandlerSetupDocsComponent } from "./persistence-handler-setup-docs.component"; +import { PersistenceHandlerSetupComponent } from "./persistence-handler-setup.component"; +import { getDemoFiles } from "../../../../demo-files-factory"; + +const routes = [ + { + path: "", + component: PersistenceHandlerSetupDocsComponent, + data: { + srlc: { + hideIndicator: true, + }, + showThemeSwitcher: true, + }, + }, + { + path: "example", + component: PersistenceHandlerSetupComponent, + data: { + srlc: { + hideIndicator: true, + }, + }, + }, +]; + +@NgModule({ + imports: [ + CommonModule, + HttpClientModule, + NuiDashboardsModule, + NuiDocsModule, + NuiMessageModule, + NuiSwitchModule, + NuiToastModule, + NuiButtonModule, + RouterModule.forChild(routes), + ], + declarations: [ + PersistenceHandlerSetupDocsComponent, + PersistenceHandlerSetupComponent, + ], + providers: [ + { + provide: DEMO_PATH_TOKEN, + useValue: getDemoFiles("persistence-handler-setup"), + }, + ], +}) +export default class PersistenceHandlerSetupModule {} +\\\\\\\\\\\\\\\`, + "tutorials/tutorials.module.ts": \\\\\\\\\\\\\\\`// © 2022 SolarWinds Worldwide, LLC. All rights reserved. +// +// Permission is hereby granted, free of charge, to any person obtaining a copy +// of this software and associated documentation files (the "Software"), to +// deal in the Software without restriction, including without limitation the +// rights to use, copy, modify, merge, publish, distribute, sublicense, and/or +// sell copies of the Software, and to permit persons to whom the Software is +// furnished to do so, subject to the following conditions: +// +// The above copyright notice and this permission notice shall be included in +// all copies or substantial portions of the Software. +// +// THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR +// IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, +// FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE +// AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER +// LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, +// OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN +// THE SOFTWARE. + +import { NgModule, Type } from "@angular/core"; +import { RouterModule, Routes } from "@angular/router"; + +import { ConfiguratorHeadingService } from "@nova-ui/dashboards"; + +export enum TutorialsModuleRoute { + HelloDashboards = "hello-dashboards", + DataSource = "data-source-setup", + WidgetEditor = "widget-editor-setup", + SubmitHandler = "persistence-handler-setup", + WidgetCreation = "widget-creation", + Customization = "customization", + WidgetErrorHandling = "widget-error-handling", + DynamicHeaderLinks = "dynamic-header-links", +} + +const routes: Routes = [ + { + path: TutorialsModuleRoute.HelloDashboards, + loadChildren: async () => + import( + "./hello-dashboards/hello-dashboards.module" + ) as object as Promise>, + }, + { + path: TutorialsModuleRoute.DataSource, + loadChildren: async () => + import( + "./data-source-setup/data-source-setup.module" + ) as object as Promise>, + }, + { + path: TutorialsModuleRoute.WidgetEditor, + loadChildren: async () => + import( + "./widget-editor-setup/widget-editor-setup.module" + ) as object as Promise>, + }, + { + path: TutorialsModuleRoute.SubmitHandler, + loadChildren: async () => + import( + "./persistence-handler-setup/persistence-handler-setup.module" + ) as object as Promise>, + }, + { + path: TutorialsModuleRoute.WidgetCreation, + loadChildren: async () => + import( + "./widget-creation/widget-creation.module" + ) as object as Promise>, + }, + { + path: TutorialsModuleRoute.Customization, + loadChildren: async () => + import("./customization/customization.module") as object as Promise< + Type + >, + }, + { + path: TutorialsModuleRoute.WidgetErrorHandling, + loadChildren: async () => + import( + "./widget-error-handling/widget-error-handling.module" + ) as object as Promise>, + }, + { + path: TutorialsModuleRoute.DynamicHeaderLinks, + loadChildren: async () => + import( + "./dynamic-header-links/dynamic-header-links-docs.module" + ) as object as Promise>, + }, +]; + +@NgModule({ + imports: [RouterModule.forChild(routes)], + providers: [ConfiguratorHeadingService], +}) +export default class TutorialsModule {} +\\\\\\\\\\\\\\\`, + "tutorials/widget-creation/widget-creation-docs.component.ts": \\\\\\\\\\\\\\\`// © 2022 SolarWinds Worldwide, LLC. All rights reserved. +// +// Permission is hereby granted, free of charge, to any person obtaining a copy +// of this software and associated documentation files (the "Software"), to +// deal in the Software without restriction, including without limitation the +// rights to use, copy, modify, merge, publish, distribute, sublicense, and/or +// sell copies of the Software, and to permit persons to whom the Software is +// furnished to do so, subject to the following conditions: +// +// The above copyright notice and this permission notice shall be included in +// all copies or substantial portions of the Software. +// +// THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR +// IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, +// FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE +// AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER +// LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, +// OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN +// THE SOFTWARE. + +import { Component } from "@angular/core"; + +@Component({ + selector: "nui-dashboard-widget-creation-docs", + templateUrl: "./widget-creation-docs.component.html", + standalone: false, +}) +export class WidgetCreationDocsComponent {} +\\\\\\\\\\\\\\\`, + "tutorials/widget-creation/widget-creation.component.ts": \\\\\\\\\\\\\\\`// © 2022 SolarWinds Worldwide, LLC. All rights reserved. +// +// Permission is hereby granted, free of charge, to any person obtaining a copy +// of this software and associated documentation files (the "Software"), to +// deal in the Software without restriction, including without limitation the +// rights to use, copy, modify, merge, publish, distribute, sublicense, and/or +// sell copies of the Software, and to permit persons to whom the Software is +// furnished to do so, subject to the following conditions: +// +// The above copyright notice and this permission notice shall be included in +// all copies or substantial portions of the Software. +// +// THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR +// IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, +// FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE +// AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER +// LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, +// OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN +// THE SOFTWARE. + +import { HttpClient, HttpErrorResponse } from "@angular/common/http"; +import { + Component, + EventEmitter, + Injectable, + OnDestroy, + OnInit, + Output, + ViewChild, +} from "@angular/core"; +import { GridsterConfig, GridsterItem } from "angular-gridster2"; +import { BehaviorSubject, Observable, Subject } from "rxjs"; +import { finalize, take, takeUntil } from "rxjs/operators"; + +import { + DataSourceService, + IFilteringOutputs, + ToastService, + uuid, +} from "@nova-ui/bits"; +import { + DashboardComponent, + DATA_SOURCE, + DEFAULT_PIZZAGNA_ROOT, + IDashboard, + IDashboardPersistenceHandler, + IDataSourceOutput, + IKpiData, + IProviderConfiguration, + IRefresherProperties, + IWidget, + IWidgets, + IWidgetSelector, + IWidgetTemplateSelector, + KpiComponent, + NOVA_KPI_DATASOURCE_ADAPTER, + PizzagnaLayer, + ProviderRegistryService, + WellKnownPathKey, + WellKnownProviders, + WidgetClonerService, + WidgetTypesService, +} from "@nova-ui/dashboards"; + +// Interface of a widget item +interface IWidgetItem { + name: string; + widget: IWidget; +} + +// This component acts as the first step, or page, in the wizard where the user selects a wizard type to create. +// It's recommended to have this component in a different file. For this tutorial, it's included in the same +// file for simplicity. +@Component({ + selector: "widget-template-selection", + styleUrls: ["./widget-creation.component.less"], + template: \\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\` +
+ + +
+ + +
+
{{ item.name }}
+
+
+ \\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\`, + standalone: false, +}) +export class WidgetTemplateSelectionComponent + implements IWidgetTemplateSelector, OnInit +{ + // This output will notify the wizard that a widget has been selected. + @Output() public widgetSelected = new EventEmitter(); + + public widgetItems: IWidgetItem[] = []; + public widgetSelection: IWidgetItem[]; + + constructor(private widgetTypesService: WidgetTypesService) {} + + public ngOnInit(): void { + // Here we combine the widget structure from the WidgetTypesService with the corresponding widget + // configuration to create an array of widget objects for the itemSource on the repeat component. + this.widgetItems = [ + { + name: "Fully Configured KPI Widget", + widget: this.widgetTypesService.mergeWithWidgetType( + fullKpiWidgetConfig + ), + }, + { + name: "Unconfigured Proportional Widget", + // Note that 'partialPropWidgetConfig' sets 'metadata.needsConfiguration' to true. + // When this widget is selected in the wizard, the 'Create Widget' button will be hidden + // to guide the user to the second step where they can complete the configuration. + widget: this.widgetTypesService.mergeWithWidgetType( + partialPropWidgetConfig + ), + }, + ]; + + // You can optionally auto-select a widget by doing the following + // this.onSelect([this.widgetItems[0]]); + } + + public onSelect(selectedItems: any[]): void { + // We emit the selected widget to communicate the selection to the configurator + this.widgetSelected.emit(selectedItems[0].widget); + this.widgetSelection = selectedItems; + } +} + +/** + * A simple persistence handler that is tied to the widget editor directive + */ +@Injectable() +// The realizer of IDashboardPersistenceHandler may implement a trySubmit and/or a tryRemove method. +export class PersistenceHandler implements IDashboardPersistenceHandler { + // This variable is just to show how to handle error handling. + private persistenceSucceeded: boolean = true; + + // The example uses the toast service to demonstrate the + // invocation of each of the persistence handler callbacks + constructor(private toastService: ToastService) { + // toastService options to let it sit on the page for 2 seconds. + this.toastService.setConfig({ + timeOut: 2000, + }); + } + + // This method will be invoked anytime the widget editor form gets submitted. + public trySubmit = (widget: IWidget): Observable => { + // Since we are working asynchronously, we'll return a subject. So, after the submit attempt + // succeeds or fails, we can let the subscriber know the result. + const subject = new Subject(); + + if (!widget.id) { + // Creates an id if the widget has no id. + // (This step will make more sense in the context of the widget cloning tutorial + // in which we handle the persistence of a newly created widget.) + widget.id = uuid(); + } + + // For this example, we're using a setTimeout to mock an asynchronous persistence request to a backend + setTimeout(() => { + if (this.persistenceSucceeded) { + // Passes along the new widget after one second. + subject.next(widget); + // Toast on the page on success. + this.toastService.success({ + title: $localize\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\`Submit succeeded.\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\`, + }); + } else { + const errorText = $localize\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\`Submit failed.\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\`; + // Toast on the page on failure. + this.toastService.error({ title: errorText }); + // Makes the subject say there is an error. + subject.error(errorText); + } + // Completes the subject so whoever subscribes to it knows its finished. + subject.complete(); + }, 1000); + + // Returns the subject as an observable. + return subject.asObservable(); + }; + + // This method will be invoked anytime there's a widget removal attempt. + public tryRemove = (widgetId: string): Observable => { + const subject = new Subject(); + + setTimeout(() => { + if (this.persistenceSucceeded) { + // Pass through the id of the widget that was removed. + subject.next(widgetId); + this.toastService.success({ + title: $localize\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\`Removal success\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\`, + }); + } else { + const errorText = $localize\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\`Removal failed.\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\`; + this.toastService.error({ title: errorText }); + subject.error(errorText); + } + subject.complete(); + }, 1000); + + return subject.asObservable(); + }; +} + +/** + * A component that instantiates the dashboard + */ +@Component({ + selector: "widget-creation", + templateUrl: "./widget-creation.component.html", + styleUrls: ["./widget-creation.component.less"], + // Here we provide our persistence handler at the component level; this can also be done in the module. + providers: [PersistenceHandler], + standalone: false, +}) +export class WidgetCreationComponent implements OnInit { + // The WidgetClonerService will need this for updating the dashboard + @ViewChild(DashboardComponent, { static: true }) + dashboardComponent: DashboardComponent; + // This variable will hold all the data needed to define the layout and behavior of the widgets. + // Pass this to the dashboard component's dashboard input in the template. + public dashboard: IDashboard; + + // Angular gridster requires a configuration object even if it's empty. + // Pass this to the dashboard component's gridsterConfig input in the template. + public gridsterConfig: GridsterConfig = { + // These values will be used to set the initial widget dimensions on creation. + // If not set, they each default to 6. + defaultItemCols: 3, + defaultItemRows: 5, + }; + + // Boolean the dashboard takes in as an input; if it's set to true + // the dashboard allows you to resize widgets and move them around. + public editMode: boolean = false; + + // Subject used for auto-unsubscribing from subscriptions on component destruction + private readonly destroy$ = new Subject(); + + constructor( + // WidgetTypesService provides the widget's necessary structure information + private widgetTypesService: WidgetTypesService, + + // In general, the ProviderRegistryService is used for making entities available for injection into dynamically loaded components. + private providerRegistry: ProviderRegistryService, + + // Injecting the PersistenceHandler we created and assigning it to a property we use in the template. + public persistenceHandler: PersistenceHandler, + + // Injecting the cloner service which is needed for opening up the cloner wizard. + private widgetClonerService: WidgetClonerService + ) {} + + public ngOnInit(): void { + // Grabbing the widget's default template which will be needed as a parameter for setNode + const kpiTemplate = this.widgetTypesService.getWidgetType("kpi", 1); + const proportionalTemplate = this.widgetTypesService.getWidgetType( + "proportional", + 1 + ); + + // Registering our data sources as dropdown options in the widget editor/configurator + // Note: This could also be done in the parent module's constructor so that + // multiple dashboards could have access to the same widget template modification. + this.widgetTypesService.setNode( + // This is the template we grabbed above with getWidgetType + proportionalTemplate, + // Setting the editor/configurator part of the widget template + "configurator", + // This indicates which node you are changing and we want to change + // the data source providers available for selection in the editor. + WellKnownPathKey.DataSourceProviders, + // Setting the data sources available for selection in the editor + [RandomCitiesProportionalDataSource.providerId] + ); + + // Same as above, but for the KPI data sources + this.widgetTypesService.setNode( + kpiTemplate, + "configurator", + WellKnownPathKey.DataSourceProviders, + [ + AverageRatingKpiDataSource.providerId, + RatingsCountKpiDataSource.providerId, + ] + ); + + // Registering the data sources available for injection into the KPI tiles and proportional widget. + // Note: Each tile of a KPI widget is assigned its own instance of a data source. + this.providerRegistry.setProviders({ + [AverageRatingKpiDataSource.providerId]: { + provide: DATA_SOURCE, + useClass: AverageRatingKpiDataSource, + // Any dependencies that need to be injected into the provider must be listed here + deps: [HttpClient], + }, + [RatingsCountKpiDataSource.providerId]: { + provide: DATA_SOURCE, + useClass: RatingsCountKpiDataSource, + deps: [HttpClient], + }, + [RandomCitiesProportionalDataSource.providerId]: { + provide: DATA_SOURCE, + useClass: RandomCitiesProportionalDataSource, + deps: [], + }, + }); + + this.initializeDashboard(); + } + + public onCreateWidget(): void { + const widgetSelector: IWidgetSelector = { + // Template ref of the dashboard component. + dashboardComponent: this.dashboardComponent, + // A trySubmit function; in this case, we use the trySubmit from the PersistenceHandler created in the previous tutorial. + trySubmit: this.persistenceHandler.trySubmit, + // WidgetTemplateSelectionComponent will act as step one of the wizard to allow the user to select which widget will be cloned. + widgetSelectionComponentType: WidgetTemplateSelectionComponent, + }; + this.widgetClonerService + .open(widgetSelector) + .pipe( + // Auto-unsubscribe after one emission or on component destruction + take(1), + takeUntil(this.destroy$) + ) + .subscribe(); + } + + public initializeDashboard(): void { + // We're using a static configuration object for this example (see widgetConfig at the bottom of the file), + // but this is where the widget's configuration could potentially be populated from a database + const kpiWidget = fullKpiWidgetConfig; + const widgetIndex: IWidgets = { + // Complete the KPI widget with information coming from its type definition + [kpiWidget.id]: + this.widgetTypesService.mergeWithWidgetType(kpiWidget), + }; + + // Setting the widget dimensions and position (this is for gridster) + // Note: If no position is given for a widget the 'defaultItemCols' and 'defaultItemRows' properties + // from the gridsterConfig will be used for the dimensions + const positions: Record = { + [kpiWidget.id]: { + cols: 3, + rows: 5, + y: 0, + x: 0, + }, + }; + + // Finally, assigning the variables we created above to the dashboard + this.dashboard = { + positions, + widgets: widgetIndex, + }; + } +} + +// Interface for each data point in a proportional widget. +interface IProportionalWidgetData { + id: string; + name: string; + data: number[]; + icon: string; + link: string; + value: string; +} + +@Injectable() +export class RandomCitiesProportionalDataSource implements OnDestroy { + public static providerId = "RandomCitiesProportionalDataSource"; + + public outputsSubject = new Subject< + IDataSourceOutput + >(); + + // Every time applyFilters gets ran we are changing the data source. + public applyFilters(): void { + setTimeout(() => { + this.outputsSubject.next({ + result: this.getRandomProportionalWidgetData(), + }); + }, 1000); + } + + public ngOnDestroy(): void { + this.outputsSubject.complete(); + } + + private getRandomProportionalWidgetData(): IProportionalWidgetData[] { + return [ + { + id: "Down", + name: "Down", + data: [Math.round(Math.random() * 100)], + icon: "status_down", + link: "https://en.wikipedia.org/wiki/Brno", + value: "Brno", + }, + { + id: "Critical", + name: "Critical", + data: [Math.round(Math.random() * 100)], + icon: "status_critical", + link: "https://en.wikipedia.org/wiki/Kyiv", + value: "Kyiv", + }, + { + id: "Warning", + name: "Warning", + data: [Math.round(Math.random() * 100)], + icon: "status_warning", + link: "https://en.wikipedia.org/wiki/Austin", + value: "Austin", + }, + { + id: "Unknown", + name: "Unknown", + data: [Math.round(Math.random() * 100)], + icon: "status_unknown", + link: "https://en.wikipedia.org/wiki/Lisbon", + value: "Lisbon", + }, + { + id: "Up", + name: "Up", + data: [Math.round(Math.random() * 100)], + icon: "status_up", + link: "https://en.wikipedia.org/wiki/Sydney", + value: "Sydney", + }, + { + id: "Unmanaged", + name: "Unmanaged", + data: [Math.round(Math.random() * 100)], + icon: "status_unmanaged", + link: "https://en.wikipedia.org/wiki/Nur-Sultan", + value: "Nur-Sultan", + }, + ]; + } +} + +/** + * A simple KPI data source to retrieve the average rating of Harry Potter and the Sorcerer's Stone (book) via googleapis + */ +@Injectable() +export class AverageRatingKpiDataSource + extends DataSourceService + implements OnDestroy +{ + // This is the ID we'll use to identify the provider + public static providerId = "AverageRatingKpiDataSource"; + + // Use this subject to communicate the data source's busy state + public busy = new BehaviorSubject(false); + + constructor(private http: HttpClient) { + super(); + } + + // In this example, getFilteredData is invoked every 10 minutes (Take a look at the refresher + // provider definition in the widget configuration below to see how the interval is set) + public async getFilteredData(): Promise { + this.busy.next(true); + return new Promise((resolve) => { + // *** Make a resource request to an external API (if needed) + this.http + .get("https://www.googleapis.com/books/v1/volumes/5MQFrgEACAAJ") + .pipe(finalize(() => this.busy.next(false))) + .subscribe({ + next: (data: any) => { + resolve({ + result: { + value: data.volumeInfo.averageRating, + }, + }); + }, + error: (error: HttpErrorResponse) => { + resolve({ + result: null, + error: { + type: error.status, + }, + }); + }, + }); + }); + } + + public ngOnDestroy(): void { + this.outputsSubject.complete(); + } +} + +/** + * A simple KPI data source to retrieve the ratings count of Harry Potter and the Sorcerer's Stone (book) via googleapis + */ +@Injectable() +export class RatingsCountKpiDataSource + extends DataSourceService + implements OnDestroy +{ + public static providerId = "RatingsCountKpiDataSource"; + + // Use this subject to communicate the data source's busy state + public busy = new BehaviorSubject(false); + + constructor(private http: HttpClient) { + super(); + } + + public async getFilteredData(): Promise { + this.busy.next(true); + return new Promise((resolve) => { + this.http + .get("https://www.googleapis.com/books/v1/volumes/5MQFrgEACAAJ") + .pipe(finalize(() => this.busy.next(false))) + .subscribe({ + next: (data: any) => { + resolve({ + result: { + value: data.volumeInfo.ratingsCount, + }, + }); + }, + error: (error: HttpErrorResponse) => { + resolve({ + result: null, + error: { + type: error.status, + }, + }); + }, + }); + }); + } + + public ngOnDestroy(): void { + this.outputsSubject.complete(); + } +} + +const fullKpiWidgetConfig: IWidget = { + id: "widget1", + type: "kpi", + pizzagna: { + [PizzagnaLayer.Configuration]: { + [DEFAULT_PIZZAGNA_ROOT]: { + providers: { + [WellKnownProviders.Refresher]: { + properties: { + // Configuring the refresher interval so that our data source is invoked every ten minutes + interval: 60 * 10, + enabled: true, + } as IRefresherProperties, + } as Partial, + }, + }, + header: { + properties: { + title: "Harry Potter and the Sorcerer's Stone", + subtitle: "By J. K. Rowling", + }, + }, + tiles: { + properties: { + nodes: ["kpi1"], + }, + }, + kpi1: { + id: "kpi1", + componentType: KpiComponent.lateLoadKey, + properties: { + widgetData: { + units: \\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\`out of 5 Stars\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\`, + label: \\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\`Average Rating\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\`, + }, + }, + providers: { + [WellKnownProviders.DataSource]: { + // Setting the data source providerId for the tile with id "kpi1" + providerId: AverageRatingKpiDataSource.providerId, + } as IProviderConfiguration, + [WellKnownProviders.Adapter]: { + providerId: NOVA_KPI_DATASOURCE_ADAPTER, + properties: { + componentId: "kpi1", + propertyPath: "widgetData", + }, + } as IProviderConfiguration, + }, + }, + }, + }, +}; + +const partialPropWidgetConfig: IWidget = { + id: "widget2", + type: "proportional", + metadata: { + // Set 'needsConfiguration' to true if the widget needs further configuration before it can be + // placed on the dashboard. The "Create Widget" button will be hidden in the wizard when this + // widget is selected. + needsConfiguration: true, + }, + pizzagna: { + [PizzagnaLayer.Configuration]: { + header: { + properties: { + title: "*New Proportional Widget*", + }, + }, + }, + }, +}; +\\\\\\\\\\\\\\\`, + "tutorials/widget-creation/widget-creation.module.ts": \\\\\\\\\\\\\\\`// © 2022 SolarWinds Worldwide, LLC. All rights reserved. +// +// Permission is hereby granted, free of charge, to any person obtaining a copy +// of this software and associated documentation files (the "Software"), to +// deal in the Software without restriction, including without limitation the +// rights to use, copy, modify, merge, publish, distribute, sublicense, and/or +// sell copies of the Software, and to permit persons to whom the Software is +// furnished to do so, subject to the following conditions: +// +// The above copyright notice and this permission notice shall be included in +// all copies or substantial portions of the Software. +// +// THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR +// IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, +// FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE +// AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER +// LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, +// OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN +// THE SOFTWARE. + +import { CommonModule } from "@angular/common"; +import { HttpClientModule } from "@angular/common/http"; +import { NgModule } from "@angular/core"; +import { RouterModule } from "@angular/router"; + +import { + NuiButtonModule, + NuiDocsModule, + NuiImageModule, + NuiMessageModule, + NuiRepeatModule, + NuiSwitchModule, + NuiToastModule, + DEMO_PATH_TOKEN, +} from "@nova-ui/bits"; +import { NuiDashboardsModule } from "@nova-ui/dashboards"; + +import { WidgetCreationDocsComponent } from "./widget-creation-docs.component"; +import { + WidgetCreationComponent, + WidgetTemplateSelectionComponent, +} from "./widget-creation.component"; +import { getDemoFiles } from "../../../../demo-files-factory"; + +const routes = [ + { + path: "", + component: WidgetCreationDocsComponent, + data: { + srlc: { + hideIndicator: true, + }, + showThemeSwitcher: true, + }, + }, + { + path: "example", + component: WidgetCreationComponent, + data: { + srlc: { + hideIndicator: true, + }, + }, + }, +]; + +@NgModule({ + imports: [ + CommonModule, + HttpClientModule, + NuiDashboardsModule, + NuiDocsModule, + NuiMessageModule, + NuiSwitchModule, + NuiToastModule, + NuiButtonModule, + NuiRepeatModule, + NuiImageModule, + RouterModule.forChild(routes), + ], + declarations: [ + WidgetCreationDocsComponent, + WidgetCreationComponent, + WidgetTemplateSelectionComponent, + ], + providers: [ + { + provide: DEMO_PATH_TOKEN, + useValue: getDemoFiles("widget-creation"), + }, + ], +}) +export default class WidgetCreationModule {} +\\\\\\\\\\\\\\\`, + "tutorials/widget-editor-setup/widget-editor-setup-docs.component.ts": \\\\\\\\\\\\\\\`// © 2022 SolarWinds Worldwide, LLC. All rights reserved. +// +// Permission is hereby granted, free of charge, to any person obtaining a copy +// of this software and associated documentation files (the "Software"), to +// deal in the Software without restriction, including without limitation the +// rights to use, copy, modify, merge, publish, distribute, sublicense, and/or +// sell copies of the Software, and to permit persons to whom the Software is +// furnished to do so, subject to the following conditions: +// +// The above copyright notice and this permission notice shall be included in +// all copies or substantial portions of the Software. +// +// THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR +// IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, +// FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE +// AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER +// LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, +// OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN +// THE SOFTWARE. + +import { Component } from "@angular/core"; + +@Component({ + selector: "nui-dashboard-widget-editor-docs", + templateUrl: "./widget-editor-setup-docs.component.html", + standalone: false, +}) +export class WidgetEditorDocsComponent {} +\\\\\\\\\\\\\\\`, + "tutorials/widget-editor-setup/widget-editor-setup.component.ts": \\\\\\\\\\\\\\\`// © 2022 SolarWinds Worldwide, LLC. All rights reserved. +// +// Permission is hereby granted, free of charge, to any person obtaining a copy +// of this software and associated documentation files (the "Software"), to +// deal in the Software without restriction, including without limitation the +// rights to use, copy, modify, merge, publish, distribute, sublicense, and/or +// sell copies of the Software, and to permit persons to whom the Software is +// furnished to do so, subject to the following conditions: +// +// The above copyright notice and this permission notice shall be included in +// all copies or substantial portions of the Software. +// +// THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR +// IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, +// FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE +// AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER +// LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, +// OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN +// THE SOFTWARE. + +import { HttpClient, HttpErrorResponse } from "@angular/common/http"; +import { + ChangeDetectorRef, + Component, + Injectable, + OnDestroy, + OnInit, +} from "@angular/core"; +import { GridsterConfig, GridsterItem } from "angular-gridster2"; +import { BehaviorSubject } from "rxjs"; +import { finalize } from "rxjs/operators"; + +import { DataSourceService, IFilteringOutputs } from "@nova-ui/bits"; +import { + DATA_SOURCE, + DEFAULT_PIZZAGNA_ROOT, + IDashboard, + IKpiData, + IProviderConfiguration, + IRefresherProperties, + IWidget, + IWidgets, + KpiComponent, + NOVA_KPI_DATASOURCE_ADAPTER, + PizzagnaLayer, + ProviderRegistryService, + WellKnownPathKey, + WellKnownProviders, + WidgetTypesService, +} from "@nova-ui/dashboards"; + +/** + * A simple KPI data source to retrieve the average rating of Harry Potter and the Sorcerer's Stone (book) via googleapis + */ +@Injectable() +export class AverageRatingKpiDataSource + extends DataSourceService + implements OnDestroy +{ + // This is the ID we'll use to identify the provider + public static providerId = "AverageRatingKpiDataSource"; + + // Use this subject to communicate the data source's busy state + public busy = new BehaviorSubject(false); + + constructor(private http: HttpClient) { + super(); + } + + // In this example, getFilteredData is invoked every 10 minutes (Take a look at the refresher + // provider definition in the widget configuration below to see how the interval is set) + public async getFilteredData(): Promise { + this.busy.next(true); + return new Promise((resolve) => { + // *** Make a resource request to an external API (if needed) + this.http + .get("https://www.googleapis.com/books/v1/volumes/5MQFrgEACAAJ") + .pipe(finalize(() => this.busy.next(false))) + .subscribe({ + next: (data: any) => { + resolve({ + result: { + value: data.volumeInfo.averageRating, + }, + }); + }, + error: (error: HttpErrorResponse) => { + resolve({ + result: null, + error: { + type: error.status, + }, + }); + }, + }); + }); + } + + public ngOnDestroy(): void { + this.outputsSubject.complete(); + } +} + +/** + * A simple KPI data source to retrieve the ratings count of Harry Potter and the Sorcerer's Stone (book) via googleapis + */ +@Injectable() +export class RatingsCountKpiDataSource + extends DataSourceService + implements OnDestroy +{ + public static providerId = "RatingsCountKpiDataSource"; + + // Use this subject to communicate the data source's busy state + public busy = new BehaviorSubject(false); + + constructor(private http: HttpClient) { + super(); + } + + public async getFilteredData(): Promise { + this.busy.next(true); + return new Promise((resolve) => { + this.http + .get("https://www.googleapis.com/books/v1/volumes/5MQFrgEACAAJ") + .pipe(finalize(() => this.busy.next(false))) + .subscribe({ + next: (data: any) => { + resolve({ + result: { + value: data.volumeInfo.ratingsCount, + }, + }); + }, + error: (error: HttpErrorResponse) => { + resolve({ + result: null, + error: { + type: error.status, + }, + }); + }, + }); + }); + } + + public ngOnDestroy(): void { + this.outputsSubject.complete(); + } +} + +/** + * A component that instantiates the dashboard + */ +@Component({ + selector: "widget-editor-setup", + templateUrl: "./widget-editor-setup.component.html", + styleUrls: ["./widget-editor-setup.component.less"], + standalone: false, +}) +export class WidgetEditorSetupComponent implements OnInit { + // This variable will hold all the data needed to define the layout and behavior of the widgets. + // Pass this to the dashboard component's dashboard input in the template. + public dashboard: IDashboard | undefined; + + // Angular gridster requires a configuration object even if it's empty. + // Pass this to the dashboard component's gridsterConfig input in the template. + public gridsterConfig: GridsterConfig = {}; + + // Boolean which dashboard takes in as an input if its true it allows you to move widgets around. + public editMode: boolean = false; + + constructor( + // WidgetTypesService provides the widget's necessary structure information + private widgetTypesService: WidgetTypesService, + + // In general, the ProviderRegistryService is used for making entities available for injection into dynamically loaded components. + private providerRegistry: ProviderRegistryService, + private changeDetectorRef: ChangeDetectorRef + ) {} + + public ngOnInit(): void { + // Grabbing the widget's default template which will be needed as a parameter for setNode + const widgetTemplate = this.widgetTypesService.getWidgetType("kpi", 1); + // Registering our data sources as dropdown options in the widget editor/configurator + // Note: This could also be done in the parent module's constructor so that + // multiple dashboards could have access to the same widget template modification. + this.widgetTypesService.setNode( + // This is the template we grabbed above with getWidgetType + widgetTemplate, + // We are setting the editor/configurator part of the widget template + "configurator", + // This indicates which node you are changing and we want to change + // the data source providers available for selection in the editor. + WellKnownPathKey.DataSourceProviders, + // We are setting the data sources available for selection in the editor + [ + AverageRatingKpiDataSource.providerId, + RatingsCountKpiDataSource.providerId, + ] + ); + + // Registering the data sources available for injection into the KPI tiles. + // Note: Each tile of a KPI widget is assigned its own instance of a data source + this.providerRegistry.setProviders({ + [AverageRatingKpiDataSource.providerId]: { + provide: DATA_SOURCE, + useClass: AverageRatingKpiDataSource, + // Any dependencies that need to be injected into the provider must be listed here + deps: [HttpClient], + }, + [RatingsCountKpiDataSource.providerId]: { + provide: DATA_SOURCE, + useClass: RatingsCountKpiDataSource, + deps: [HttpClient], + }, + }); + + this.initializeDashboard(); + } + + /** Used for restoring widgets state */ + public reInitializeDashboard(): void { + // destroys the components and their providers so the dashboard can re init data + this.dashboard = undefined; + this.changeDetectorRef.detectChanges(); + + this.initializeDashboard(); + } + + public initializeDashboard(): void { + // We're using a static configuration object for this example (see widgetConfig at the bottom of the file), + // but this is where the widget's configuration could potentially be populated from a database + const kpiWidget = widgetConfig; + const widgetIndex: IWidgets = { + // Complete the KPI widget with information coming from its type definition + [kpiWidget.id]: + this.widgetTypesService.mergeWithWidgetType(kpiWidget), + }; + + // Setting the widget dimensions and position (this is for gridster) + const positions: Record = { + [kpiWidget.id]: { + cols: 4, + rows: 6, + y: 0, + x: 0, + }, + }; + + // Finally, assigning the variables we created above to the dashboard + this.dashboard = { + positions, + widgets: widgetIndex, + }; + } +} + +const widgetConfig: IWidget = { + id: "widget1", + type: "kpi", + pizzagna: { + [PizzagnaLayer.Configuration]: { + [DEFAULT_PIZZAGNA_ROOT]: { + providers: { + [WellKnownProviders.Refresher]: { + properties: { + // Configuring the refresher interval so that our data source is invoked every ten minutes + interval: 60 * 10, + enabled: true, + } as IRefresherProperties, + } as Partial, + }, + }, + header: { + properties: { + title: "Harry Potter and the Sorcerer's Stone", + subtitle: "By J. K. Rowling", + }, + }, + tiles: { + properties: { + nodes: ["kpi1"], + }, + }, + kpi1: { + id: "kpi1", + componentType: KpiComponent.lateLoadKey, + properties: { + widgetData: { + units: "out of 5 Stars", + label: "Average Rating", + }, + }, + providers: { + [WellKnownProviders.DataSource]: { + // Setting the data source providerId for the tile with id "kpi1" + providerId: AverageRatingKpiDataSource.providerId, + } as IProviderConfiguration, + [WellKnownProviders.Adapter]: { + providerId: NOVA_KPI_DATASOURCE_ADAPTER, + properties: { + componentId: "kpi1", + propertyPath: "widgetData", + }, + } as IProviderConfiguration, + }, + }, + }, + }, +}; +\\\\\\\\\\\\\\\`, + "tutorials/widget-editor-setup/widget-editor-setup.module.ts": \\\\\\\\\\\\\\\`// © 2022 SolarWinds Worldwide, LLC. All rights reserved. +// +// Permission is hereby granted, free of charge, to any person obtaining a copy +// of this software and associated documentation files (the "Software"), to +// deal in the Software without restriction, including without limitation the +// rights to use, copy, modify, merge, publish, distribute, sublicense, and/or +// sell copies of the Software, and to permit persons to whom the Software is +// furnished to do so, subject to the following conditions: +// +// The above copyright notice and this permission notice shall be included in +// all copies or substantial portions of the Software. +// +// THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR +// IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, +// FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE +// AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER +// LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, +// OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN +// THE SOFTWARE. + +import { CommonModule } from "@angular/common"; +import { HttpClientModule } from "@angular/common/http"; +import { NgModule } from "@angular/core"; +import { RouterModule } from "@angular/router"; + +import { + NuiButtonModule, + NuiDocsModule, + NuiMessageModule, + NuiSwitchModule, + DEMO_PATH_TOKEN, +} from "@nova-ui/bits"; +import { NuiDashboardsModule } from "@nova-ui/dashboards"; + +import { WidgetEditorDocsComponent } from "./widget-editor-setup-docs.component"; +import { WidgetEditorSetupComponent } from "./widget-editor-setup.component"; +import { getDemoFiles } from "../../../../demo-files-factory"; + +const routes = [ + { + path: "", + component: WidgetEditorDocsComponent, + data: { + srlc: { + hideIndicator: true, + }, + showThemeSwitcher: true, + }, + }, + { + path: "example", + component: WidgetEditorSetupComponent, + data: { + srlc: { + hideIndicator: true, + }, + }, + }, +]; + +@NgModule({ + imports: [ + CommonModule, + HttpClientModule, + NuiDashboardsModule, + NuiDocsModule, + NuiMessageModule, + NuiSwitchModule, + NuiButtonModule, + RouterModule.forChild(routes), + ], + declarations: [WidgetEditorDocsComponent, WidgetEditorSetupComponent], + providers: [ + { + provide: DEMO_PATH_TOKEN, + useValue: getDemoFiles("widget-editor-setup"), + }, + ], +}) +export default class WidgetEditorSetupModule {} +\\\\\\\\\\\\\\\`, + "tutorials/widget-error-handling/widget-error-handling-docs.component.ts": \\\\\\\\\\\\\\\`// © 2022 SolarWinds Worldwide, LLC. All rights reserved. +// +// Permission is hereby granted, free of charge, to any person obtaining a copy +// of this software and associated documentation files (the "Software"), to +// deal in the Software without restriction, including without limitation the +// rights to use, copy, modify, merge, publish, distribute, sublicense, and/or +// sell copies of the Software, and to permit persons to whom the Software is +// furnished to do so, subject to the following conditions: +// +// The above copyright notice and this permission notice shall be included in +// all copies or substantial portions of the Software. +// +// THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR +// IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, +// FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE +// AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER +// LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, +// OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN +// THE SOFTWARE. + +import { Component } from "@angular/core"; + +@Component({ + selector: "nui-widget-error-handling-docs", + templateUrl: "./widget-error-handling-docs.component.html", + standalone: false, +}) +export class WidgetErrorHandlingDocsComponent { + public fallbackAdapter = \\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\` +@Injectable() +export class StatusContentFallbackAdapter implements OnDestroy, IHasComponent { + + protected readonly destroy$ = new Subject(); + protected componentId: string; + + constructor(@Inject(PIZZAGNA_EVENT_BUS) protected eventBus: EventBus, + protected pizzagnaService: PizzagnaService) { + this.eventBus.getStream(DATA_SOURCE_OUTPUT) + .pipe(takeUntil(this.destroy$)).subscribe((event: IEvent>) => { + this.handleDataSourceOutput(event); + }); + } + + public ngOnDestroy(): void { + this.destroy$.next(); + this.destroy$.complete(); + } + + public setComponent(component: any, componentId: string) { + this.componentId = componentId; + } + + protected handleDataSourceOutput(event: IEvent>) { + this.pizzagnaService.setProperty({ + componentId: this.componentId, + propertyPath: ["fallbackKey"], + pizzagnaKey: PizzagnaLayer.Data, + }, typeof event.payload?.error?.type !== "undefined" ? event.payload?.error?.type.toString() : undefined); + } +}\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\`; + public errorsMap = \\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\` +export const ERROR_FALLBACK_MAP: Record = { + [HttpStatusCode.Unknown]: ErrorNodeKey.ErrorUnknown, + [HttpStatusCode.Forbidden]: ErrorNodeKey.ErrorForbidden, + [HttpStatusCode.NotFound]: ErrorNodeKey.ErrorNotFound, +}; +\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\`; + public errorNodes = \\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\` +export const ERROR_NODES: Record = { + [ErrorNodeKey.ErrorUnknown]: { + id: ErrorNodeKey.ErrorUnknown, + componentType: WidgetErrorComponent.lateLoadKey, + properties: { + image: "no-data-to-show", + title: $localize\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\`Whoops, something went wrong\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\`, + description: $localize\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\`There was an unexpected error.\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\`, + } as IWidgetErrorDisplayProperties, + }, + [ErrorNodeKey.ErrorForbidden]: { + id: ErrorNodeKey.ErrorForbidden, + componentType: WidgetErrorComponent.lateLoadKey, + properties: { + image: "no-data-to-show", + title: $localize\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\`403 - Forbidden\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\`, + description: $localize\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\`The requested action was forbidden.\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\`, + } as IWidgetErrorDisplayProperties, + }, + [ErrorNodeKey.ErrorNotFound]: { + id: ErrorNodeKey.ErrorNotFound, + componentType: WidgetErrorComponent.lateLoadKey, + properties: { + image: "no-data-to-show", + title: $localize\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\`404 - Not Found\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\`, + description: $localize\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\`The requested resource could not be found.\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\`, + } as IWidgetErrorDisplayProperties, + }, +};\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\`; + public widgetBodyContentNodesSignature = \\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\` +/** + * Retrieves an index of the basic widget body content nodes including fallback nodes + * + * @param mainContentNodeKey The key corresponding to the main body content node + * @param fallbackAdapterId The id for the adapter responsible for activating fallback content in case of an error + * @param fallbackMap A map of node keys to fallback content definitions + * @param fallbackNodes An index of fallback content definitions + * + * @returns An index of component configurations + */ +export function widgetBodyContentNodes( + mainContentNodeKey: string, + fallbackAdapterId = NOVA_STATUS_CONTENT_FALLBACK_ADAPTER, + fallbackMap: Record = ERROR_FALLBACK_MAP, + fallbackNodes: Record = ERROR_NODES +): Record { ... } +\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\`; +} +\\\\\\\\\\\\\\\`, + "tutorials/widget-error-handling/widget-error-handling.component.ts": \\\\\\\\\\\\\\\`// © 2022 SolarWinds Worldwide, LLC. All rights reserved. +// +// Permission is hereby granted, free of charge, to any person obtaining a copy +// of this software and associated documentation files (the "Software"), to +// deal in the Software without restriction, including without limitation the +// rights to use, copy, modify, merge, publish, distribute, sublicense, and/or +// sell copies of the Software, and to permit persons to whom the Software is +// furnished to do so, subject to the following conditions: +// +// The above copyright notice and this permission notice shall be included in +// all copies or substantial portions of the Software. +// +// THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR +// IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, +// FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE +// AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER +// LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, +// OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN +// THE SOFTWARE. + +import { HttpClient, HttpErrorResponse } from "@angular/common/http"; +import { + ChangeDetectorRef, + Component, + Injectable, + OnDestroy, + OnInit, +} from "@angular/core"; +import { GridsterConfig, GridsterItem } from "angular-gridster2"; +import { BehaviorSubject } from "rxjs"; +import { finalize } from "rxjs/operators"; + +import { DataSourceService, IFilteringOutputs } from "@nova-ui/bits"; +import { + DATA_SOURCE, + DEFAULT_PIZZAGNA_ROOT, + HttpStatusCode, + IDashboard, + IKpiData, + IProviderConfiguration, + IRefresherProperties, + IWidget, + IWidgets, + KpiComponent, + NOVA_KPI_DATASOURCE_ADAPTER, + PizzagnaLayer, + ProviderRegistryService, + WellKnownPathKey, + WellKnownProviders, + WidgetTypesService, +} from "@nova-ui/dashboards"; + +/** + * A simple KPI data source to retrieve the average rating of Harry Potter and the Sorcerer's Stone (book) via googleapis + */ +@Injectable() +export class AverageRatingKpiDataSource + extends DataSourceService + implements OnDestroy +{ + // This is the ID we'll use to identify the provider + public static providerId = "AverageRatingKpiDataSource"; + + // Use this subject to communicate the data source's busy state + public busy = new BehaviorSubject(false); + + constructor(private http: HttpClient) { + super(); + } + + // In this example, getFilteredData is invoked every 10 minutes (Take a look at the refresher + // provider definition in the widget configuration below to see how the interval is set) + public async getFilteredData(): Promise { + this.busy.next(true); + return new Promise((resolve) => { + // *** Make a resource request to an external API (if needed) + this.http + .get("https://www.googleapis.com/books/v1/volumes/5MQFrgEACAAJ") + .pipe(finalize(() => this.busy.next(false))) + .subscribe({ + next: (data: any) => { + resolve({ + result: { + value: data.volumeInfo.averageRating, + }, + }); + }, + error: (error: HttpErrorResponse) => { + resolve({ + result: null, + error: { + type: error.status, + }, + }); + }, + }); + }); + } + + public ngOnDestroy(): void { + this.outputsSubject.complete(); + } +} + +/** + * A simple KPI data source to retrieve the average rating of Harry Potter and the Sorcerer's Stone (book) via googleapis + */ +@Injectable() +export class ErrorUnknownDataSource + extends DataSourceService + implements OnDestroy +{ + // This is the ID we'll use to identify the provider + public static providerId = "ErrorUnknownDataSource"; + + // Use this subject to communicate the data source's busy state + public busy = new BehaviorSubject(false); + + // In this example, getFilteredData is invoked every 10 minutes (Take a look at the refresher + // provider definition in the widget configuration below to see how the interval is set) + public async getFilteredData(): Promise { + this.busy.next(true); + const mockError = { + result: null, + error: { type: HttpStatusCode.Unknown }, + }; + this.busy.next(false); + return mockError; + } + + public ngOnDestroy(): void { + this.outputsSubject.complete(); + } +} + +/** + * A simple KPI data source to retrieve the ratings count of Harry Potter and the Sorcerer's Stone (book) via googleapis + */ +@Injectable() +export class ErrorForbiddenDataSource + extends DataSourceService + implements OnDestroy +{ + public static providerId = "ErrorForbiddenDataSource"; + + // Use this subject to communicate the data source's busy state + public busy = new BehaviorSubject(false); + + constructor(private http: HttpClient) { + super(); + } + + public async getFilteredData(): Promise { + this.busy.next(true); + // generate a 403 + return new Promise((resolve) => { + this.http + .get( + "http://www.mocky.io/v2/5ecc724a3200000f0023614a?mocky-delay=4000ms" + ) + .pipe(finalize(() => this.busy.next(false))) + .subscribe({ + error: (error: HttpErrorResponse) => { + resolve({ + result: null, + error: { + type: error.status, + }, + }); + }, + }); + }); + } + + public ngOnDestroy(): void { + this.outputsSubject.complete(); + } +} + +/** + * A simple KPI data source to retrieve the ratings count of Harry Potter and the Sorcerer's Stone (book) via googleapis + */ +@Injectable() +export class ErrorNotFoundDataSource + extends DataSourceService + implements OnDestroy +{ + public static providerId = "ErrorNotFoundDataSource"; + + // Use this subject to communicate the data source's busy state + public busy = new BehaviorSubject(false); + + constructor(private http: HttpClient) { + super(); + } + + public async getFilteredData(): Promise { + this.busy.next(true); + // generate a 404 + return new Promise((resolve) => { + this.http + .get( + "http://www.mocky.io/v2/5ec6bfd93200007800d75100?mocky-delay=1000ms" + ) + .pipe(finalize(() => this.busy.next(false))) + .subscribe({ + error: (error: HttpErrorResponse) => { + resolve({ + result: null, + error: { + type: error.status, + }, + }); + }, + }); + }); + } + + public ngOnDestroy(): void { + this.outputsSubject.complete(); + } +} + +/** + * A component that instantiates the dashboard + */ +@Component({ + selector: "widget-error-handling", + templateUrl: "./widget-error-handling.component.html", + styleUrls: ["./widget-error-handling.component.less"], + standalone: false, +}) +export class WidgetErrorHandlingComponent implements OnInit { + // This variable will hold all the data needed to define the layout and behavior of the widgets. + // Pass this to the dashboard component's dashboard input in the template. + public dashboard: IDashboard | undefined; + + // Angular gridster requires a configuration object even if it's empty. + // Pass this to the dashboard component's gridsterConfig input in the template. + public gridsterConfig: GridsterConfig = {}; + + // Boolean which dashboard takes in as an input if its true it allows you to move widgets around. + public editMode: boolean = false; + + constructor( + // WidgetTypesService provides the widget's necessary structure information + private widgetTypesService: WidgetTypesService, + + // In general, the ProviderRegistryService is used for making entities available for injection into dynamically loaded components. + private providerRegistry: ProviderRegistryService, + private changeDetectorRef: ChangeDetectorRef + ) {} + + public ngOnInit(): void { + // Grab the widget's default template which will be needed as a parameter for setNode. + const widgetTemplate = this.widgetTypesService.getWidgetType("kpi", 1); + // Register our data sources as dropdown options in the widget editor/configurator + // Note: This could also be done in the parent module's constructor so that + // multiple dashboards could have access to the same widget template modification. + this.widgetTypesService.setNode( + // This is the template we grabbed above with getWidgetType + widgetTemplate, + // We are setting the editor/configurator part of the widget template + "configurator", + // This indicates which node you are changing and we want to change + // the data source providers available for selection in the editor. + WellKnownPathKey.DataSourceProviders, + // We are setting the data sources available for selection in the editor + [ + ErrorUnknownDataSource.providerId, + ErrorForbiddenDataSource.providerId, + ErrorNotFoundDataSource.providerId, + AverageRatingKpiDataSource.providerId, + ] + ); + + // Register the data sources available for injection into the KPI tiles. + // Note: Each tile of a KPI widget is assigned its own instance of a data source + this.providerRegistry.setProviders({ + [ErrorUnknownDataSource.providerId]: { + provide: DATA_SOURCE, + useClass: ErrorUnknownDataSource, + // Any dependencies that need to be injected into the provider must be listed here + deps: [HttpClient], + }, + [ErrorForbiddenDataSource.providerId]: { + provide: DATA_SOURCE, + useClass: ErrorForbiddenDataSource, + deps: [HttpClient], + }, + [ErrorNotFoundDataSource.providerId]: { + provide: DATA_SOURCE, + useClass: ErrorNotFoundDataSource, + deps: [HttpClient], + }, + [AverageRatingKpiDataSource.providerId]: { + provide: DATA_SOURCE, + useClass: AverageRatingKpiDataSource, + // Any dependencies that need to be injected into the provider must be listed here + deps: [HttpClient], + }, + }); + + this.initializeDashboard(); + } + + /** Used for restoring widgets state */ + public reInitializeDashboard(): void { + // destroys the components and their providers so the dashboard can re init data + this.dashboard = undefined; + this.changeDetectorRef.detectChanges(); + + this.initializeDashboard(); + } + + public initializeDashboard(): void { + // We're using a static configuration object for this example (see widgetConfig at the bottom of the file), + // but this is where the widget's configuration could potentially be populated from a database + const kpiWidget = widgetConfig; + const widgetIndex: IWidgets = { + // Complete the KPI widget with information coming from its type definition + [kpiWidget.id]: + this.widgetTypesService.mergeWithWidgetType(kpiWidget), + }; + + // Setting the widget dimensions and position (this is for gridster) + const positions: Record = { + [kpiWidget.id]: { + cols: 4, + rows: 6, + y: 0, + x: 0, + }, + }; + + // Finally, assigning the variables we created above to the dashboard + this.dashboard = { + positions, + widgets: widgetIndex, + }; + } +} + +const widgetConfig: IWidget = { + id: "widget1", + type: "kpi", + pizzagna: { + [PizzagnaLayer.Configuration]: { + [DEFAULT_PIZZAGNA_ROOT]: { + providers: { + [WellKnownProviders.Refresher]: { + properties: { + // Configuring the refresher interval so that our data source is invoked every ten minutes + interval: 60 * 10, + enabled: true, + } as IRefresherProperties, + } as Partial, + }, + }, + header: { + properties: { + title: "Harry Potter and the Sorcerer's Stone", + subtitle: "By J. K. Rowling", + }, + }, + tiles: { + properties: { + nodes: ["kpi1"], + }, + }, + kpi1: { + id: "kpi1", + componentType: KpiComponent.lateLoadKey, + properties: { + widgetData: { + units: "out of 5 Stars", + label: "Average Rating", + }, + }, + providers: { + [WellKnownProviders.DataSource]: { + // Setting the data source providerId for the tile with id "kpi1" + providerId: ErrorUnknownDataSource.providerId, + } as IProviderConfiguration, + [WellKnownProviders.Adapter]: { + providerId: NOVA_KPI_DATASOURCE_ADAPTER, + properties: { + componentId: "kpi1", + propertyPath: "widgetData", + }, + } as IProviderConfiguration, + }, + }, + }, + }, +}; +\\\\\\\\\\\\\\\`, + "tutorials/widget-error-handling/widget-error-handling.module.ts": \\\\\\\\\\\\\\\`// © 2022 SolarWinds Worldwide, LLC. All rights reserved. +// +// Permission is hereby granted, free of charge, to any person obtaining a copy +// of this software and associated documentation files (the "Software"), to +// deal in the Software without restriction, including without limitation the +// rights to use, copy, modify, merge, publish, distribute, sublicense, and/or +// sell copies of the Software, and to permit persons to whom the Software is +// furnished to do so, subject to the following conditions: +// +// The above copyright notice and this permission notice shall be included in +// all copies or substantial portions of the Software. +// +// THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR +// IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, +// FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE +// AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER +// LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, +// OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN +// THE SOFTWARE. + +import { CommonModule } from "@angular/common"; +import { HttpClientModule } from "@angular/common/http"; +import { NgModule } from "@angular/core"; +import { ReactiveFormsModule } from "@angular/forms"; +import { RouterModule } from "@angular/router"; + +import { + NuiButtonModule, + NuiDocsModule, + NuiFormFieldModule, + NuiIconModule, + NuiMessageModule, + NuiSwitchModule, + NuiTextboxModule, + DEMO_PATH_TOKEN, +} from "@nova-ui/bits"; +import { + NuiDashboardConfiguratorModule, + NuiDashboardsModule, +} from "@nova-ui/dashboards"; + +import { WidgetErrorHandlingDocsComponent } from "./widget-error-handling-docs.component"; +import { WidgetErrorHandlingComponent } from "./widget-error-handling.component"; +import { getDemoFiles } from "../../../../demo-files-factory"; + +const routes = [ + { + path: "", + component: WidgetErrorHandlingDocsComponent, + data: { + srlc: { + hideIndicator: true, + }, + showThemeSwitcher: true, + }, + }, + { + path: "example", + component: WidgetErrorHandlingComponent, + data: { + srlc: { + hideIndicator: true, + }, + }, + }, +]; + +@NgModule({ + imports: [ + CommonModule, + ReactiveFormsModule, + HttpClientModule, + NuiButtonModule, + NuiDashboardsModule, + NuiDashboardConfiguratorModule, + NuiDocsModule, + NuiFormFieldModule, + NuiIconModule, + NuiMessageModule, + NuiIconModule, + NuiTextboxModule, + NuiIconModule, + NuiSwitchModule, + RouterModule.forChild(routes), + ], + declarations: [ + WidgetErrorHandlingDocsComponent, + WidgetErrorHandlingComponent, + ], + providers: [ + { + provide: DEMO_PATH_TOKEN, + useValue: getDemoFiles("widget-error-handling"), + }, + ], +}) +export default class WidgetErrorHandlingModule {} +\\\\\\\\\\\\\\\`, + "types.ts": \\\\\\\\\\\\\\\`// © 2022 SolarWinds Worldwide, LLC. All rights reserved. +// +// Permission is hereby granted, free of charge, to any person obtaining a copy +// of this software and associated documentation files (the "Software"), to +// deal in the Software without restriction, including without limitation the +// rights to use, copy, modify, merge, publish, distribute, sublicense, and/or +// sell copies of the Software, and to permit persons to whom the Software is +// furnished to do so, subject to the following conditions: +// +// The above copyright notice and this permission notice shall be included in +// all copies or substantial portions of the Software. +// +// THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR +// IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, +// FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE +// AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER +// LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, +// OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN +// THE SOFTWARE. + +export enum APOLLO_API_NAMESPACE { + COUNTRIES = "countries", +} +\\\\\\\\\\\\\\\`, + "widget-types/drilldown/drilldown-multi-request-widget/drilldown-multi-request-widget-example.component.ts": \\\\\\\\\\\\\\\`// © 2022 SolarWinds Worldwide, LLC. All rights reserved. +// +// Permission is hereby granted, free of charge, to any person obtaining a copy +// of this software and associated documentation files (the "Software"), to +// deal in the Software without restriction, including without limitation the +// rights to use, copy, modify, merge, publish, distribute, sublicense, and/or +// sell copies of the Software, and to permit persons to whom the Software is +// furnished to do so, subject to the following conditions: +// +// The above copyright notice and this permission notice shall be included in +// all copies or substantial portions of the Software. +// +// THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR +// IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, +// FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE +// AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER +// LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, +// OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN +// THE SOFTWARE. + +import { HttpClient } from "@angular/common/http"; +import { + ChangeDetectorRef, + Component, + Injectable, + OnDestroy, + OnInit, +} from "@angular/core"; +import { GridsterConfig, GridsterItem } from "angular-gridster2"; +import { Apollo, gql } from "apollo-angular"; +import { BehaviorSubject, Observable, of, Subject } from "rxjs"; +// eslint-disable-next-line import/no-deprecated +import { finalize, map, switchMap, tap } from "rxjs/operators"; + +import { + DataSourceService, + IconStatus, + IDataField, + IFilters, + INovaFilters, +} from "@nova-ui/bits"; +import { + DATA_SOURCE, + DEFAULT_PIZZAGNA_ROOT, + IDashboard, + IDrilldownComponentsConfiguration, + IListWidgetConfiguration, + IProviderConfiguration, + IWidget, + IWidgets, + ListGroupItemComponent, + ListLeafItemComponent, + NOVA_DRILLDOWN_DATASOURCE_ADAPTER, + PizzagnaLayer, + ProviderRegistryService, + WellKnownPathKey, + WellKnownProviders, + WidgetTypesService, +} from "@nova-ui/dashboards"; + +import { APOLLO_API_NAMESPACE } from "../../../types"; + +/** + * A simple KPI data source to retrieve the average rating of Harry Potter and the Sorcerer's Stone (book) via googleapis + */ +@Injectable() +export class DrilldownDataSource + extends DataSourceService + implements OnDestroy +{ + // This is the ID we'll use to identify the provider + public static providerId = "DrilldownDataSource"; + + // Use this subject to communicate the data source's busy state + public busy = new BehaviorSubject(false); + + public dataFields: Partial[] = [ + { id: "Region", label: "Region name" }, + { id: "Subregion", label: "Subregion name" }, + ]; + + private drillState: string[] = []; + private groupBy: string[]; + private cache: any; + private lastDrillState: string[] = []; + private leafGroup: string = "country"; + private applyFilters$ = new Subject(); + + constructor(private http: HttpClient, private apollo: Apollo) { + super(); + + // TODO: remove Partial in vNext after marking dataType field as optional - NUI-5838 + ( + this.dataFieldsConfig.dataFields$ as BehaviorSubject< + Partial[] + > + ).next(this.dataFields); + + this.applyFilters$ + // eslint-disable-next-line import/no-deprecated + .pipe(switchMap((filters) => this.getData(filters))) + .subscribe(async (res) => { + this.outputsSubject.next(await this.getFilteredData(res)); + }); + } + + private groupedDataHistory: any[] = []; + + // In this example, getFilteredData is invoked every 10 minutes (Take a look at the refresher + // provider definition in the widget configuration below to see how the interval is set) + public async getFilteredData(data: any): Promise { + return of(data) + .pipe( + map((entries) => { + if (this.isDrillDown()) { + const activeDrillLvl = this.drillState.length; + const group = this.groupBy[activeDrillLvl]; + const lastGroupedValue = + this.getTransformedDataForGroup( + entries, + group, + getLast(this.drillState) + ); + + this.groupedDataHistory.push(lastGroupedValue); + + return lastGroupedValue; + } + + const mapIconsToEntries = entries.map((item: any) => ({ + ...item, + icon: "virtual-host", + icon_status: IconStatus.Up, + })); + this.groupedDataHistory.push(mapIconsToEntries); + const widgetInput = this.getOutput(entries); + + return widgetInput; + }) + ) + .toPromise(); + } + + public ngOnDestroy(): void { + this.outputsSubject.complete(); + } + + // redefine parent method + public async applyFilters(): Promise { + this.applyFilters$.next(this.getFilters()); + } + + private getQuery(key: string, value: string) { + const groupToRequestMap: Record = { + Region: \\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\`{ Region { name } }\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\`, + Subregion: \\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\`{ Subregion(filter: { region: { name: "\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\${value}" } } ) { name } }\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\`, + Country: \\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\`{ Country(filter: { subregion: { name: "\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\${value}" } } ) { name capital } }\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\`, + }; + + return gql\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\` + \\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\${groupToRequestMap[key]} + \\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\`; + } + + private getData(filters: INovaFilters): Observable { + this.drillState = filters.drillstate?.value; + this.groupBy = filters.group?.value; + const group = this.groupBy[this.drillState.length]; + const isDrillUp = this.drillState.length < this.lastDrillState.length; + + this.lastDrillState = [...this.drillState]; + + if (!this.drillState.length) { + this.groupedDataHistory.length = 0; + } + + this.busy.next(true); + + if (this.cache && (isDrillUp || this.isHome())) { + return of(this.cache).pipe( + map((data) => data.data[group]), + finalize(() => this.busy.next(false)) + ); + } else { + return this.apollo + .use(APOLLO_API_NAMESPACE.COUNTRIES) + .query({ + query: this.getQuery( + group || this.leafGroup, + getLast(this.drillState) + ), + }) + .pipe( + tap( + (data) => + (this.cache = { + data: { ...this.cache?.data, ...data?.data }, + }) + ), + map((data) => data.data[group || this.leafGroup]), + finalize(() => this.busy.next(false)) + ); + } + } + + private getTransformedDataForGroup( + data: any, + group: string, + drillStateValue: string + ) { + const fallback: string = \\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\`No \\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\${group} for \\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\${drillStateValue}\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\`; + const dataArr = Object.values(data).map((val: any) => ({ + id: val.name || fallback, + label: val.name || fallback, + statuses: [ + { key: "state_ok", value: val.name?.length }, + { + key: "status_unreachable", + value: generateNumberUpTo(100000), + }, + { key: "status_warning", value: generateNumberUpTo(10000) }, + { key: "status_unknown", value: generateNumberUpTo(1000) }, + ], + })); + + return dataArr; + } + + private isHome(): boolean { + return this.drillState.length === 0; + } + + private isDrillDown(): boolean { + return this.drillState.length !== this.groupBy.length; + } + + private getOutput(data: any) { + if (this.isHome()) { + this.groupedDataHistory.length = 0; + } + + const lastHistoryValue = getLast(this.groupedDataHistory); + + if (!lastHistoryValue) { + return data; + } + + return lastHistoryValue[getLast(this.drillState)] || lastHistoryValue; + } +} + +@Component({ + selector: "drilldown-multi-request-widget-example", + templateUrl: "./drilldown-multi-request-widget-example.component.html", + styleUrls: ["./drilldown-multi-request-widget-example.component.less"], + standalone: false, +}) +export class DrilldownMultiRequestWidgetExampleComponent implements OnInit { + // This variable will hold all the data needed to define the layout and behavior of the widgets. + // Pass this to the dashboard component's dashboard input in the template. + public dashboard: IDashboard | undefined; + + // Angular gridster requires a configuration object even if it's empty. + // Pass this to the dashboard component's gridsterConfig input in the template. + public gridsterConfig: GridsterConfig = {}; + + // Boolean passed as an input to the dashboard. When true, widgets can be moved, resized, removed, or edited + public editMode: boolean = false; + + constructor( + // WidgetTypesService provides the widget's necessary structure information + private widgetTypesService: WidgetTypesService, + private providerRegistry: ProviderRegistryService, + private changeDetectorRef: ChangeDetectorRef + ) {} + + public ngOnInit(): void { + // this.prepareNovaDashboards(); + this.initializeDashboard(); + const widgetTemplate = this.widgetTypesService.getWidgetType( + "drilldown", + 1 + ); + this.widgetTypesService.setNode( + widgetTemplate, + "configurator", + WellKnownPathKey.DataSourceProviders, + [DrilldownDataSource.providerId] + ); + + // Registering the data source for injection into the KPI tile. + // Note: Each tile of a KPI widget is assigned its own instance of the data source + this.providerRegistry.setProviders({ + [DrilldownDataSource.providerId]: { + provide: DATA_SOURCE, + useClass: DrilldownDataSource, + // Any dependencies that need to be injected into the provider must be listed here + deps: [HttpClient, Apollo], + }, + }); + } + + /** Used for restoring widgets state */ + public reInitializeDashboard(): void { + // destroys the components and their providers so the dashboard can re init data + this.dashboard = undefined; + this.changeDetectorRef.detectChanges(); + + this.initializeDashboard(); + } + + public initializeDashboard(): void { + // We're using a static configuration object for this example, but this is where + // the widget's configuration could potentially be populated from a database + const drilldownWidget = widgetConfig; + const widgets: IWidgets = { + // Complete the widget with information coming from its type definition + [drilldownWidget.id]: + this.widgetTypesService.mergeWithWidgetType(drilldownWidget), + }; + + // Setting the widget dimensions and position (this is for gridster) + const positions: Record = { + [drilldownWidget.id]: { + cols: 10, + rows: 10, + y: 0, + x: 0, + }, + }; + + // Finally, assigning the variables we created above to the dashboard + this.dashboard = { positions, widgets }; + } +} + +const widgetConfig: IWidget = { + id: "drilldown", + type: "drilldown", + pizzagna: { + [PizzagnaLayer.Configuration]: { + header: { + properties: { + title: "Drilldown Widget", + subtitle: "Countries BY continent THEN currency", + }, + }, + [DEFAULT_PIZZAGNA_ROOT]: { + providers: { + [WellKnownProviders.DataSource]: { + // Setting the data source providerId for the tile with id "kpi1" + providerId: DrilldownDataSource.providerId, + properties: {}, + } as IProviderConfiguration, + }, + }, + listWidget: { + providers: { + [WellKnownProviders.Adapter]: { + providerId: NOVA_DRILLDOWN_DATASOURCE_ADAPTER, + properties: { + // widget + navigationBarId: "navigationBar", + componentId: "listWidget", + dataPath: "data", + + // adapter props + drillstate: [], + groups: ["Region", "Subregion"], + groupBy: ["Region", "Subregion"], + + // components + componentsConfig: { + group: { + componentType: + ListGroupItemComponent.lateLoadKey, + properties: { + dataFieldIds: { + id: "id", + label: "label", + statuses: "statuses", + }, + }, + itemProperties: { + canNavigate: true, + }, + }, + leaf: { + componentType: + ListLeafItemComponent.lateLoadKey, + properties: { + dataFieldIds: { + icon: "icon", + status: "icon_status", + detailedUrl: "capital", + label: "name", + }, + }, + itemProperties: { + canNavigate: false, + }, + }, + } as IDrilldownComponentsConfiguration, + }, + }, + }, + properties: { + configuration: { + // FORMAT: + // componentType: ListLeafItemComponent.lateLoadKey, + // properties: { + // dataFieldIds: { + // icon: "", + // status: "code", + // detailedUrl: "capital", + // label: "name", + // }, + // }, + // + } as IListWidgetConfiguration, + }, + }, + }, + }, +}; + +const getLast = (arr: any[]) => arr[arr.length - 1]; + +const generateNumberUpTo = (upperLimit: number): number => + Math.floor(Math.random() * upperLimit + 1); +\\\\\\\\\\\\\\\`, + "widget-types/drilldown/drilldown-widget/data-mock.ts": \\\\\\\\\\\\\\\`// © 2022 SolarWinds Worldwide, LLC. All rights reserved. +// +// Permission is hereby granted, free of charge, to any person obtaining a copy +// of this software and associated documentation files (the "Software"), to +// deal in the Software without restriction, including without limitation the +// rights to use, copy, modify, merge, publish, distribute, sublicense, and/or +// sell copies of the Software, and to permit persons to whom the Software is +// furnished to do so, subject to the following conditions: +// +// The above copyright notice and this permission notice shall be included in +// all copies or substantial portions of the Software. +// +// THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR +// IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, +// FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE +// AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER +// LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, +// OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN +// THE SOFTWARE. + +import { IconStatus } from "@nova-ui/bits"; + +export const GRAPH_DATA_MOCK = { + data: { + countries: [ + { + name: "Andorra", + code: "AD", + icon: "virtual-host", + icon_status: IconStatus.Up, + capital: "Andorra la Vella", + continent: { + name: "Europe", + }, + currency: "EUR", + languages: [ + { + name: "Catalan", + }, + ], + url: "https://en.wikipedia.org/wiki/Andorra", + }, + { + name: "United Arab Emirates", + code: "AE", + icon: "virtual-host", + icon_status: IconStatus.Up, + capital: "Abu Dhabi", + continent: { + name: "Asia", + }, + currency: "AED", + languages: [ + { + name: "Arabic", + }, + ], + url: "https://en.wikipedia.org/wiki/United_Arab_Emirates", + }, + { + name: "Afghanistan", + code: "AF", + icon: "virtual-host", + icon_status: IconStatus.Up, + capital: "Kabul", + continent: { + name: "Asia", + }, + currency: "AFN", + languages: [ + { + name: "Pashto", + }, + { + name: "Uzbek", + }, + { + name: "Turkmen", + }, + ], + url: "https://en.wikipedia.org/wiki/Afghanistan", + }, + { + name: "Antigua and Barbuda", + code: "AG", + icon: "virtual-host", + icon_status: IconStatus.Up, + capital: "Saint John's", + continent: { + name: "North America", + }, + currency: "XCD", + languages: [ + { + name: "English", + }, + ], + url: "https://en.wikipedia.org/wiki/Antigua_and_Barbuda", + }, + { + name: "Anguilla", + code: "AI", + icon: "virtual-host", + icon_status: IconStatus.Up, + capital: "The Valley", + continent: { + name: "North America", + }, + currency: "XCD", + languages: [ + { + name: "English", + }, + ], + url: "https://en.wikipedia.org/wiki/Anguilla", + }, + { + name: "Albania", + code: "AL", + icon: "virtual-host", + icon_status: IconStatus.Up, + capital: "Tirana", + continent: { + name: "Europe", + }, + currency: "ALL", + languages: [ + { + name: "Albanian", + }, + ], + url: "https://en.wikipedia.org/wiki/Albania", + }, + { + name: "Armenia", + code: "AM", + icon: "virtual-host", + icon_status: IconStatus.Up, + capital: "Yerevan", + continent: { + name: "Asia", + }, + currency: "AMD", + languages: [ + { + name: "Armenian", + }, + { + name: "Russian", + }, + ], + url: "https://en.wikipedia.org/wiki/Armenia", + }, + { + name: "Angola", + code: "AO", + icon: "virtual-host", + icon_status: IconStatus.Up, + capital: "Luanda", + continent: { + name: "Africa", + }, + currency: "AOA", + languages: [ + { + name: "Portuguese", + }, + ], + }, + { + name: "Antarctica", + code: "AQ", + icon: "virtual-host", + icon_status: IconStatus.Up, + capital: null, + continent: { + name: "Antarctica", + }, + currency: null, + languages: [], + }, + { + name: "Argentina", + code: "AR", + icon: "virtual-host", + icon_status: IconStatus.Up, + capital: "Buenos Aires", + continent: { + name: "South America", + }, + currency: "ARS", + languages: [ + { + name: "Spanish", + }, + { + name: "Guarani", + }, + ], + }, + { + name: "American Samoa", + code: "AS", + icon: "virtual-host", + icon_status: IconStatus.Up, + capital: "Pago Pago", + continent: { + name: "Oceania", + }, + currency: "USD", + languages: [ + { + name: "English", + }, + { + name: "Samoan", + }, + ], + }, + { + name: "Austria", + code: "AT", + icon: "virtual-host", + icon_status: IconStatus.Up, + capital: "Vienna", + continent: { + name: "Europe", + }, + currency: "EUR", + languages: [ + { + name: "German", + }, + ], + }, + { + name: "Australia", + code: "AU", + icon: "virtual-host", + icon_status: IconStatus.Up, + capital: "Canberra", + continent: { + name: "Oceania", + }, + currency: "AUD", + languages: [ + { + name: "English", + }, + ], + }, + { + name: "Aruba", + code: "AW", + icon: "virtual-host", + icon_status: IconStatus.Up, + capital: "Oranjestad", + continent: { + name: "North America", + }, + currency: "AWG", + languages: [ + { + name: "Dutch", + }, + { + name: "Panjabi / Punjabi", + }, + ], + }, + { + name: "Åland", + code: "AX", + icon: "virtual-host", + icon_status: IconStatus.Up, + capital: "Mariehamn", + continent: { + name: "Europe", + }, + currency: "EUR", + languages: [ + { + name: "Swedish", + }, + ], + }, + { + name: "Azerbaijan", + code: "AZ", + icon: "virtual-host", + icon_status: IconStatus.Up, + capital: "Baku", + continent: { + name: "Asia", + }, + currency: "AZN", + languages: [ + { + name: "Azerbaijani", + }, + ], + }, + { + name: "Bosnia and Herzegovina", + code: "BA", + icon: "virtual-host", + icon_status: IconStatus.Up, + capital: "Sarajevo", + continent: { + name: "Europe", + }, + currency: "BAM", + languages: [ + { + name: "Bosnian", + }, + { + name: "Croatian", + }, + { + name: "Serbian", + }, + ], + }, + { + name: "Barbados", + code: "BB", + icon: "virtual-host", + icon_status: IconStatus.Up, + capital: "Bridgetown", + continent: { + name: "North America", + }, + currency: "BBD", + languages: [ + { + name: "English", + }, + ], + }, + { + name: "Bangladesh", + code: "BD", + icon: "virtual-host", + icon_status: IconStatus.Up, + capital: "Dhaka", + continent: { + name: "Asia", + }, + currency: "BDT", + languages: [ + { + name: "Bengali", + }, + ], + }, + { + name: "Belgium", + code: "BE", + icon: "virtual-host", + icon_status: IconStatus.Up, + capital: "Brussels", + continent: { + name: "Europe", + }, + currency: "EUR", + languages: [ + { + name: "Dutch", + }, + { + name: "French", + }, + { + name: "German", + }, + ], + }, + { + name: "Burkina Faso", + code: "BF", + icon: "virtual-host", + icon_status: IconStatus.Up, + capital: "Ouagadougou", + continent: { + name: "Africa", + }, + currency: "XOF", + languages: [ + { + name: "French", + }, + { + name: "Peul", + }, + ], + }, + { + name: "Bulgaria", + code: "BG", + icon: "virtual-host", + icon_status: IconStatus.Up, + capital: "Sofia", + continent: { + name: "Europe", + }, + currency: "BGN", + languages: [ + { + name: "Bulgarian", + }, + ], + }, + { + name: "Bahrain", + code: "BH", + icon: "virtual-host", + icon_status: IconStatus.Up, + capital: "Manama", + continent: { + name: "Asia", + }, + currency: "BHD", + languages: [ + { + name: "Arabic", + }, + ], + }, + { + name: "Burundi", + code: "BI", + icon: "virtual-host", + icon_status: IconStatus.Up, + capital: "Bujumbura", + continent: { + name: "Africa", + }, + currency: "BIF", + languages: [ + { + name: "French", + }, + { + name: "Kirundi", + }, + ], + }, + { + name: "Benin", + code: "BJ", + icon: "virtual-host", + icon_status: IconStatus.Up, + capital: "Porto-Novo", + continent: { + name: "Africa", + }, + currency: "XOF", + languages: [ + { + name: "French", + }, + ], + }, + { + name: "Saint Barthélemy", + code: "BL", + icon: "virtual-host", + icon_status: IconStatus.Up, + capital: "Gustavia", + continent: { + name: "North America", + }, + currency: "EUR", + languages: [ + { + name: "French", + }, + ], + }, + { + name: "Bermuda", + code: "BM", + icon: "virtual-host", + icon_status: IconStatus.Up, + capital: "Hamilton", + continent: { + name: "North America", + }, + currency: "BMD", + languages: [ + { + name: "English", + }, + ], + }, + { + name: "Brunei", + code: "BN", + icon: "virtual-host", + icon_status: IconStatus.Up, + capital: "Bandar Seri Begawan", + continent: { + name: "Asia", + }, + currency: "BND", + languages: [ + { + name: "Malay", + }, + ], + }, + { + name: "Bolivia", + code: "BO", + icon: "virtual-host", + icon_status: IconStatus.Up, + capital: "Sucre", + continent: { + name: "South America", + }, + currency: "BOB,BOV", + languages: [ + { + name: "Spanish", + }, + { + name: "Aymara", + }, + { + name: "Quechua", + }, + ], + }, + { + name: "Bonaire", + code: "BQ", + icon: "virtual-host", + icon_status: IconStatus.Up, + capital: "Kralendijk", + continent: { + name: "North America", + }, + currency: "USD", + languages: [ + { + name: "Dutch", + }, + ], + }, + { + name: "Brazil", + code: "BR", + icon: "virtual-host", + icon_status: IconStatus.Up, + capital: "Brasília", + continent: { + name: "South America", + }, + currency: "BRL", + languages: [ + { + name: "Portuguese", + }, + ], + }, + { + name: "Bahamas", + code: "BS", + icon: "virtual-host", + icon_status: IconStatus.Up, + capital: "Nassau", + continent: { + name: "North America", + }, + currency: "BSD", + languages: [ + { + name: "English", + }, + ], + }, + { + name: "Bhutan", + code: "BT", + icon: "virtual-host", + icon_status: IconStatus.Up, + capital: "Thimphu", + continent: { + name: "Asia", + }, + currency: "BTN,INR", + languages: [ + { + name: "Dzongkha", + }, + ], + }, + { + name: "Bouvet Island", + code: "BV", + icon: "virtual-host", + icon_status: IconStatus.Up, + capital: null, + continent: { + name: "Antarctica", + }, + currency: "NOK", + languages: [ + { + name: "Norwegian", + }, + { + name: "Norwegian Bokmål", + }, + { + name: "Norwegian Nynorsk", + }, + ], + }, + { + name: "Botswana", + code: "BW", + icon: "virtual-host", + icon_status: IconStatus.Up, + capital: "Gaborone", + continent: { + name: "Africa", + }, + currency: "BWP", + languages: [ + { + name: "English", + }, + { + name: "Tswana", + }, + ], + }, + { + name: "Belarus", + code: "BY", + icon: "virtual-host", + icon_status: IconStatus.Up, + capital: "Minsk", + continent: { + name: "Europe", + }, + currency: "BYN", + languages: [ + { + name: "Belarusian", + }, + { + name: "Russian", + }, + ], + }, + { + name: "Belize", + code: "BZ", + icon: "virtual-host", + icon_status: IconStatus.Up, + capital: "Belmopan", + continent: { + name: "North America", + }, + currency: "BZD", + languages: [ + { + name: "English", + }, + { + name: "Spanish", + }, + ], + }, + { + name: "Canada", + code: "CA", + icon: "virtual-host", + icon_status: IconStatus.Up, + capital: "Ottawa", + continent: { + name: "North America", + }, + currency: "CAD", + languages: [ + { + name: "English", + }, + { + name: "French", + }, + ], + }, + { + name: "Cocos [Keeling] Islands", + code: "CC", + icon: "virtual-host", + icon_status: IconStatus.Up, + capital: "West Island", + continent: { + name: "Asia", + }, + currency: "AUD", + languages: [ + { + name: "English", + }, + ], + }, + { + name: "Democratic Republic of the Congo", + code: "CD", + icon: "virtual-host", + icon_status: IconStatus.Up, + capital: "Kinshasa", + continent: { + name: "Africa", + }, + currency: "CDF", + languages: [ + { + name: "French", + }, + { + name: "Lingala", + }, + { + name: "Kongo", + }, + { + name: "Swahili", + }, + { + name: "Luba-Katanga", + }, + ], + }, + { + name: "Central African Republic", + code: "CF", + icon: "virtual-host", + icon_status: IconStatus.Up, + capital: "Bangui", + continent: { + name: "Africa", + }, + currency: "XAF", + languages: [ + { + name: "French", + }, + { + name: "Sango", + }, + ], + }, + { + name: "Republic of the Congo", + code: "CG", + icon: "virtual-host", + icon_status: IconStatus.Up, + capital: "Brazzaville", + continent: { + name: "Africa", + }, + currency: "XAF", + languages: [ + { + name: "French", + }, + { + name: "Lingala", + }, + ], + }, + { + name: "Switzerland", + code: "CH", + icon: "virtual-host", + icon_status: IconStatus.Up, + capital: "Bern", + continent: { + name: "Europe", + }, + currency: "CHE,CHF,CHW", + languages: [ + { + name: "German", + }, + { + name: "French", + }, + { + name: "Italian", + }, + ], + }, + { + name: "Ivory Coast", + code: "CI", + icon: "virtual-host", + icon_status: IconStatus.Up, + capital: "Yamoussoukro", + continent: { + name: "Africa", + }, + currency: "XOF", + languages: [ + { + name: "French", + }, + ], + }, + { + name: "Cook Islands", + code: "CK", + icon: "virtual-host", + icon_status: IconStatus.Up, + capital: "Avarua", + continent: { + name: "Oceania", + }, + currency: "NZD", + languages: [ + { + name: "English", + }, + ], + }, + { + name: "Chile", + code: "CL", + icon: "virtual-host", + icon_status: IconStatus.Up, + capital: "Santiago", + continent: { + name: "South America", + }, + currency: "CLF,CLP", + languages: [ + { + name: "Spanish", + }, + ], + }, + { + name: "Cameroon", + code: "CM", + icon: "virtual-host", + icon_status: IconStatus.Up, + capital: "Yaoundé", + continent: { + name: "Africa", + }, + currency: "XAF", + languages: [ + { + name: "English", + }, + { + name: "French", + }, + ], + }, + { + name: "China", + code: "CN", + icon: "virtual-host", + icon_status: IconStatus.Up, + capital: "Beijing", + continent: { + name: "Asia", + }, + currency: "CNY", + languages: [ + { + name: "Chinese", + }, + ], + }, + { + name: "Colombia", + code: "CO", + icon: "virtual-host", + icon_status: IconStatus.Up, + capital: "Bogotá", + continent: { + name: "South America", + }, + currency: "COP", + languages: [ + { + name: "Spanish", + }, + ], + }, + { + name: "Costa Rica", + code: "CR", + icon: "virtual-host", + icon_status: IconStatus.Up, + capital: "San José", + continent: { + name: "North America", + }, + currency: "CRC", + languages: [ + { + name: "Spanish", + }, + ], + }, + { + name: "Cuba", + code: "CU", + icon: "virtual-host", + icon_status: IconStatus.Up, + capital: "Havana", + continent: { + name: "North America", + }, + currency: "CUC,CUP", + languages: [ + { + name: "Spanish", + }, + ], + }, + { + name: "Cape Verde", + code: "CV", + icon: "virtual-host", + icon_status: IconStatus.Up, + capital: "Praia", + continent: { + name: "Africa", + }, + currency: "CVE", + languages: [ + { + name: "Portuguese", + }, + ], + }, + { + name: "Curacao", + code: "CW", + icon: "virtual-host", + icon_status: IconStatus.Up, + capital: "Willemstad", + continent: { + name: "North America", + }, + currency: "ANG", + languages: [ + { + name: "Dutch", + }, + { + name: "Panjabi / Punjabi", + }, + { + name: "English", + }, + ], + }, + { + name: "Christmas Island", + code: "CX", + icon: "virtual-host", + icon_status: IconStatus.Up, + capital: "Flying Fish Cove", + continent: { + name: "Asia", + }, + currency: "AUD", + languages: [ + { + name: "English", + }, + ], + }, + { + name: "Cyprus", + code: "CY", + icon: "virtual-host", + icon_status: IconStatus.Up, + capital: "Nicosia", + continent: { + name: "Europe", + }, + currency: "EUR", + languages: [ + { + name: "Greek", + }, + { + name: "Turkish", + }, + { + name: "Armenian", + }, + ], + }, + { + name: "Czech Republic", + code: "CZ", + icon: "virtual-host", + icon_status: IconStatus.Up, + capital: "Prague", + continent: { + name: "Europe", + }, + currency: "CZK", + languages: [ + { + name: "Czech", + }, + { + name: "Slovak", + }, + ], + }, + { + name: "Germany", + code: "DE", + icon: "virtual-host", + icon_status: IconStatus.Up, + capital: "Berlin", + continent: { + name: "Europe", + }, + currency: "EUR", + languages: [ + { + name: "German", + }, + ], + }, + { + name: "Djibouti", + code: "DJ", + icon: "virtual-host", + icon_status: IconStatus.Up, + capital: "Djibouti", + continent: { + name: "Africa", + }, + currency: "DJF", + languages: [ + { + name: "French", + }, + { + name: "Arabic", + }, + ], + }, + { + name: "Denmark", + code: "DK", + icon: "virtual-host", + icon_status: IconStatus.Up, + capital: "Copenhagen", + continent: { + name: "Europe", + }, + currency: "DKK", + languages: [ + { + name: "Danish", + }, + ], + }, + { + name: "Dominica", + code: "DM", + icon: "virtual-host", + icon_status: IconStatus.Up, + capital: "Roseau", + continent: { + name: "North America", + }, + currency: "XCD", + languages: [ + { + name: "English", + }, + ], + }, + { + name: "Dominican Republic", + code: "DO", + icon: "virtual-host", + icon_status: IconStatus.Up, + capital: "Santo Domingo", + continent: { + name: "North America", + }, + currency: "DOP", + languages: [ + { + name: "Spanish", + }, + ], + }, + { + name: "Algeria", + code: "DZ", + icon: "virtual-host", + icon_status: IconStatus.Up, + capital: "Algiers", + continent: { + name: "Africa", + }, + currency: "DZD", + languages: [ + { + name: "Arabic", + }, + ], + }, + { + name: "Ecuador", + code: "EC", + icon: "virtual-host", + icon_status: IconStatus.Up, + capital: "Quito", + continent: { + name: "South America", + }, + currency: "USD", + languages: [ + { + name: "Spanish", + }, + ], + }, + { + name: "Estonia", + code: "EE", + icon: "virtual-host", + icon_status: IconStatus.Up, + capital: "Tallinn", + continent: { + name: "Europe", + }, + currency: "EUR", + languages: [ + { + name: "Estonian", + }, + ], + }, + { + name: "Egypt", + code: "EG", + icon: "virtual-host", + icon_status: IconStatus.Up, + capital: "Cairo", + continent: { + name: "Africa", + }, + currency: "EGP", + languages: [ + { + name: "Arabic", + }, + ], + }, + { + name: "Western Sahara", + code: "EH", + icon: "virtual-host", + icon_status: IconStatus.Up, + capital: "El Aaiún", + continent: { + name: "Africa", + }, + currency: "MAD,DZD,MRU", + languages: [ + { + name: "Spanish", + }, + ], + }, + { + name: "Eritrea", + code: "ER", + icon: "virtual-host", + icon_status: IconStatus.Up, + capital: "Asmara", + continent: { + name: "Africa", + }, + currency: "ERN", + languages: [ + { + name: "Tigrinya", + }, + { + name: "Arabic", + }, + { + name: "English", + }, + ], + }, + { + name: "Spain", + code: "ES", + icon: "virtual-host", + icon_status: IconStatus.Up, + capital: "Madrid", + continent: { + name: "Europe", + }, + currency: "EUR", + languages: [ + { + name: "Spanish", + }, + { + name: "Basque", + }, + { + name: "Catalan", + }, + { + name: "Galician", + }, + { + name: "Occitan", + }, + ], + }, + { + name: "Ethiopia", + code: "ET", + icon: "virtual-host", + icon_status: IconStatus.Up, + capital: "Addis Ababa", + continent: { + name: "Africa", + }, + currency: "ETB", + languages: [ + { + name: "Amharic", + }, + ], + }, + { + name: "Finland", + code: "FI", + icon: "virtual-host", + icon_status: IconStatus.Up, + capital: "Helsinki", + continent: { + name: "Europe", + }, + currency: "EUR", + languages: [ + { + name: "Finnish", + }, + { + name: "Swedish", + }, + ], + }, + { + name: "Fiji", + code: "FJ", + icon: "virtual-host", + icon_status: IconStatus.Up, + capital: "Suva", + continent: { + name: "Oceania", + }, + currency: "FJD", + languages: [ + { + name: "English", + }, + { + name: "Fijian", + }, + { + name: "Hindi", + }, + { + name: "Urdu", + }, + ], + }, + { + name: "Falkland Islands", + code: "FK", + icon: "virtual-host", + icon_status: IconStatus.Up, + capital: "Stanley", + continent: { + name: "South America", + }, + currency: "FKP", + languages: [ + { + name: "English", + }, + ], + }, + { + name: "Micronesia", + code: "FM", + icon: "virtual-host", + icon_status: IconStatus.Up, + capital: "Palikir", + continent: { + name: "Oceania", + }, + currency: "USD", + languages: [ + { + name: "English", + }, + ], + }, + { + name: "Faroe Islands", + code: "FO", + icon: "virtual-host", + icon_status: IconStatus.Up, + capital: "Tórshavn", + continent: { + name: "Europe", + }, + currency: "DKK", + languages: [ + { + name: "Faroese", + }, + ], + }, + { + name: "France", + code: "FR", + icon: "virtual-host", + icon_status: IconStatus.Up, + capital: "Paris", + continent: { + name: "Europe", + }, + currency: "EUR", + languages: [ + { + name: "French", + }, + ], + }, + { + name: "Gabon", + code: "GA", + icon: "virtual-host", + icon_status: IconStatus.Up, + capital: "Libreville", + continent: { + name: "Africa", + }, + currency: "XAF", + languages: [ + { + name: "French", + }, + ], + }, + { + name: "United Kingdom", + code: "GB", + icon: "virtual-host", + icon_status: IconStatus.Up, + capital: "London", + continent: { + name: "Europe", + }, + currency: "GBP", + languages: [ + { + name: "English", + }, + ], + }, + { + name: "Grenada", + code: "GD", + icon: "virtual-host", + icon_status: IconStatus.Up, + capital: "St. George's", + continent: { + name: "North America", + }, + currency: "XCD", + languages: [ + { + name: "English", + }, + ], + }, + { + name: "Georgia", + code: "GE", + icon: "virtual-host", + icon_status: IconStatus.Up, + capital: "Tbilisi", + continent: { + name: "Asia", + }, + currency: "GEL", + languages: [ + { + name: "Georgian", + }, + ], + }, + { + name: "French Guiana", + code: "GF", + icon: "virtual-host", + icon_status: IconStatus.Up, + capital: "Cayenne", + continent: { + name: "South America", + }, + currency: "EUR", + languages: [ + { + name: "French", + }, + ], + }, + { + name: "Guernsey", + code: "GG", + icon: "virtual-host", + icon_status: IconStatus.Up, + capital: "St. Peter Port", + continent: { + name: "Europe", + }, + currency: "GBP", + languages: [ + { + name: "English", + }, + { + name: "French", + }, + ], + }, + { + name: "Ghana", + code: "GH", + icon: "virtual-host", + icon_status: IconStatus.Up, + capital: "Accra", + continent: { + name: "Africa", + }, + currency: "GHS", + languages: [ + { + name: "English", + }, + ], + }, + { + name: "Gibraltar", + code: "GI", + icon: "virtual-host", + icon_status: IconStatus.Up, + capital: "Gibraltar", + continent: { + name: "Europe", + }, + currency: "GIP", + languages: [ + { + name: "English", + }, + ], + }, + { + name: "Greenland", + code: "GL", + icon: "virtual-host", + icon_status: IconStatus.Up, + capital: "Nuuk", + continent: { + name: "North America", + }, + currency: "DKK", + languages: [ + { + name: "Greenlandic", + }, + ], + }, + { + name: "Gambia", + code: "GM", + icon: "virtual-host", + icon_status: IconStatus.Up, + capital: "Banjul", + continent: { + name: "Africa", + }, + currency: "GMD", + languages: [ + { + name: "English", + }, + ], + }, + { + name: "Guinea", + code: "GN", + icon: "virtual-host", + icon_status: IconStatus.Up, + capital: "Conakry", + continent: { + name: "Africa", + }, + currency: "GNF", + languages: [ + { + name: "French", + }, + { + name: "Peul", + }, + ], + }, + { + name: "Guadeloupe", + code: "GP", + icon: "virtual-host", + icon_status: IconStatus.Up, + capital: "Basse-Terre", + continent: { + name: "North America", + }, + currency: "EUR", + languages: [ + { + name: "French", + }, + ], + }, + { + name: "Equatorial Guinea", + code: "GQ", + icon: "virtual-host", + icon_status: IconStatus.Up, + capital: "Malabo", + continent: { + name: "Africa", + }, + currency: "XAF", + languages: [ + { + name: "Spanish", + }, + { + name: "French", + }, + ], + }, + { + name: "Greece", + code: "GR", + icon: "virtual-host", + icon_status: IconStatus.Up, + capital: "Athens", + continent: { + name: "Europe", + }, + currency: "EUR", + languages: [ + { + name: "Greek", + }, + ], + }, + { + name: "South Georgia and the South Sandwich Islands", + code: "GS", + icon: "virtual-host", + icon_status: IconStatus.Up, + capital: "King Edward Point", + continent: { + name: "Antarctica", + }, + currency: "GBP", + languages: [ + { + name: "English", + }, + ], + }, + { + name: "Guatemala", + code: "GT", + icon: "virtual-host", + icon_status: IconStatus.Up, + capital: "Guatemala City", + continent: { + name: "North America", + }, + currency: "GTQ", + languages: [ + { + name: "Spanish", + }, + ], + }, + { + name: "Guam", + code: "GU", + icon: "virtual-host", + icon_status: IconStatus.Up, + capital: "Hagåtña", + continent: { + name: "Oceania", + }, + currency: "USD", + languages: [ + { + name: "English", + }, + { + name: "Chamorro", + }, + { + name: "Spanish", + }, + ], + }, + { + name: "Guinea-Bissau", + code: "GW", + icon: "virtual-host", + icon_status: IconStatus.Up, + capital: "Bissau", + continent: { + name: "Africa", + }, + currency: "XOF", + languages: [ + { + name: "Portuguese", + }, + ], + }, + { + name: "Guyana", + code: "GY", + icon: "virtual-host", + icon_status: IconStatus.Up, + capital: "Georgetown", + continent: { + name: "South America", + }, + currency: "GYD", + languages: [ + { + name: "English", + }, + ], + }, + { + name: "Hong Kong", + code: "HK", + icon: "virtual-host", + icon_status: IconStatus.Up, + capital: "City of Victoria", + continent: { + name: "Asia", + }, + currency: "HKD", + languages: [ + { + name: "Chinese", + }, + { + name: "English", + }, + ], + }, + { + name: "Heard Island and McDonald Islands", + code: "HM", + icon: "virtual-host", + icon_status: IconStatus.Up, + capital: null, + continent: { + name: "Antarctica", + }, + currency: "AUD", + languages: [ + { + name: "English", + }, + ], + }, + { + name: "Honduras", + code: "HN", + icon: "virtual-host", + icon_status: IconStatus.Up, + capital: "Tegucigalpa", + continent: { + name: "North America", + }, + currency: "HNL", + languages: [ + { + name: "Spanish", + }, + ], + }, + { + name: "Croatia", + code: "HR", + icon: "virtual-host", + icon_status: IconStatus.Up, + capital: "Zagreb", + continent: { + name: "Europe", + }, + currency: "HRK", + languages: [ + { + name: "Croatian", + }, + ], + }, + { + name: "Haiti", + code: "HT", + icon: "virtual-host", + icon_status: IconStatus.Up, + capital: "Port-au-Prince", + continent: { + name: "North America", + }, + currency: "HTG,USD", + languages: [ + { + name: "French", + }, + { + name: "Haitian", + }, + ], + }, + { + name: "Hungary", + code: "HU", + icon: "virtual-host", + icon_status: IconStatus.Up, + capital: "Budapest", + continent: { + name: "Europe", + }, + currency: "HUF", + languages: [ + { + name: "Hungarian", + }, + ], + }, + { + name: "Indonesia", + code: "ID", + icon: "virtual-host", + icon_status: IconStatus.Up, + capital: "Jakarta", + continent: { + name: "Asia", + }, + currency: "IDR", + languages: [ + { + name: "Indonesian", + }, + ], + }, + { + name: "Ireland", + code: "IE", + icon: "virtual-host", + icon_status: IconStatus.Up, + capital: "Dublin", + continent: { + name: "Europe", + }, + currency: "EUR", + languages: [ + { + name: "Irish", + }, + { + name: "English", + }, + ], + }, + { + name: "Israel", + code: "IL", + icon: "virtual-host", + icon_status: IconStatus.Up, + capital: "Jerusalem", + continent: { + name: "Asia", + }, + currency: "ILS", + languages: [ + { + name: "Hebrew", + }, + { + name: "Arabic", + }, + ], + }, + { + name: "Isle of Man", + code: "IM", + icon: "virtual-host", + icon_status: IconStatus.Up, + capital: "Douglas", + continent: { + name: "Europe", + }, + currency: "GBP", + languages: [ + { + name: "English", + }, + { + name: "Manx", + }, + ], + }, + { + name: "India", + code: "IN", + icon: "virtual-host", + icon_status: IconStatus.Up, + capital: "New Delhi", + continent: { + name: "Asia", + }, + currency: "INR", + languages: [ + { + name: "Hindi", + }, + { + name: "English", + }, + ], + }, + { + name: "British Indian Ocean Territory", + code: "IO", + icon: "virtual-host", + icon_status: IconStatus.Up, + capital: "Diego Garcia", + continent: { + name: "Asia", + }, + currency: "USD", + languages: [ + { + name: "English", + }, + ], + }, + { + name: "Iraq", + code: "IQ", + icon: "virtual-host", + icon_status: IconStatus.Up, + capital: "Baghdad", + continent: { + name: "Asia", + }, + currency: "IQD", + languages: [ + { + name: "Arabic", + }, + { + name: "Kurdish", + }, + ], + }, + { + name: "Iran", + code: "IR", + icon: "virtual-host", + icon_status: IconStatus.Up, + capital: "Tehran", + continent: { + name: "Asia", + }, + currency: "IRR", + languages: [ + { + name: "Persian", + }, + ], + }, + { + name: "Iceland", + code: "IS", + icon: "virtual-host", + icon_status: IconStatus.Up, + capital: "Reykjavik", + continent: { + name: "Europe", + }, + currency: "ISK", + languages: [ + { + name: "Icelandic", + }, + ], + }, + { + name: "Italy", + code: "IT", + icon: "virtual-host", + icon_status: IconStatus.Up, + capital: "Rome", + continent: { + name: "Europe", + }, + currency: "EUR", + languages: [ + { + name: "Italian", + }, + ], + }, + { + name: "Jersey", + code: "JE", + icon: "virtual-host", + icon_status: IconStatus.Up, + capital: "Saint Helier", + continent: { + name: "Europe", + }, + currency: "GBP", + languages: [ + { + name: "English", + }, + { + name: "French", + }, + ], + }, + { + name: "Jamaica", + code: "JM", + icon: "virtual-host", + icon_status: IconStatus.Up, + capital: "Kingston", + continent: { + name: "North America", + }, + currency: "JMD", + languages: [ + { + name: "English", + }, + ], + }, + { + name: "Jordan", + code: "JO", + icon: "virtual-host", + icon_status: IconStatus.Up, + capital: "Amman", + continent: { + name: "Asia", + }, + currency: "JOD", + languages: [ + { + name: "Arabic", + }, + ], + }, + { + name: "Japan", + code: "JP", + icon: "virtual-host", + icon_status: IconStatus.Up, + capital: "Tokyo", + continent: { + name: "Asia", + }, + currency: "JPY", + languages: [ + { + name: "Japanese", + }, + ], + }, + { + name: "Kenya", + code: "KE", + icon: "virtual-host", + icon_status: IconStatus.Up, + capital: "Nairobi", + continent: { + name: "Africa", + }, + currency: "KES", + languages: [ + { + name: "English", + }, + { + name: "Swahili", + }, + ], + }, + { + name: "Kyrgyzstan", + code: "KG", + icon: "virtual-host", + icon_status: IconStatus.Up, + capital: "Bishkek", + continent: { + name: "Asia", + }, + currency: "KGS", + languages: [ + { + name: "Kirghiz", + }, + { + name: "Russian", + }, + ], + }, + { + name: "Cambodia", + code: "KH", + icon: "virtual-host", + icon_status: IconStatus.Up, + capital: "Phnom Penh", + continent: { + name: "Asia", + }, + currency: "KHR", + languages: [ + { + name: "Cambodian", + }, + ], + }, + { + name: "Kiribati", + code: "KI", + icon: "virtual-host", + icon_status: IconStatus.Up, + capital: "South Tarawa", + continent: { + name: "Oceania", + }, + currency: "AUD", + languages: [ + { + name: "English", + }, + ], + }, + { + name: "Comoros", + code: "KM", + icon: "virtual-host", + icon_status: IconStatus.Up, + capital: "Moroni", + continent: { + name: "Africa", + }, + currency: "KMF", + languages: [ + { + name: "Arabic", + }, + { + name: "French", + }, + ], + }, + { + name: "Saint Kitts and Nevis", + code: "KN", + icon: "virtual-host", + icon_status: IconStatus.Up, + capital: "Basseterre", + continent: { + name: "North America", + }, + currency: "XCD", + languages: [ + { + name: "English", + }, + ], + }, + { + name: "North Korea", + code: "KP", + icon: "virtual-host", + icon_status: IconStatus.Up, + capital: "Pyongyang", + continent: { + name: "Asia", + }, + currency: "KPW", + languages: [ + { + name: "Korean", + }, + ], + }, + { + name: "South Korea", + code: "KR", + icon: "virtual-host", + icon_status: IconStatus.Up, + capital: "Seoul", + continent: { + name: "Asia", + }, + currency: "KRW", + languages: [ + { + name: "Korean", + }, + ], + }, + { + name: "Kuwait", + code: "KW", + icon: "virtual-host", + icon_status: IconStatus.Up, + capital: "Kuwait City", + continent: { + name: "Asia", + }, + currency: "KWD", + languages: [ + { + name: "Arabic", + }, + ], + }, + { + name: "Cayman Islands", + code: "KY", + icon: "virtual-host", + icon_status: IconStatus.Up, + capital: "George Town", + continent: { + name: "North America", + }, + currency: "KYD", + languages: [ + { + name: "English", + }, + ], + }, + { + name: "Kazakhstan", + code: "KZ", + icon: "virtual-host", + icon_status: IconStatus.Up, + capital: "Astana", + continent: { + name: "Asia", + }, + currency: "KZT", + languages: [ + { + name: "Kazakh", + }, + { + name: "Russian", + }, + ], + }, + { + name: "Laos", + code: "LA", + icon: "virtual-host", + icon_status: IconStatus.Up, + capital: "Vientiane", + continent: { + name: "Asia", + }, + currency: "LAK", + languages: [ + { + name: "Laotian", + }, + ], + }, + { + name: "Lebanon", + code: "LB", + icon: "virtual-host", + icon_status: IconStatus.Up, + capital: "Beirut", + continent: { + name: "Asia", + }, + currency: "LBP", + languages: [ + { + name: "Arabic", + }, + { + name: "French", + }, + ], + }, + { + name: "Saint Lucia", + code: "LC", + icon: "virtual-host", + icon_status: IconStatus.Up, + capital: "Castries", + continent: { + name: "North America", + }, + currency: "XCD", + languages: [ + { + name: "English", + }, + ], + }, + { + name: "Liechtenstein", + code: "LI", + icon: "virtual-host", + icon_status: IconStatus.Up, + capital: "Vaduz", + continent: { + name: "Europe", + }, + currency: "CHF", + languages: [ + { + name: "German", + }, + ], + }, + { + name: "Sri Lanka", + code: "LK", + icon: "virtual-host", + icon_status: IconStatus.Up, + capital: "Colombo", + continent: { + name: "Asia", + }, + currency: "LKR", + languages: [ + { + name: "Sinhalese", + }, + { + name: "Tamil", + }, + ], + }, + { + name: "Liberia", + code: "LR", + icon: "virtual-host", + icon_status: IconStatus.Up, + capital: "Monrovia", + continent: { + name: "Africa", + }, + currency: "LRD", + languages: [ + { + name: "English", + }, + ], + }, + { + name: "Lesotho", + code: "LS", + icon: "virtual-host", + icon_status: IconStatus.Up, + capital: "Maseru", + continent: { + name: "Africa", + }, + currency: "LSL,ZAR", + languages: [ + { + name: "English", + }, + { + name: "Southern Sotho", + }, + ], + }, + { + name: "Lithuania", + code: "LT", + icon: "virtual-host", + icon_status: IconStatus.Up, + capital: "Vilnius", + continent: { + name: "Europe", + }, + currency: "EUR", + languages: [ + { + name: "Lithuanian", + }, + ], + }, + { + name: "Luxembourg", + code: "LU", + icon: "virtual-host", + icon_status: IconStatus.Up, + capital: "Luxembourg", + continent: { + name: "Europe", + }, + currency: "EUR", + languages: [ + { + name: "French", + }, + { + name: "German", + }, + { + name: "Luxembourgish", + }, + ], + }, + { + name: "Latvia", + code: "LV", + icon: "virtual-host", + icon_status: IconStatus.Up, + capital: "Riga", + continent: { + name: "Europe", + }, + currency: "EUR", + languages: [ + { + name: "Latvian", + }, + ], + }, + { + name: "Libya", + code: "LY", + icon: "virtual-host", + icon_status: IconStatus.Up, + capital: "Tripoli", + continent: { + name: "Africa", + }, + currency: "LYD", + languages: [ + { + name: "Arabic", + }, + ], + }, + { + name: "Morocco", + code: "MA", + icon: "virtual-host", + icon_status: IconStatus.Up, + capital: "Rabat", + continent: { + name: "Africa", + }, + currency: "MAD", + languages: [ + { + name: "Arabic", + }, + ], + }, + { + name: "Monaco", + code: "MC", + icon: "virtual-host", + icon_status: IconStatus.Up, + capital: "Monaco", + continent: { + name: "Europe", + }, + currency: "EUR", + languages: [ + { + name: "French", + }, + ], + }, + { + name: "Moldova", + code: "MD", + icon: "virtual-host", + icon_status: IconStatus.Up, + capital: "Chișinău", + continent: { + name: "Europe", + }, + currency: "MDL", + languages: [ + { + name: "Romanian", + }, + ], + }, + { + name: "Montenegro", + code: "ME", + icon: "virtual-host", + icon_status: IconStatus.Up, + capital: "Podgorica", + continent: { + name: "Europe", + }, + currency: "EUR", + languages: [ + { + name: "Serbian", + }, + { + name: "Bosnian", + }, + { + name: "Albanian", + }, + { + name: "Croatian", + }, + ], + }, + { + name: "Saint Martin", + code: "MF", + icon: "virtual-host", + icon_status: IconStatus.Up, + capital: "Marigot", + continent: { + name: "North America", + }, + currency: "EUR", + languages: [ + { + name: "English", + }, + { + name: "French", + }, + { + name: "Dutch", + }, + ], + }, + { + name: "Madagascar", + code: "MG", + icon: "virtual-host", + icon_status: IconStatus.Up, + capital: "Antananarivo", + continent: { + name: "Africa", + }, + currency: "MGA", + languages: [ + { + name: "French", + }, + { + name: "Malagasy", + }, + ], + }, + { + name: "Marshall Islands", + code: "MH", + icon: "virtual-host", + icon_status: IconStatus.Up, + capital: "Majuro", + continent: { + name: "Oceania", + }, + currency: "USD", + languages: [ + { + name: "English", + }, + { + name: "Marshallese", + }, + ], + }, + { + name: "North Macedonia", + code: "MK", + icon: "virtual-host", + icon_status: IconStatus.Up, + capital: "Skopje", + continent: { + name: "Europe", + }, + currency: "MKD", + languages: [ + { + name: "Macedonian", + }, + ], + }, + { + name: "Mali", + code: "ML", + icon: "virtual-host", + icon_status: IconStatus.Up, + capital: "Bamako", + continent: { + name: "Africa", + }, + currency: "XOF", + languages: [ + { + name: "French", + }, + ], + }, + { + name: "Myanmar [Burma]", + code: "MM", + icon: "virtual-host", + icon_status: IconStatus.Up, + capital: "Naypyidaw", + continent: { + name: "Asia", + }, + currency: "MMK", + languages: [ + { + name: "Burmese", + }, + ], + }, + { + name: "Mongolia", + code: "MN", + icon: "virtual-host", + icon_status: IconStatus.Up, + capital: "Ulan Bator", + continent: { + name: "Asia", + }, + currency: "MNT", + languages: [ + { + name: "Mongolian", + }, + ], + }, + { + name: "Macao", + code: "MO", + icon: "virtual-host", + icon_status: IconStatus.Up, + capital: null, + continent: { + name: "Asia", + }, + currency: "MOP", + languages: [ + { + name: "Chinese", + }, + { + name: "Portuguese", + }, + ], + }, + { + name: "Northern Mariana Islands", + code: "MP", + icon: "virtual-host", + icon_status: IconStatus.Up, + capital: "Saipan", + continent: { + name: "Oceania", + }, + currency: "USD", + languages: [ + { + name: "English", + }, + { + name: "Chamorro", + }, + ], + }, + { + name: "Martinique", + code: "MQ", + icon: "virtual-host", + icon_status: IconStatus.Up, + capital: "Fort-de-France", + continent: { + name: "North America", + }, + currency: "EUR", + languages: [ + { + name: "French", + }, + ], + }, + { + name: "Mauritania", + code: "MR", + icon: "virtual-host", + icon_status: IconStatus.Up, + capital: "Nouakchott", + continent: { + name: "Africa", + }, + currency: "MRU", + languages: [ + { + name: "Arabic", + }, + ], + }, + { + name: "Montserrat", + code: "MS", + icon: "virtual-host", + icon_status: IconStatus.Up, + capital: "Plymouth", + continent: { + name: "North America", + }, + currency: "XCD", + languages: [ + { + name: "English", + }, + ], + }, + { + name: "Malta", + code: "MT", + icon: "virtual-host", + icon_status: IconStatus.Up, + capital: "Valletta", + continent: { + name: "Europe", + }, + currency: "EUR", + languages: [ + { + name: "Maltese", + }, + { + name: "English", + }, + ], + }, + { + name: "Mauritius", + code: "MU", + icon: "virtual-host", + icon_status: IconStatus.Up, + capital: "Port Louis", + continent: { + name: "Africa", + }, + currency: "MUR", + languages: [ + { + name: "English", + }, + ], + }, + { + name: "Maldives", + code: "MV", + icon: "virtual-host", + icon_status: IconStatus.Up, + capital: "Malé", + continent: { + name: "Asia", + }, + currency: "MVR", + languages: [ + { + name: "Divehi", + }, + ], + }, + { + name: "Malawi", + code: "MW", + icon: "virtual-host", + icon_status: IconStatus.Up, + capital: "Lilongwe", + continent: { + name: "Africa", + }, + currency: "MWK", + languages: [ + { + name: "English", + }, + { + name: "Chichewa", + }, + ], + }, + { + name: "Mexico", + code: "MX", + icon: "virtual-host", + icon_status: IconStatus.Up, + capital: "Mexico City", + continent: { + name: "North America", + }, + currency: "MXN", + languages: [ + { + name: "Spanish", + }, + ], + }, + { + name: "Malaysia", + code: "MY", + icon: "virtual-host", + icon_status: IconStatus.Up, + capital: "Kuala Lumpur", + continent: { + name: "Asia", + }, + currency: "MYR", + languages: [ + { + name: "Malay", + }, + ], + }, + { + name: "Mozambique", + code: "MZ", + icon: "virtual-host", + icon_status: IconStatus.Up, + capital: "Maputo", + continent: { + name: "Africa", + }, + currency: "MZN", + languages: [ + { + name: "Portuguese", + }, + ], + }, + { + name: "Namibia", + code: "NA", + icon: "virtual-host", + icon_status: IconStatus.Up, + capital: "Windhoek", + continent: { + name: "Africa", + }, + currency: "NAD,ZAR", + languages: [ + { + name: "English", + }, + { + name: "Afrikaans", + }, + ], + }, + { + name: "New Caledonia", + code: "NC", + icon: "virtual-host", + icon_status: IconStatus.Up, + capital: "Nouméa", + continent: { + name: "Oceania", + }, + currency: "XPF", + languages: [ + { + name: "French", + }, + ], + }, + { + name: "Niger", + code: "NE", + icon: "virtual-host", + icon_status: IconStatus.Up, + capital: "Niamey", + continent: { + name: "Africa", + }, + currency: "XOF", + languages: [ + { + name: "French", + }, + ], + }, + { + name: "Norfolk Island", + code: "NF", + icon: "virtual-host", + icon_status: IconStatus.Up, + capital: "Kingston", + continent: { + name: "Oceania", + }, + currency: "AUD", + languages: [ + { + name: "English", + }, + ], + }, + { + name: "Nigeria", + code: "NG", + icon: "virtual-host", + icon_status: IconStatus.Up, + capital: "Abuja", + continent: { + name: "Africa", + }, + currency: "NGN", + languages: [ + { + name: "English", + }, + ], + }, + { + name: "Nicaragua", + code: "NI", + icon: "virtual-host", + icon_status: IconStatus.Up, + capital: "Managua", + continent: { + name: "North America", + }, + currency: "NIO", + languages: [ + { + name: "Spanish", + }, + ], + }, + { + name: "Netherlands", + code: "NL", + icon: "virtual-host", + icon_status: IconStatus.Up, + capital: "Amsterdam", + continent: { + name: "Europe", + }, + currency: "EUR", + languages: [ + { + name: "Dutch", + }, + ], + }, + { + name: "Norway", + code: "NO", + icon: "virtual-host", + icon_status: IconStatus.Up, + capital: "Oslo", + continent: { + name: "Europe", + }, + currency: "NOK", + languages: [ + { + name: "Norwegian", + }, + { + name: "Norwegian Bokmål", + }, + { + name: "Norwegian Nynorsk", + }, + ], + }, + { + name: "Nepal", + code: "NP", + icon: "virtual-host", + icon_status: IconStatus.Up, + capital: "Kathmandu", + continent: { + name: "Asia", + }, + currency: "NPR", + languages: [ + { + name: "Nepali", + }, + ], + }, + { + name: "Nauru", + code: "NR", + icon: "virtual-host", + icon_status: IconStatus.Up, + capital: "Yaren", + continent: { + name: "Oceania", + }, + currency: "AUD", + languages: [ + { + name: "English", + }, + { + name: "Nauruan", + }, + ], + }, + { + name: "Niue", + code: "NU", + icon: "virtual-host", + icon_status: IconStatus.Up, + capital: "Alofi", + continent: { + name: "Oceania", + }, + currency: "NZD", + languages: [ + { + name: "English", + }, + ], + }, + { + name: "New Zealand", + code: "NZ", + icon: "virtual-host", + icon_status: IconStatus.Up, + capital: "Wellington", + continent: { + name: "Oceania", + }, + currency: "NZD", + languages: [ + { + name: "English", + }, + { + name: "Maori", + }, + ], + }, + { + name: "Oman", + code: "OM", + icon: "virtual-host", + icon_status: IconStatus.Up, + capital: "Muscat", + continent: { + name: "Asia", + }, + currency: "OMR", + languages: [ + { + name: "Arabic", + }, + ], + }, + { + name: "Panama", + code: "PA", + icon: "virtual-host", + icon_status: IconStatus.Up, + capital: "Panama City", + continent: { + name: "North America", + }, + currency: "PAB,USD", + languages: [ + { + name: "Spanish", + }, + ], + }, + { + name: "Peru", + code: "PE", + icon: "virtual-host", + icon_status: IconStatus.Up, + capital: "Lima", + continent: { + name: "South America", + }, + currency: "PEN", + languages: [ + { + name: "Spanish", + }, + ], + }, + { + name: "French Polynesia", + code: "PF", + icon: "virtual-host", + icon_status: IconStatus.Up, + capital: "Papeetē", + continent: { + name: "Oceania", + }, + currency: "XPF", + languages: [ + { + name: "French", + }, + ], + }, + { + name: "Papua New Guinea", + code: "PG", + icon: "virtual-host", + icon_status: IconStatus.Up, + capital: "Port Moresby", + continent: { + name: "Oceania", + }, + currency: "PGK", + languages: [ + { + name: "English", + }, + ], + }, + { + name: "Philippines", + code: "PH", + icon: "virtual-host", + icon_status: IconStatus.Up, + capital: "Manila", + continent: { + name: "Asia", + }, + currency: "PHP", + languages: [ + { + name: "English", + }, + ], + }, + { + name: "Pakistan", + code: "PK", + icon: "virtual-host", + icon_status: IconStatus.Up, + capital: "Islamabad", + continent: { + name: "Asia", + }, + currency: "PKR", + languages: [ + { + name: "English", + }, + { + name: "Urdu", + }, + ], + }, + { + name: "Poland", + code: "PL", + icon: "virtual-host", + icon_status: IconStatus.Up, + capital: "Warsaw", + continent: { + name: "Europe", + }, + currency: "PLN", + languages: [ + { + name: "Polish", + }, + ], + }, + { + name: "Saint Pierre and Miquelon", + code: "PM", + icon: "virtual-host", + icon_status: IconStatus.Up, + capital: "Saint-Pierre", + continent: { + name: "North America", + }, + currency: "EUR", + languages: [ + { + name: "French", + }, + ], + }, + { + name: "Pitcairn Islands", + code: "PN", + icon: "virtual-host", + icon_status: IconStatus.Up, + capital: "Adamstown", + continent: { + name: "Oceania", + }, + currency: "NZD", + languages: [ + { + name: "English", + }, + ], + }, + { + name: "Puerto Rico", + code: "PR", + icon: "virtual-host", + icon_status: IconStatus.Up, + capital: "San Juan", + continent: { + name: "North America", + }, + currency: "USD", + languages: [ + { + name: "Spanish", + }, + { + name: "English", + }, + ], + }, + { + name: "Palestine", + code: "PS", + icon: "virtual-host", + icon_status: IconStatus.Up, + capital: "Ramallah", + continent: { + name: "Asia", + }, + currency: "ILS", + languages: [ + { + name: "Arabic", + }, + ], + }, + { + name: "Portugal", + code: "PT", + icon: "virtual-host", + icon_status: IconStatus.Up, + capital: "Lisbon", + continent: { + name: "Europe", + }, + currency: "EUR", + languages: [ + { + name: "Portuguese", + }, + ], + }, + { + name: "Palau", + code: "PW", + icon: "virtual-host", + icon_status: IconStatus.Up, + capital: "Ngerulmud", + continent: { + name: "Oceania", + }, + currency: "USD", + languages: [ + { + name: "English", + }, + ], + }, + { + name: "Paraguay", + code: "PY", + icon: "virtual-host", + icon_status: IconStatus.Up, + capital: "Asunción", + continent: { + name: "South America", + }, + currency: "PYG", + languages: [ + { + name: "Spanish", + }, + { + name: "Guarani", + }, + ], + }, + { + name: "Qatar", + code: "QA", + icon: "virtual-host", + icon_status: IconStatus.Up, + capital: "Doha", + continent: { + name: "Asia", + }, + currency: "QAR", + languages: [ + { + name: "Arabic", + }, + ], + }, + { + name: "Réunion", + code: "RE", + icon: "virtual-host", + icon_status: IconStatus.Up, + capital: "Saint-Denis", + continent: { + name: "Africa", + }, + currency: "EUR", + languages: [ + { + name: "French", + }, + ], + }, + { + name: "Romania", + code: "RO", + icon: "virtual-host", + icon_status: IconStatus.Up, + capital: "Bucharest", + continent: { + name: "Europe", + }, + currency: "RON", + languages: [ + { + name: "Romanian", + }, + ], + }, + { + name: "Serbia", + code: "RS", + icon: "virtual-host", + icon_status: IconStatus.Up, + capital: "Belgrade", + continent: { + name: "Europe", + }, + currency: "RSD", + languages: [ + { + name: "Serbian", + }, + ], + }, + { + name: "Russia", + code: "RU", + icon: "virtual-host", + icon_status: IconStatus.Up, + capital: "Moscow", + continent: { + name: "Europe", + }, + currency: "RUB", + languages: [ + { + name: "Russian", + }, + ], + }, + { + name: "Rwanda", + code: "RW", + icon: "virtual-host", + icon_status: IconStatus.Up, + capital: "Kigali", + continent: { + name: "Africa", + }, + currency: "RWF", + languages: [ + { + name: "Rwandi", + }, + { + name: "English", + }, + { + name: "French", + }, + ], + }, + { + name: "Saudi Arabia", + code: "SA", + icon: "virtual-host", + icon_status: IconStatus.Up, + capital: "Riyadh", + continent: { + name: "Asia", + }, + currency: "SAR", + languages: [ + { + name: "Arabic", + }, + ], + }, + { + name: "Solomon Islands", + code: "SB", + icon: "virtual-host", + icon_status: IconStatus.Up, + capital: "Honiara", + continent: { + name: "Oceania", + }, + currency: "SBD", + languages: [ + { + name: "English", + }, + ], + }, + { + name: "Seychelles", + code: "SC", + icon: "virtual-host", + icon_status: IconStatus.Up, + capital: "Victoria", + continent: { + name: "Africa", + }, + currency: "SCR", + languages: [ + { + name: "French", + }, + { + name: "English", + }, + ], + }, + { + name: "Sudan", + code: "SD", + icon: "virtual-host", + icon_status: IconStatus.Up, + capital: "Khartoum", + continent: { + name: "Africa", + }, + currency: "SDG", + languages: [ + { + name: "Arabic", + }, + { + name: "English", + }, + ], + }, + { + name: "Sweden", + code: "SE", + icon: "virtual-host", + icon_status: IconStatus.Up, + capital: "Stockholm", + continent: { + name: "Europe", + }, + currency: "SEK", + languages: [ + { + name: "Swedish", + }, + ], + }, + { + name: "Singapore", + code: "SG", + icon: "virtual-host", + icon_status: IconStatus.Up, + capital: "Singapore", + continent: { + name: "Asia", + }, + currency: "SGD", + languages: [ + { + name: "English", + }, + { + name: "Malay", + }, + { + name: "Tamil", + }, + { + name: "Chinese", + }, + ], + }, + { + name: "Saint Helena", + code: "SH", + icon: "virtual-host", + icon_status: IconStatus.Up, + capital: "Jamestown", + continent: { + name: "Africa", + }, + currency: "SHP", + languages: [ + { + name: "English", + }, + ], + }, + { + name: "Slovenia", + code: "SI", + icon: "virtual-host", + icon_status: IconStatus.Up, + capital: "Ljubljana", + continent: { + name: "Europe", + }, + currency: "EUR", + languages: [ + { + name: "Slovenian", + }, + ], + }, + { + name: "Svalbard and Jan Mayen", + code: "SJ", + icon: "virtual-host", + icon_status: IconStatus.Up, + capital: "Longyearbyen", + continent: { + name: "Europe", + }, + currency: "NOK", + languages: [ + { + name: "Norwegian", + }, + ], + }, + { + name: "Slovakia", + code: "SK", + icon: "virtual-host", + icon_status: IconStatus.Up, + capital: "Bratislava", + continent: { + name: "Europe", + }, + currency: "EUR", + languages: [ + { + name: "Slovak", + }, + ], + }, + { + name: "Sierra Leone", + code: "SL", + icon: "virtual-host", + icon_status: IconStatus.Up, + capital: "Freetown", + continent: { + name: "Africa", + }, + currency: "SLL", + languages: [ + { + name: "English", + }, + ], + }, + { + name: "San Marino", + code: "SM", + icon: "virtual-host", + icon_status: IconStatus.Up, + capital: "City of San Marino", + continent: { + name: "Europe", + }, + currency: "EUR", + languages: [ + { + name: "Italian", + }, + ], + }, + { + name: "Senegal", + code: "SN", + icon: "virtual-host", + icon_status: IconStatus.Up, + capital: "Dakar", + continent: { + name: "Africa", + }, + currency: "XOF", + languages: [ + { + name: "French", + }, + ], + }, + { + name: "Somalia", + code: "SO", + icon: "virtual-host", + icon_status: IconStatus.Up, + capital: "Mogadishu", + continent: { + name: "Africa", + }, + currency: "SOS", + languages: [ + { + name: "Somalia", + }, + { + name: "Arabic", + }, + ], + }, + { + name: "Suriname", + code: "SR", + icon: "virtual-host", + icon_status: IconStatus.Up, + capital: "Paramaribo", + continent: { + name: "South America", + }, + currency: "SRD", + languages: [ + { + name: "Dutch", + }, + ], + }, + { + name: "South Sudan", + code: "SS", + icon: "virtual-host", + icon_status: IconStatus.Up, + capital: "Juba", + continent: { + name: "Africa", + }, + currency: "SSP", + languages: [ + { + name: "English", + }, + ], + }, + { + name: "São Tomé and Príncipe", + code: "ST", + icon: "virtual-host", + icon_status: IconStatus.Up, + capital: "São Tomé", + continent: { + name: "Africa", + }, + currency: "STN", + languages: [ + { + name: "Portuguese", + }, + ], + }, + { + name: "El Salvador", + code: "SV", + icon: "virtual-host", + icon_status: IconStatus.Up, + capital: "San Salvador", + continent: { + name: "North America", + }, + currency: "SVC,USD", + languages: [ + { + name: "Spanish", + }, + ], + }, + { + name: "Sint Maarten", + code: "SX", + icon: "virtual-host", + icon_status: IconStatus.Up, + capital: "Philipsburg", + continent: { + name: "North America", + }, + currency: "ANG", + languages: [ + { + name: "Dutch", + }, + { + name: "English", + }, + ], + }, + { + name: "Syria", + code: "SY", + icon: "virtual-host", + icon_status: IconStatus.Up, + capital: "Damascus", + continent: { + name: "Asia", + }, + currency: "SYP", + languages: [ + { + name: "Arabic", + }, + ], + }, + { + name: "Swaziland", + code: "SZ", + icon: "virtual-host", + icon_status: IconStatus.Up, + capital: "Lobamba", + continent: { + name: "Africa", + }, + currency: "SZL", + languages: [ + { + name: "English", + }, + { + name: "Swati", + }, + ], + }, + { + name: "Turks and Caicos Islands", + code: "TC", + icon: "virtual-host", + icon_status: IconStatus.Up, + capital: "Cockburn Town", + continent: { + name: "North America", + }, + currency: "USD", + languages: [ + { + name: "English", + }, + ], + }, + { + name: "Chad", + code: "TD", + icon: "virtual-host", + icon_status: IconStatus.Up, + capital: "N'Djamena", + continent: { + name: "Africa", + }, + currency: "XAF", + languages: [ + { + name: "French", + }, + { + name: "Arabic", + }, + ], + }, + { + name: "French Southern Territories", + code: "TF", + icon: "virtual-host", + icon_status: IconStatus.Up, + capital: "Port-aux-Français", + continent: { + name: "Antarctica", + }, + currency: "EUR", + languages: [ + { + name: "French", + }, + ], + }, + { + name: "Togo", + code: "TG", + icon: "virtual-host", + icon_status: IconStatus.Up, + capital: "Lomé", + continent: { + name: "Africa", + }, + currency: "XOF", + languages: [ + { + name: "French", + }, + ], + }, + { + name: "Thailand", + code: "TH", + icon: "virtual-host", + icon_status: IconStatus.Up, + capital: "Bangkok", + continent: { + name: "Asia", + }, + currency: "THB", + languages: [ + { + name: "Thai", + }, + ], + }, + { + name: "Tajikistan", + code: "TJ", + icon: "virtual-host", + icon_status: IconStatus.Up, + capital: "Dushanbe", + continent: { + name: "Asia", + }, + currency: "TJS", + languages: [ + { + name: "Tajik", + }, + { + name: "Russian", + }, + ], + }, + { + name: "Tokelau", + code: "TK", + icon: "virtual-host", + icon_status: IconStatus.Up, + capital: "Fakaofo", + continent: { + name: "Oceania", + }, + currency: "NZD", + languages: [ + { + name: "English", + }, + ], + }, + { + name: "East Timor", + code: "TL", + icon: "virtual-host", + icon_status: IconStatus.Up, + capital: "Dili", + continent: { + name: "Oceania", + }, + currency: "USD", + languages: [ + { + name: "Portuguese", + }, + ], + }, + { + name: "Turkmenistan", + code: "TM", + icon: "virtual-host", + icon_status: IconStatus.Up, + capital: "Ashgabat", + continent: { + name: "Asia", + }, + currency: "TMT", + languages: [ + { + name: "Turkmen", + }, + { + name: "Russian", + }, + ], + }, + { + name: "Tunisia", + code: "TN", + icon: "virtual-host", + icon_status: IconStatus.Up, + capital: "Tunis", + continent: { + name: "Africa", + }, + currency: "TND", + languages: [ + { + name: "Arabic", + }, + ], + }, + { + name: "Tonga", + code: "TO", + icon: "virtual-host", + icon_status: IconStatus.Up, + capital: "Nuku'alofa", + continent: { + name: "Oceania", + }, + currency: "TOP", + languages: [ + { + name: "English", + }, + { + name: "Tonga", + }, + ], + }, + { + name: "Turkey", + code: "TR", + icon: "virtual-host", + icon_status: IconStatus.Up, + capital: "Ankara", + continent: { + name: "Asia", + }, + currency: "TRY", + languages: [ + { + name: "Turkish", + }, + ], + }, + { + name: "Trinidad and Tobago", + code: "TT", + icon: "virtual-host", + icon_status: IconStatus.Up, + capital: "Port of Spain", + continent: { + name: "North America", + }, + currency: "TTD", + languages: [ + { + name: "English", + }, + ], + }, + { + name: "Tuvalu", + code: "TV", + icon: "virtual-host", + icon_status: IconStatus.Up, + capital: "Funafuti", + continent: { + name: "Oceania", + }, + currency: "AUD", + languages: [ + { + name: "English", + }, + ], + }, + { + name: "Taiwan", + code: "TW", + icon: "virtual-host", + icon_status: IconStatus.Up, + capital: "Taipei", + continent: { + name: "Asia", + }, + currency: "TWD", + languages: [ + { + name: "Chinese", + }, + ], + }, + { + name: "Tanzania", + code: "TZ", + icon: "virtual-host", + icon_status: IconStatus.Up, + capital: "Dodoma", + continent: { + name: "Africa", + }, + currency: "TZS", + languages: [ + { + name: "Swahili", + }, + { + name: "English", + }, + ], + }, + { + name: "Ukraine", + code: "UA", + icon: "virtual-host", + icon_status: IconStatus.Up, + capital: "Kyiv", + continent: { + name: "Europe", + }, + currency: "UAH", + languages: [ + { + name: "Ukrainian", + }, + ], + }, + { + name: "Uganda", + code: "UG", + icon: "virtual-host", + icon_status: IconStatus.Up, + capital: "Kampala", + continent: { + name: "Africa", + }, + currency: "UGX", + languages: [ + { + name: "English", + }, + { + name: "Swahili", + }, + ], + }, + { + name: "U.S. Minor Outlying Islands", + code: "UM", + icon: "virtual-host", + icon_status: IconStatus.Up, + capital: null, + continent: { + name: "Oceania", + }, + currency: "USD", + languages: [ + { + name: "English", + }, + ], + }, + { + name: "United States", + code: "US", + icon: "virtual-host", + icon_status: IconStatus.Up, + capital: "Washington D.C.", + continent: { + name: "North America", + }, + currency: "USD,USN,USS", + languages: [ + { + name: "English", + }, + ], + }, + { + name: "Uruguay", + code: "UY", + icon: "virtual-host", + icon_status: IconStatus.Up, + capital: "Montevideo", + continent: { + name: "South America", + }, + currency: "UYI,UYU", + languages: [ + { + name: "Spanish", + }, + ], + }, + { + name: "Uzbekistan", + code: "UZ", + icon: "virtual-host", + icon_status: IconStatus.Up, + capital: "Tashkent", + continent: { + name: "Asia", + }, + currency: "UZS", + languages: [ + { + name: "Uzbek", + }, + { + name: "Russian", + }, + ], + }, + { + name: "Vatican City", + code: "VA", + icon: "virtual-host", + icon_status: IconStatus.Up, + capital: "Vatican City", + continent: { + name: "Europe", + }, + currency: "EUR", + languages: [ + { + name: "Italian", + }, + { + name: "Latin", + }, + ], + }, + { + name: "Saint Vincent and the Grenadines", + code: "VC", + icon: "virtual-host", + icon_status: IconStatus.Up, + capital: "Kingstown", + continent: { + name: "North America", + }, + currency: "XCD", + languages: [ + { + name: "English", + }, + ], + }, + { + name: "Venezuela", + code: "VE", + icon: "virtual-host", + icon_status: IconStatus.Up, + capital: "Caracas", + continent: { + name: "South America", + }, + currency: "VES", + languages: [ + { + name: "Spanish", + }, + ], + }, + { + name: "British Virgin Islands", + code: "VG", + icon: "virtual-host", + icon_status: IconStatus.Up, + capital: "Road Town", + continent: { + name: "North America", + }, + currency: "USD", + languages: [ + { + name: "English", + }, + ], + }, + { + name: "U.S. Virgin Islands", + code: "VI", + icon: "virtual-host", + icon_status: IconStatus.Up, + capital: "Charlotte Amalie", + continent: { + name: "North America", + }, + currency: "USD", + languages: [ + { + name: "English", + }, + ], + }, + { + name: "Vietnam", + code: "VN", + icon: "virtual-host", + icon_status: IconStatus.Up, + capital: "Hanoi", + continent: { + name: "Asia", + }, + currency: "VND", + languages: [ + { + name: "Vietnamese", + }, + ], + }, + { + name: "Vanuatu", + code: "VU", + icon: "virtual-host", + icon_status: IconStatus.Up, + capital: "Port Vila", + continent: { + name: "Oceania", + }, + currency: "VUV", + languages: [ + { + name: "Bislama", + }, + { + name: "English", + }, + { + name: "French", + }, + ], + }, + { + name: "Wallis and Futuna", + code: "WF", + icon: "virtual-host", + icon_status: IconStatus.Up, + capital: "Mata-Utu", + continent: { + name: "Oceania", + }, + currency: "XPF", + languages: [ + { + name: "French", + }, + ], + }, + { + name: "Samoa", + code: "WS", + icon: "virtual-host", + icon_status: IconStatus.Up, + capital: "Apia", + continent: { + name: "Oceania", + }, + currency: "WST", + languages: [ + { + name: "Samoan", + }, + { + name: "English", + }, + ], + }, + { + name: "Kosovo", + code: "XK", + icon: "virtual-host", + icon_status: IconStatus.Up, + capital: "Pristina", + continent: { + name: "Europe", + }, + currency: "EUR", + languages: [ + { + name: "Albanian", + }, + { + name: "Serbian", + }, + ], + }, + { + name: "Yemen", + code: "YE", + icon: "virtual-host", + icon_status: IconStatus.Up, + capital: "Sana'a", + continent: { + name: "Asia", + }, + currency: "YER", + languages: [ + { + name: "Arabic", + }, + ], + }, + { + name: "Mayotte", + code: "YT", + icon: "virtual-host", + icon_status: IconStatus.Up, + capital: "Mamoudzou", + continent: { + name: "Africa", + }, + currency: "EUR", + languages: [ + { + name: "French", + }, + ], + }, + { + name: "South Africa", + code: "ZA", + icon: "virtual-host", + icon_status: IconStatus.Up, + capital: "Pretoria", + continent: { + name: "Africa", + }, + currency: "ZAR", + languages: [ + { + name: "Afrikaans", + }, + { + name: "English", + }, + { + name: "South Ndebele", + }, + { + name: "Southern Sotho", + }, + { + name: "Swati", + }, + { + name: "Tswana", + }, + { + name: "Tsonga", + }, + { + name: "Venda", + }, + { + name: "Xhosa", + }, + { + name: "Zulu", + }, + ], + }, + { + name: "Zambia", + code: "ZM", + icon: "virtual-host", + icon_status: IconStatus.Up, + capital: "Lusaka", + continent: { + name: "Africa", + }, + currency: "ZMW", + languages: [ + { + name: "English", + }, + ], + }, + { + name: "Zimbabwe", + code: "ZW", + icon: "virtual-host", + icon_status: IconStatus.Up, + capital: "Harare", + continent: { + name: "Africa", + }, + currency: "USD,ZAR,BWP,GBP,AUD,CNY,INR,JPY", + languages: [ + { + name: "English", + }, + { + name: "Shona", + }, + { + name: "North Ndebele", + }, + ], + }, + ], + }, +}; +\\\\\\\\\\\\\\\`, + "widget-types/drilldown/drilldown-widget/drilldown-widget-example.component.ts": \\\\\\\\\\\\\\\`// © 2022 SolarWinds Worldwide, LLC. All rights reserved. +// +// Permission is hereby granted, free of charge, to any person obtaining a copy +// of this software and associated documentation files (the "Software"), to +// deal in the Software without restriction, including without limitation the +// rights to use, copy, modify, merge, publish, distribute, sublicense, and/or +// sell copies of the Software, and to permit persons to whom the Software is +// furnished to do so, subject to the following conditions: +// +// The above copyright notice and this permission notice shall be included in +// all copies or substantial portions of the Software. +// +// THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR +// IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, +// FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE +// AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER +// LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, +// OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN +// THE SOFTWARE. + +import { HttpClient } from "@angular/common/http"; +import { + ChangeDetectorRef, + Component, + Injectable, + OnDestroy, + OnInit, +} from "@angular/core"; +import { GridsterConfig, GridsterItem } from "angular-gridster2"; +import { Apollo, gql } from "apollo-angular"; +import groupBy from "lodash/groupBy"; +import { BehaviorSubject, Observable, of } from "rxjs"; +import { catchError, delay, filter, map } from "rxjs/operators"; + +import { + DataSourceFeatures, + IconStatus, + IDataField, + IDataSource, + IDataSourceFeatures, + IDataSourceFeaturesConfiguration, + INovaFilters, + LoggerService, + ServerSideDataSource, + IFilters, +} from "@nova-ui/bits"; +import { + DATA_SOURCE, + DEFAULT_PIZZAGNA_ROOT, + IDashboard, + IDrilldownComponentsConfiguration, + IListWidgetConfiguration, + IProviderConfiguration, + IWidget, + IWidgets, + ListGroupItemComponent, + ListLeafItemComponent, + NOVA_DRILLDOWN_DATASOURCE_ADAPTER, + PizzagnaLayer, + ProviderRegistryService, + WellKnownPathKey, + WellKnownProviders, + WidgetTypesService, +} from "@nova-ui/dashboards"; + +import { DrilldownDataSource } from "./mock-data-source"; + +/** + * A simple KPI data source to retrieve the average rating of Harry Potter and the Sorcerer's Stone (book) via googleapis + */ +@Injectable() +export class DrilldownDataSourceRealApi + extends ServerSideDataSource + implements OnDestroy, IDataSource +{ + // This is the ID we'll use to identify the provider + public static providerId = "DrilldownDataSourceRealApi"; + + // Use this subject to communicate the data source's busy state + public busy = new BehaviorSubject(false); + public dataFields: Partial[] = [ + { id: "regionName", label: "Region name" }, + { id: "subregionName", label: "Subregion name" }, + ]; + + public features: IDataSourceFeaturesConfiguration; + private supportedFeatures: IDataSourceFeatures = { + search: { enabled: true }, + }; + + private drillState: string[] = []; + private groupBy: string[]; + + constructor( + private logger: LoggerService, + private http: HttpClient, + private apollo: Apollo + ) { + super(); + this.features = new DataSourceFeatures(this.supportedFeatures); + // TODO: remove Partial in vNext after marking dataType field as optional - NUI-5838 + ( + this.dataFieldsConfig.dataFields$ as BehaviorSubject< + Partial[] + > + ).next(this.dataFields); + } + + private groupedDataHistory: Array> = []; + + // In this example, getFilteredData is invoked every 10 minutes (Take a look at the refresher + // provider definition in the widget configuration below to see how the interval is set) + public async getFilteredData(data: IFilters): Promise { + return of(data) + .pipe( + filter(() => !!this.drillState), + map((countries) => { + const lastHistory = () => getLast(this.groupedDataHistory); + + if (!this.drillState.length && !this.groupBy.length) { + return countries; + } + + // adding "ROOT" as a root level for drilling + const fullDrillState = ["ROOT", ...this.drillState]; + const activeDrillLvl = fullDrillState.length; + const historyLvl = this.groupedDataHistory.length; + + // checking how many lvls we have to group for drilling, in case some are missed + const drillLvlDiff = activeDrillLvl - historyLvl; + + if (!drillLvlDiff) { + return lastHistory() || countries; + } + + const drillToGroup = fullDrillState.slice( + fullDrillState.length - drillLvlDiff + ); + + for (const drill of drillToGroup) { + const drillIdx = fullDrillState.findIndex( + (v) => v === drill + ); + const group = this.groupBy[drillIdx]; + + if (group) { + const dataToGroup = lastHistory() + ? lastHistory()[drill] + : countries; + const lastGroupedValue = groupBy( + dataToGroup, + group + ); + + this.groupedDataHistory.push(lastGroupedValue); + } + } + + // take last if we have all data grouped + if (this.groupBy.length === this.drillState.length) { + return lastHistory()[getLast(this.drillState)]; + } + + // get groping and transform to raw data format + return this.getGroupsWidgetData(lastHistory()); + }) + ) + .toPromise(); + } + + public ngOnDestroy(): void { + this.outputsSubject.complete(); + } + + // This method is expected to return all data needed for repeat/paginator/filterGroups in order to work. + // In case of custom filtering participants feel free to extend INovaFilteringOutputs. + protected getBackendData(filters: INovaFilters): Observable { + const mainRequest = this.apollo.watchQuery<{ countries: any }>({ + query: this.generateQuery(filters), + }); + + return mainRequest.valueChanges.pipe( + // mock delay + delay(300), + // data mapping, !DS specific! + map((res) => res.data.countries), + // adds mock icons to be displayed on leaf nodes !DS specific! + map((res: any[]) => + res.map((v) => ({ + ...v, + icon: "virtual-host", + icon_status: IconStatus.Up, + subregionName: + v.subregion?.name || "No Subregion Specified", + regionName: + v.subregion?.region?.name || "No Region Specified", + })) + ), + catchError((e) => { + this.logger.error(e); + return of({} as any); + }) + ); + } + + private generateQuery(filters: INovaFilters) { + const { search } = filters; + const searchValue = search?.value ? \\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\`^[\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\${search.value}]*\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\` : ""; + + const queryString = \\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\` + query { + countries(filter: {name: {regex: "\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\${searchValue}"} }) { + name + native + capital + languages { + name + } + currencies + subdivisions { + name + } + } + } + \\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\`; + + return gql\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\` + \\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\${queryString} + \\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\`; + } + + // Overrides default ServerSideDataSource.beforeApplyFilters implementation + // to save some filters that are used internally + // -- !DS specific + protected beforeApplyFilters(filters: INovaFilters): void { + this.busy.next(true); + + this.drillState = filters.drillstate?.value; + this.groupBy = filters.group?.value; + + if (this.isHome()) { + this.groupedDataHistory.length = 0; + } + + if (this.isBack()) { + this.groupedDataHistory.length = this.groupedDataHistory.length - 1; + } + + if (this.getFilters()["search"] && this.filterChanged("search")) { + this.groupedDataHistory.length = 0; + } + } + + private getGroupsWidgetData(groupByObj: Record) { + return Object.keys(groupByObj).map((property) => ({ + id: property, + label: property, + // statuses that will be displayed on group item + statuses: [ + { key: "virtual-host", value: groupByObj[property].length }, + { + key: "acknowledge", + value: this.getPopulation(groupByObj[property]), + }, + ], + })); + } + + private isHome(): boolean { + return this.drillState?.length === 0; + } + + private isBack(): boolean { + return ( + this.groupedDataHistory?.length > this.drillState?.length && + !this.isHome() + ); + } + + /** + * Gets population for the country(ies) + */ + private getPopulation(countries: any[]) { + const totalPopulation = countries.reduce( + (acc, next) => (acc += next.population), + 0 + ); + return \\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\`\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\${totalPopulation * Math.pow(10, -3)} k\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\`; + } +} + +@Component({ + selector: "drilldown-widget-example", + templateUrl: "./drilldown-widget-example.component.html", + styleUrls: ["./drilldown-widget-example.component.less"], + standalone: false, +}) +export class DrilldownWidgetExampleComponent implements OnInit { + // This variable will hold all the data needed to define the layout and behavior of the widgets. + // Pass this to the dashboard component's dashboard input in the template. + public dashboard: IDashboard | undefined; + + // Angular gridster requires a configuration object even if it's empty. + // Pass this to the dashboard component's gridsterConfig input in the template. + public gridsterConfig: GridsterConfig = {}; + + // Boolean passed as an input to the dashboard. When true, widgets can be moved, resized, removed, or edited + public editMode: boolean = false; + + constructor( + // WidgetTypesService provides the widget's necessary structure information + private widgetTypesService: WidgetTypesService, + private providerRegistry: ProviderRegistryService, + private changeDetectorRef: ChangeDetectorRef + ) {} + + public ngOnInit(): void { + // Registering the data source for injection into the KPI tile. + // Note: Each tile of a KPI widget is assigned its own instance of the data source + this.providerRegistry.setProviders({ + [DrilldownDataSource.providerId]: { + provide: DATA_SOURCE, + useClass: DrilldownDataSource, + // Any dependencies that need to be injected into the provider must be listed here + deps: [HttpClient], + }, + [DrilldownDataSourceRealApi.providerId]: { + provide: DATA_SOURCE, + useClass: DrilldownDataSourceRealApi, + // Any dependencies that need to be injected into the provider must be listed here + deps: [LoggerService, HttpClient, Apollo], + }, + }); + + this.initializeDashboard(); + const widgetTemplate = this.widgetTypesService.getWidgetType( + "drilldown", + 1 + ); + this.widgetTypesService.setNode( + widgetTemplate, + "configurator", + WellKnownPathKey.DataSourceProviders, + [ + DrilldownDataSourceRealApi.providerId, + DrilldownDataSource.providerId, + ] + ); + } + + public initializeDashboard(): void { + // We're using a static configuration object for this example, but this is where + // the widget's configuration could potentially be populated from a database + const drilldownWidget = widgetConfig; + const widgets: IWidgets = { + // Complete the widget with information coming from its type definition + [drilldownWidget.id]: + this.widgetTypesService.mergeWithWidgetType(drilldownWidget), + }; + + // Setting the widget dimensions and position (this is for gridster) + const positions: Record = { + [drilldownWidget.id]: { + cols: 10, + rows: 10, + y: 0, + x: 0, + }, + }; + + // Finally, assigning the variables we created above to the dashboard + this.dashboard = { positions, widgets }; + } + + public reInitializeDashboard(): void { + // destroys the components and their providers so the dashboard can re init data + this.dashboard = undefined; + this.changeDetectorRef.detectChanges(); + + const adapterProperties = + widgetConfig.pizzagna[PizzagnaLayer.Configuration].listWidget + .providers?.adapter?.properties; + + if (adapterProperties) { + adapterProperties.drillstate = []; + } + + this.initializeDashboard(); + } +} + +const widgetConfig: IWidget = { + id: "drilldown", + type: "drilldown", + pizzagna: { + [PizzagnaLayer.Configuration]: { + [DEFAULT_PIZZAGNA_ROOT]: { + providers: { + [WellKnownProviders.DataSource]: { + // Setting the data source providerId for the tile with id "kpi1" + providerId: DrilldownDataSourceRealApi.providerId, + properties: {}, + } as IProviderConfiguration, + }, + }, + header: { + properties: { + title: "Drilldown Widget", + subtitle: "Search is case sensitive!", + }, + }, + listWidget: { + providers: { + [WellKnownProviders.Adapter]: { + providerId: NOVA_DRILLDOWN_DATASOURCE_ADAPTER, + properties: { + // widget + navigationBarId: "navigationBar", + componentId: "listWidget", + dataPath: "data", + + // adapter props + drillstate: [], + groupBy: ["regionName", "subregionName"], + groups: ["regionName", "subregionName"], + + // components + componentsConfig: { + group: { + componentType: + ListGroupItemComponent.lateLoadKey, + properties: { + dataFieldIds: { + id: "id", + label: "label", + statuses: "statuses", + }, + }, + itemProperties: { + canNavigate: true, + }, + }, + leaf: { + componentType: + ListLeafItemComponent.lateLoadKey, + properties: { + dataFieldIds: { + icon: "icon", + status: "icon_status", + detailedUrl: "capital", + label: "name", + }, + }, + itemProperties: { + canNavigate: false, + }, + }, + } as IDrilldownComponentsConfiguration, + }, + }, + }, + properties: { + configuration: { + // FORMAT: + // componentType: ListLeafItemComponent.lateLoadKey, + // properties: { + // dataFieldIds: { + // icon: "", + // status: "code", + // detailedUrl: "capital", + // label: "name", + // }, + // }, + // + } as IListWidgetConfiguration, + }, + }, + }, + }, +}; + +const getLast = (arr: any[]) => arr[arr.length - 1]; +\\\\\\\\\\\\\\\`, + "widget-types/drilldown/drilldown-widget/mock-data-source.ts": \\\\\\\\\\\\\\\`// © 2022 SolarWinds Worldwide, LLC. All rights reserved. +// +// Permission is hereby granted, free of charge, to any person obtaining a copy +// of this software and associated documentation files (the "Software"), to +// deal in the Software without restriction, including without limitation the +// rights to use, copy, modify, merge, publish, distribute, sublicense, and/or +// sell copies of the Software, and to permit persons to whom the Software is +// furnished to do so, subject to the following conditions: +// +// The above copyright notice and this permission notice shall be included in +// all copies or substantial portions of the Software. +// +// THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR +// IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, +// FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE +// AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER +// LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, +// OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN +// THE SOFTWARE. + +import { Injectable, OnDestroy } from "@angular/core"; +import groupBy from "lodash/groupBy"; +import { BehaviorSubject, Observable, of, Subject } from "rxjs"; +import { + catchError, + delay, + finalize, + map, + // eslint-disable-next-line import/no-deprecated + switchMap, + tap, +} from "rxjs/operators"; + +import { + DataSourceService, + IDataField, + IDataSource, + IFilters, + INovaFilters, +} from "@nova-ui/bits"; + +import { GRAPH_DATA_MOCK } from "./data-mock"; + +/** + * A simple KPI data source to retrieve the average rating of Harry Potter and the Sorcerer's Stone (book) via googleapis + */ +@Injectable() +export class DrilldownDataSource + extends DataSourceService + implements IDataSource, OnDestroy +{ + // This is the ID we'll use to identify the provider + public static providerId = "DrilldownDataSource"; + + // Use this subject to communicate the data source's busy state + public busy = new BehaviorSubject(false); + public dataFields: Partial[] = [ + { id: "continent.name", label: "Continent name" }, + { id: "currency", label: "Currency" }, + ]; + + private drillState: string[] = []; + private groupBy: string[]; + private cache: any; + private applyFilters$ = new Subject(); + + constructor() { + super(); + + // TODO: remove Partial in vNext after marking dataType field as optional - NUI-5838 + ( + this.dataFieldsConfig.dataFields$ as BehaviorSubject< + Partial[] + > + ).next(this.dataFields); + + this.applyFilters$ + // eslint-disable-next-line import/no-deprecated + .pipe(switchMap((filters) => this.getData(filters))) + .subscribe(async (res) => { + this.outputsSubject.next(await this.getFilteredData(res)); + }); + } + + private groupedDataHistory: any[] = []; + + // In this example, getFilteredData is invoked every 10 minutes (Take a look at the refresher + // provider definition in the widget configuration below to see how the interval is set) + public async getFilteredData(data: any): Promise { + return of(data) + .pipe( + map((countries) => { + const widgetInput = this.getOutput(countries); + + if (this.isDrillDown()) { + const activeDrillLvl = this.drillState.length; + const group = this.groupBy[activeDrillLvl]; + const [lastGroupedValue, groupedData] = + this.getTransformedDataForGroup(widgetInput, group); + + this.groupedDataHistory.push(lastGroupedValue); + + return groupedData; + } + + return widgetInput; + }) + ) + .toPromise(); + } + + public ngOnDestroy(): void { + this.outputsSubject.complete(); + } + + // redefine parent method + public async applyFilters(): Promise { + this.applyFilters$.next(this.getFilters()); + } + + private getData(filters: INovaFilters): Observable { + this.drillState = filters.drillstate?.value; + this.groupBy = filters.group?.value; + + this.busy.next(true); + + return of(this.cache || GRAPH_DATA_MOCK).pipe( + delay(1000), + tap((data) => (this.cache = data)), + map((data) => data.data.countries), + catchError((e) => of([])), + finalize(() => this.busy.next(false)) + ); + } + + private getTransformedDataForGroup(data: any, groupName: string) { + const groupedDict = groupBy(data, groupName); + const dataArr = Object.keys(groupedDict).map((property) => ({ + id: property, + label: property, + // TODO: apply groups mapping here + statuses: [ + { key: "state_ok", value: groupedDict[property].length }, + { + key: "status_unreachable", + value: generateNumberUpTo(100000), + }, + { key: "status_warning", value: generateNumberUpTo(10000) }, + { key: "status_unknown", value: generateNumberUpTo(1000) }, + ], + })); + + return [groupedDict, dataArr]; + } + + private isHome(): boolean { + return !this.drillState || this.drillState.length === 0; + } + + private isBack(): boolean { + return ( + this.groupedDataHistory.length > this.drillState?.length && + !this.isHome() + ); + } + + private isDrillDown(): boolean { + return this.drillState?.length !== this.groupBy?.length; + } + + private getOutput(data: any) { + if (this.isHome()) { + this.groupedDataHistory.length = 0; + } + + if (this.isBack()) { + this.groupedDataHistory.length = this.groupedDataHistory.length - 1; + } + + const lastHistoryValue = getLast(this.groupedDataHistory); + + if (!lastHistoryValue) { + return data; + } + + return lastHistoryValue[getLast(this.drillState)] || lastHistoryValue; + } +} + +const getLast = (arr: any[]) => arr[arr.length - 1]; +const generateNumberUpTo = (upperLimit: number): number => + Math.floor(Math.random() * upperLimit + 1); +\\\\\\\\\\\\\\\`, + "widget-types/drilldown/drilldown-widget-docs.component.ts": \\\\\\\\\\\\\\\`// © 2022 SolarWinds Worldwide, LLC. All rights reserved. +// +// Permission is hereby granted, free of charge, to any person obtaining a copy +// of this software and associated documentation files (the "Software"), to +// deal in the Software without restriction, including without limitation the +// rights to use, copy, modify, merge, publish, distribute, sublicense, and/or +// sell copies of the Software, and to permit persons to whom the Software is +// furnished to do so, subject to the following conditions: +// +// The above copyright notice and this permission notice shall be included in +// all copies or substantial portions of the Software. +// +// THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR +// IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, +// FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE +// AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER +// LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, +// OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN +// THE SOFTWARE. + +import { Component, OnInit } from "@angular/core"; + +import { mapContentFile } from "../../../../demo-files-factory"; + +@Component({ + selector: "nui-drilldown-docs", + templateUrl: "./drilldown-widget-docs.component.html", + standalone: false, +}) +export class DrilldownDocsComponent implements OnInit { + public widgetFileText = ""; + public configuratorFileText = ""; + + public predefinedGroping = \\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\` +listWidget: { + providers: { + [WellKnownProviders.Adapter]: { + providerId: NOVA_DRILLDOWN_DATASOURCE_ADAPTER, + properties: { + ... + // adapter props + drillstate: [], + groupBy: ["regionName", "subregionName"], + groups: ["regionName", "subregionName"], + ... + }, + }, + }, +}, +\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\`; + public featuredDeclaredText = \\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\` + private supportedFeatures: IDataSourceFeatures = { + search: { enabled: true }, + };\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\`; + public featuresUsedText = \\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\` + this.features = new DataSourceFeatures(this.supportedFeatures); + \\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\`; + + public async ngOnInit(): Promise { + this.widgetFileText = await import( + "./../../../../../../src/lib/widget-types/drilldown/drilldown-widget" + ).then(mapContentFile); + this.configuratorFileText = await import( + "./../../../../../../src/lib/widget-types/drilldown/drilldown-configurator" + ).then(mapContentFile); + } +} +\\\\\\\\\\\\\\\`, + "widget-types/drilldown/drilldown-widget-docs.module.ts": \\\\\\\\\\\\\\\`// © 2022 SolarWinds Worldwide, LLC. All rights reserved. +// +// Permission is hereby granted, free of charge, to any person obtaining a copy +// of this software and associated documentation files (the "Software"), to +// deal in the Software without restriction, including without limitation the +// rights to use, copy, modify, merge, publish, distribute, sublicense, and/or +// sell copies of the Software, and to permit persons to whom the Software is +// furnished to do so, subject to the following conditions: +// +// The above copyright notice and this permission notice shall be included in +// all copies or substantial portions of the Software. +// +// THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR +// IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, +// FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE +// AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER +// LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, +// OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN +// THE SOFTWARE. + +import { NgModule } from "@angular/core"; +import { RouterModule, Routes } from "@angular/router"; + +// eslint-disable-next-line max-len +import { + NuiButtonModule, + NuiDocsModule, + NuiMessageModule, + NuiSwitchModule, + DEMO_PATH_TOKEN, +} from "@nova-ui/bits"; +import { NuiDashboardsModule } from "@nova-ui/dashboards"; + +import { DrilldownMultiRequestWidgetExampleComponent } from "./drilldown-multi-request-widget/drilldown-multi-request-widget-example.component"; +import { DrilldownWidgetExampleComponent } from "./drilldown-widget/drilldown-widget-example.component"; +import { DrilldownDocsComponent } from "./drilldown-widget-docs.component"; +import { getDemoFiles } from "../../../../demo-files-factory"; + +const routes: Routes = [ + { + path: "", + component: DrilldownDocsComponent, + data: { + srlc: { + hideIndicator: true, + }, + }, + }, + { + path: "example", + component: DrilldownWidgetExampleComponent, + data: { + srlc: { + hideIndicator: true, + }, + }, + }, + { + path: "multiple-requests", + component: DrilldownMultiRequestWidgetExampleComponent, + data: { + srlc: { + hideIndicator: true, + }, + }, + }, +]; + +@NgModule({ + imports: [ + RouterModule.forChild(routes), + NuiButtonModule, + NuiDocsModule, + NuiMessageModule, + NuiDashboardsModule, + NuiSwitchModule, + ], + declarations: [ + DrilldownDocsComponent, + DrilldownWidgetExampleComponent, + DrilldownMultiRequestWidgetExampleComponent, + ], + providers: [ + { + provide: DEMO_PATH_TOKEN, + useValue: getDemoFiles("drilldown"), + }, + ], +}) +export default class DrilldownDocsModule {} +\\\\\\\\\\\\\\\`, + "widget-types/embedded-content/embedded-content-docs.component.ts": \\\\\\\\\\\\\\\`// © 2022 SolarWinds Worldwide, LLC. All rights reserved. +// +// Permission is hereby granted, free of charge, to any person obtaining a copy +// of this software and associated documentation files (the "Software"), to +// deal in the Software without restriction, including without limitation the +// rights to use, copy, modify, merge, publish, distribute, sublicense, and/or +// sell copies of the Software, and to permit persons to whom the Software is +// furnished to do so, subject to the following conditions: +// +// The above copyright notice and this permission notice shall be included in +// all copies or substantial portions of the Software. +// +// THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR +// IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, +// FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE +// AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER +// LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, +// OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN +// THE SOFTWARE. + +import { Component, OnInit } from "@angular/core"; + +import { mapContentFile } from "../../../../demo-files-factory"; + +@Component({ + selector: "nui-embedded-content-docs", + templateUrl: "./embedded-content-docs.component.html", + standalone: false, +}) +export class EmbeddedContentDocsComponent implements OnInit { + public embeddedContentWidgetFileText = ""; + public embeddedContentConfiguratorFileText = ""; + + public async ngOnInit(): Promise { + this.embeddedContentWidgetFileText = await import( + "./../../../../../../src/lib/widget-types/embedded-content/embedded-content-widget" + ).then(mapContentFile); + this.embeddedContentWidgetFileText = await import( + "./../../../../../../src/lib/widget-types/embedded-content/embedded-content-configurator" + ).then(mapContentFile); + } +} +\\\\\\\\\\\\\\\`, + "widget-types/embedded-content/embedded-content-docs.module.ts": \\\\\\\\\\\\\\\`// © 2022 SolarWinds Worldwide, LLC. All rights reserved. +// +// Permission is hereby granted, free of charge, to any person obtaining a copy +// of this software and associated documentation files (the "Software"), to +// deal in the Software without restriction, including without limitation the +// rights to use, copy, modify, merge, publish, distribute, sublicense, and/or +// sell copies of the Software, and to permit persons to whom the Software is +// furnished to do so, subject to the following conditions: +// +// The above copyright notice and this permission notice shall be included in +// all copies or substantial portions of the Software. +// +// THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR +// IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, +// FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE +// AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER +// LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, +// OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN +// THE SOFTWARE. + +import { NgModule } from "@angular/core"; +import { RouterModule, Routes } from "@angular/router"; + +// eslint-disable-next-line max-len +import { + NuiButtonModule, + NuiDocsModule, + NuiMessageModule, + NuiSwitchModule, + DEMO_PATH_TOKEN, +} from "@nova-ui/bits"; +import { NuiDashboardsModule } from "@nova-ui/dashboards"; + +import { EmbeddedContentDocsComponent } from "./embedded-content-docs.component"; +import { EmbeddedContentWidgetExampleComponent } from "./embedded-content-widget-example/embedded-content-widget-example.component"; +import { getDemoFiles } from "../../../../demo-files-factory"; + +const routes: Routes = [ + { + path: "", + component: EmbeddedContentDocsComponent, + data: { + srlc: { + hideIndicator: true, + }, + }, + }, + { + path: "example", + component: EmbeddedContentWidgetExampleComponent, + data: { + srlc: { + hideIndicator: true, + }, + }, + }, +]; + +@NgModule({ + imports: [ + RouterModule.forChild(routes), + NuiButtonModule, + NuiDocsModule, + NuiMessageModule, + NuiDashboardsModule, + NuiSwitchModule, + ], + declarations: [ + EmbeddedContentDocsComponent, + EmbeddedContentWidgetExampleComponent, + ], + providers: [ + { + provide: DEMO_PATH_TOKEN, + useValue: getDemoFiles("embedded-content"), + }, + ], +}) +export default class EmbeddedContentDocsModule {} +\\\\\\\\\\\\\\\`, + "widget-types/embedded-content/embedded-content-widget-example/embedded-content-widget-example.component.ts": \\\\\\\\\\\\\\\`// © 2022 SolarWinds Worldwide, LLC. All rights reserved. +// +// Permission is hereby granted, free of charge, to any person obtaining a copy +// of this software and associated documentation files (the "Software"), to +// deal in the Software without restriction, including without limitation the +// rights to use, copy, modify, merge, publish, distribute, sublicense, and/or +// sell copies of the Software, and to permit persons to whom the Software is +// furnished to do so, subject to the following conditions: +// +// The above copyright notice and this permission notice shall be included in +// all copies or substantial portions of the Software. +// +// THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR +// IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, +// FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE +// AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER +// LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, +// OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN +// THE SOFTWARE. + +import { ChangeDetectorRef, Component, OnInit } from "@angular/core"; +import { GridsterConfig, GridsterItem } from "angular-gridster2"; + +import { + ComponentRegistryService, + EmbeddedContentComponent, + EmbeddedContentConfigurationComponent, + EmbeddedContentMode, + IDashboard, + IWidget, + IWidgets, + PizzagnaLayer, + WidgetTypesService, +} from "@nova-ui/dashboards"; + +@Component({ + selector: "embedded-content-widget-example", + templateUrl: "./embedded-content-widget-example.component.html", + styleUrls: ["./embedded-content-widget-example.component.less"], + standalone: false, +}) +export class EmbeddedContentWidgetExampleComponent implements OnInit { + // This variable will hold all the data needed to define the layout and behavior of the widgets. + // Pass this to the dashboard component's dashboard input in the template. + public dashboard: IDashboard | undefined; + + // Angular gridster requires a configuration object even if it's empty. + // Pass this to the dashboard component's gridsterConfig input in the template. + public gridsterConfig: GridsterConfig = {}; + + // Boolean passed as an input to the dashboard. When true, widgets can be moved, resized, removed, or edited + public editMode: boolean = false; + + constructor( + // WidgetTypesService provides the widget's necessary structure information + private widgetTypesService: WidgetTypesService, + + private componentRegistry: ComponentRegistryService, + private changeDetectorRef: ChangeDetectorRef + ) {} + + public ngOnInit(): void { + this.prepareNovaDashboards(); + this.initializeDashboard(); + } + + /** Used for restoring widgets state */ + public reInitializeDashboard(): void { + // destroys the components and their providers so the dashboard can re init data + this.dashboard = undefined; + this.changeDetectorRef.detectChanges(); + + this.initializeDashboard(); + } + + public initializeDashboard(): void { + // We're using a static configuration object for this example, but this is where + // the widget's configuration could potentially be populated from a database + const embeddedContentWidget = widgetConfig; + const widgets: IWidgets = { + // Complete the widget with information coming from its type definition + [embeddedContentWidget.id]: + this.widgetTypesService.mergeWithWidgetType( + embeddedContentWidget + ), + }; + + // Setting the widget dimensions and position (this is for gridster) + const positions: Record = { + [embeddedContentWidget.id]: { + cols: 10, + rows: 10, + y: 0, + x: 0, + }, + }; + + // Finally, assigning the variables we created above to the dashboard + this.dashboard = { positions, widgets }; + } + + private prepareNovaDashboards() { + this.componentRegistry.registerByLateLoadKey(EmbeddedContentComponent); + this.componentRegistry.registerByLateLoadKey( + EmbeddedContentConfigurationComponent + ); + } +} + +const widgetConfig: IWidget = { + id: "embeddedContentWidgetId", + type: "embedded-content", + pizzagna: { + [PizzagnaLayer.Configuration]: { + header: { + properties: { + title: "Embedded Content Widget", + subtitle: "", + }, + }, + mainContent: { + properties: { + sanitized: true, + mode: EmbeddedContentMode.URL, + customEmbeddedContent: "https://www.ventusky.com/", + }, + }, + }, + }, +}; +\\\\\\\\\\\\\\\`, + "widget-types/kpi/kpi-docs.component.ts": \\\\\\\\\\\\\\\`// © 2022 SolarWinds Worldwide, LLC. All rights reserved. +// +// Permission is hereby granted, free of charge, to any person obtaining a copy +// of this software and associated documentation files (the "Software"), to +// deal in the Software without restriction, including without limitation the +// rights to use, copy, modify, merge, publish, distribute, sublicense, and/or +// sell copies of the Software, and to permit persons to whom the Software is +// furnished to do so, subject to the following conditions: +// +// The above copyright notice and this permission notice shall be included in +// all copies or substantial portions of the Software. +// +// THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR +// IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, +// FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE +// AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER +// LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, +// OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN +// THE SOFTWARE. + +import { Component, OnInit } from "@angular/core"; + +import { mapContentFile } from "../../../../demo-files-factory"; + +@Component({ + selector: "nui-kpi-docs", + templateUrl: "./kpi-docs.component.html", + standalone: false, +}) +export class KpiDocsComponent implements OnInit { + public kpiWidgetFileText = ""; + public kpiConfiguratorFileText = ""; + + public async ngOnInit(): Promise { + this.kpiWidgetFileText = await import( + "./../../../../../../src/lib/widget-types/kpi/kpi-widget" + ).then(mapContentFile); + this.kpiConfiguratorFileText = await import( + "./../../../../../../src/lib/widget-types/kpi/kpi-configurator" + ).then(mapContentFile); + } +} +\\\\\\\\\\\\\\\`, + "widget-types/kpi/kpi-docs.module.ts": \\\\\\\\\\\\\\\`// © 2022 SolarWinds Worldwide, LLC. All rights reserved. +// +// Permission is hereby granted, free of charge, to any person obtaining a copy +// of this software and associated documentation files (the "Software"), to +// deal in the Software without restriction, including without limitation the +// rights to use, copy, modify, merge, publish, distribute, sublicense, and/or +// sell copies of the Software, and to permit persons to whom the Software is +// furnished to do so, subject to the following conditions: +// +// The above copyright notice and this permission notice shall be included in +// all copies or substantial portions of the Software. +// +// THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR +// IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, +// FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE +// AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER +// LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, +// OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN +// THE SOFTWARE. + +import { NgModule } from "@angular/core"; +import { RouterModule, Routes } from "@angular/router"; + +import { + NuiButtonModule, + NuiDocsModule, + NuiMessageModule, + NuiSwitchModule, + DEMO_PATH_TOKEN, +} from "@nova-ui/bits"; +import { + KpiColorComparatorsRegistryService, + NuiDashboardsModule, +} from "@nova-ui/dashboards"; + +import { KpiDocsComponent } from "./kpi-docs.component"; +import { KpiSyncBrokerExampleComponent } from "./kpi-sync-broker/kpi-sync-broker-example.component"; +import { KpiSyncBrokerDocsComponent } from "./kpi-sync-broker-docs.component"; +import { KpiSyncBrokerForAllTilesExampleComponent } from "./kpi-sync-broker-for-all-tiles/kpi-sync-broker-for-all-tiles-example.component"; +import { KpiWidgetExampleComponent } from "./kpi-widget/kpi-widget-example.component"; +import { KpiWidgetBackgroundColorExampleComponent } from "./kpi-widget-background-color/kpi-widget-background-color-example.component"; +import { KpiWidgetBackgroundColorDocsComponent } from "./kpi-widget-background-color-docs.component"; +import { KpiWidgetInteractiveExampleComponent } from "./kpi-widget-interactive/kpi-widget-interactive-example.component"; +import { getDemoFiles } from "../../../../demo-files-factory"; + +const routes: Routes = [ + { + path: "", + component: KpiDocsComponent, + data: { + srlc: { + hideIndicator: true, + }, + showThemeSwitcher: true, + }, + }, + { + path: "example", + component: KpiWidgetExampleComponent, + data: { + srlc: { + hideIndicator: true, + }, + }, + }, + { + path: "background-color", + component: KpiWidgetBackgroundColorDocsComponent, + data: { + srlc: { + hideIndicator: true, + }, + }, + }, + { + path: "sync-broker", + component: KpiSyncBrokerDocsComponent, + data: { + srlc: { + hideIndicator: true, + }, + }, + }, +]; + +@NgModule({ + imports: [ + RouterModule.forChild(routes), + NuiButtonModule, + NuiDocsModule, + NuiMessageModule, + NuiDashboardsModule, + NuiSwitchModule, + ], + declarations: [ + KpiDocsComponent, + KpiWidgetExampleComponent, + KpiWidgetInteractiveExampleComponent, + KpiWidgetBackgroundColorDocsComponent, + KpiWidgetBackgroundColorExampleComponent, + KpiSyncBrokerDocsComponent, + KpiSyncBrokerExampleComponent, + KpiSyncBrokerForAllTilesExampleComponent, + ], + providers: [ + KpiColorComparatorsRegistryService, + { + provide: DEMO_PATH_TOKEN, + useValue: getDemoFiles("kpi"), + }, + ], +}) +export default class KpiDocsModule { + constructor( + private comparatorsRegistry: KpiColorComparatorsRegistryService + ) { + this.backgroundColorDocsSetup(); + } + + private backgroundColorDocsSetup() { + this.comparatorsRegistry.registerComparators({ + "!=": { + comparatorFn: (actual: any, reference: any) => + // eslint-disable-next-line eqeqeq + actual != reference, + label: "Not equal", + }, + }); + } +} +\\\\\\\\\\\\\\\`, + "widget-types/kpi/kpi-sync-broker/kpi-sync-broker-example.component.ts": \\\\\\\\\\\\\\\`// © 2022 SolarWinds Worldwide, LLC. All rights reserved. +// +// Permission is hereby granted, free of charge, to any person obtaining a copy +// of this software and associated documentation files (the "Software"), to +// deal in the Software without restriction, including without limitation the +// rights to use, copy, modify, merge, publish, distribute, sublicense, and/or +// sell copies of the Software, and to permit persons to whom the Software is +// furnished to do so, subject to the following conditions: +// +// The above copyright notice and this permission notice shall be included in +// all copies or substantial portions of the Software. +// +// THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR +// IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, +// FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE +// AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER +// LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, +// OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN +// THE SOFTWARE. + +import { HttpClient, HttpErrorResponse } from "@angular/common/http"; +import { + ChangeDetectorRef, + Component, + Injectable, + OnDestroy, + OnInit, +} from "@angular/core"; +import { GridsterConfig, GridsterItem } from "angular-gridster2"; +import keyBy from "lodash/keyBy"; +import { BehaviorSubject, of } from "rxjs"; +import { delay, finalize, take } from "rxjs/operators"; + +import { DataSourceService, IFilteringOutputs } from "@nova-ui/bits"; +import { + DATA_SOURCE, + IDashboard, + IKpiData, + IProviderConfiguration, + IWidget, + KpiComponent, + NOVA_KPI_DATASOURCE_ADAPTER, + NOVA_KPI_SCALE_SYNC_BROKER, + PizzagnaLayer, + ProviderRegistryService, + WellKnownPathKey, + WellKnownProviders, + WidgetTypesService, +} from "@nova-ui/dashboards"; + +/** + * A simple KPI data source to retrieve the average rating of Harry Potter and the Sorcerer's Stone (book) via googleapis + */ +@Injectable() +export class AverageRatingKpiDataSource + extends DataSourceService + implements OnDestroy +{ + public static providerId = "AverageRatingKpiDataSource"; + public busy = new BehaviorSubject(false); + + constructor(private http: HttpClient) { + super(); + } + + public async getFilteredData(): Promise { + this.busy.next(true); + return new Promise((resolve) => { + // *** Make a resource request to an external API (if needed) + this.http + .get("https://www.googleapis.com/books/v1/volumes/5MQFrgEACAAJ") + .pipe(finalize(() => this.busy.next(false))) + .subscribe({ + next: (data: any) => { + resolve({ + result: { + value: data.volumeInfo.averageRating, + }, + }); + }, + error: (error: HttpErrorResponse) => { + resolve({ + result: null, + error: { + type: error.status, + }, + }); + }, + }); + }); + } + + public ngOnDestroy(): void { + this.outputsSubject.complete(); + } +} + +/** + * A simple KPI data source to retrieve the ratings count of Harry Potter and the Sorcerer's Stone (book) via googleapis + */ +@Injectable() +export class RatingsCountKpiDataSource + extends DataSourceService + implements OnDestroy +{ + public static providerId = "RatingsCountKpiDataSource"; + + // Use this subject to communicate the data source's busy state + public busy = new BehaviorSubject(false); + + constructor(private http: HttpClient) { + super(); + } + + public async getFilteredData(): Promise { + this.busy.next(true); + return new Promise((resolve) => { + this.http + .get("https://www.googleapis.com/books/v1/volumes/5MQFrgEACAAJ") + .pipe( + delay(2000), + finalize(() => this.busy.next(false)) + ) + .subscribe({ + next: (data: any) => { + resolve({ + result: { + value: data.volumeInfo.ratingsCount, + }, + }); + }, + error: (error: HttpErrorResponse) => { + resolve({ + result: null, + error: { + type: error.status, + }, + }); + }, + }); + }); + } + + public ngOnDestroy(): void { + this.outputsSubject.complete(); + } +} +/** + * A simple KPI data source to retrieve the ratings count of Harry Potter and the Sorcerer's Stone (book) via googleapis + */ +@Injectable() +export class MockKpiDataSource + extends DataSourceService + implements OnDestroy +{ + public static providerId = "MockKpiDataSource"; + + // Use this subject to communicate the data source's busy state + public busy = new BehaviorSubject(false); + + constructor() { + super(); + } + + public async getFilteredData(): Promise { + this.busy.next(true); + return new Promise((resolve) => { + of(3381342) + .pipe( + delay(5000), + take(1), + finalize(() => this.busy.next(false)) + ) + .subscribe({ + next: (data: any) => { + resolve({ + result: { + value: data, + }, + }); + }, + }); + }); + } + + public ngOnDestroy(): void { + this.outputsSubject.complete(); + } +} + +/** + * A component that instantiates the dashboard + */ +@Component({ + selector: "kpi-sync-broker-example", + templateUrl: "./kpi-sync-broker-example.component.html", + styleUrls: ["./kpi-sync-broker-example.component.less"], + standalone: false, +}) +export class KpiSyncBrokerExampleComponent implements OnInit { + public dashboard: IDashboard | undefined; + public gridsterConfig: GridsterConfig = {}; + public editMode: boolean = false; + + constructor( + private widgetTypesService: WidgetTypesService, + private providerRegistry: ProviderRegistryService, + private changeDetectorRef: ChangeDetectorRef + ) {} + + public ngOnInit(): void { + this.setupDashboard(); + + this.initializeDashboard(); + } + + private setupDashboard() { + const widgetTemplate = this.widgetTypesService.getWidgetType("kpi", 1); + + this.widgetTypesService.setNode( + widgetTemplate, + "configurator", + WellKnownPathKey.DataSourceProviders, + [ + AverageRatingKpiDataSource.providerId, + RatingsCountKpiDataSource.providerId, + MockKpiDataSource.providerId, + ] + ); + + this.providerRegistry.setProviders({ + [AverageRatingKpiDataSource.providerId]: { + provide: DATA_SOURCE, + useClass: AverageRatingKpiDataSource, + deps: [HttpClient], + }, + [RatingsCountKpiDataSource.providerId]: { + provide: DATA_SOURCE, + useClass: RatingsCountKpiDataSource, + deps: [HttpClient], + }, + [MockKpiDataSource.providerId]: { + provide: DATA_SOURCE, + useClass: MockKpiDataSource, + deps: [], + }, + }); + } + + /** Used for restoring widgets state */ + public reInitializeDashboard(): void { + // destroys the components and their providers so the dashboard can re init data + this.dashboard = undefined; + this.changeDetectorRef.detectChanges(); + + this.initializeDashboard(); + } + + private initializeDashboard(): void { + const widgetsWithStructure = widgetsConfig.map((w) => + this.widgetTypesService.mergeWithWidgetType(w) + ); + const widgetsIndex = keyBy(widgetsWithStructure, (w: IWidget) => w.id); + + const positions: Record = { + kpiWidgetId: { + cols: 3, + rows: 6, + y: 0, + x: 0, + }, + kpiWidgetId2: { + cols: 3, + rows: 6, + y: 0, + x: 0, + }, + }; + + this.dashboard = { + positions, + widgets: widgetsIndex, + }; + } +} + +const widgetsConfig: IWidget[] = [ + { + id: "kpiWidgetId", + type: "kpi", + pizzagna: { + [PizzagnaLayer.Configuration]: { + header: { + properties: { + title: "NO Sync Broker", + subtitle: "Values sizes are being not synced", + }, + }, + tiles: { + properties: { + nodes: ["kpi1", "kpi2", "kpi3"], + }, + }, + kpi1: { + id: "kpi1", + componentType: KpiComponent.lateLoadKey, + properties: { + widgetData: { + units: \\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\`out of 5 Stars\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\`, + label: \\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\`Average Rating\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\`, + backgroundColor: "lightpink", + }, + }, + providers: { + [WellKnownProviders.DataSource]: { + providerId: AverageRatingKpiDataSource.providerId, + } as IProviderConfiguration, + [WellKnownProviders.Adapter]: { + providerId: NOVA_KPI_DATASOURCE_ADAPTER, + properties: { + componentId: "kpi1", + propertyPath: "widgetData", + }, + } as IProviderConfiguration, + }, + }, + kpi2: { + id: "kpi2", + componentType: KpiComponent.lateLoadKey, + properties: { + widgetData: { + label: \\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\`Another label which might be a pretty long one\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\`, + units: \\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\`Which comes from somewhere\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\`, + backgroundColor: "skyblue", + }, + }, + providers: { + [WellKnownProviders.DataSource]: { + providerId: RatingsCountKpiDataSource.providerId, + } as IProviderConfiguration, + [WellKnownProviders.Adapter]: { + providerId: NOVA_KPI_DATASOURCE_ADAPTER, + properties: { + componentId: "kpi2", + propertyPath: "widgetData", + }, + } as IProviderConfiguration, + }, + }, + kpi3: { + id: "kpi3", + componentType: KpiComponent.lateLoadKey, + properties: { + widgetData: { + label: \\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\`Random\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\`, + units: \\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\`Data\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\`, + }, + }, + providers: { + [WellKnownProviders.DataSource]: { + providerId: MockKpiDataSource.providerId, + } as IProviderConfiguration, + [WellKnownProviders.Adapter]: { + providerId: NOVA_KPI_DATASOURCE_ADAPTER, + properties: { + componentId: "kpi3", + propertyPath: "widgetData", + }, + } as IProviderConfiguration, + }, + }, + }, + }, + }, + { + id: "kpiWidgetId2", + type: "kpi", + pizzagna: { + [PizzagnaLayer.Configuration]: { + header: { + properties: { + title: "WITH Sync Broker", + subtitle: + "Now the values of label, units, and value are being synced", + }, + }, + tiles: { + properties: { + nodes: ["kpi4", "kpi5", "kpi6"], + }, + providers: { + // This is where and how you set the sync broker provider + kpiScaleSyncBroker: { + providerId: NOVA_KPI_SCALE_SYNC_BROKER, + properties: { + scaleSyncConfig: [ + // You can decide which values to keep in sync. For instance, you can leave only 'label' id in the array below + { id: "value" }, + { id: "label" }, + { id: "units" }, + ], + }, + }, + }, + }, + kpi4: { + id: "kpi4", + componentType: KpiComponent.lateLoadKey, + properties: { + widgetData: { + units: \\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\`out of 5 Stars\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\`, + label: \\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\`Average Rating\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\`, + backgroundColor: "lightpink", + }, + }, + providers: { + [WellKnownProviders.DataSource]: { + providerId: AverageRatingKpiDataSource.providerId, + } as IProviderConfiguration, + [WellKnownProviders.Adapter]: { + providerId: NOVA_KPI_DATASOURCE_ADAPTER, + properties: { + componentId: "kpi4", + propertyPath: "widgetData", + }, + } as IProviderConfiguration, + }, + }, + kpi5: { + id: "kpi5", + componentType: KpiComponent.lateLoadKey, + properties: { + widgetData: { + label: \\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\`Another label which might be a pretty long one\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\`, + units: \\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\`Which comes from somewhere\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\`, + backgroundColor: "skyblue", + }, + }, + providers: { + [WellKnownProviders.DataSource]: { + providerId: RatingsCountKpiDataSource.providerId, + } as IProviderConfiguration, + [WellKnownProviders.Adapter]: { + providerId: NOVA_KPI_DATASOURCE_ADAPTER, + properties: { + componentId: "kpi5", + propertyPath: "widgetData", + }, + } as IProviderConfiguration, + }, + }, + kpi6: { + id: "kpi6", + componentType: KpiComponent.lateLoadKey, + properties: { + widgetData: { + label: \\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\`Random\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\`, + units: \\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\`Data\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\`, + }, + }, + providers: { + [WellKnownProviders.DataSource]: { + providerId: MockKpiDataSource.providerId, + } as IProviderConfiguration, + [WellKnownProviders.Adapter]: { + providerId: NOVA_KPI_DATASOURCE_ADAPTER, + properties: { + componentId: "kpi6", + propertyPath: "widgetData", + }, + } as IProviderConfiguration, + }, + }, + }, + }, + }, +]; +\\\\\\\\\\\\\\\`, + "widget-types/kpi/kpi-sync-broker-docs.component.ts": \\\\\\\\\\\\\\\`// © 2022 SolarWinds Worldwide, LLC. All rights reserved. +// +// Permission is hereby granted, free of charge, to any person obtaining a copy +// of this software and associated documentation files (the "Software"), to +// deal in the Software without restriction, including without limitation the +// rights to use, copy, modify, merge, publish, distribute, sublicense, and/or +// sell copies of the Software, and to permit persons to whom the Software is +// furnished to do so, subject to the following conditions: +// +// The above copyright notice and this permission notice shall be included in +// all copies or substantial portions of the Software. +// +// THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR +// IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, +// FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE +// AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER +// LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, +// OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN +// THE SOFTWARE. + +import { Component } from "@angular/core"; + +@Component({ + selector: "kpi-sync-broker-docs", + templateUrl: "./kpi-sync-broker-docs.component.html", + standalone: false, +}) +export class KpiSyncBrokerDocsComponent { + public kpiScaleSyncBroker = \\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\` +"tiles": { + "providers": { + kpiScaleSyncBroker: { + providerId: NOVA_KPI_SCALE_SYNC_BROKER, + properties: { + scaleSyncConfig: [ + { id: "value" }, + { id: "label" }, + { id: "units" }, + ], + }, + }, + }, +}, +\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\`; + + public defineScaleBrokerOnDashboardSetup = \\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\` +// To add the sync broker globally to all the kpi tiles you may start with setting up the broker config +// Here you define which values to keep in sync +const brokerConfig = { + providerId: NOVA_KPI_SCALE_SYNC_BROKER, + properties: { + scaleSyncConfig: [ + { id: "value" }, + { id: "label" }, + { id: "units" }, + ], + }, + }; + +// And here is how you set the sync broker for every KPI widget in the dashboard. +// Later, you will be able to override this setting for each separate KPI widget in the configuration (just like it is shown in the third +// width of the example with the 'kpiWidgetId3') +this.widgetTypesService.setNode( + widgetTemplate, + "widget", + "tiles.providers.kpiScaleSyncBroker", + brokerConfig +); +\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\`; +} +\\\\\\\\\\\\\\\`, + "widget-types/kpi/kpi-sync-broker-for-all-tiles/kpi-sync-broker-for-all-tiles-example.component.ts": \\\\\\\\\\\\\\\`// © 2022 SolarWinds Worldwide, LLC. All rights reserved. +// +// Permission is hereby granted, free of charge, to any person obtaining a copy +// of this software and associated documentation files (the "Software"), to +// deal in the Software without restriction, including without limitation the +// rights to use, copy, modify, merge, publish, distribute, sublicense, and/or +// sell copies of the Software, and to permit persons to whom the Software is +// furnished to do so, subject to the following conditions: +// +// The above copyright notice and this permission notice shall be included in +// all copies or substantial portions of the Software. +// +// THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR +// IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, +// FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE +// AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER +// LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, +// OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN +// THE SOFTWARE. + +import { HttpClient, HttpErrorResponse } from "@angular/common/http"; +import { + ChangeDetectorRef, + Component, + Injectable, + OnDestroy, + OnInit, +} from "@angular/core"; +import { GridsterConfig, GridsterItem } from "angular-gridster2"; +import keyBy from "lodash/keyBy"; +import { BehaviorSubject, of } from "rxjs"; +import { delay, finalize, take } from "rxjs/operators"; + +import { DataSourceService, IFilteringOutputs } from "@nova-ui/bits"; +import { + DATA_SOURCE, + IDashboard, + IKpiData, + IProviderConfiguration, + IWidget, + KpiComponent, + NOVA_KPI_DATASOURCE_ADAPTER, + NOVA_KPI_SCALE_SYNC_BROKER, + PizzagnaLayer, + ProviderRegistryService, + WellKnownPathKey, + WellKnownProviders, + WidgetTypesService, +} from "@nova-ui/dashboards"; + +/** + * A simple KPI data source to retrieve the average rating of Harry Potter and the Sorcerer's Stone (book) via googleapis + */ +@Injectable() +export class AverageRatingKpiDataSource + extends DataSourceService + implements OnDestroy +{ + public static providerId = "AverageRatingKpiDataSource"; + public busy = new BehaviorSubject(false); + + constructor(private http: HttpClient) { + super(); + } + + public async getFilteredData(): Promise { + this.busy.next(true); + return new Promise((resolve) => { + // *** Make a resource request to an external API (if needed) + this.http + .get("https://www.googleapis.com/books/v1/volumes/5MQFrgEACAAJ") + .pipe(finalize(() => this.busy.next(false))) + .subscribe({ + next: (data: any) => { + resolve({ + result: { + value: data.volumeInfo.averageRating, + }, + }); + }, + error: (error: HttpErrorResponse) => { + resolve({ + result: null, + error: { + type: error.status, + }, + }); + }, + }); + }); + } + + public ngOnDestroy(): void { + this.outputsSubject.complete(); + } +} + +/** + * A simple KPI data source to retrieve the ratings count of Harry Potter and the Sorcerer's Stone (book) via googleapis + */ +@Injectable() +export class RatingsCountKpiDataSource + extends DataSourceService + implements OnDestroy +{ + public static providerId = "RatingsCountKpiDataSource"; + + // Use this subject to communicate the data source's busy state + public busy = new BehaviorSubject(false); + + constructor(private http: HttpClient) { + super(); + } + + public async getFilteredData(): Promise { + this.busy.next(true); + return new Promise((resolve) => { + this.http + .get("https://www.googleapis.com/books/v1/volumes/5MQFrgEACAAJ") + .pipe( + delay(2000), + finalize(() => this.busy.next(false)) + ) + .subscribe({ + next: (data: any) => { + resolve({ + result: { + value: data.volumeInfo.ratingsCount, + }, + }); + }, + error: (error: HttpErrorResponse) => { + resolve({ + result: null, + error: { + type: error.status, + }, + }); + }, + }); + }); + } + + public ngOnDestroy(): void { + this.outputsSubject.complete(); + } +} +/** + * A simple KPI data source to retrieve the ratings count of Harry Potter and the Sorcerer's Stone (book) via googleapis + */ +@Injectable() +export class MockKpiDataSource + extends DataSourceService + implements OnDestroy +{ + public static providerId = "MockKpiDataSource"; + + // Use this subject to communicate the data source's busy state + public busy = new BehaviorSubject(false); + public value: number = 3381342; + + constructor() { + super(); + } + + public async getFilteredData(): Promise { + this.busy.next(true); + return new Promise((resolve) => { + of(this.value) + .pipe( + delay(5000), + take(1), + finalize(() => this.busy.next(false)) + ) + .subscribe({ + next: (data: any) => { + resolve({ + result: { + value: data, + }, + }); + }, + }); + }); + } + + public ngOnDestroy(): void { + this.outputsSubject.complete(); + } +} + +/** + * A component that instantiates the dashboard + */ +@Component({ + selector: "kpi-sync-broker-for-all-tiles-example", + templateUrl: "./kpi-sync-broker-for-all-tiles-example.component.html", + styleUrls: ["./kpi-sync-broker-for-all-tiles-example.component.less"], + standalone: false, +}) +export class KpiSyncBrokerForAllTilesExampleComponent implements OnInit { + public dashboard: IDashboard | undefined; + public gridsterConfig: GridsterConfig = {}; + public editMode: boolean = false; + + constructor( + private widgetTypesService: WidgetTypesService, + private providerRegistry: ProviderRegistryService, + private changeDetectorRef: ChangeDetectorRef + ) {} + + public ngOnInit(): void { + this.setupDashboard(); + + this.initializeDashboard(); + } + + /** Used for restoring widgets state */ + public reInitializeDashboard(): void { + // destroys the components and their providers so the dashboard can re init data + this.dashboard = undefined; + this.changeDetectorRef.detectChanges(); + + this.initializeDashboard(); + } + + private setupDashboard() { + // To add the sync broker globally to all the kpi tiles you may start with setting up the broker config + // Here you define which values to keep in sync + const brokerConfig = { + providerId: NOVA_KPI_SCALE_SYNC_BROKER, + properties: { + scaleSyncConfig: [ + { id: "value" }, + { id: "label" }, + { id: "units" }, + ], + }, + }; + const widgetTemplate = this.widgetTypesService.getWidgetType("kpi", 1); + + this.widgetTypesService.setNode( + widgetTemplate, + "configurator", + WellKnownPathKey.DataSourceProviders, + [ + AverageRatingKpiDataSource.providerId, + RatingsCountKpiDataSource.providerId, + MockKpiDataSource.providerId, + ] + ); + + // And here is how you set the sync broker for every KPI widget in the dashboard. + // Later, you will be able to override this setting for each separate KPI widget in the configuration (just like it is shown in the third + // width of the example with the 'kpiWidgetId3') + this.widgetTypesService.setNode( + widgetTemplate, + "widget", + "tiles.providers.kpiScaleSyncBroker", + brokerConfig + ); + + this.providerRegistry.setProviders({ + [AverageRatingKpiDataSource.providerId]: { + provide: DATA_SOURCE, + useClass: AverageRatingKpiDataSource, + deps: [HttpClient], + }, + [RatingsCountKpiDataSource.providerId]: { + provide: DATA_SOURCE, + useClass: RatingsCountKpiDataSource, + deps: [HttpClient], + }, + [MockKpiDataSource.providerId]: { + provide: DATA_SOURCE, + useClass: MockKpiDataSource, + deps: [], + }, + }); + } + + private initializeDashboard(): void { + const widgetsWithStructure = widgetsConfig.map((w) => + this.widgetTypesService.mergeWithWidgetType(w) + ); + const widgetsIndex = keyBy(widgetsWithStructure, (w: IWidget) => w.id); + + const positions: Record = { + kpiWidgetId: { + cols: 3, + rows: 6, + y: 0, + x: 0, + }, + kpiWidgetId2: { + cols: 3, + rows: 6, + y: 0, + x: 3, + }, + kpiWidgetId3: { + cols: 3, + rows: 6, + y: 0, + x: 6, + }, + }; + + this.dashboard = { + positions, + widgets: widgetsIndex, + }; + } +} + +const widgetsConfig: IWidget[] = [ + { + id: "kpiWidgetId", + type: "kpi", + pizzagna: { + [PizzagnaLayer.Configuration]: { + header: { + properties: { + title: "Sync Broker Applied for ALL Widgets", + subtitle: "Values are being synced", + }, + }, + tiles: { + properties: { + nodes: ["kpi1", "kpi2", "kpi3"], + }, + }, + kpi1: { + id: "kpi1", + componentType: KpiComponent.lateLoadKey, + properties: { + widgetData: { + units: \\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\`out of 5 Stars\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\`, + label: \\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\`Average Rating\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\`, + backgroundColor: "lightpink", + }, + }, + providers: { + [WellKnownProviders.DataSource]: { + providerId: AverageRatingKpiDataSource.providerId, + } as IProviderConfiguration, + [WellKnownProviders.Adapter]: { + providerId: NOVA_KPI_DATASOURCE_ADAPTER, + properties: { + componentId: "kpi1", + propertyPath: "widgetData", + }, + } as IProviderConfiguration, + }, + }, + kpi2: { + id: "kpi2", + componentType: KpiComponent.lateLoadKey, + properties: { + widgetData: { + label: \\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\`Another label which might be a pretty long one\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\`, + units: \\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\`Which comes from somewhere\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\`, + backgroundColor: "skyblue", + }, + }, + providers: { + [WellKnownProviders.DataSource]: { + providerId: RatingsCountKpiDataSource.providerId, + } as IProviderConfiguration, + [WellKnownProviders.Adapter]: { + providerId: NOVA_KPI_DATASOURCE_ADAPTER, + properties: { + componentId: "kpi2", + propertyPath: "widgetData", + }, + } as IProviderConfiguration, + }, + }, + kpi3: { + id: "kpi3", + componentType: KpiComponent.lateLoadKey, + properties: { + widgetData: { + label: \\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\`Random\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\`, + units: \\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\`Data\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\`, + }, + }, + providers: { + [WellKnownProviders.DataSource]: { + providerId: MockKpiDataSource.providerId, + } as IProviderConfiguration, + [WellKnownProviders.Adapter]: { + providerId: NOVA_KPI_DATASOURCE_ADAPTER, + properties: { + componentId: "kpi3", + propertyPath: "widgetData", + }, + } as IProviderConfiguration, + }, + }, + }, + }, + }, + { + id: "kpiWidgetId2", + type: "kpi", + pizzagna: { + [PizzagnaLayer.Configuration]: { + header: { + properties: { + title: "Sync Broker Applied for ALL Widgets", + subtitle: + "Now the values of label, units, and value are being synced", + }, + }, + tiles: { + properties: { + nodes: ["kpi1", "kpi2", "kpi3"], + }, + }, + kpi1: { + id: "kpi1", + componentType: KpiComponent.lateLoadKey, + properties: { + widgetData: { + units: \\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\`out of 5 Stars\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\`, + label: \\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\`Average Rating\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\`, + backgroundColor: "lightpink", + }, + }, + providers: { + [WellKnownProviders.DataSource]: { + providerId: AverageRatingKpiDataSource.providerId, + } as IProviderConfiguration, + [WellKnownProviders.Adapter]: { + providerId: NOVA_KPI_DATASOURCE_ADAPTER, + properties: { + componentId: "kpi1", + propertyPath: "widgetData", + }, + } as IProviderConfiguration, + }, + }, + kpi2: { + id: "kpi2", + componentType: KpiComponent.lateLoadKey, + properties: { + widgetData: { + label: \\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\`Another label which might be a pretty long one\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\`, + units: \\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\`Which comes from somewhere\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\`, + backgroundColor: "skyblue", + }, + }, + providers: { + [WellKnownProviders.DataSource]: { + providerId: RatingsCountKpiDataSource.providerId, + } as IProviderConfiguration, + [WellKnownProviders.Adapter]: { + providerId: NOVA_KPI_DATASOURCE_ADAPTER, + properties: { + componentId: "kpi2", + propertyPath: "widgetData", + }, + } as IProviderConfiguration, + }, + }, + kpi3: { + id: "kpi3", + componentType: KpiComponent.lateLoadKey, + properties: { + widgetData: { + label: \\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\`Random\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\`, + units: \\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\`Data\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\`, + }, + }, + providers: { + [WellKnownProviders.DataSource]: { + providerId: MockKpiDataSource.providerId, + } as IProviderConfiguration, + [WellKnownProviders.Adapter]: { + providerId: NOVA_KPI_DATASOURCE_ADAPTER, + properties: { + componentId: "kpi3", + propertyPath: "widgetData", + }, + } as IProviderConfiguration, + }, + }, + }, + }, + }, + { + id: "kpiWidgetId3", + type: "kpi", + pizzagna: { + [PizzagnaLayer.Configuration]: { + header: { + properties: { + title: "Here We Sync Only Labels and Units", + subtitle: + "Now only the label, and units are being synced", + }, + }, + tiles: { + properties: { + nodes: ["kpi1", "kpi2", "kpi3"], + }, + providers: { + // This is where and how you can override the globally set broker config + kpiScaleSyncBroker: { + providerId: NOVA_KPI_SCALE_SYNC_BROKER, + properties: { + scaleSyncConfig: [ + { id: "label" }, + { id: "units" }, + ], + }, + }, + }, + }, + kpi1: { + id: "kpi1", + componentType: KpiComponent.lateLoadKey, + properties: { + widgetData: { + units: \\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\`out of 5 Stars\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\`, + label: \\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\`Average Rating\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\`, + backgroundColor: "lightpink", + }, + }, + providers: { + [WellKnownProviders.DataSource]: { + providerId: AverageRatingKpiDataSource.providerId, + } as IProviderConfiguration, + [WellKnownProviders.Adapter]: { + providerId: NOVA_KPI_DATASOURCE_ADAPTER, + properties: { + componentId: "kpi1", + propertyPath: "widgetData", + }, + } as IProviderConfiguration, + }, + }, + kpi2: { + id: "kpi2", + componentType: KpiComponent.lateLoadKey, + properties: { + widgetData: { + label: \\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\`Another label which might be a pretty long one\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\`, + units: \\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\`Which comes from somewhere\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\`, + backgroundColor: "skyblue", + }, + }, + providers: { + [WellKnownProviders.DataSource]: { + providerId: RatingsCountKpiDataSource.providerId, + } as IProviderConfiguration, + [WellKnownProviders.Adapter]: { + providerId: NOVA_KPI_DATASOURCE_ADAPTER, + properties: { + componentId: "kpi2", + propertyPath: "widgetData", + }, + } as IProviderConfiguration, + }, + }, + kpi3: { + id: "kpi3", + componentType: KpiComponent.lateLoadKey, + properties: { + widgetData: { + label: \\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\`Random\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\`, + units: \\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\`Data\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\`, + }, + }, + providers: { + [WellKnownProviders.DataSource]: { + providerId: MockKpiDataSource.providerId, + } as IProviderConfiguration, + [WellKnownProviders.Adapter]: { + providerId: NOVA_KPI_DATASOURCE_ADAPTER, + properties: { + componentId: "kpi3", + propertyPath: "widgetData", + }, + } as IProviderConfiguration, + }, + }, + }, + }, + }, +]; +\\\\\\\\\\\\\\\`, + "widget-types/kpi/kpi-widget/kpi-widget-example.component.ts": \\\\\\\\\\\\\\\`// © 2022 SolarWinds Worldwide, LLC. All rights reserved. +// +// Permission is hereby granted, free of charge, to any person obtaining a copy +// of this software and associated documentation files (the "Software"), to +// deal in the Software without restriction, including without limitation the +// rights to use, copy, modify, merge, publish, distribute, sublicense, and/or +// sell copies of the Software, and to permit persons to whom the Software is +// furnished to do so, subject to the following conditions: +// +// The above copyright notice and this permission notice shall be included in +// all copies or substantial portions of the Software. +// +// THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR +// IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, +// FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE +// AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER +// LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, +// OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN +// THE SOFTWARE. + +import { HttpClient, HttpErrorResponse } from "@angular/common/http"; +import { Component, Injectable, OnDestroy, OnInit } from "@angular/core"; +import { GridsterConfig, GridsterItem } from "angular-gridster2"; +import { BehaviorSubject } from "rxjs"; +import { finalize } from "rxjs/operators"; + +import { DataSourceService, IFilteringOutputs } from "@nova-ui/bits"; +import { + DATA_SOURCE, + DEFAULT_PIZZAGNA_ROOT, + IDashboard, + IKpiData, + IProviderConfiguration, + IRefresherProperties, + IWidget, + IWidgets, + KpiComponent, + NOVA_KPI_DATASOURCE_ADAPTER, + PizzagnaLayer, + ProviderRegistryService, + WellKnownPathKey, + WellKnownProviders, + WidgetTypesService, +} from "@nova-ui/dashboards"; + +/** + * A simple KPI data source to retrieve the average rating of Harry Potter and the Sorcerer's Stone (book) via googleapis + */ +@Injectable() +export class AverageRatingKpiDataSource + extends DataSourceService + implements OnDestroy +{ + // This is the ID we'll use to identify the provider + public static providerId = "AverageRatingKpiDataSource"; + + // Use this subject to communicate the data source's busy state + public busy = new BehaviorSubject(false); + + constructor(private http: HttpClient) { + super(); + } + + // In this example, getFilteredData is invoked every 10 minutes (Take a look at the refresher + // provider definition in the widget configuration below to see how the interval is set) + public async getFilteredData(): Promise { + this.busy.next(true); + return new Promise((resolve) => { + // *** Make a resource request to an external API (if needed) + this.http + .get("https://www.googleapis.com/books/v1/volumes/5MQFrgEACAAJ") + .pipe(finalize(() => this.busy.next(false))) + .subscribe({ + next: (data: any) => { + resolve({ + result: { + value: data.volumeInfo.averageRating, + }, + }); + }, + error: (error: HttpErrorResponse) => { + resolve({ + result: null, + error: { + type: error.status, + }, + }); + }, + }); + }); + } + + public ngOnDestroy(): void { + this.outputsSubject.complete(); + } +} + +/** + * A component that instantiates the dashboard + */ +@Component({ + selector: "kpi-widget-example", + templateUrl: "./kpi-widget-example.component.html", + styleUrls: ["./kpi-widget-example.component.less"], + standalone: false, +}) +export class KpiWidgetExampleComponent implements OnInit { + // This variable will hold all the data needed to define the layout and behavior of the widgets. + // Pass this to the dashboard component's dashboard input in the template. + public dashboard: IDashboard; + + // Angular gridster requires a configuration object even if it's empty. + // Pass this to the dashboard component's gridsterConfig input in the template. + public gridsterConfig: GridsterConfig = {}; + + // Boolean passed as an input to the dashboard. When true, widgets can be moved, resized, removed, or edited + public editMode: boolean = false; + + constructor( + // WidgetTypesService provides the widget's necessary structure information + private widgetTypesService: WidgetTypesService, + + // In general, the ProviderRegistryService is used for making entities available for injection into dynamically loaded components. + private providerRegistry: ProviderRegistryService + ) {} + + public ngOnInit(): void { + // Grabbing the widget's default template which will be needed as a parameter for setNode + const widgetTemplate = this.widgetTypesService.getWidgetType("kpi", 1); + // Registering our data sources as dropdown options in the widget editor/configurator + // Note: This could also be done in the parent module's constructor so that + // multiple dashboards could have access to the same widget template modification. + this.widgetTypesService.setNode( + // This is the template we grabbed above with getWidgetType + widgetTemplate, + // We are setting the editor/configurator part of the widget template + "configurator", + // This indicates which node you are changing and we want to change + // the data source providers available for selection in the editor. + WellKnownPathKey.DataSourceProviders, + // We are setting the data sources available for selection in the editor + [AverageRatingKpiDataSource.providerId] + ); + + // Registering the data source for injection into the KPI tile. + // Note: Each tile of a KPI widget is assigned its own instance of the data source + this.providerRegistry.setProviders({ + [AverageRatingKpiDataSource.providerId]: { + provide: DATA_SOURCE, + useClass: AverageRatingKpiDataSource, + // Any dependencies that need to be injected into the provider must be listed here + deps: [HttpClient], + }, + }); + + this.initializeDashboard(); + } + + public initializeDashboard(): void { + // We're using a static configuration object for this example, but this is where + // the widget's configuration could potentially be populated from a database + const kpiWidget = widgetConfig; + const widgetIndex: IWidgets = { + // Complete the KPI widget with information coming from its type definition + [kpiWidget.id]: + this.widgetTypesService.mergeWithWidgetType(kpiWidget), + }; + + // Setting the widget dimensions and position (this is for gridster) + const positions: Record = { + [kpiWidget.id]: { + cols: 4, + rows: 6, + y: 0, + x: 0, + }, + }; + + // Finally, assigning the variables we created above to the dashboard + this.dashboard = { + positions, + widgets: widgetIndex, + }; + } +} + +const widgetConfig: IWidget = { + id: "kpiWidgetId", + type: "kpi", + pizzagna: { + [PizzagnaLayer.Configuration]: { + [DEFAULT_PIZZAGNA_ROOT]: { + providers: { + [WellKnownProviders.Refresher]: { + properties: { + // Configuring the refresher interval so that our data source is invoked every ten minutes + interval: 60 * 10, + enabled: true, + } as IRefresherProperties, + } as Partial, + }, + }, + header: { + properties: { + title: "Harry Potter and the Sorcerer's Stone", + subtitle: "By J. K. Rowling", + }, + }, + tiles: { + properties: { + nodes: ["kpi1"], + }, + }, + kpi1: { + id: "kpi1", + componentType: KpiComponent.lateLoadKey, + properties: { + widgetData: { + units: \\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\`out of 5 Stars\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\`, + label: \\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\`Average Rating\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\`, + }, + }, + providers: { + [WellKnownProviders.DataSource]: { + // Setting the data source providerId for the tile with id "kpi1" + providerId: AverageRatingKpiDataSource.providerId, + } as IProviderConfiguration, + [WellKnownProviders.Adapter]: { + providerId: NOVA_KPI_DATASOURCE_ADAPTER, + properties: { + componentId: "kpi1", + propertyPath: "widgetData", + }, + } as IProviderConfiguration, + }, + }, + }, + }, +}; +\\\\\\\\\\\\\\\`, + "widget-types/kpi/kpi-widget-background-color/kpi-widget-background-color-example.component.ts": \\\\\\\\\\\\\\\`// © 2022 SolarWinds Worldwide, LLC. All rights reserved. +// +// Permission is hereby granted, free of charge, to any person obtaining a copy +// of this software and associated documentation files (the "Software"), to +// deal in the Software without restriction, including without limitation the +// rights to use, copy, modify, merge, publish, distribute, sublicense, and/or +// sell copies of the Software, and to permit persons to whom the Software is +// furnished to do so, subject to the following conditions: +// +// The above copyright notice and this permission notice shall be included in +// all copies or substantial portions of the Software. +// +// THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR +// IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, +// FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE +// AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER +// LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, +// OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN +// THE SOFTWARE. + +import { HttpClient, HttpErrorResponse } from "@angular/common/http"; +import { + ChangeDetectorRef, + Component, + Injectable, + OnDestroy, + OnInit, +} from "@angular/core"; +import { GridsterConfig, GridsterItem } from "angular-gridster2"; +import { BehaviorSubject } from "rxjs"; +import { finalize } from "rxjs/operators"; + +import { DataSourceService, IFilteringOutputs } from "@nova-ui/bits"; +import { + DATA_SOURCE, + DEFAULT_KPI_BACKGROUND_COLORS, + IDashboard, + IKpiColorRules, + IKpiData, + IProviderConfiguration, + IWidget, + IWidgets, + KpiComponent, + NOVA_KPI_COLOR_PRIORITIZER, + NOVA_KPI_DATASOURCE_ADAPTER, + PizzagnaLayer, + ProviderRegistryService, + WellKnownPathKey, + WellKnownProviders, + WidgetTypesService, +} from "@nova-ui/dashboards"; + +/** + * A simple KPI data source to retrieve the average rating of Harry Potter and the Sorcerer's Stone (book) via googleapis + */ +@Injectable() +export class AverageRatingKpiDataSource + extends DataSourceService + implements OnDestroy +{ + public static providerId = "AverageRatingKpiDataSource"; + public busy = new BehaviorSubject(false); + + constructor(private http: HttpClient) { + super(); + } + + public async getFilteredData(): Promise { + this.busy.next(true); + return new Promise((resolve) => { + // *** Make a resource request to an external API (if needed) + this.http + .get("https://www.googleapis.com/books/v1/volumes/5MQFrgEACAAJ") + .pipe(finalize(() => this.busy.next(false))) + .subscribe({ + next: (data: any) => { + resolve({ + result: { + value: data.volumeInfo.averageRating, + // setting the color on the dataSource "Sea Green", + // uncomment to get the background color update from the "Data" layer + // backgroundColor: "var(--nui-color-chart-three)", + }, + }); + }, + error: (error: HttpErrorResponse) => { + resolve({ + result: null, + error: { + type: error.status, + }, + }); + }, + }); + }); + } + + public ngOnDestroy(): void { + this.outputsSubject.complete(); + } +} + +/** + * A component that instantiates the dashboard + */ +@Component({ + selector: "kpi-widget-background-color-example", + templateUrl: "./kpi-widget-background-color-example.component.html", + styleUrls: ["./kpi-widget-background-color-example.component.less"], + standalone: false, +}) +export class KpiWidgetBackgroundColorExampleComponent implements OnInit { + public dashboard: IDashboard | undefined; + public gridsterConfig: GridsterConfig = {}; + public editMode: boolean = false; + + constructor( + private widgetTypesService: WidgetTypesService, + private providerRegistry: ProviderRegistryService, + private changeDetectorRef: ChangeDetectorRef + ) {} + + public ngOnInit(): void { + this.setupDashboard(); + + // KPI tile default color setup + this.setupDefaultColorStructure(); + + // Sets the custom pallette to the 'Description' section + this.setupCustomPalletteDescription(); + + // Sets the custom pallette to the 'Background color rules' section + this.setupCustomPalletteRules(); + + this.initializeDashboard(); + } + + /** Used for restoring widgets state */ + public reInitializeDashboard(): void { + // destroys the components and their providers so the dashboard can re init data + this.dashboard = undefined; + this.changeDetectorRef.detectChanges(); + + this.initializeDashboard(); + } + + private setupCustomPalletteDescription() { + const kpiWidgetTemplate = this.widgetTypesService.getWidgetType( + "kpi", + 1 + ); + this.widgetTypesService.setNode( + kpiWidgetTemplate, + "configurator", + WellKnownPathKey.TileDescriptionBackgroundColors, + [ + { color: "var(--nui-color-chart-one)", label: "Blue" }, + { + color: "var(--nui-color-chart-one-light)", + label: "Blue Light", + }, + { + color: "var(--nui-color-chart-one-dark)", + label: "Blue Dark", + }, + ] + ); + } + + private setupCustomPalletteRules() { + const kpiWidgetTemplate = this.widgetTypesService.getWidgetType( + "kpi", + 1 + ); + this.widgetTypesService.setNode( + kpiWidgetTemplate, + "configurator", + WellKnownPathKey.TileBackgroundColorRulesBackgroundColors, + [ + { color: "red", label: "Native Red" }, + ...DEFAULT_KPI_BACKGROUND_COLORS, + ] + ); + } + + private setupDefaultColorStructure() { + const widgetTemplate = this.widgetTypesService.getWidgetType("kpi", 1); + this.widgetTypesService.setNode( + widgetTemplate, + "widget", + "tiles.properties.template.properties.widgetData.backgroundColor", + "red" + ); + } + + private setupDashboard() { + const widgetTemplate = this.widgetTypesService.getWidgetType("kpi", 1); + this.widgetTypesService.setNode( + widgetTemplate, + "configurator", + WellKnownPathKey.DataSourceProviders, + [AverageRatingKpiDataSource.providerId] + ); + + this.providerRegistry.setProviders({ + [AverageRatingKpiDataSource.providerId]: { + provide: DATA_SOURCE, + useClass: AverageRatingKpiDataSource, + deps: [HttpClient], + }, + }); + } + + private initializeDashboard(): void { + const kpiWidget = widgetConfig; + const widgetIndex: IWidgets = { + [kpiWidget.id]: + this.widgetTypesService.mergeWithWidgetType(kpiWidget), + }; + + const positions: Record = { + [kpiWidget.id]: { + cols: 4, + rows: 6, + y: 0, + x: 0, + }, + }; + + this.dashboard = { + positions, + widgets: widgetIndex, + }; + } +} + +const widgetConfig: IWidget = { + id: "kpiWidgetId", + type: "kpi", + pizzagna: { + [PizzagnaLayer.Configuration]: { + header: { + properties: { + title: "Harry Potter and the Sorcerer's Stone", + subtitle: "By J. K. Rowling", + }, + }, + tiles: { + properties: { + nodes: ["kpi1"], + }, + }, + kpi1: { + id: "kpi1", + componentType: KpiComponent.lateLoadKey, + properties: { + widgetData: { + units: \\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\`out of 5 Stars\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\`, + label: \\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\`Average Rating\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\`, + // Configuration color "Blue" + backgroundColor: "var(--nui-color-chart-one)", + }, + }, + providers: { + [WellKnownProviders.DataSource]: { + providerId: AverageRatingKpiDataSource.providerId, + } as IProviderConfiguration, + [WellKnownProviders.Adapter]: { + providerId: NOVA_KPI_DATASOURCE_ADAPTER, + properties: { + componentId: "kpi1", + propertyPath: "widgetData", + }, + } as IProviderConfiguration, + [WellKnownProviders.KpiColorPrioritizer]: { + providerId: NOVA_KPI_COLOR_PRIORITIZER, + properties: { + // Color Prioritizer Rules + // settings rules - if the value is more than "2" display "Violet" color + rules: [ + { + comparisonType: ">", + value: 2, + color: "var(--nui-color-chart-four)", + }, + ] as IKpiColorRules[], + }, + } as IProviderConfiguration, + }, + }, + }, + }, +}; +\\\\\\\\\\\\\\\`, + "widget-types/kpi/kpi-widget-background-color-docs.component.ts": \\\\\\\\\\\\\\\`// © 2022 SolarWinds Worldwide, LLC. All rights reserved. +// +// Permission is hereby granted, free of charge, to any person obtaining a copy +// of this software and associated documentation files (the "Software"), to +// deal in the Software without restriction, including without limitation the +// rights to use, copy, modify, merge, publish, distribute, sublicense, and/or +// sell copies of the Software, and to permit persons to whom the Software is +// furnished to do so, subject to the following conditions: +// +// The above copyright notice and this permission notice shall be included in +// all copies or substantial portions of the Software. +// +// THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR +// IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, +// FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE +// AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER +// LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, +// OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN +// THE SOFTWARE. + +import { Component } from "@angular/core"; + +@Component({ + selector: "nui-kpi-background-color-docs", + templateUrl: "./kpi-widget-background-color-docs.component.html", + standalone: false, +}) +export class KpiWidgetBackgroundColorDocsComponent { + public comparatorsRegistryCode = \\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\` + this.comparatorsRegistry.registerComparators({ + "!=": { + comparatorFn: (actual: any, reference: any) => actual != reference, + label: "Not equal", + }, + }); + \\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\`; +} +\\\\\\\\\\\\\\\`, + "widget-types/kpi/kpi-widget-interactive/kpi-widget-interactive-example.component.ts": \\\\\\\\\\\\\\\`// © 2022 SolarWinds Worldwide, LLC. All rights reserved. +// +// Permission is hereby granted, free of charge, to any person obtaining a copy +// of this software and associated documentation files (the "Software"), to +// deal in the Software without restriction, including without limitation the +// rights to use, copy, modify, merge, publish, distribute, sublicense, and/or +// sell copies of the Software, and to permit persons to whom the Software is +// furnished to do so, subject to the following conditions: +// +// The above copyright notice and this permission notice shall be included in +// all copies or substantial portions of the Software. +// +// THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR +// IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, +// FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE +// AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER +// LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, +// OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN +// THE SOFTWARE. + +import { HttpClient, HttpErrorResponse } from "@angular/common/http"; +import { Component, Injectable, OnDestroy, OnInit } from "@angular/core"; +import { GridsterConfig, GridsterItem } from "angular-gridster2"; +import { BehaviorSubject } from "rxjs"; +import { finalize } from "rxjs/operators"; + +import { DataSourceService, IFilteringOutputs } from "@nova-ui/bits"; +import { + DATA_SOURCE, + IDashboard, + IKpiData, + IProviderConfiguration, + IWidget, + IWidgets, + KpiComponent, + NOVA_KPI_DATASOURCE_ADAPTER, + NOVA_URL_INTERACTION_HANDLER, + PizzagnaLayer, + ProviderRegistryService, + WellKnownPathKey, + WellKnownProviders, + WidgetTypesService, +} from "@nova-ui/dashboards"; + +/** + * A simple KPI data source to retrieve the average rating of Harry Potter and the Sorcerer's Stone (book) via googleapis + */ +@Injectable() +export class BookRatingDataSource + extends DataSourceService + implements OnDestroy +{ + // This is the ID we'll use to identify the provider + public static providerId = "BookRatingDataSource"; + + // Use this subject to communicate the data source's busy state + public busy = new BehaviorSubject(false); + + constructor(private http: HttpClient) { + super(); + } + + // In this example, getFilteredData is invoked every 10 minutes (Take a look at the refresher + // provider definition in the widget configuration below to see how the interval is set) + public async getFilteredData(): Promise { + this.busy.next(true); + return new Promise((resolve) => { + // *** Make a resource request to an external API (if needed) + this.http + .get("https://www.googleapis.com/books/v1/volumes/zpvysRGsBlwC") + .pipe(finalize(() => this.busy.next(false))) + .subscribe({ + next: (data: any) => { + resolve({ + result: { + value: data.volumeInfo.averageRating, + link: data.volumeInfo.infoLink, + }, + }); + }, + error: (error: HttpErrorResponse) => { + resolve({ + result: null, + error: { + type: error.status, + }, + }); + }, + }); + }); + } + + public ngOnDestroy(): void { + this.outputsSubject.complete(); + } +} + +/** + * A component that instantiates the dashboard + */ +@Component({ + selector: "kpi-widget-interactive-example", + templateUrl: "./kpi-widget-interactive-example.component.html", + styleUrls: ["./kpi-widget-interactive-example.component.less"], + standalone: false, +}) +export class KpiWidgetInteractiveExampleComponent implements OnInit { + // This variable will hold all the data needed to define the layout and behavior of the widgets. + // Pass this to the dashboard component's dashboard input in the template. + public dashboard: IDashboard; + + // Angular gridster requires a configuration object even if it's empty. + // Pass this to the dashboard component's gridsterConfig input in the template. + public gridsterConfig: GridsterConfig = {}; + + // Boolean passed as an input to the dashboard. When true, widgets can be moved, resized, removed, or edited + public editMode: boolean = false; + + constructor( + // WidgetTypesService provides the widget's necessary structure information + private widgetTypesService: WidgetTypesService, + + // In general, the ProviderRegistryService is used for making entities available for injection into dynamically loaded components. + private providerRegistry: ProviderRegistryService + ) {} + + public ngOnInit(): void { + // Grabbing the widget's default template which will be needed as a parameter for setNode + const widgetTemplate = this.widgetTypesService.getWidgetType("kpi", 1); + // Registering our data sources as dropdown options in the widget editor/configurator + // Note: This could also be done in the parent module's constructor so that + // multiple dashboards could have access to the same widget template modification. + this.widgetTypesService.setNode( + // This is the template we grabbed above with getWidgetType + widgetTemplate, + // We are setting the editor/configurator part of the widget template + "configurator", + // This indicates which node you are changing and we want to change + // the data source providers available for selection in the editor. + WellKnownPathKey.DataSourceProviders, + // We are setting the data sources available for selection in the editor + [BookRatingDataSource.providerId] + ); + + // Registering the data source for injection into the KPI tile. + // Note: Each tile of a KPI widget is assigned its own instance of the data source + this.providerRegistry.setProviders({ + [BookRatingDataSource.providerId]: { + provide: DATA_SOURCE, + useClass: BookRatingDataSource, + // Any dependencies that need to be injected into the provider must be listed here + deps: [HttpClient], + }, + }); + + this.initializeDashboard(); + } + + public initializeDashboard(): void { + // We're using a static configuration object for this example, but this is where + // the widget's configuration could potentially be populated from a database + const kpiWidget = widgetConfig; + const widgetIndex: IWidgets = { + // Complete the KPI widget with information coming from its type definition + [kpiWidget.id]: + this.widgetTypesService.mergeWithWidgetType(kpiWidget), + }; + + // Setting the widget dimensions and position (this is for gridster) + const positions: Record = { + [kpiWidget.id]: { + cols: 4, + rows: 6, + y: 0, + x: 0, + }, + }; + + // Finally, assigning the variables we created above to the dashboard + this.dashboard = { + positions, + widgets: widgetIndex, + }; + } +} + +const widgetConfig: IWidget = { + id: "kpiWidgetId", + type: "kpi", + pizzagna: { + [PizzagnaLayer.Configuration]: { + header: { + properties: { + title: "Harry Potter and the Order of the Phoenix", + subtitle: "By: J. K. Rowling", + }, + }, + tiles: { + providers: { + interaction: { + // Configuring the UrlInteractionHandler for interactions on the tiles + providerId: NOVA_URL_INTERACTION_HANDLER, + properties: { + // the 'url' property tells the handler what link to use when interaction occurs on the series + url: "\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\${data.link}", + }, + }, + }, + properties: { + nodes: ["kpi1"], + }, + }, + kpi1: { + id: "kpi1", + componentType: KpiComponent.lateLoadKey, + properties: { + widgetData: { + units: \\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\`out of 5 stars\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\`, + label: \\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\`Average Rating\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\`, + value: 0, + // the link property that is passed to the UrlInteractionHandler when the title is clicked + // this will be updated in BookRatingDataSource's 'getFilteredData' call. + link: "http://www.google.com", + }, + }, + providers: { + [WellKnownProviders.DataSource]: { + // Setting the data source providerId for the tile with id "kpi1" + providerId: BookRatingDataSource.providerId, + } as IProviderConfiguration, + [WellKnownProviders.Adapter]: { + providerId: NOVA_KPI_DATASOURCE_ADAPTER, + properties: { + componentId: "kpi1", + propertyPath: "widgetData", + }, + } as IProviderConfiguration, + }, + }, + }, + }, +}; +\\\\\\\\\\\\\\\`, + "widget-types/proportional/models.ts": \\\\\\\\\\\\\\\`export interface IMockBeerReview { + id: string; + name: string; + data: number[]; + icon: string; + link?: string; + value: string; + color?: string; +} +\\\\\\\\\\\\\\\`, + "widget-types/proportional/proportional-docs.component.ts": \\\\\\\\\\\\\\\`// © 2022 SolarWinds Worldwide, LLC. All rights reserved. +// +// Permission is hereby granted, free of charge, to any person obtaining a copy +// of this software and associated documentation files (the "Software"), to +// deal in the Software without restriction, including without limitation the +// rights to use, copy, modify, merge, publish, distribute, sublicense, and/or +// sell copies of the Software, and to permit persons to whom the Software is +// furnished to do so, subject to the following conditions: +// +// The above copyright notice and this permission notice shall be included in +// all copies or substantial portions of the Software. +// +// THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR +// IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, +// FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE +// AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER +// LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, +// OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN +// THE SOFTWARE. + +import { Component, OnInit } from "@angular/core"; + +import { mapContentFile } from "../../../../demo-files-factory"; + +@Component({ + selector: "nui-proportional-docs", + templateUrl: "./proportional-docs.component.html", + standalone: false, +}) +export class ProportionalDocsComponent implements OnInit { + public proportionalWidgetFileText = ""; + public proportionalConfiguratorFileText = ""; + + public async ngOnInit(): Promise { + this.proportionalWidgetFileText = await import( + "./../../../../../../src/lib/widget-types/proportional/proportional-widget" + ).then(mapContentFile); + this.proportionalConfiguratorFileText = await import( + "./../../../../../../src/lib/widget-types/proportional/proportional-configurator" + ).then(mapContentFile); + } +} +\\\\\\\\\\\\\\\`, + "widget-types/proportional/proportional-docs.module.ts": \\\\\\\\\\\\\\\`// © 2022 SolarWinds Worldwide, LLC. All rights reserved. +// +// Permission is hereby granted, free of charge, to any person obtaining a copy +// of this software and associated documentation files (the "Software"), to +// deal in the Software without restriction, including without limitation the +// rights to use, copy, modify, merge, publish, distribute, sublicense, and/or +// sell copies of the Software, and to permit persons to whom the Software is +// furnished to do so, subject to the following conditions: +// +// The above copyright notice and this permission notice shall be included in +// all copies or substantial portions of the Software. +// +// THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR +// IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, +// FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE +// AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER +// LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, +// OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN +// THE SOFTWARE. + +import { NgModule } from "@angular/core"; +import { RouterModule, Routes } from "@angular/router"; + +import { + NuiButtonModule, + NuiDocsModule, + NuiMessageModule, + NuiSwitchModule, + DEMO_PATH_TOKEN, +} from "@nova-ui/bits"; +import { NuiDashboardsModule } from "@nova-ui/dashboards"; + +import { ProportionalDocsComponent } from "./proportional-docs.component"; +import { ProportionalDonutContentDocsComponent } from "./proportional-donut-content-docs.component"; +import { ProportionalWidgetDonutContentFormattersExampleComponent } from "./proportional-donut-content-formatters/proportional-donut-content-formatters-example.component"; +import { ProportionalWidgetExampleComponent } from "./proportional-widget/proportional-widget-example.component"; +import { ProportionalWidgetInteractiveExampleComponent } from "./proportional-widget-interactive/proportional-widget-interactive-example.component"; +import { getDemoFiles } from "../../../../demo-files-factory"; + +const routes: Routes = [ + { + path: "", + component: ProportionalDocsComponent, + data: { + srlc: { + hideIndicator: true, + }, + showThemeSwitcher: true, + }, + }, + { + path: "example", + component: ProportionalWidgetExampleComponent, + data: { + srlc: { + hideIndicator: true, + }, + }, + }, + { + path: "donut-content-formatters", + component: ProportionalDonutContentDocsComponent, + data: { + srlc: { + hideIndicator: true, + }, + }, + }, + { + path: "donut-content-formatters-example", + component: ProportionalWidgetDonutContentFormattersExampleComponent, + data: { + srlc: { + hideIndicator: true, + }, + }, + }, + { + path: "proportional-widget-interactive-example", + component: ProportionalWidgetInteractiveExampleComponent, + data: { + srlc: { + hideIndicator: true, + }, + }, + }, +]; + +@NgModule({ + imports: [ + RouterModule.forChild(routes), + NuiButtonModule, + NuiDocsModule, + NuiDashboardsModule, + NuiMessageModule, + NuiSwitchModule, + ], + declarations: [ + ProportionalDocsComponent, + ProportionalWidgetExampleComponent, + ProportionalWidgetInteractiveExampleComponent, + ProportionalWidgetDonutContentFormattersExampleComponent, + ProportionalDonutContentDocsComponent, + ], + providers: [ + { + provide: DEMO_PATH_TOKEN, + useValue: getDemoFiles("proportional"), + }, + ], +}) +export default class ProportionalDocsModule {} +\\\\\\\\\\\\\\\`, + "widget-types/proportional/proportional-donut-content-docs.component.ts": \\\\\\\\\\\\\\\`// © 2022 SolarWinds Worldwide, LLC. All rights reserved. +// +// Permission is hereby granted, free of charge, to any person obtaining a copy +// of this software and associated documentation files (the "Software"), to +// deal in the Software without restriction, including without limitation the +// rights to use, copy, modify, merge, publish, distribute, sublicense, and/or +// sell copies of the Software, and to permit persons to whom the Software is +// furnished to do so, subject to the following conditions: +// +// The above copyright notice and this permission notice shall be included in +// all copies or substantial portions of the Software. +// +// THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR +// IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, +// FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE +// AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER +// LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, +// OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN +// THE SOFTWARE. + +import { Component } from "@angular/core"; + +@Component({ + selector: "nui-proportional-donut-content-docs", + templateUrl: "./proportional-donut-content-docs.component.html", + standalone: false, +}) +export class ProportionalDonutContentDocsComponent { + public dataSourceDataFieldsConfig = \\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\` +public dataFieldsConfig: IProportionalDataFieldsConfig = { + dataFields$: new BehaviorSubject(this.dataFields), + chartSeriesDataFields$: new BehaviorSubject(this.chartSeriesDataFields), +}; + \\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\`; + + public widgetConfigSlice = \\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\` +"properties": { + "configuration": { + "chartOptions": { + donutContentConfig: { + formatter: { + componentType: SiUnitsFormatterComponent.lateLoadKey, + }, + aggregator: { + aggregatorType: sumAggregator.aggregatorType, + }, + }, + } + } +} + + \\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\`; +} +\\\\\\\\\\\\\\\`, + "widget-types/proportional/proportional-donut-content-formatters/proportional-donut-content-formatters-example.component.ts": \\\\\\\\\\\\\\\`// © 2022 SolarWinds Worldwide, LLC. All rights reserved. +// +// Permission is hereby granted, free of charge, to any person obtaining a copy +// of this software and associated documentation files (the "Software"), to +// deal in the Software without restriction, including without limitation the +// rights to use, copy, modify, merge, publish, distribute, sublicense, and/or +// sell copies of the Software, and to permit persons to whom the Software is +// furnished to do so, subject to the following conditions: +// +// The above copyright notice and this permission notice shall be included in +// all copies or substantial portions of the Software. +// +// THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR +// IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, +// FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE +// AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER +// LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, +// OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN +// THE SOFTWARE. + +import { + ChangeDetectorRef, + Component, + Injectable, + OnDestroy, + OnInit, +} from "@angular/core"; +import { GridsterConfig, GridsterItem } from "angular-gridster2"; +import { BehaviorSubject } from "rxjs"; + +import { + DataSourceService, + IDataField, + IDataSource, + IFilteringOutputs, +} from "@nova-ui/bits"; +import { IAccessors, IChartAssistSeries } from "@nova-ui/charts"; +import { + DATA_SOURCE, + DEFAULT_LEGEND_FORMATTERS, + DEFAULT_PIZZAGNA_ROOT, + DEFAULT_PROPORTIONAL_CONTENT_AGGREGATORS, + DEFAULT_PROPORTIONAL_CONTENT_FORMATTERS, + DONUT_CONTENT_CONFIGURATION_SLICE, + IDashboard, + IDonutContentConfig, + IProportionalDataFieldsConfig, + IProportionalWidgetChartOptions, + IProportionalWidgetConfig, + IProviderConfiguration, + IWidget, + IWidgets, + LegendPlacement, + PizzagnaLayer, + ProportionalContentAggregatorsRegistryService, + ProportionalDonutContentFormattersRegistryService, + ProportionalLegendFormattersRegistryService, + ProportionalWidgetChartTypes, + ProviderRegistryService, + SiUnitsFormatterComponent, + sumAggregator, + WellKnownPathKey, + WellKnownProviders, + WidgetTypesService, +} from "@nova-ui/dashboards"; + +import { IMockBeerReview } from "../models"; + +/** + * A simple proportional data source to retrieve beer review counts by city + */ +@Injectable() +export class BeerReviewCountsByCityMockDataSource + extends DataSourceService> + implements IDataSource>, OnDestroy +{ + public static providerId = "BeerReviewCountsByCityMockDataSource"; + public busy = new BehaviorSubject(false); + + protected dataFields: IDataField[] = [ + { + id: "Brno", + label: "Brno", + // @ts-ignore + dataType: null, + }, + { + id: "kyiv", + label: "Kyiv", + // @ts-ignore + dataType: null, + }, + { + id: "austin", + label: "Austin", + // @ts-ignore + dataType: null, + }, + { + id: "lisbon", + label: "Lisbon", + // @ts-ignore + dataType: null, + }, + { + id: "sydney", + label: "Sydney", + // @ts-ignore + dataType: null, + }, + { + id: "nur-sultan", + label: "Nur-Sultan", + // @ts-ignore + dataType: null, + }, + ]; + protected chartSeriesDataFields: IDataField[] = [ + // default field in the chart series that is used for the aggregation + { + id: "data[0]", + label: "data", + // @ts-ignore + dataType: null, + }, + // any custom field in the chart series that is used for the aggregation + { + id: "customDonutContent", + label: "Custom Donut Content", + // @ts-ignore + dataType: null, + }, + ]; + + /** + * DataSource needs to implement the "IDataFieldsConfig" for this scenario. + * + * It's necessary to provide the "chartSeriesDataFields", + * that's why proportional widget dataSource has it's own interface for that - IProportionalDataFieldsConfig. + * + * dataFields$ - stands for possible series fields + * chartSeriesDataFields$ - stands for the fields IN the series + * + * see declaration of "dataFields" and "chartSeriesDataFields" for the example. + */ + public dataFieldsConfig: IProportionalDataFieldsConfig = { + dataFields$: new BehaviorSubject(this.dataFields), + chartSeriesDataFields$: new BehaviorSubject( + this.chartSeriesDataFields + ), + }; + + public async getFilteredData(): Promise { + this.busy.next(true); + return new Promise((resolve) => { + setTimeout(() => { + this.outputsSubject.next({ + result: getMockBeerReviewCountsByCity(), + }); + this.busy.next(false); + }, 300); + }); + } + + public ngOnDestroy(): void { + this.outputsSubject.complete(); + } +} + +/** + * A component that instantiates the dashboard + */ +@Component({ + selector: "proportional-widget-donut-content-formatters-example", + templateUrl: "./proportional-donut-content-formatters-example.component.html", + styleUrls: [ + "./proportional-donut-content-formatters-example.component.less", + ], + standalone: false, +}) +export class ProportionalWidgetDonutContentFormattersExampleComponent + implements OnInit +{ + public dashboard: IDashboard | undefined; + + // Angular gridster requires a configuration object even if it's empty. + // Pass this to the dashboard component's gridsterConfig input in the template. + public gridsterConfig: GridsterConfig = {}; + + // Boolean passed as an input to the dashboard. When true, widgets can be moved, resized, removed, or edited + public editMode: boolean = false; + + constructor( + // WidgetTypesService provides the widget's necessary structure information + private widgetTypesService: WidgetTypesService, + // In general, the ProviderRegistryService is used for making entities available for injection into dynamically loaded components. + private providerRegistry: ProviderRegistryService, + // registry for adding the formatter for donut content + contentFormattersRegistry: ProportionalDonutContentFormattersRegistryService, + // registry for adding the formatter for proportional legend + legendFormattersRegistry: ProportionalLegendFormattersRegistryService, + // registry for adding the aggregators for donut content + aggregatorRegistry: ProportionalContentAggregatorsRegistryService, + private changeDetectorRef: ChangeDetectorRef + ) { + // on the dashboard startup, it's necessary to add possible content formatters, legend formatters and content aggregators to the registry. + // using registry is a way for setting the available formatters. + legendFormattersRegistry.addItems(DEFAULT_LEGEND_FORMATTERS); + contentFormattersRegistry.addItems( + DEFAULT_PROPORTIONAL_CONTENT_FORMATTERS + ); + aggregatorRegistry.addItems(DEFAULT_PROPORTIONAL_CONTENT_AGGREGATORS); + } + + public ngOnInit(): void { + // Grabbing the widget's default template which will be needed as a parameter for setNode + const widgetTemplate = this.widgetTypesService.getWidgetType( + "proportional", + 1 + ); + + // Registering our data sources as dropdown options in the widget editor/configurator + // Note: This could also be done in the parent module's constructor so that + // multiple dashboards could have access to the same widget template modification. + this.widgetTypesService.setNode( + // This is the template we grabbed above with getWidgetType + widgetTemplate, + // We are setting the editor/configurator part of the widget template + "configurator", + // This indicates which node you are changing and we want to change + // the data source providers available for selection in the editor. + WellKnownPathKey.DataSourceProviders, + // We are setting the data sources available for selection in the editor + [BeerReviewCountsByCityMockDataSource.providerId] + ); + + // Setup of the configurator is done here + this.setupConfigurator(); + + // Registering the data source for injection into the Proportional widget. + this.providerRegistry.setProviders({ + [BeerReviewCountsByCityMockDataSource.providerId]: { + provide: DATA_SOURCE, + useClass: BeerReviewCountsByCityMockDataSource, + deps: [], + }, + }); + + this.initializeDashboard(); + } + + /** Used for restoring widgets state */ + public reInitializeDashboard(): void { + // destroys the components and their providers so the dashboard can re init data + this.dashboard = undefined; + this.changeDetectorRef.detectChanges(); + + this.initializeDashboard(); + } + + private initializeDashboard(): void { + // We're using a static configuration object for this example, but this is where + // the widget's configuration could potentially be populated from a database + const widgetIndex: IWidgets = { + // Complete the proportional widget with information coming from its type definition + [widgetConfig.id]: + this.widgetTypesService.mergeWithWidgetType(widgetConfig), + }; + + // Setting the widget dimensions and position (this is for gridster) + const positions: Record = { + [widgetConfig.id]: { + cols: 6, + rows: 6, + y: 0, + x: 0, + }, + }; + + // Finally, assigning the variables we created above to the dashboard + this.dashboard = { + positions, + widgets: widgetIndex, + }; + } + + /** + * Sets up the configurator sections for proportional donut + */ + private setupConfigurator() { + const widgetTemplate = this.widgetTypesService.getWidgetType( + "proportional", + 1 + ); + + // remove old "presentation", "chartOptionsEditor" and "donutContentConfiguration" sections from the configurator + delete widgetTemplate.configurator?.structure?.presentation; + delete widgetTemplate.configurator?.structure?.chartOptionsEditor; + delete widgetTemplate.configurator?.structure + ?.donutContentConfiguration; + + // add new "presentation" section + this.widgetTypesService.setNode( + widgetTemplate, + "configurator", + "presentation", + DONUT_CONTENT_CONFIGURATION_SLICE.presentation + ); + // add new "chartOptionsEditor" section + this.widgetTypesService.setNode( + widgetTemplate, + "configurator", + "chartOptionsEditor", + DONUT_CONTENT_CONFIGURATION_SLICE.chartOptionsEditor + ); + // add new "donutContentConfiguration" section + this.widgetTypesService.setNode( + widgetTemplate, + "configurator", + "donutContentConfiguration", + DONUT_CONTENT_CONFIGURATION_SLICE.donutContentConfiguration + ); + } +} + +const widgetConfig: IWidget = { + id: "proportionalWidgetId", + type: "proportional", + pizzagna: { + [PizzagnaLayer.Configuration]: { + [DEFAULT_PIZZAGNA_ROOT]: { + providers: {}, + }, + header: { + properties: { + title: "Beer Review Tally by City", + subtitle: "These People Love Beer", + }, + }, + chart: { + providers: { + [WellKnownProviders.DataSource]: { + // Setting the data source providerId for the chart + providerId: + BeerReviewCountsByCityMockDataSource.providerId, + } as IProviderConfiguration, + }, + properties: { + configuration: { + chartOptions: { + type: ProportionalWidgetChartTypes.DonutChart, + legendPlacement: LegendPlacement.Right, + // old configuration looks like this + // contentFormatter: { + // componentType: DonutContentSumFormatterComponent.lateLoadKey, + // }, + + // NEW configuration looks like this + donutContentConfig: { + formatter: { + componentType: + SiUnitsFormatterComponent.lateLoadKey, + }, + aggregator: { + aggregatorType: + sumAggregator.aggregatorType, + properties: { + // example of a default metric to be used for the percentage calculation + // activeMetricId: "austin", + }, + }, + } as IDonutContentConfig, + } as IProportionalWidgetChartOptions, + } as IProportionalWidgetConfig, + }, + }, + }, + }, +}; + +export function getMockBeerReviewCountsByCity(): IMockBeerReview[] { + return [ + { + id: "Brno", + name: "Brno", + data: [Math.round(Math.random() * 1000000)], + icon: "status_down", + link: "https://en.wikipedia.org/wiki/Brno", + value: "Brno", + customDonutContent: "Custom Brno", + }, + { + id: "kyiv", + name: "Kyiv", + data: [Math.round(Math.random() * 1000000)], + icon: "status_critical", + link: "https://en.wikipedia.org/wiki/Kyiv", + value: "Kyiv", + customDonutContent: "Custom Kyiv", + }, + { + id: "austin", + name: "Austin", + data: [Math.round(Math.random() * 1000000)], + icon: "status_warning", + link: "https://en.wikipedia.org/wiki/Austin", + value: "Austin", + customDonutContent: "Custom Austin", + }, + { + id: "lisbon", + name: "Lisbon", + data: [Math.round(Math.random() * 1000000)], + icon: "status_unknown", + link: "https://en.wikipedia.org/wiki/Lisbon", + value: "Lisbon", + customDonutContent: "Custom Lisbon", + }, + { + id: "sydney", + name: "Sydney", + data: [Math.round(Math.random() * 1000000)], + icon: "status_up", + link: "https://en.wikipedia.org/wiki/Sydney", + value: "Sydney", + customDonutContent: "Custom Sydney", + }, + { + id: "nur-sultan", + name: "Nur-Sultan", + data: [Math.round(Math.random() * 1000000)], + icon: "status_unmanaged", + link: "https://en.wikipedia.org/wiki/Nur-Sultan", + value: "Nur-Sultan", + customDonutContent: "Custom Nur-Sultan", + }, + ].sort((a, b) => a.data[0] - b.data[0]); +} +\\\\\\\\\\\\\\\`, + "widget-types/proportional/proportional-widget/proportional-widget-example.component.ts": \\\\\\\\\\\\\\\`// © 2022 SolarWinds Worldwide, LLC. All rights reserved. +// +// Permission is hereby granted, free of charge, to any person obtaining a copy +// of this software and associated documentation files (the "Software"), to +// deal in the Software without restriction, including without limitation the +// rights to use, copy, modify, merge, publish, distribute, sublicense, and/or +// sell copies of the Software, and to permit persons to whom the Software is +// furnished to do so, subject to the following conditions: +// +// The above copyright notice and this permission notice shall be included in +// all copies or substantial portions of the Software. +// +// THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR +// IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, +// FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE +// AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER +// LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, +// OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN +// THE SOFTWARE. + +import { + ChangeDetectorRef, + Component, + Injectable, + OnDestroy, + OnInit, +} from "@angular/core"; +import { GridsterConfig, GridsterItem } from "angular-gridster2"; +import { BehaviorSubject } from "rxjs"; + +import { + DataSourceService, + IDataSource, + IFilteringOutputs, +} from "@nova-ui/bits"; +import { + DATA_SOURCE, + DEFAULT_PIZZAGNA_ROOT, + IDashboard, + IProportionalWidgetChartOptions, + IProportionalWidgetConfig, + IProportionalWidgetData, + IProviderConfiguration, + IRefresherProperties, + IWidget, + IWidgets, + LegendPlacement, + PizzagnaLayer, + ProportionalWidgetChartTypes, + ProviderRegistryService, + WellKnownPathKey, + WellKnownProviders, + WidgetTypesService, +} from "@nova-ui/dashboards"; + +import { IMockBeerReview } from "../models"; + +/** + * A simple proportional data source to retrieve beer review counts by city + */ +@Injectable() +export class BeerReviewCountsByCityMockDataSource + extends DataSourceService + implements IDataSource, OnDestroy +{ + // This is the ID we'll use to identify the provider + public static providerId = "BeerReviewCountsByCityMockDataSource"; + public busy = new BehaviorSubject(false); + + public async getFilteredData(): Promise { + this.busy.next(true); + return new Promise((resolve) => { + setTimeout(() => { + this.outputsSubject.next({ + result: getMockBeerReviewCountsByCity(), + }); + this.busy.next(false); + }, 300); + }); + } + + public ngOnDestroy(): void { + this.outputsSubject.complete(); + } +} + +/** + * A component that instantiates the dashboard + */ +@Component({ + selector: "proportional-widget-example", + templateUrl: "./proportional-widget-example.component.html", + styleUrls: ["./proportional-widget-example.component.less"], + standalone: false, +}) +export class ProportionalWidgetExampleComponent implements OnInit { + // This variable will hold all the data needed to define the layout and behavior of the widgets. + // Pass this to the dashboard component's dashboard input in the template. + public dashboard: IDashboard | undefined; + + // Angular gridster requires a configuration object even if it's empty. + // Pass this to the dashboard component's gridsterConfig input in the template. + public gridsterConfig: GridsterConfig = {}; + + // Boolean passed as an input to the dashboard. When true, widgets can be moved, resized, removed, or edited + public editMode: boolean = false; + + constructor( + // WidgetTypesService provides the widget's necessary structure information + private widgetTypesService: WidgetTypesService, + // In general, the ProviderRegistryService is used for making entities available for injection into dynamically loaded components. + private providerRegistry: ProviderRegistryService, + private changeDetectorRef: ChangeDetectorRef + ) {} + + public ngOnInit(): void { + // Grabbing the widget's default template which will be needed as a parameter for setNode + const widgetTemplate = this.widgetTypesService.getWidgetType( + "proportional", + 1 + ); + + // Registering our data sources as dropdown options in the widget editor/configurator + // Note: This could also be done in the parent module's constructor so that + // multiple dashboards could have access to the same widget template modification. + this.widgetTypesService.setNode( + // This is the template we grabbed above with getWidgetType + widgetTemplate, + // We are setting the editor/configurator part of the widget template + "configurator", + // This indicates which node you are changing and we want to change + // the data source providers available for selection in the editor. + WellKnownPathKey.DataSourceProviders, + // We are setting the data sources available for selection in the editor + [BeerReviewCountsByCityMockDataSource.providerId] + ); + + // Registering the data source for injection into the Proportional widget. + this.providerRegistry.setProviders({ + [BeerReviewCountsByCityMockDataSource.providerId]: { + provide: DATA_SOURCE, + useClass: BeerReviewCountsByCityMockDataSource, + deps: [], + }, + }); + + this.initializeDashboard(); + } + + /** Used for restoring widgets state */ + public reInitializeDashboard(): void { + // destroys the components and their providers so the dashboard can re init data + this.dashboard = undefined; + this.changeDetectorRef.detectChanges(); + + this.initializeDashboard(); + } + + public initializeDashboard(): void { + // We're using a static configuration object for this example, but this is where + // the widget's configuration could potentially be populated from a database + const widgetIndex: IWidgets = { + // Complete the proportional widget with information coming from its type definition + [widgetConfig.id]: + this.widgetTypesService.mergeWithWidgetType(widgetConfig), + }; + + // Setting the widget dimensions and position (this is for gridster) + const positions: Record = { + [widgetConfig.id]: { + cols: 5, + rows: 6, + y: 0, + x: 0, + }, + }; + + // Finally, assigning the variables we created above to the dashboard + this.dashboard = { + positions, + widgets: widgetIndex, + }; + } +} + +const widgetConfig: IWidget = { + id: "proportionalWidgetId", + type: "proportional", + pizzagna: { + [PizzagnaLayer.Configuration]: { + [DEFAULT_PIZZAGNA_ROOT]: { + providers: { + [WellKnownProviders.Refresher]: { + properties: { + // Configuring the refresher interval so that our data source is invoked every ten minutes + interval: 60 * 10, + enabled: true, + } as IRefresherProperties, + } as Partial, + }, + }, + header: { + properties: { + title: "Beer Review Tally by City", + subtitle: "These People Love Beer", + }, + }, + chart: { + providers: { + [WellKnownProviders.DataSource]: { + // Setting the data source providerId for the chart + providerId: + BeerReviewCountsByCityMockDataSource.providerId, + } as IProviderConfiguration, + }, + properties: { + configuration: { + chartOptions: { + type: ProportionalWidgetChartTypes.DonutChart, + legendPlacement: LegendPlacement.Right, + } as IProportionalWidgetChartOptions, + // You can optionally define custom colors for the chart by setting the 'chartColors' configuration property + // "chartColors": [ + // "var(--nui-color-chart-five)", + // "var(--nui-color-chart-six)", + // "var(--nui-color-chart-seven)", + // "var(--nui-color-chart-eight)", + // "var(--nui-color-chart-nine)", + // "var(--nui-color-chart-ten)", + // ], + // or use-mapped structure + chartColors: { + Brno: "var(--nui-color-chart-five)", + kyiv: "var(--nui-color-chart-six)", + austin: "var(--nui-color-chart-seven)", + lisbon: "var(--nui-color-chart-eight)", + sydney: "var(--nui-color-chart-nine)", + "nur-sultan": "var(--nui-color-chart-ten)", + }, + prioritizeWidgetColors: false, + } as IProportionalWidgetConfig, + }, + }, + }, + }, +}; + +export function getMockBeerReviewCountsByCity(): IMockBeerReview[] { + return [ + { + id: "Brno", + name: "Brno", + data: [Math.round(Math.random() * 100000)], + icon: "status_down", + link: "https://en.wikipedia.org/wiki/Brno", + value: "Brno", + color: "var(--nui-color-chart-one)", + }, + { + id: "kyiv", + name: "Kyiv", + data: [Math.round(Math.random() * 100000)], + icon: "status_critical", + link: "https://en.wikipedia.org/wiki/Kyiv", + value: "Kyiv", + color: "var(--nui-color-chart-two)", + }, + { + id: "austin", + name: "Austin", + data: [Math.round(Math.random() * 100000)], + icon: "status_warning", + link: "https://en.wikipedia.org/wiki/Austin", + value: "Austin", + color: "var(--nui-color-chart-three)", + }, + { + id: "lisbon", + name: "Lisbon", + data: [Math.round(Math.random() * 100000)], + icon: "status_unknown", + link: "https://en.wikipedia.org/wiki/Lisbon", + value: "Lisbon", + color: "var(--nui-color-chart-four)", + }, + { + id: "sydney", + name: "Sydney", + data: [Math.round(Math.random() * 100000)], + icon: "status_up", + link: "https://en.wikipedia.org/wiki/Sydney", + value: "Sydney", + color: "var(--nui-color-chart-five)", + }, + { + id: "nur-sultan", + name: "Nur-Sultan", + data: [Math.round(Math.random() * 100000)], + icon: "status_unmanaged", + link: "https://en.wikipedia.org/wiki/Nur-Sultan", + value: "Nur-Sultan", + color: "var(--nui-color-chart-six)", + }, + ].sort((a, b) => a.data[0] - b.data[0]); +} +\\\\\\\\\\\\\\\`, + "widget-types/proportional/proportional-widget-interactive/proportional-widget-interactive-example.component.ts": \\\\\\\\\\\\\\\`// © 2022 SolarWinds Worldwide, LLC. All rights reserved. +// +// Permission is hereby granted, free of charge, to any person obtaining a copy +// of this software and associated documentation files (the "Software"), to +// deal in the Software without restriction, including without limitation the +// rights to use, copy, modify, merge, publish, distribute, sublicense, and/or +// sell copies of the Software, and to permit persons to whom the Software is +// furnished to do so, subject to the following conditions: +// +// The above copyright notice and this permission notice shall be included in +// all copies or substantial portions of the Software. +// +// THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR +// IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, +// FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE +// AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER +// LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, +// OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN +// THE SOFTWARE. + +import { + ChangeDetectorRef, + Component, + Injectable, + OnDestroy, + OnInit, +} from "@angular/core"; +import { GridsterConfig, GridsterItem } from "angular-gridster2"; +import keyBy from "lodash/keyBy"; +import { BehaviorSubject } from "rxjs"; + +import { + DataSourceService, + IDataSource, + IFilteringOutputs, +} from "@nova-ui/bits"; +import { + DATA_SOURCE, + DEFAULT_PIZZAGNA_ROOT, + IDashboard, + IProportionalWidgetChartOptions, + IProportionalWidgetConfig, + IProportionalWidgetData, + IProviderConfiguration, + IWidget, + LegendPlacement, + NOVA_URL_INTERACTION_HANDLER, + PizzagnaLayer, + ProportionalWidgetChartTypes, + ProviderRegistryService, + WellKnownPathKey, + WellKnownProviders, + WidgetTypesService, +} from "@nova-ui/dashboards"; + +import { IMockBeerReview } from "../models"; + +/** + * A simple proportional data source to retrieve beer review counts by city + */ +@Injectable() +export class ReviewCountsByCityMockDataSource + extends DataSourceService + implements IDataSource, OnDestroy +{ + // This is the ID we'll use to identify the provider + public static providerId = "ReviewCountsByCityMockDataSource"; + public busy = new BehaviorSubject(false); + + public async getFilteredData(): Promise { + this.busy.next(true); + return new Promise((resolve) => { + setTimeout(() => { + this.outputsSubject.next({ + result: getMockBeerReviewCountsByCity(), + }); + this.busy.next(false); + }, 300); + }); + } + + public ngOnDestroy(): void { + this.outputsSubject.complete(); + } +} + +/** + * A component that instantiates the dashboard + */ +@Component({ + selector: "proportional-widget-interactive-example", + templateUrl: "./proportional-widget-interactive-example.component.html", + styleUrls: ["./proportional-widget-interactive-example.component.less"], + standalone: false, +}) +export class ProportionalWidgetInteractiveExampleComponent implements OnInit { + // This variable will hold all the data needed to define the layout and behavior of the widgets. + // Pass this to the dashboard component's dashboard input in the template. + public dashboard: IDashboard | undefined; + + // Angular gridster requires a configuration object even if it's empty. + // Pass this to the dashboard component's gridsterConfig input in the template. + public gridsterConfig: GridsterConfig = {}; + + // Boolean passed as an input to the dashboard. When true, widgets can be moved, resized, removed, or edited + public editMode: boolean = false; + + constructor( + // WidgetTypesService provides the widget's necessary structure information + private widgetTypesService: WidgetTypesService, + // In general, the ProviderRegistryService is used for making entities available for injection into dynamically loaded components. + private providerRegistry: ProviderRegistryService, + private changeDetectorRef: ChangeDetectorRef + ) {} + + public ngOnInit(): void { + // Grabbing the widget's default template which will be needed as a parameter for setNode + const widgetTemplate = this.widgetTypesService.getWidgetType( + "proportional", + 1 + ); + + // Registering our data sources as dropdown options in the widget editor/configurator + // Note: This could also be done in the parent module's constructor so that + // multiple dashboards could have access to the same widget template modification. + this.widgetTypesService.setNode( + // This is the template we grabbed above with getWidgetType + widgetTemplate, + // We are setting the editor/configurator part of the widget template + "configurator", + // This indicates which node you are changing and we want to change + // the data source providers available for selection in the editor. + WellKnownPathKey.DataSourceProviders, + // We are setting the data sources available for selection in the editor + [ReviewCountsByCityMockDataSource.providerId] + ); + + // Registering the data source for injection into the Proportional widget. + this.providerRegistry.setProviders({ + [ReviewCountsByCityMockDataSource.providerId]: { + provide: DATA_SOURCE, + useClass: ReviewCountsByCityMockDataSource, + deps: [], + }, + }); + + this.initializeDashboard(); + } + + /** Used for restoring widgets state */ + public reInitializeDashboard(): void { + // destroys the components and their providers so the dashboard can re init data + this.dashboard = undefined; + this.changeDetectorRef.detectChanges(); + + this.initializeDashboard(); + } + + public initializeDashboard(): void { + // We're using a static configuration object for this example, but this is where + // the widget's configuration could potentially be populated from a database + const widgetsWithStructure = widgetConfigs.map((w) => + this.widgetTypesService.mergeWithWidgetType(w) + ); + const widgetsIndex = keyBy(widgetsWithStructure, (w: IWidget) => w.id); + + // Setting the widget dimensions and position (this is for gridster) + const positions: Record = { + [widgetConfigs[0].id]: { + cols: 6, + rows: 6, + y: 0, + x: 0, + }, + [widgetConfigs[1].id]: { + cols: 6, + rows: 6, + y: 0, + x: 6, + }, + }; + + // Finally, assigning the variables we created above to the dashboard + this.dashboard = { + positions, + widgets: widgetsIndex, + }; + } +} + +const widgetConfigs: IWidget[] = [ + { + id: "widget1", + type: "proportional", + pizzagna: { + [PizzagnaLayer.Configuration]: { + [DEFAULT_PIZZAGNA_ROOT]: { + providers: { + // Configuring the UrlInteractionHandler to handle interactions + [WellKnownProviders.InteractionHandler]: { + providerId: NOVA_URL_INTERACTION_HANDLER, + properties: { + // the 'url' property tells the handler what link to use when interaction occurs on the series + // if the series does not have a link we are passing one to the handler + url: "\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\${data.link || 'https://en.wikipedia.org/wiki/'+data.id}", + // by default the link is opened in the current window, set 'newWindow' to true to open in a new tab instead + // newWindow: true, + }, + }, + }, + }, + header: { + properties: { + title: "Proportional Widget", + subtitle: "With interaction handler", + }, + }, + chart: { + providers: { + [WellKnownProviders.DataSource]: { + // Setting the data source providerId for the chart + providerId: + ReviewCountsByCityMockDataSource.providerId, + } as IProviderConfiguration, + }, + properties: { + configuration: { + // Setting the interactive to true + interactive: true, + chartOptions: { + type: ProportionalWidgetChartTypes.VerticalBarChart, + legendPlacement: LegendPlacement.Bottom, + } as IProportionalWidgetChartOptions, + prioritizeWidgetColors: false, + } as IProportionalWidgetConfig, + }, + }, + }, + }, + }, + { + id: "widget2", + type: "proportional", + pizzagna: { + [PizzagnaLayer.Configuration]: { + header: { + properties: { + title: "Proportional Widget", + subtitle: "Without interaction handler", + }, + }, + chart: { + providers: { + [WellKnownProviders.DataSource]: { + // Setting the data source providerId for the chart + providerId: + ReviewCountsByCityMockDataSource.providerId, + } as IProviderConfiguration, + }, + properties: { + configuration: { + // interactive set to false so series without links are not styled like a link + interactive: false, + chartOptions: { + type: ProportionalWidgetChartTypes.HorizontalBarChart, + legendPlacement: LegendPlacement.Bottom, + } as IProportionalWidgetChartOptions, + prioritizeWidgetColors: false, + } as IProportionalWidgetConfig, + }, + }, + }, + }, + }, +]; + +export function getMockBeerReviewCountsByCity(): IMockBeerReview[] { + return [ + { + id: "Brno", + name: "Brno", + data: [Math.round(Math.random() * 100000)], + icon: "status_down", + link: "https://en.wikipedia.org/wiki/Brno", + value: "Brno", + color: "var(--nui-color-chart-one)", + }, + { + id: "kyiv", + name: "Kyiv", + data: [Math.round(Math.random() * 100000)], + icon: "status_critical", + link: "https://en.wikipedia.org/wiki/Kyiv", + value: "Kyiv", + color: "var(--nui-color-chart-two)", + }, + { + id: "austin", + name: "Austin", + data: [Math.round(Math.random() * 100000)], + icon: "status_warning", + value: "Austin", + color: "var(--nui-color-chart-three)", + }, + { + id: "lisbon", + name: "Lisbon", + data: [Math.round(Math.random() * 100000)], + icon: "status_unknown", + link: "https://en.wikipedia.org/wiki/Lisbon", + value: "Lisbon", + color: "var(--nui-color-chart-four)", + }, + { + id: "sydney", + name: "Sydney", + data: [Math.round(Math.random() * 100000)], + icon: "status_up", + value: "Sydney", + color: "var(--nui-color-chart-five)", + }, + { + id: "nur-sultan", + name: "Nur-Sultan", + data: [Math.round(Math.random() * 100000)], + icon: "status_unmanaged", + value: "Nur-Sultan", + color: "var(--nui-color-chart-six)", + }, + ].sort((a, b) => a.data[0] - b.data[0]); +} +\\\\\\\\\\\\\\\`, + "widget-types/risk-score/risk-score-docs.component.ts": \\\\\\\\\\\\\\\`// © 2023 SolarWinds Worldwide, LLC. All rights reserved. +// +// Permission is hereby granted, free of charge, to any person obtaining a copy +// of this software and associated documentation files (the "Software"), to +// deal in the Software without restriction, including without limitation the +// rights to use, copy, modify, merge, publish, distribute, sublicense, and/or +// sell copies of the Software, and to permit persons to whom the Software is +// furnished to do so, subject to the following conditions: +// +// The above copyright notice and this permission notice shall be included in +// all copies or substantial portions of the Software. +// +// THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR +// IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, +// FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE +// AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER +// LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, +// OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN +// THE SOFTWARE. + +import { Component, OnInit } from "@angular/core"; + +import { mapContentFile } from "../../../../demo-files-factory"; + +@Component({ + selector: "nui-risk-score-docs", + templateUrl: "./risk-score-docs.component.html", + standalone: false, +}) +export class RiskScoreDocsComponent implements OnInit { + public riskScoreWidgetFileText = ""; + public riskScoreConfiguratorFileText = ""; + + public async ngOnInit(): Promise { + this.riskScoreWidgetFileText = await import( + "./../../../../../../src/lib/widget-types/risk-score/risk-score-widget" + ).then(mapContentFile); + this.riskScoreConfiguratorFileText = await import( + "./../../../../../../src/lib/widget-types/risk-score/risk-score-configurator" + ).then(mapContentFile); + } +} +\\\\\\\\\\\\\\\`, + "widget-types/risk-score/risk-score-docs.module.ts": \\\\\\\\\\\\\\\`// © 2023 SolarWinds Worldwide, LLC. All rights reserved. +// +// Permission is hereby granted, free of charge, to any person obtaining a copy +// of this software and associated documentation files (the "Software"), to +// deal in the Software without restriction, including without limitation the +// rights to use, copy, modify, merge, publish, distribute, sublicense, and/or +// sell copies of the Software, and to permit persons to whom the Software is +// furnished to do so, subject to the following conditions: +// +// The above copyright notice and this permission notice shall be included in +// all copies or substantial portions of the Software. +// +// THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR +// IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, +// FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE +// AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER +// LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, +// OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN +// THE SOFTWARE. + +import { NgModule } from "@angular/core"; +import { RouterModule, Routes } from "@angular/router"; + +import { + DEMO_PATH_TOKEN, + NuiButtonModule, + NuiDocsModule, + NuiMessageModule, + NuiSwitchModule, +} from "@nova-ui/bits"; +import { NuiDashboardsModule } from "@nova-ui/dashboards"; + +import { RiskScoreDocsComponent } from "./risk-score-docs.component"; +import { RiskScoreWidgetExampleComponent } from "./risk-score-widget-example/risk-score-widget-example.component"; +import { getDemoFiles } from "../../../../demo-files-factory"; + +const routes: Routes = [ + { + path: "", + component: RiskScoreDocsComponent, + data: { + srlc: { + hideIndicator: true, + }, + showThemeSwitcher: true, + }, + }, + { + path: "example", + component: RiskScoreWidgetExampleComponent, + data: { + srlc: { + hideIndicator: true, + }, + }, + }, +]; + +@NgModule({ + imports: [ + RouterModule.forChild(routes), + NuiButtonModule, + NuiDocsModule, + NuiMessageModule, + NuiDashboardsModule, + NuiSwitchModule, + ], + declarations: [RiskScoreDocsComponent, RiskScoreWidgetExampleComponent], + providers: [ + { + provide: DEMO_PATH_TOKEN, + useValue: getDemoFiles("risk-score"), + }, + ], +}) +export default class RiskScoreDocsModule {} +\\\\\\\\\\\\\\\`, + "widget-types/risk-score/risk-score-widget-example/risk-score-widget-example.component.ts": \\\\\\\\\\\\\\\`// © 2023 SolarWinds Worldwide, LLC. All rights reserved. +// +// Permission is hereby granted, free of charge, to any person obtaining a copy +// of this software and associated documentation files (the "Software"), to +// deal in the Software without restriction, including without limitation the +// rights to use, copy, modify, merge, publish, distribute, sublicense, and/or +// sell copies of the Software, and to permit persons to whom the Software is +// furnished to do so, subject to the following conditions: +// +// The above copyright notice and this permission notice shall be included in +// all copies or substantial portions of the Software. +// +// THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR +// IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, +// FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE +// AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER +// LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, +// OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN +// THE SOFTWARE. + +import { HttpClient, HttpErrorResponse } from "@angular/common/http"; +import { Component, Injectable, OnDestroy, OnInit } from "@angular/core"; +import { GridsterConfig, GridsterItem } from "angular-gridster2"; +import { BehaviorSubject } from "rxjs"; +import { finalize } from "rxjs/operators"; + +import { DataSourceService, IFilteringOutputs } from "@nova-ui/bits"; +import { + DATA_SOURCE, + DEFAULT_PIZZAGNA_ROOT, + IDashboard, + IRiskScoreData, + IProviderConfiguration, + IRefresherProperties, + IWidget, + IWidgets, + RiskScoreTileComponent, + NOVA_KPI_DATASOURCE_ADAPTER, + PizzagnaLayer, + ProviderRegistryService, + WellKnownPathKey, + WellKnownProviders, + WidgetTypesService, +} from "@nova-ui/dashboards"; + +/** + * A simple KPI data source to retrieve the average rating of Harry Potter and the Sorcerer's Stone (book) via googleapis + */ +@Injectable() +export class AverageRatingRiskScoreDataSource + extends DataSourceService + implements OnDestroy +{ + // This is the ID we'll use to identify the provider + public static providerId = "AverageRatingRiskScoreDataSource"; + + // Use this subject to communicate the data source's busy state + public busy = new BehaviorSubject(false); + + constructor(private http: HttpClient) { + super(); + } + + // In this example, getFilteredData is invoked every 10 minutes (Take a look at the refresher + // provider definition in the widget configuration below to see how the interval is set) + public async getFilteredData(): Promise { + this.busy.next(true); + return new Promise((resolve) => { + // *** Make a resource request to an external API (if needed) + this.http + .get("https://www.googleapis.com/books/v1/volumes/5MQFrgEACAAJ") + .pipe(finalize(() => this.busy.next(false))) + .subscribe({ + next: (data: any) => { + resolve({ + result: { + value: data.volumeInfo.averageRating, + }, + }); + }, + error: (error: HttpErrorResponse) => { + resolve({ + result: null, + error: { + type: error.status, + }, + }); + }, + }); + }); + } + + public ngOnDestroy(): void { + this.outputsSubject.complete(); + } +} + +/** + * A component that instantiates the dashboard + */ +@Component({ + selector: "risk-score-widget-example", + templateUrl: "./risk-score-widget-example.component.html", + styleUrls: ["./risk-score-widget-example.component.less"], + standalone: false, +}) +export class RiskScoreWidgetExampleComponent implements OnInit { + // This variable will hold all the data needed to define the layout and behavior of the widgets. + // Pass this to the dashboard component's dashboard input in the template. + public dashboard: IDashboard; + + // Angular gridster requires a configuration object even if it's empty. + // Pass this to the dashboard component's gridsterConfig input in the template. + public gridsterConfig: GridsterConfig = {}; + + // Boolean passed as an input to the dashboard. When true, widgets can be moved, resized, removed, or edited + public editMode: boolean = false; + + constructor( + // WidgetTypesService provides the widget's necessary structure information + private widgetTypesService: WidgetTypesService, + + // In general, the ProviderRegistryService is used for making entities available for injection into dynamically loaded components. + private providerRegistry: ProviderRegistryService + ) {} + + public ngOnInit(): void { + // Grabbing the widget's default template which will be needed as a parameter for setNode + const widgetTemplate = this.widgetTypesService.getWidgetType( + "risk-score", + 1 + ); + // Registering our data sources as dropdown options in the widget editor/configurator + // Note: This could also be done in the parent module's constructor so that + // multiple dashboards could have access to the same widget template modification. + this.widgetTypesService.setNode( + // This is the template we grabbed above with getWidgetType + widgetTemplate, + // We are setting the editor/configurator part of the widget template + "configurator", + // This indicates which node you are changing and we want to change + // the data source providers available for selection in the editor. + WellKnownPathKey.DataSourceProviders, + // We are setting the data sources available for selection in the editor + [AverageRatingRiskScoreDataSource.providerId] + ); + + // Registering the data source for injection into the KPI tile. + // Note: Each tile of a KPI widget is assigned its own instance of the data source + this.providerRegistry.setProviders({ + [AverageRatingRiskScoreDataSource.providerId]: { + provide: DATA_SOURCE, + useClass: AverageRatingRiskScoreDataSource, + // Any dependencies that need to be injected into the provider must be listed here + deps: [HttpClient], + }, + }); + + this.initializeDashboard(); + } + + public initializeDashboard(): void { + // We're using a static configuration object for this example, but this is where + // the widget's configuration could potentially be populated from a database + const riskScoreWidget = widgetConfig; + const widgetIndex: IWidgets = { + // Complete the KPI widget with information coming from its type definition + [riskScoreWidget.id]: + this.widgetTypesService.mergeWithWidgetType(riskScoreWidget), + }; + + // Setting the widget dimensions and position (this is for gridster) + const positions: Record = { + [riskScoreWidget.id]: { + cols: 4, + rows: 6, + y: 0, + x: 0, + }, + }; + + // Finally, assigning the variables we created above to the dashboard + this.dashboard = { + positions, + widgets: widgetIndex, + }; + } +} + +const widgetConfig: IWidget = { + id: "riskScoreWidgetId", + type: "risk-score", + pizzagna: { + [PizzagnaLayer.Configuration]: { + [DEFAULT_PIZZAGNA_ROOT]: { + providers: { + [WellKnownProviders.Refresher]: { + properties: { + // Configuring the refresher interval so that our data source is invoked every ten minutes + interval: 60 * 10, + enabled: true, + } as IRefresherProperties, + } as Partial, + }, + }, + header: { + properties: { + title: "Harry Potter and the Sorcerer's Stone", + subtitle: "By J. K. Rowling", + }, + }, + tiles: { + properties: { + nodes: ["riskScore1"], + }, + }, + riskScore1: { + id: "riskScore1", + componentType: RiskScoreTileComponent.lateLoadKey, + properties: { + widgetData: { + minValue: 0, + maxValue: 5, + useStaticLabel: false, + staticLabel: undefined, + label: \\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\`Average Rating\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\`, + description: \\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\`Harry Potter and the Sorcerer's Stone By J. K. Rowling Average Rating Risk Score\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\`, + }, + }, + providers: { + [WellKnownProviders.DataSource]: { + // Setting the data source providerId for the tile with id "kpi1" + providerId: AverageRatingRiskScoreDataSource.providerId, + } as IProviderConfiguration, + [WellKnownProviders.Adapter]: { + providerId: NOVA_KPI_DATASOURCE_ADAPTER, + properties: { + componentId: "riskScore1", + propertyPath: "widgetData", + }, + } as IProviderConfiguration, + }, + }, + }, + }, +}; +\\\\\\\\\\\\\\\`, + "widget-types/table/table-docs.component.ts": \\\\\\\\\\\\\\\`// © 2022 SolarWinds Worldwide, LLC. All rights reserved. +// +// Permission is hereby granted, free of charge, to any person obtaining a copy +// of this software and associated documentation files (the "Software"), to +// deal in the Software without restriction, including without limitation the +// rights to use, copy, modify, merge, publish, distribute, sublicense, and/or +// sell copies of the Software, and to permit persons to whom the Software is +// furnished to do so, subject to the following conditions: +// +// The above copyright notice and this permission notice shall be included in +// all copies or substantial portions of the Software. +// +// THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR +// IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, +// FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE +// AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER +// LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, +// OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN +// THE SOFTWARE. + +import { Component, OnInit } from "@angular/core"; + +import { mapContentFile } from "../../../../demo-files-factory"; + +@Component({ + selector: "nui-table-docs", + templateUrl: "./table-docs.component.html", + standalone: false, +}) +export class TableDocsComponent implements OnInit { + public widgetFileText = ""; + public configuratorFileText = ""; + + public async ngOnInit(): Promise { + this.widgetFileText = await import( + "./../../../../../../src/lib/widget-types/table/table-widget" + ).then(mapContentFile); + this.configuratorFileText = await import( + "./../../../../../../src/lib/widget-types/table/table-configurator" + ).then(mapContentFile); + } +} +\\\\\\\\\\\\\\\`, + "widget-types/table/table-docs.module.ts": \\\\\\\\\\\\\\\`// © 2022 SolarWinds Worldwide, LLC. All rights reserved. +// +// Permission is hereby granted, free of charge, to any person obtaining a copy +// of this software and associated documentation files (the "Software"), to +// deal in the Software without restriction, including without limitation the +// rights to use, copy, modify, merge, publish, distribute, sublicense, and/or +// sell copies of the Software, and to permit persons to whom the Software is +// furnished to do so, subject to the following conditions: +// +// The above copyright notice and this permission notice shall be included in +// all copies or substantial portions of the Software. +// +// THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR +// IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, +// FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE +// AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER +// LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, +// OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN +// THE SOFTWARE. + +import { NgModule } from "@angular/core"; +import { RouterModule, Routes } from "@angular/router"; + +import { DEMO_PATH_TOKEN } from "@nova-ui/bits"; +import { + NuiButtonModule, + NuiDocsModule, + NuiMessageModule, + NuiSwitchModule, +} from "@nova-ui/bits"; +import { + NuiDashboardsModule, + TableFormatterRegistryService, +} from "@nova-ui/dashboards"; + +import { TableDocsComponent } from "./table-docs.component"; +import { TablePaginatorDocsComponent } from "./table-paginator-docs.component"; +import { TableSelectableDocsComponent } from "./table-selectable-docs.component"; +import { TableWidgetExampleComponent } from "./table-widget/table-widget-example.component"; +import { TableWidgetInteractiveExampleComponent } from "./table-widget-interactive/table-widget-interactive-example.component"; +import { TableWidgetPaginatorExampleComponent } from "./table-widget-paginator/table-widget-paginator-example.component"; +import { TableWidgetSearchExampleComponent } from "./table-widget-search/table-widget-search-example.component"; +import { TableSearchDocsComponent } from "./table-widget-search-docs.component"; +import { TableWidgetSelectableMultiExampleComponent } from "./table-widget-selectable/table-widget-selectable-multi/table-widget-selectable-multi.example.component"; +import { TableWidgetSelectableRadioExampleComponent } from "./table-widget-selectable/table-widget-selectable-radio/table-widget-selectable-radio.example.component"; +import { TableWidgetSelectableSingleExampleComponent } from "./table-widget-selectable/table-widget-selectable-single/table-widget-selectable-single.example.component"; +import { TableWidgetSelectableExampleComponent } from "./table-widget-selectable/table-widget-selectable.example.component"; +import { DEFAULT_TABLE_FORMATTERS } from "../../../../../../src/lib/widget-types/table/default-table-formatters"; +import { getDemoFiles } from "../../../../demo-files-factory"; + +const routes: Routes = [ + { + path: "", + component: TableDocsComponent, + data: { + srlc: { + hideIndicator: true, + }, + showThemeSwitcher: true, + }, + }, + { + path: "example", + component: TableWidgetExampleComponent, + data: { + srlc: { + hideIndicator: true, + }, + }, + }, + { + path: "table-search", + component: TableSearchDocsComponent, + data: { + srlc: { + hideIndicator: true, + }, + showThemeSwitcher: true, + }, + }, + { + path: "table-paginator", + component: TablePaginatorDocsComponent, + data: { + srlc: { + hideIndicator: true, + }, + showThemeSwitcher: true, + }, + }, + { + path: "table-select", + component: TableSelectableDocsComponent, + data: { + srlc: { + hideIndicator: true, + }, + showThemeSwitcher: true, + }, + }, +]; + +@NgModule({ + imports: [ + RouterModule.forChild(routes), + NuiButtonModule, + NuiDocsModule, + NuiMessageModule, + NuiSwitchModule, + NuiDashboardsModule, + ], + declarations: [ + TableDocsComponent, + TableSearchDocsComponent, + TablePaginatorDocsComponent, + TableWidgetPaginatorExampleComponent, + TableSelectableDocsComponent, + TableWidgetInteractiveExampleComponent, + TableWidgetExampleComponent, + TableWidgetSearchExampleComponent, + TableWidgetSelectableExampleComponent, + TableWidgetSelectableMultiExampleComponent, + TableWidgetSelectableSingleExampleComponent, + TableWidgetSelectableRadioExampleComponent, + ], + providers: [ + { + provide: DEMO_PATH_TOKEN, + useValue: getDemoFiles("table"), + }, + ], +}) +export default class TableDocsModule { + constructor(tableFormattersRegistryService: TableFormatterRegistryService) { + tableFormattersRegistryService.addItems(DEFAULT_TABLE_FORMATTERS); + } +} +\\\\\\\\\\\\\\\`, + "widget-types/table/table-paginator-docs.component.ts": \\\\\\\\\\\\\\\`// © 2022 SolarWinds Worldwide, LLC. All rights reserved. +// +// Permission is hereby granted, free of charge, to any person obtaining a copy +// of this software and associated documentation files (the "Software"), to +// deal in the Software without restriction, including without limitation the +// rights to use, copy, modify, merge, publish, distribute, sublicense, and/or +// sell copies of the Software, and to permit persons to whom the Software is +// furnished to do so, subject to the following conditions: +// +// The above copyright notice and this permission notice shall be included in +// all copies or substantial portions of the Software. +// +// THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR +// IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, +// FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE +// AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER +// LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, +// OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN +// THE SOFTWARE. + +import { Component } from "@angular/core"; + +@Component({ + selector: "nui-table-paginator-docs", + templateUrl: "./table-paginator-docs.component.html", + standalone: false, +}) +export class TablePaginatorDocsComponent { + public tableConfigurationText = \\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\` + "table": { + ... + properties: { + configuration: { + // define paginator configuration here + scrollType: ScrollType.paginator, + paginatorConfiguration: { + pageSize: 10, // Value have to be one of pageSizeSet values + pageSizeSet: [10, 20, 30], + }, + // If not specified, default is set to + // pageSize: 10, + // pageSizeSet: [10, 20, 50], + hasVirtualScroll: false, // Has to be speciefied because of backward compatibility + } as ITableWidgetConfig, + }, + }, + \\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\`; +} +\\\\\\\\\\\\\\\`, + "widget-types/table/table-selectable-docs.component.ts": \\\\\\\\\\\\\\\`// © 2022 SolarWinds Worldwide, LLC. All rights reserved. +// +// Permission is hereby granted, free of charge, to any person obtaining a copy +// of this software and associated documentation files (the "Software"), to +// deal in the Software without restriction, including without limitation the +// rights to use, copy, modify, merge, publish, distribute, sublicense, and/or +// sell copies of the Software, and to permit persons to whom the Software is +// furnished to do so, subject to the following conditions: +// +// The above copyright notice and this permission notice shall be included in +// all copies or substantial portions of the Software. +// +// THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR +// IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, +// FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE +// AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER +// LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, +// OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN +// THE SOFTWARE. + +import { Component } from "@angular/core"; + +@Component({ + selector: "nui-table-selectable-docs", + templateUrl: "./table-selectable-docs.component.html", + standalone: false, +}) +export class TableSelectableDocsComponent { + public tableConfigurationText = \\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\` + "table": { + ... + properties: { + // enabling selection here + selectionConfiguration: { + // whether the selection is enabled or disabled + enabled: true, + // can be Multi | Radio | Single + selectionMode: TableSelectionMode.Multi, + // property that uniquely identifies row in a table + trackByProperty: "id", + // whether clicking on row should select it + clickableRow: true, + }, + }, + }, + \\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\`; + + public eventSubscriptionText = \\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\` +... +constructor(Inject(PIZZAGNA_EVENT_BUS) eventBus: EventBus) { + eventBus + .getStream(SELECTION) + // don't forget to unsubscribe! + .subscribe((selection: ISelection) => ...) +} +... + \\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\`; +} +\\\\\\\\\\\\\\\`, + "widget-types/table/table-widget/table-widget-example.component.ts": \\\\\\\\\\\\\\\`// © 2022 SolarWinds Worldwide, LLC. All rights reserved. +// +// Permission is hereby granted, free of charge, to any person obtaining a copy +// of this software and associated documentation files (the "Software"), to +// deal in the Software without restriction, including without limitation the +// rights to use, copy, modify, merge, publish, distribute, sublicense, and/or +// sell copies of the Software, and to permit persons to whom the Software is +// furnished to do so, subject to the following conditions: +// +// The above copyright notice and this permission notice shall be included in +// all copies or substantial portions of the Software. +// +// THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR +// IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, +// FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE +// AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER +// LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, +// OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN +// THE SOFTWARE. + +import { ChangeDetectorRef, Component, OnInit } from "@angular/core"; +import { GridsterConfig, GridsterItem } from "angular-gridster2"; +import orderBy from "lodash/orderBy"; +import { BehaviorSubject, firstValueFrom, from } from "rxjs"; +import { map, tap } from "rxjs/operators"; + +import { + DataSourceService, + IDataField, + INovaFilteringOutputs, + INovaFilters, + nameof, +} from "@nova-ui/bits"; +import { + DATA_SOURCE, + IDashboard, + ITableWidgetColumnConfig, + IWidget, + IWidgets, + ProviderRegistryService, + WellKnownPathKey, + WellKnownProviders, + WidgetTypesService, +} from "@nova-ui/dashboards"; + +export const BREW_API_URL = "https://api.punkapi.com/v2/beers"; + +export interface IBrewInfo { + id: string; + name: string; + tagline: string; + first_brewed: string; + description: string; + brewers_tips: string; +} + +export interface IBrewDatasourceResponse { + brewInfo: IBrewInfo[]; + total: number; +} + +export class BeerDataSource extends DataSourceService { + public static providerId = "BeerDataSource"; + + private cache: IBrewInfo[] = []; + + public busy = new BehaviorSubject(false); + + public dataFields: Array = [ + { + id: nameof("id"), + label: "No", + dataType: "number", + sortable: true, + }, + // To indicate that a column should not be sortable, set the optional IDataField 'sortable' property to false + { + id: nameof("name"), + label: "Name", + dataType: "string", + sortable: true, + }, + { + id: nameof("tagline"), + label: "Tagline", + dataType: "string", + sortable: true, + }, + { + id: nameof("first_brewed"), + label: "First Brewed", + dataType: "string", + sortable: true, + }, + { + id: nameof("description"), + label: "Description", + dataType: "string", + sortable: false, + }, + { + id: nameof("brewers_tips"), + label: "Brewer's Tips", + dataType: "string", + sortable: false, + }, + ]; + + public async getFilteredData( + filters: INovaFilters + ): Promise { + const start = filters.virtualScroll?.value?.start ?? 0; + const end = filters.virtualScroll?.value?.end ?? 0; + + // Resetting cache on first page request + if (start === 0) { + this.cache = []; + } + + // extract sorter settings to send to the backend + // filters.sorterValue.sortBy; filters.sorterValue.direction + return firstValueFrom( + from(this.fetch(start, end)).pipe( + tap((response: IBrewDatasourceResponse | undefined) => { + if (!response) { + return; + } + this.cache = this.sortData( + this.cache.concat(response.brewInfo), + filters + ); + this.dataSubject.next(this.cache); + }), + map(() => ({ + repeat: { itemsSource: this.cache }, + dataFields: this.dataFields, + })) + ) + ); + } + + public async fetch( + start: number, + end: number + ): Promise { + const delta: number = end - start; + const currentPage: number = end / delta || 0; + const response: object | Array = await ( + await fetch( + \\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\`\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\${BREW_API_URL}/?page=\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\${currentPage}&per_page=\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\${delta}\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\` + ) + ).json(); + + // Note: In case request fails we should not proceed with mapping + if (!Array.isArray(response)) { + return undefined; + } + + return { + brewInfo: response.map((result: IBrewInfo) => ({ + id: result.id, + name: result.name, + tagline: result.tagline, + first_brewed: result.first_brewed, + description: result.description, + brewers_tips: result.brewers_tips, + })), + total: response.length, + }; + } + + private sortData(data: IBrewInfo[], filters: INovaFilters): IBrewInfo[] { + return orderBy( + data, + filters.sorter?.value?.sortBy, + filters.sorter?.value?.direction as "desc" | "asc" + ); + } +} + +/** + * A component that instantiates the dashboard + */ +@Component({ + selector: "table-widget-example", + templateUrl: "./table-widget-example.component.html", + styleUrls: ["./table-widget-example.component.less"], + standalone: false, +}) +export class TableWidgetExampleComponent implements OnInit { + // This variable will hold all the data needed to define the layout and behavior of the widgets. + // Pass this to the dashboard component's dashboard input in the template. + public dashboard: IDashboard | undefined; + + // Angular gridster requires a configuration object even if it's empty. + // Pass this to the dashboard component's gridsterConfig input in the template. + public gridsterConfig: GridsterConfig = {}; + + // Boolean passed as an input to the dashboard. When true, widgets can be moved, resized, removed, or edited + public editMode: boolean = false; + + constructor( + // WidgetTypesService provides the widget's necessary structure information + private widgetTypesService: WidgetTypesService, + // In general, the ProviderRegistryService is used for making entities available for injection into dynamically loaded components. + private providerRegistry: ProviderRegistryService, + private changeDetectorRef: ChangeDetectorRef + ) {} + + public ngOnInit(): void { + // Grabbing the widget's default template which will be needed as a parameter for setNode + const widgetTemplate = this.widgetTypesService.getWidgetType( + "table", + 1 + ); + + // Registering our data sources as dropdown options in the widget editor/configurator + // Note: This could also be done in the parent module's constructor so that + // multiple dashboards could have access to the same widget template modification. + this.widgetTypesService.setNode( + // This is the template we grabbed above with getWidgetType + widgetTemplate, + // We are setting the editor/configurator part of the widget template + "configurator", + // This indicates which node you are changing and we want to change + // the data source providers available for selection in the editor. + WellKnownPathKey.DataSourceProviders, + // We are setting the data sources available for selection in the editor + [BeerDataSource.providerId] + ); + + // Registering the data source for injection into the widget. + this.providerRegistry.setProviders({ + [BeerDataSource.providerId]: { + provide: DATA_SOURCE, + useClass: BeerDataSource, + // Any dependencies that need to be injected into the provider must be listed here + deps: [], + }, + }); + + this.initializeDashboard(); + } + + /** Used for restoring widgets state */ + public reInitializeDashboard(): void { + // destroys the components and their providers so the dashboard can re init data + this.dashboard = undefined; + this.changeDetectorRef.detectChanges(); + + this.initializeDashboard(); + } + + public initializeDashboard(): void { + // We're using a static configuration object for this example, but this is where + // the widget's configuration could potentially be populated from a database + const tableWidget = widgetConfig; + const widgetIndex: IWidgets = { + // Enhance the widget with information coming from it's type definition + [tableWidget.id]: + this.widgetTypesService.mergeWithWidgetType(tableWidget), + }; + + // Setting the widget dimensions and position (this is for gridster) + const positions: Record = { + [tableWidget.id]: { + cols: 12, + rows: 6, + y: 0, + x: 0, + }, + }; + + // Finally, assigning the variables we created above to the dashboard + this.dashboard = { + positions, + widgets: widgetIndex, + }; + } +} + +const TABLE_COLUMNS: ITableWidgetColumnConfig[] = [ + { + id: "column1", + label: $localize\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\`Beer Name\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\`, + isActive: true, + width: 185, + formatter: { + componentType: "RawFormatterComponent", + properties: { + dataFieldIds: { + value: "name", + }, + }, + }, + }, + { + id: "column2", + label: $localize\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\`Tagline\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\`, + isActive: true, + width: 250, + formatter: { + componentType: "RawFormatterComponent", + properties: { + dataFieldIds: { + value: "tagline", + }, + }, + }, + }, + { + id: "column3", + label: $localize\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\`First Brewed\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\`, + isActive: true, + width: 100, + formatter: { + componentType: "RawFormatterComponent", + properties: { + dataFieldIds: { + value: "first_brewed", + }, + }, + }, + }, + { + id: "column4", + label: $localize\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\`Description\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\`, + isActive: true, + formatter: { + componentType: "RawFormatterComponent", + properties: { + dataFieldIds: { + value: "description", + }, + }, + }, + }, +]; + +export const widgetConfig: IWidget = { + id: "tableWidgetId", + type: "table", + pizzagna: { + configuration: { + header: { + properties: { + title: "Stupendous Suds", + subtitle: "Try These Brilliant Brews", + }, + }, + table: { + providers: { + [WellKnownProviders.DataSource]: { + providerId: BeerDataSource.providerId, + }, + }, + properties: { + configuration: { + columns: TABLE_COLUMNS, + sortable: true, + sorterConfiguration: { + descendantSorting: false, + sortBy: "", + }, + hasVirtualScroll: true, + }, + }, + }, + }, + }, +}; +\\\\\\\\\\\\\\\`, + "widget-types/table/table-widget-interactive/table-widget-interactive-example.component.ts": \\\\\\\\\\\\\\\`// © 2022 SolarWinds Worldwide, LLC. All rights reserved. +// +// Permission is hereby granted, free of charge, to any person obtaining a copy +// of this software and associated documentation files (the "Software"), to +// deal in the Software without restriction, including without limitation the +// rights to use, copy, modify, merge, publish, distribute, sublicense, and/or +// sell copies of the Software, and to permit persons to whom the Software is +// furnished to do so, subject to the following conditions: +// +// The above copyright notice and this permission notice shall be included in +// all copies or substantial portions of the Software. +// +// THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR +// IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, +// FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE +// AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER +// LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, +// OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN +// THE SOFTWARE. + +import { ChangeDetectorRef, Component, OnInit } from "@angular/core"; +import { GridsterConfig, GridsterItem } from "angular-gridster2"; +import orderBy from "lodash/orderBy"; +import { BehaviorSubject, firstValueFrom, from } from "rxjs"; +import { map, tap } from "rxjs/operators"; + +import { + DataSourceService, + IDataField, + INovaFilteringOutputs, + INovaFilters, + nameof, +} from "@nova-ui/bits"; +import { + DATA_SOURCE, + DEFAULT_PIZZAGNA_ROOT, + IDashboard, + ITableWidgetColumnConfig, + IWidget, + IWidgets, + NOVA_URL_INTERACTION_HANDLER, + ProviderRegistryService, + WellKnownPathKey, + WellKnownProviders, + WidgetTypesService, +} from "@nova-ui/dashboards"; + +export const BREW_API_URL = "https://api.punkapi.com/v2/beers"; + +export interface IBrewInfo { + id: string; + name: string; + tagline: string; + first_brewed: string; + description: string; + brewers_tips: string; +} + +export interface IBrewDatasourceResponse { + brewInfo: IBrewInfo[]; + total: number; +} + +export class MockBeerDataSource extends DataSourceService { + public static providerId = "MockBeerDataSource"; + + private cache: IBrewInfo[] = []; + + public busy = new BehaviorSubject(false); + + public dataFields: Array = [ + { + id: nameof("id"), + label: "No", + dataType: "number", + sortable: true, + }, + // To indicate that a column should not be sortable, set the optional IDataField 'sortable' property to false + { + id: nameof("name"), + label: "Name", + dataType: "string", + sortable: true, + }, + { + id: nameof("tagline"), + label: "Tagline", + dataType: "string", + sortable: true, + }, + { + id: nameof("first_brewed"), + label: "First Brewed", + dataType: "string", + sortable: true, + }, + { + id: nameof("description"), + label: "Description", + dataType: "string", + sortable: false, + }, + { + id: nameof("brewers_tips"), + label: "Brewer's Tips", + dataType: "string", + sortable: false, + }, + ]; + + public async getFilteredData( + filters: INovaFilters + ): Promise { + const start = filters.virtualScroll?.value?.start ?? 0; + const end = filters.virtualScroll?.value?.end ?? 0; + + // Resetting cache on first page request + if (start === 0) { + this.cache = []; + } + + // extract sorter settings to send to the backend + // filters.sorterValue.sortBy; filters.sorterValue.direction + return firstValueFrom( + from(this.fetch(start, end)).pipe( + tap((response) => { + if (!response) { + return; + } + this.cache = this.sortData( + this.cache.concat(response.brewInfo), + filters + ); + this.dataSubject.next(this.cache); + }), + map(() => ({ + repeat: { itemsSource: this.cache }, + dataFields: this.dataFields, + })) + ) + ); + } + + public async fetch( + start: number, + end: number + ): Promise { + const delta: number = end - start; + const currentPage: number = end / delta || 0; + const response: object | Array = await ( + await fetch( + \\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\`\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\${BREW_API_URL}/?page=\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\${currentPage}&per_page=\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\${delta}\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\` + ) + ).json(); + console.log( + "📘 table-widget-interactive-example.component: 85# -> response:", + response + ); + + // Note: In case request fails we should not proceed with mapping + if (!Array.isArray(response)) { + return undefined; + } + + return { + brewInfo: response.map((result: IBrewInfo) => ({ + id: result.id, + name: result.name, + tagline: result.tagline, + first_brewed: result.first_brewed, + description: result.description, + brewers_tips: result.brewers_tips, + })), + total: response.length, + }; + } + + private sortData(data: IBrewInfo[], filters: INovaFilters): IBrewInfo[] { + return orderBy( + data, + filters.sorter?.value?.sortBy, + filters.sorter?.value?.direction as "desc" | "asc" + ); + } +} + +/** + * A component that instantiates the dashboard + */ +@Component({ + selector: "table-widget-interactive-example", + templateUrl: "./table-widget-interactive-example.component.html", + styleUrls: ["./table-widget-interactive-example.component.less"], + standalone: false, +}) +export class TableWidgetInteractiveExampleComponent implements OnInit { + // This variable will hold all the data needed to define the layout and behavior of the widgets. + // Pass this to the dashboard component's dashboard input in the template. + public dashboard: IDashboard | undefined; + + // Angular gridster requires a configuration object even if it's empty. + // Pass this to the dashboard component's gridsterConfig input in the template. + public gridsterConfig: GridsterConfig = {}; + + // Boolean passed as an input to the dashboard. When true, widgets can be moved, resized, removed, or edited + public editMode: boolean = false; + + constructor( + // WidgetTypesService provides the widget's necessary structure information + private widgetTypesService: WidgetTypesService, + // In general, the ProviderRegistryService is used for making entities available for injection into dynamically loaded components. + private providerRegistry: ProviderRegistryService, + private changeDetectorRef: ChangeDetectorRef + ) {} + + public ngOnInit(): void { + // Grabbing the widget's default template which will be needed as a parameter for setNode + const widgetTemplate = this.widgetTypesService.getWidgetType( + "table", + 1 + ); + + // Registering our data sources as dropdown options in the widget editor/configurator + // Note: This could also be done in the parent module's constructor so that + // multiple dashboards could have access to the same widget template modification. + this.widgetTypesService.setNode( + // This is the template we grabbed above with getWidgetType + widgetTemplate, + // We are setting the editor/configurator part of the widget template + "configurator", + // This indicates which node you are changing and we want to change + // the data source providers available for selection in the editor. + WellKnownPathKey.DataSourceProviders, + // We are setting the data sources available for selection in the editor + [MockBeerDataSource.providerId] + ); + + // Registering the data source for injection into the widget. + this.providerRegistry.setProviders({ + [MockBeerDataSource.providerId]: { + provide: DATA_SOURCE, + useClass: MockBeerDataSource, + // Any dependencies that need to be injected into the provider must be listed here + deps: [], + }, + }); + + this.initializeDashboard(); + } + + /** Used for restoring widgets state */ + public reInitializeDashboard(): void { + // destroys the components and their providers so the dashboard can re init data + this.dashboard = undefined; + this.changeDetectorRef.detectChanges(); + + this.initializeDashboard(); + } + + public initializeDashboard(): void { + // We're using a static configuration object for this example, but this is where + // the widget's configuration could potentially be populated from a database + const tableWidget = widgetConfig; + const widgetIndex: IWidgets = { + // Enhance the widget with information coming from it's type definition + [tableWidget.id]: + this.widgetTypesService.mergeWithWidgetType(tableWidget), + }; + + // Setting the widget dimensions and position (this is for gridster) + const positions: Record = { + [tableWidget.id]: { + cols: 12, + rows: 6, + y: 0, + x: 0, + }, + }; + + // Finally, assigning the variables we created above to the dashboard + this.dashboard = { + positions, + widgets: widgetIndex, + }; + } +} + +const TABLE_COLUMNS: ITableWidgetColumnConfig[] = [ + { + id: "column1", + label: $localize\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\`Beer Name\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\`, + isActive: true, + width: 185, + formatter: { + componentType: "RawFormatterComponent", + properties: { + dataFieldIds: { + value: "name", + }, + }, + }, + }, + { + id: "column2", + label: $localize\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\`Tagline\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\`, + isActive: true, + width: 250, + formatter: { + componentType: "RawFormatterComponent", + properties: { + dataFieldIds: { + value: "tagline", + }, + }, + }, + }, + { + id: "column3", + label: $localize\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\`First Brewed\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\`, + isActive: true, + width: 100, + formatter: { + componentType: "RawFormatterComponent", + properties: { + dataFieldIds: { + value: "first_brewed", + }, + }, + }, + }, + { + id: "column4", + label: $localize\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\`Description\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\`, + isActive: true, + formatter: { + componentType: "RawFormatterComponent", + properties: { + dataFieldIds: { + value: "description", + }, + }, + }, + }, +]; + +export const widgetConfig: IWidget = { + id: "tableWidgetId", + type: "table", + pizzagna: { + configuration: { + [DEFAULT_PIZZAGNA_ROOT]: { + providers: { + [WellKnownProviders.InteractionHandler]: { + // Configuring the UrlInteractionHandler to handle interactions + providerId: NOVA_URL_INTERACTION_HANDLER, + properties: { + // the 'url' property tells the handler what link to use when interaction occurs on the series + url: "\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\${'https://untappd.com/search?q='+data.name}", + // by default the link is opened in the current window, set 'newWindow' to true to open in a new tab instead + newWindow: true, + }, + }, + }, + }, + header: { + properties: { + title: "Stupendous Suds", + subtitle: "Try These Brilliant Brews", + }, + }, + table: { + providers: { + [WellKnownProviders.DataSource]: { + providerId: MockBeerDataSource.providerId, + }, + }, + properties: { + configuration: { + // set interactions to true on the table + interactive: true, + columns: TABLE_COLUMNS, + sortable: true, + sorterConfiguration: { + descendantSorting: false, + sortBy: "", + }, + hasVirtualScroll: true, + }, + }, + }, + }, + }, +}; +\\\\\\\\\\\\\\\`, + "widget-types/table/table-widget-paginator/table-widget-paginator-example.component.ts": \\\\\\\\\\\\\\\`// © 2022 SolarWinds Worldwide, LLC. All rights reserved. +// +// Permission is hereby granted, free of charge, to any person obtaining a copy +// of this software and associated documentation files (the "Software"), to +// deal in the Software without restriction, including without limitation the +// rights to use, copy, modify, merge, publish, distribute, sublicense, and/or +// sell copies of the Software, and to permit persons to whom the Software is +// furnished to do so, subject to the following conditions: +// +// The above copyright notice and this permission notice shall be included in +// all copies or substantial portions of the Software. +// +// THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR +// IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, +// FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE +// AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER +// LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, +// OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN +// THE SOFTWARE. + +import { HttpClient } from "@angular/common/http"; +import { ChangeDetectorRef, Component, OnInit } from "@angular/core"; +import { GridsterConfig, GridsterItem } from "angular-gridster2"; + +import { LoggerService } from "@nova-ui/bits"; +import { + DATA_SOURCE, + DEFAULT_PIZZAGNA_ROOT, + IDashboard, + IProviderConfiguration, + ITableWidgetConfig, + IWidget, + IWidgets, + NOVA_URL_INTERACTION_HANDLER, + PizzagnaLayer, + ProviderRegistryService, + RawFormatterComponent, + ScrollType, + WellKnownPathKey, + WellKnownProviders, + WidgetTypesService, +} from "@nova-ui/dashboards"; + +import { AcmeTableMockDataSource } from "../../../../prototypes/data/table/acme-table-mock-data-source.service"; + +/** + * A component that instantiates the dashboard + */ +@Component({ + selector: "table-widget-paginator-example", + templateUrl: "./table-widget-paginator-example.component.html", + styleUrls: ["./table-widget-paginator-example.component.less"], + standalone: false, +}) +export class TableWidgetPaginatorExampleComponent implements OnInit { + public dashboard: IDashboard | undefined; + public gridsterConfig: GridsterConfig = {}; + public editMode: boolean = false; + + constructor( + private widgetTypesService: WidgetTypesService, + private providerRegistry: ProviderRegistryService, + private changeDetectorRef: ChangeDetectorRef + ) {} + + public ngOnInit(): void { + const widgetTemplate = this.widgetTypesService.getWidgetType( + "table", + 1 + ); + this.widgetTypesService.setNode( + widgetTemplate, + "configurator", + WellKnownPathKey.DataSourceProviders, + [AcmeTableMockDataSource.providerId] + ); + + this.providerRegistry.setProviders({ + [AcmeTableMockDataSource.providerId]: { + provide: DATA_SOURCE, + useClass: AcmeTableMockDataSource, + deps: [LoggerService, HttpClient], + }, + }); + + this.initializeDashboard(); + } + + /** Used for restoring widgets state */ + public reInitializeDashboard(): void { + // destroys the components and their providers so the dashboard can re init data + this.dashboard = undefined; + this.changeDetectorRef.detectChanges(); + + this.initializeDashboard(); + } + + public initializeDashboard(): void { + const tableWithPaginator = tableWidgetWithPaginator; + const tableWithVirtualScroll = tableWidgetWithVirtualScroll; + + const widgetIndex: IWidgets = { + [tableWithPaginator.id]: + this.widgetTypesService.mergeWithWidgetType(tableWithPaginator), + [tableWithVirtualScroll.id]: + this.widgetTypesService.mergeWithWidgetType( + tableWithVirtualScroll + ), + }; + + const positions: Record = { + [tableWithPaginator.id]: { + cols: 6, + rows: 6, + y: 0, + x: 0, + }, + [tableWithVirtualScroll.id]: { + cols: 6, + rows: 6, + y: 0, + x: 0, + }, + }; + + this.dashboard = { + positions, + widgets: widgetIndex, + }; + } +} + +export const tableWidgetWithPaginator: IWidget = { + id: "widget1", + type: "table", + pizzagna: { + [PizzagnaLayer.Configuration]: { + [DEFAULT_PIZZAGNA_ROOT]: { + providers: { + [WellKnownProviders.InteractionHandler]: { + providerId: NOVA_URL_INTERACTION_HANDLER, + }, + }, + }, + header: { + properties: { + title: "Table Widget with paginator!", + subtitle: "Basic table widget", + collapsible: true, + }, + }, + table: { + providers: { + [WellKnownProviders.DataSource]: { + providerId: AcmeTableMockDataSource.providerId, + } as IProviderConfiguration, + }, + properties: { + configuration: { + interactive: true, + columns: [ + { + id: "column1", + label: "No.", + isActive: true, + formatter: { + componentType: + RawFormatterComponent.lateLoadKey, + properties: { + dataFieldIds: { + value: "position", + }, + }, + }, + }, + { + id: "column2", + label: "Name", + isActive: true, + formatter: { + componentType: + RawFormatterComponent.lateLoadKey, + properties: { + dataFieldIds: { + value: "name", + }, + }, + }, + }, + { + id: "column3", + label: "Status", + isActive: true, + formatter: { + componentType: + RawFormatterComponent.lateLoadKey, + properties: { + dataFieldIds: { + value: "status", + }, + }, + }, + }, + ], + sorterConfiguration: { + descendantSorting: false, + sortBy: "column1", + }, + scrollType: ScrollType.paginator, + paginatorConfiguration: { + pageSize: 5, + pageSizeSet: [5, 10, 20, 30], + }, + hasVirtualScroll: false, + searchConfiguration: { + enabled: true, + }, + } as ITableWidgetConfig, + }, + }, + }, + }, +}; + +export const tableWidgetWithVirtualScroll: IWidget = { + id: "widget2", + type: "table", + pizzagna: { + [PizzagnaLayer.Configuration]: { + [DEFAULT_PIZZAGNA_ROOT]: { + providers: { + [WellKnownProviders.InteractionHandler]: { + providerId: NOVA_URL_INTERACTION_HANDLER, + }, + }, + }, + header: { + properties: { + title: "Table Widget with virtual scroll!", + subtitle: "Basic table widget", + collapsible: true, + }, + }, + table: { + providers: { + [WellKnownProviders.DataSource]: { + providerId: AcmeTableMockDataSource.providerId, + } as IProviderConfiguration, + }, + properties: { + configuration: { + interactive: true, + columns: [ + { + id: "column1", + label: "No.", + isActive: true, + formatter: { + componentType: + RawFormatterComponent.lateLoadKey, + properties: { + dataFieldIds: { + value: "position", + }, + }, + }, + }, + { + id: "column2", + label: "Name", + isActive: true, + formatter: { + componentType: + RawFormatterComponent.lateLoadKey, + properties: { + dataFieldIds: { + value: "name", + }, + }, + }, + }, + { + id: "column3", + label: "Status", + isActive: true, + formatter: { + componentType: + RawFormatterComponent.lateLoadKey, + properties: { + dataFieldIds: { + value: "status", + }, + }, + }, + }, + ], + sorterConfiguration: { + descendantSorting: false, + sortBy: "column1", + }, + hasVirtualScroll: true, + searchConfiguration: { + enabled: true, + }, + } as ITableWidgetConfig, + }, + }, + }, + }, +}; +\\\\\\\\\\\\\\\`, + "widget-types/table/table-widget-search/table-widget-search-example.component.ts": \\\\\\\\\\\\\\\`// © 2022 SolarWinds Worldwide, LLC. All rights reserved. +// +// Permission is hereby granted, free of charge, to any person obtaining a copy +// of this software and associated documentation files (the "Software"), to +// deal in the Software without restriction, including without limitation the +// rights to use, copy, modify, merge, publish, distribute, sublicense, and/or +// sell copies of the Software, and to permit persons to whom the Software is +// furnished to do so, subject to the following conditions: +// +// The above copyright notice and this permission notice shall be included in +// all copies or substantial portions of the Software. +// +// THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR +// IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, +// FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE +// AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER +// LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, +// OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN +// THE SOFTWARE. + +import { HttpClient } from "@angular/common/http"; +import { + ChangeDetectorRef, + Component, + Injectable, + OnInit, +} from "@angular/core"; +import { GridsterConfig, GridsterItem } from "angular-gridster2"; +import isEqual from "lodash/isEqual"; +import isNil from "lodash/isNil"; +import { BehaviorSubject, firstValueFrom, Observable, of, Subject } from "rxjs"; +import { + catchError, + delay, + finalize, + map, + // eslint-disable-next-line import/no-deprecated + switchMap, + tap, +} from "rxjs/operators"; + +import { + DataSourceFeatures, + DataSourceService, + IDataField, + IDataSource, + IDataSourceFeatures, + IDataSourceFeaturesConfiguration, + IDataSourceOutput, + IFilter, + IFilters, + INovaFilteringOutputs, + INovaFilters, + LoggerService, +} from "@nova-ui/bits"; +import { + DATA_SOURCE, + IDashboard, + ITableWidgetConfig, + IWidget, + IWidgets, + ProviderRegistryService, + WellKnownPathKey, + WellKnownProviders, + WidgetTypesService, +} from "@nova-ui/dashboards"; + +import { GBOOKS_API_URL } from "../../../../prototypes/data/table/constants"; + +interface IGBooksApiResponse { + kind: string; + totalItems: number; + items: IGBooksItemModel[]; + [key: string]: any; +} + +interface IGBooksItemModel { + id: string; + volumeInfo: { + title: string; + subtitle: string; + authors: string[]; + [key: string]: any; + }; + accessInfo: { [key: string]: any }; + saleInfo: { [key: string]: any }; +} + +interface IGBooksData { + books: IGBooksVolume[]; + totalItems: number; +} + +interface IGBooksVolume { + title: string; + authors: string; +} + +type searchableColumnType = "title" | "authors"; + +@Injectable() +export class AcmeTableGBooksDataSource + extends DataSourceService + implements IDataSource +{ + public static providerId = "AcmeTableGBooksDataSource"; + public static mockError = false; + + public searchableColumn: searchableColumnType = "title"; + + public page: number = 1; + public busy = new BehaviorSubject(false); + public features: IDataSourceFeaturesConfiguration; + + private cache = Array.from({ length: 0 }); + private previousFilters: INovaFilters; + // DataSource Features declared + private supportedFeatures: IDataSourceFeatures = { + search: { enabled: true }, + pagination: { enabled: true }, + }; + private columnToQueryParamMap: { [k in searchableColumnType]: string } = { + title: "intitle", + authors: "inauthor", + }; + + private applyFilters$ = new Subject(); + + public dataFields: Array = [ + { + id: "title", + label: $localize\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\`Title\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\`, + dataType: "string", + sortable: false, + }, + { + id: "authors", + label: $localize\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\`Authors\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\`, + dataType: "string", + sortable: false, + }, + ]; + + constructor(private logger: LoggerService, private http: HttpClient) { + super(); + // Using Nova DataSourceFeatures implementation for the features + this.features = new DataSourceFeatures(this.supportedFeatures); + + this.applyFilters$ + // eslint-disable-next-line import/no-deprecated + .pipe(switchMap((filters) => this.getData(filters))) + .subscribe(async (res) => { + this.outputsSubject.next(await this.getFilteredData(res)); + }); + } + + public async getFilteredData( + booksData: IGBooksData + ): Promise> { + return firstValueFrom( + of(booksData).pipe( + tap((response) => { + this.cache = this.cache.concat(response.books); + }), + map((response) => ({ + result: { + repeat: { itemsSource: this.cache }, + paginator: { total: response.totalItems }, + dataFields: this.dataFields, + }, + })) + ) + ); + } + + private getData(filters: INovaFilters): Observable { + if ( + this.isNewSearchTerm(filters.search) && + filters.virtualScroll?.value.start === 0 + ) { + this.cache = []; + } + + return this.http + .get(this.getComposedUrl(filters)) + .pipe( + tap(() => this.busy.next(true)), + delay(300), // mock + map((response) => ({ + books: + response.items?.map((volume) => ({ + title: volume.volumeInfo.title, + authors: + volume.volumeInfo.authors?.join(", ") || "", + })) || [], + totalItems: response.totalItems, + })), + catchError((e) => { + this.logger.error(e); + return of({ + books: [], + totalItems: 0, + }); + }), + finalize(() => { + this.busy.next(false); + this.previousFilters = filters; + }) + ); + } + + private getComposedUrl(filters: INovaFilters) { + const initialUrl = \\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\`\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\${GBOOKS_API_URL}?q=\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\`; + const maxResults = \\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\`maxResults=\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\${ + (filters.virtualScroll?.value.end || 0) - + (filters.virtualScroll?.value.start || 0) + }\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\`; + + const virtualScrollPart = filters.virtualScroll + ? \\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\`startIndex=\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\${filters.virtualScroll.value.start}\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\` + : ""; + + const searchQueryParam = + this.columnToQueryParamMap[this.searchableColumn]; + const searchPart = filters.search + ? \\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\`\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\${searchQueryParam}:\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\${filters.search.value}\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\` + : "_"; // google books api requires some criteria to do the search + + return \\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\`\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\${initialUrl}\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\${searchPart}&\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\${maxResults}&\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\${virtualScrollPart}&filter=full\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\`; + } + + private isNewSearchTerm(search: IFilter | undefined) { + return ( + !isNil(search?.value) && + !isEqual(search?.value, this.previousFilters?.search?.value) + ); + } + + // redefine parent method + public async applyFilters(): Promise { + this.applyFilters$.next(this.getFilters()); + } +} + +/** + * A component that instantiates the dashboard + */ +@Component({ + selector: "table-widget-search-example", + templateUrl: "./table-widget-search-example.component.html", + styleUrls: ["./table-widget-search-example.component.less"], + standalone: false, +}) +export class TableWidgetSearchExampleComponent implements OnInit { + public dashboard: IDashboard | undefined; + public gridsterConfig: GridsterConfig = {}; + public editMode: boolean = false; + + constructor( + private widgetTypesService: WidgetTypesService, + private providerRegistry: ProviderRegistryService, + private changeDetectorRef: ChangeDetectorRef + ) {} + + public ngOnInit(): void { + const widgetTemplate = this.widgetTypesService.getWidgetType( + "table", + 1 + ); + this.widgetTypesService.setNode( + widgetTemplate, + "configurator", + WellKnownPathKey.DataSourceProviders, + [AcmeTableGBooksDataSource.providerId] + ); + + this.providerRegistry.setProviders({ + [AcmeTableGBooksDataSource.providerId]: { + provide: DATA_SOURCE, + useClass: AcmeTableGBooksDataSource, + deps: [LoggerService, HttpClient], + }, + }); + + this.initializeDashboard(); + } + + /** Used for restoring widgets state */ + public reInitializeDashboard(): void { + // destroys the components and their providers so the dashboard can re init data + this.dashboard = undefined; + this.changeDetectorRef.detectChanges(); + + this.initializeDashboard(); + } + + public initializeDashboard(): void { + const tableWidget = widgetConfig; + const widgetIndex: IWidgets = { + [tableWidget.id]: + this.widgetTypesService.mergeWithWidgetType(tableWidget), + }; + + const positions: Record = { + [tableWidget.id]: { + cols: 12, + rows: 6, + y: 0, + x: 0, + }, + }; + + this.dashboard = { + positions, + widgets: widgetIndex, + }; + } +} + +export const widgetConfig: IWidget = { + id: "tableWidgetId", + type: "table", + pizzagna: { + configuration: { + header: { + properties: { + title: "Google Books", + }, + }, + table: { + providers: { + [WellKnownProviders.DataSource]: { + providerId: AcmeTableGBooksDataSource.providerId, + }, + }, + properties: { + configuration: { + columns: [ + { + id: "column1", + label: $localize\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\`Title\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\`, + isActive: true, + formatter: { + componentType: "RawFormatterComponent", + properties: { + dataFieldIds: { + value: "title", + }, + }, + }, + }, + { + id: "column2", + label: $localize\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\`Author\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\`, + isActive: true, + formatter: { + componentType: "RawFormatterComponent", + properties: { + dataFieldIds: { + value: "authors", + }, + }, + }, + }, + ], + sortable: false, + // define search configuration here + searchConfiguration: { + enabled: true, + // following properties below can be configured as well + // searchTerm: "search criteria here", + // searchDebounce: 300, + }, + hasVirtualScroll: true, + } as ITableWidgetConfig, + }, + }, + }, + }, +}; +\\\\\\\\\\\\\\\`, + "widget-types/table/table-widget-search-docs.component.ts": \\\\\\\\\\\\\\\`// © 2022 SolarWinds Worldwide, LLC. All rights reserved. +// +// Permission is hereby granted, free of charge, to any person obtaining a copy +// of this software and associated documentation files (the "Software"), to +// deal in the Software without restriction, including without limitation the +// rights to use, copy, modify, merge, publish, distribute, sublicense, and/or +// sell copies of the Software, and to permit persons to whom the Software is +// furnished to do so, subject to the following conditions: +// +// The above copyright notice and this permission notice shall be included in +// all copies or substantial portions of the Software. +// +// THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR +// IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, +// FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE +// AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER +// LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, +// OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN +// THE SOFTWARE. + +import { Component } from "@angular/core"; + +@Component({ + selector: "nui-table-search-docs", + templateUrl: "./table-widget-search-docs.component.html", + standalone: false, +}) +export class TableSearchDocsComponent { + public featuredDeclaredText = \\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\` + private supportedFeatures: IDataSourceFeatures = { + search: { enabled: true }, + pagination: { enabled: true }, + };\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\`; + public featuresUsedText = \\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\` + this.features = new DataSourceFeatures(this.supportedFeatures); + \\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\`; + public tableConfigurationText = \\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\` + "table": { + ... + properties: { + configuration: { + // define search configuration here + searchConfiguration: { + enabled: true, + // following optional properties below can be configured as well + // searchTerm: "search criteria here", + // searchDebounce: 300, + }, + } as ITableWidgetConfig, + }, + }, + \\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\`; +} +\\\\\\\\\\\\\\\`, + "widget-types/table/table-widget-selectable/table-widget-selectable-multi/table-widget-selectable-multi.example.component.ts": \\\\\\\\\\\\\\\`// © 2022 SolarWinds Worldwide, LLC. All rights reserved. +// +// Permission is hereby granted, free of charge, to any person obtaining a copy +// of this software and associated documentation files (the "Software"), to +// deal in the Software without restriction, including without limitation the +// rights to use, copy, modify, merge, publish, distribute, sublicense, and/or +// sell copies of the Software, and to permit persons to whom the Software is +// furnished to do so, subject to the following conditions: +// +// The above copyright notice and this permission notice shall be included in +// all copies or substantial portions of the Software. +// +// THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR +// IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, +// FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE +// AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER +// LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, +// OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN +// THE SOFTWARE. + +import { Component } from "@angular/core"; + +import { TableSelectionMode } from "@nova-ui/bits"; +import { TableWidgetSelectionConfig } from "@nova-ui/dashboards"; + +/** + * A component that instantiates the dashboard + */ +@Component({ + selector: "table-widget-selectable-multi-example", + templateUrl: "./table-widget-selectable-multi.example.component.html", + styleUrls: ["./table-widget-selectable-multi.example.component.less"], + standalone: false, +}) +export class TableWidgetSelectableMultiExampleComponent { + public selectionConfiguration: TableWidgetSelectionConfig = { + enabled: true, + selectionMode: TableSelectionMode.Multi, + trackByProperty: "id", + clickableRow: true, + }; +} +\\\\\\\\\\\\\\\`, + "widget-types/table/table-widget-selectable/table-widget-selectable-radio/table-widget-selectable-radio.example.component.ts": \\\\\\\\\\\\\\\`// © 2022 SolarWinds Worldwide, LLC. All rights reserved. +// +// Permission is hereby granted, free of charge, to any person obtaining a copy +// of this software and associated documentation files (the "Software"), to +// deal in the Software without restriction, including without limitation the +// rights to use, copy, modify, merge, publish, distribute, sublicense, and/or +// sell copies of the Software, and to permit persons to whom the Software is +// furnished to do so, subject to the following conditions: +// +// The above copyright notice and this permission notice shall be included in +// all copies or substantial portions of the Software. +// +// THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR +// IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, +// FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE +// AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER +// LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, +// OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN +// THE SOFTWARE. + +import { Component } from "@angular/core"; + +import { TableSelectionMode } from "@nova-ui/bits"; +import { TableWidgetSelectionConfig } from "@nova-ui/dashboards"; + +/** + * A component that instantiates the dashboard + */ +@Component({ + selector: "table-widget-selectable-radio-example", + templateUrl: "./table-widget-selectable-radio.example.component.html", + styleUrls: ["./table-widget-selectable-radio.example.component.less"], + standalone: false, +}) +export class TableWidgetSelectableRadioExampleComponent { + public selectionConfiguration: TableWidgetSelectionConfig = { + enabled: true, + selectionMode: TableSelectionMode.Radio, + trackByProperty: "id", + clickableRow: true, + }; +} +\\\\\\\\\\\\\\\`, + "widget-types/table/table-widget-selectable/table-widget-selectable-single/table-widget-selectable-single.example.component.ts": \\\\\\\\\\\\\\\`// © 2022 SolarWinds Worldwide, LLC. All rights reserved. +// +// Permission is hereby granted, free of charge, to any person obtaining a copy +// of this software and associated documentation files (the "Software"), to +// deal in the Software without restriction, including without limitation the +// rights to use, copy, modify, merge, publish, distribute, sublicense, and/or +// sell copies of the Software, and to permit persons to whom the Software is +// furnished to do so, subject to the following conditions: +// +// The above copyright notice and this permission notice shall be included in +// all copies or substantial portions of the Software. +// +// THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR +// IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, +// FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE +// AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER +// LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, +// OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN +// THE SOFTWARE. + +import { Component } from "@angular/core"; + +import { TableSelectionMode } from "@nova-ui/bits"; +import { TableWidgetSelectionConfig } from "@nova-ui/dashboards"; + +/** + * A component that instantiates the dashboard + */ +@Component({ + selector: "table-widget-selectable-single-example", + templateUrl: "./table-widget-selectable-single.example.component.html", + styleUrls: ["./table-widget-selectable-single.example.component.less"], + standalone: false, +}) +export class TableWidgetSelectableSingleExampleComponent { + public selectionConfiguration: TableWidgetSelectionConfig = { + enabled: true, + selectionMode: TableSelectionMode.Single, + trackByProperty: "id", + }; +} +\\\\\\\\\\\\\\\`, + "widget-types/table/table-widget-selectable/table-widget-selectable.example.component.ts": \\\\\\\\\\\\\\\`// © 2022 SolarWinds Worldwide, LLC. All rights reserved. +// +// Permission is hereby granted, free of charge, to any person obtaining a copy +// of this software and associated documentation files (the "Software"), to +// deal in the Software without restriction, including without limitation the +// rights to use, copy, modify, merge, publish, distribute, sublicense, and/or +// sell copies of the Software, and to permit persons to whom the Software is +// furnished to do so, subject to the following conditions: +// +// The above copyright notice and this permission notice shall be included in +// all copies or substantial portions of the Software. +// +// THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR +// IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, +// FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE +// AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER +// LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, +// OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN +// THE SOFTWARE. + +import { HttpClient } from "@angular/common/http"; +import { ChangeDetectorRef, Component, Input, OnInit } from "@angular/core"; +import { GridsterConfig, GridsterItem } from "angular-gridster2"; + +import { LoggerService, TableSelectionMode } from "@nova-ui/bits"; +import { + DATA_SOURCE, + DEFAULT_PIZZAGNA_ROOT, + IDashboard, + IProviderConfiguration, + ITableWidgetConfig, + IWidget, + IWidgets, + NOVA_URL_INTERACTION_HANDLER, + PizzagnaLayer, + ProviderRegistryService, + RawFormatterComponent, + TableWidgetSelectionConfig, + WellKnownPathKey, + WellKnownProviders, + WidgetTypesService, +} from "@nova-ui/dashboards"; + +import { AcmeTableMockDataSource } from "../../../../prototypes/data/table/acme-table-mock-data-source.service"; + +/** + * A component that instantiates the dashboard + */ +@Component({ + selector: "table-widget-selectable-example", + templateUrl: "./table-widget-selectable.example.component.html", + styleUrls: ["./table-widget-selectable.example.component.less"], + standalone: false, +}) +export class TableWidgetSelectableExampleComponent implements OnInit { + public dashboard: IDashboard | undefined; + public gridsterConfig: GridsterConfig = {}; + public editMode: boolean = false; + + @Input() public selectionConfiguration: TableWidgetSelectionConfig = { + enabled: false, + selectionMode: TableSelectionMode.None, + }; + + constructor( + private widgetTypesService: WidgetTypesService, + private providerRegistry: ProviderRegistryService, + private changeDetectorRef: ChangeDetectorRef + ) {} + + public ngOnInit(): void { + const widgetTemplate = this.widgetTypesService.getWidgetType( + "table", + 1 + ); + this.widgetTypesService.setNode( + widgetTemplate, + "configurator", + WellKnownPathKey.DataSourceProviders, + [AcmeTableMockDataSource.providerId] + ); + + this.providerRegistry.setProviders({ + [AcmeTableMockDataSource.providerId]: { + provide: DATA_SOURCE, + useClass: AcmeTableMockDataSource, + deps: [LoggerService, HttpClient], + }, + }); + + this.initializeDashboard(); + } + + /** Used for restoring widgets state */ + public reInitializeDashboard(): void { + // destroys the components and their providers so the dashboard can re init data + this.dashboard = undefined; + this.changeDetectorRef.detectChanges(); + + this.initializeDashboard(); + } + + public initializeDashboard(): void { + const tableWidget = this.widgetConfig; + const widgetIndex: IWidgets = { + [tableWidget.id]: + this.widgetTypesService.mergeWithWidgetType(tableWidget), + }; + + const positions: Record = { + [tableWidget.id]: { + cols: 12, + rows: 6, + y: 0, + x: 0, + }, + }; + + this.dashboard = { + positions, + widgets: widgetIndex, + }; + } + + private get widgetConfig(): IWidget { + return { + id: "widget1", + type: "table", + pizzagna: { + [PizzagnaLayer.Configuration]: { + [DEFAULT_PIZZAGNA_ROOT]: { + providers: { + [WellKnownProviders.InteractionHandler]: { + providerId: NOVA_URL_INTERACTION_HANDLER, + }, + }, + }, + header: { + properties: { + title: "Table Widget with Selection!", + subtitle: "Basic table widget", + collapsible: true, + }, + }, + table: { + providers: { + [WellKnownProviders.DataSource]: { + providerId: AcmeTableMockDataSource.providerId, + } as IProviderConfiguration, + }, + properties: { + configuration: { + // enabling selection here + selectionConfiguration: + this.selectionConfiguration, + columns: [ + { + id: "column1", + label: "No.", + isActive: true, + formatter: { + componentType: + RawFormatterComponent.lateLoadKey, + properties: { + dataFieldIds: { + value: "position", + }, + }, + }, + }, + { + id: "column2", + label: "Name", + isActive: true, + formatter: { + componentType: + RawFormatterComponent.lateLoadKey, + properties: { + dataFieldIds: { + value: "name", + }, + }, + }, + }, + { + id: "column3", + label: "Status", + isActive: true, + formatter: { + componentType: + RawFormatterComponent.lateLoadKey, + properties: { + dataFieldIds: { + value: "status", + }, + }, + }, + }, + ], + } as ITableWidgetConfig, + }, + }, + }, + }, + }; + } +} +\\\\\\\\\\\\\\\`, + "widget-types/timeseries/timeseries-docs.component.ts": \\\\\\\\\\\\\\\`// © 2022 SolarWinds Worldwide, LLC. All rights reserved. +// +// Permission is hereby granted, free of charge, to any person obtaining a copy +// of this software and associated documentation files (the "Software"), to +// deal in the Software without restriction, including without limitation the +// rights to use, copy, modify, merge, publish, distribute, sublicense, and/or +// sell copies of the Software, and to permit persons to whom the Software is +// furnished to do so, subject to the following conditions: +// +// The above copyright notice and this permission notice shall be included in +// all copies or substantial portions of the Software. +// +// THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR +// IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, +// FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE +// AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER +// LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, +// OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN +// THE SOFTWARE. + +import { Component, OnInit } from "@angular/core"; + +import { mapContentFile } from "../../../../demo-files-factory"; + +@Component({ + selector: "nui-timeseries-docs", + templateUrl: "./timeseries-docs.component.html", + standalone: false, +}) +export class TimeseriesDocsComponent implements OnInit { + public timeseriesWidgetFileText = ""; + public timeseriesConfiguratorFileText = ""; + + async ngOnInit(): Promise { + this.timeseriesWidgetFileText = await import( + "./../../../../../../src/lib/widget-types/timeseries/timeseries-widget" + ).then(mapContentFile); + this.timeseriesConfiguratorFileText = await import( + "./../../../../../../src/lib/widget-types/timeseries/timeseries-configurator" + ).then(mapContentFile); + } +} +\\\\\\\\\\\\\\\`, + "widget-types/timeseries/timeseries-docs.module.ts": \\\\\\\\\\\\\\\`// © 2022 SolarWinds Worldwide, LLC. All rights reserved. +// +// Permission is hereby granted, free of charge, to any person obtaining a copy +// of this software and associated documentation files (the "Software"), to +// deal in the Software without restriction, including without limitation the +// rights to use, copy, modify, merge, publish, distribute, sublicense, and/or +// sell copies of the Software, and to permit persons to whom the Software is +// furnished to do so, subject to the following conditions: +// +// The above copyright notice and this permission notice shall be included in +// all copies or substantial portions of the Software. +// +// THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR +// IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, +// FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE +// AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER +// LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, +// OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN +// THE SOFTWARE. + +import { NgModule } from "@angular/core"; +import { RouterModule, Routes } from "@angular/router"; + +import { DEMO_PATH_TOKEN } from "@nova-ui/bits"; +import { + NuiButtonModule, + NuiDocsModule, + NuiMessageModule, + NuiSwitchModule, +} from "@nova-ui/bits"; +import { NuiDashboardsModule } from "@nova-ui/dashboards"; + +import { TimeseriesDocsComponent } from "./timeseries-docs.component"; +import { TimeseriesWidgetExampleComponent } from "./timeseries-widget-example/timeseries-widget-example.component"; +import { TimeseriesWidgetInteractiveExampleComponent } from "./timeseries-widget-interactive-example/timeseries-widget-interactive-example.component"; +import { TimeseriesWidgetStatusBarExampleComponent } from "./timeseries-widget-status-bar-example/timeseries-widget-status-bar-example.component"; +import { getDemoFiles } from "../../../../demo-files-factory"; + +const routes: Routes = [ + { + path: "", + component: TimeseriesDocsComponent, + data: { + srlc: { + hideIndicator: true, + }, + showThemeSwitcher: true, + }, + }, + { + path: "example", + component: TimeseriesWidgetExampleComponent, + data: { + srlc: { + hideIndicator: true, + }, + }, + }, +]; + +@NgModule({ + imports: [ + RouterModule.forChild(routes), + NuiButtonModule, + NuiDocsModule, + NuiMessageModule, + NuiSwitchModule, + NuiDashboardsModule, + ], + declarations: [ + TimeseriesDocsComponent, + TimeseriesWidgetExampleComponent, + TimeseriesWidgetInteractiveExampleComponent, + TimeseriesWidgetStatusBarExampleComponent, + ], + providers: [ + { + provide: DEMO_PATH_TOKEN, + useValue: getDemoFiles("timeseries"), + }, + ], +}) +export default class TimeseriesDocsModule {} +\\\\\\\\\\\\\\\`, + "widget-types/timeseries/timeseries-widget-example/timeseries-widget-example.component.ts": \\\\\\\\\\\\\\\`// © 2022 SolarWinds Worldwide, LLC. All rights reserved. +// +// Permission is hereby granted, free of charge, to any person obtaining a copy +// of this software and associated documentation files (the "Software"), to +// deal in the Software without restriction, including without limitation the +// rights to use, copy, modify, merge, publish, distribute, sublicense, and/or +// sell copies of the Software, and to permit persons to whom the Software is +// furnished to do so, subject to the following conditions: +// +// The above copyright notice and this permission notice shall be included in +// all copies or substantial portions of the Software. +// +// THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR +// IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, +// FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE +// AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER +// LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, +// OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN +// THE SOFTWARE. + +import { + ChangeDetectorRef, + Component, + Injectable, + OnInit, +} from "@angular/core"; +import { GridsterConfig, GridsterItem } from "angular-gridster2"; +import cloneDeep from "lodash/cloneDeep"; +import keyBy from "lodash/keyBy"; +import moment, { Moment } from "moment/moment"; +import { BehaviorSubject } from "rxjs"; + +import { + DataSourceService, + IDataSource, + INovaFilters, + ITimeframe, +} from "@nova-ui/bits"; +import { + DATA_SOURCE, + DEFAULT_PIZZAGNA_ROOT, + IDashboard, + IDataSourceOutput, + IProviderConfiguration, + ISerializableTimeframe, + ITimeseriesItemConfiguration, + ITimeseriesOutput, + ITimeseriesScaleConfig, + ITimeseriesWidgetConfig, + ITimeseriesWidgetData, + ITimeseriesWidgetSeriesData, + IWidget, + LegendPlacement, + PizzagnaLayer, + ProviderRegistryService, + TimeseriesChartPreset, + TimeseriesScaleType, + WellKnownPathKey, + WellKnownProviders, + WidgetTypesService, +} from "@nova-ui/dashboards"; + +/** + * A simple Timeseries data source implementation + */ +@Injectable() +export class BeerVsReadingMockDataSource + extends DataSourceService + implements IDataSource +{ + public static providerId = "BeerVsReadingMockDataSource"; + + public busy = new BehaviorSubject(false); + + public async getFilteredData( + filters: INovaFilters + ): Promise> { + // In this example we're using some static mock data located at the bottom of this file. In a real-world + // scenario, the data for the chart would likely be retrieved via an asynchronous backend call. + let filteredData = getData(); + + this.busy.next(true); + + // Filtering using the filter registered by the TimeFrameBar + const timeframeFilter = filters.timeframe?.value as ITimeframe; + if (timeframeFilter) { + filteredData = filteredData.map((item: ITimeseriesWidgetData) => ({ + id: item.id, + name: item.name, + description: item.description, + data: item.data.filter( + (seriesData: ITimeseriesWidgetSeriesData) => + filterDates( + seriesData.x, + timeframeFilter.startDatetime, + timeframeFilter.endDatetime + ) + ), + })); + } + + this.busy.next(false); + + return { result: { series: filteredData } }; + } +} + +function filterDates(dateToCheck: Date, startDate: Moment, endDate: Moment) { + const mom = moment(dateToCheck); + return ( + mom.isBetween(startDate, endDate) || + mom.isSame(startDate) || + mom.isSame(endDate) + ); +} + +/** + * A component that instantiates the dashboard + */ +@Component({ + selector: "timeseries-widget-example", + templateUrl: "./timeseries-widget-example.component.html", + styleUrls: ["./timeseries-widget-example.component.less"], + standalone: false, +}) +export class TimeseriesWidgetExampleComponent implements OnInit { + // This variable will hold all the data needed to define the layout and behavior of the widgets. + // Pass this to the dashboard component's dashboard input in the template. + public dashboard: IDashboard | undefined; + + // Angular gridster requires a configuration object even if it's empty. + // Pass this to the dashboard component's gridsterConfig input in the template. + public gridsterConfig: GridsterConfig = {}; + + // Boolean passed as an input to the dashboard. When true, widgets can be moved, resized, removed, or edited + public editMode: boolean = false; + + constructor( + // WidgetTypesService provides the widget's necessary structure information + private widgetTypesService: WidgetTypesService, + + // In general, the ProviderRegistryService is used for making entities available for injection into dynamically loaded components. + private providerRegistry: ProviderRegistryService, + + // Angular's ChangeDetectorRef + private changeDetectorRef: ChangeDetectorRef + ) {} + + public ngOnInit(): void { + // Grabbing the widget's default template which will be needed as a parameter for setNode + const widgetTemplate = this.widgetTypesService.getWidgetType( + "timeseries", + 1 + ); + // Registering our data sources as dropdown options in the widget editor/configurator + // Note: This could also be done in the parent module's constructor so that + // multiple dashboards could have access to the same widget template modification. + this.widgetTypesService.setNode( + // This is the template we grabbed above with getWidgetType + widgetTemplate, + // We are setting the editor/configurator part of the widget template + "configurator", + // This indicates which node you are changing and we want to change + // the data source providers available for selection in the editor. + WellKnownPathKey.DataSourceProviders, + // We are setting the data sources available for selection in the editor + [BeerVsReadingMockDataSource.providerId] + ); + + // Registering the data source for injection into the widget. + this.providerRegistry.setProviders({ + [BeerVsReadingMockDataSource.providerId]: { + provide: DATA_SOURCE, + useClass: BeerVsReadingMockDataSource, + deps: [], + }, + }); + + this.initializeDashboard(); + } + + public initializeDashboard(): void { + // We're using a static configuration object for this example, but this is where + // the widget's configuration could potentially be populated from a database + const widgetsWithStructure = widgetConfigs.map((w) => + this.widgetTypesService.mergeWithWidgetType(w) + ); + const widgetsIndex = keyBy(widgetsWithStructure, (w: IWidget) => w.id); + + // Finally, assigning the variables we created above to the dashboard + this.dashboard = { + positions: cloneDeep(positions), + widgets: widgetsIndex, + }; + } + + /** Used for restoring widgets state */ + public reInitializeDashboard(): void { + // destroys the components and their providers so the dashboard can re init data + this.dashboard = undefined; + this.changeDetectorRef.detectChanges(); + + this.initializeDashboard(); + } +} + +const widgetConfigs: IWidget[] = [ + { + id: "lineWidgetId", + type: "timeseries", + pizzagna: { + [PizzagnaLayer.Configuration]: { + [DEFAULT_PIZZAGNA_ROOT]: { + providers: { + [WellKnownProviders.DataSource]: { + // Setting the initially selected data source providerId + providerId: BeerVsReadingMockDataSource.providerId, + } as IProviderConfiguration, + }, + }, + header: { + properties: { + title: "Line Chart", + subtitle: "Survey of 1000 Solarians", + }, + }, + chart: { + providers: { + [WellKnownProviders.Adapter]: { + properties: { + // Setting the series and corresponding labels to initially display on the chart + series: [ + { + id: "series-1", + label: "Beer Tasting", + selectedSeriesId: "series-1", + }, + { + id: "series-2", + label: "Reading", + selectedSeriesId: "series-2", + }, + ] as ITimeseriesItemConfiguration[], + }, + } as Partial, + }, + properties: { + // Setting the general chart configuration + configuration: { + legendPlacement: LegendPlacement.Right, + enableZoom: true, + leftAxisLabel: "Solarians (%)", + // You can optionally define custom colors for the chart by setting the 'chartColors' configuration property + // "chartColors": [ + // "var(--nui-color-chart-eight)", + // "var(--nui-color-chart-nine)", + // "var(--nui-color-chart-ten)", + // ], + } as ITimeseriesWidgetConfig, + }, + }, + timeframeSelection: { + properties: { + // Setting the initial timeframe selected in the timeframe bar + timeframe: { + selectedPresetId: "last7Days", + } as ISerializableTimeframe, + minDate: moment().subtract(60, "days").format(), + maxDate: moment().format(), + }, + }, + }, + }, + }, + { + id: "stackedAreaWidgetId", + type: "timeseries", + pizzagna: { + [PizzagnaLayer.Configuration]: { + [DEFAULT_PIZZAGNA_ROOT]: { + providers: { + [WellKnownProviders.DataSource]: { + // Setting the initially selected data source providerId + providerId: BeerVsReadingMockDataSource.providerId, + } as IProviderConfiguration, + }, + }, + header: { + properties: { + title: "Stacked Area Chart", + subtitle: "Survey of 1000 Solarians", + }, + }, + chart: { + providers: { + [WellKnownProviders.Adapter]: { + properties: { + // Setting the series and corresponding labels to initially display on the chart + series: [ + { + id: "series-1", + label: "Beer Tasting", + selectedSeriesId: "series-1", + }, + { + id: "series-2", + label: "Reading", + selectedSeriesId: "series-2", + }, + ] as ITimeseriesItemConfiguration[], + }, + } as Partial, + }, + properties: { + // Setting the general chart configuration + configuration: { + legendPlacement: LegendPlacement.Right, + enableZoom: true, + // Setting the preset to stacked area + preset: TimeseriesChartPreset.StackedArea, + leftAxisLabel: "Solarians (%)", + // You can optionally define custom colors for the chart by setting the 'chartColors' configuration property + // "chartColors": [ + // "var(--nui-color-chart-eight)", + // "var(--nui-color-chart-nine)", + // "var(--nui-color-chart-ten)", + // ], + } as ITimeseriesWidgetConfig, + }, + }, + timeframeSelection: { + properties: { + // Setting the initial timeframe selected in the timeframe bar + timeframe: { + selectedPresetId: "last7Days", + } as ISerializableTimeframe, + minDate: moment().subtract(60, "days").format(), + maxDate: moment().format(), + }, + }, + }, + }, + }, + { + id: "stackedPercentageAreaWidgetId", + type: "timeseries", + pizzagna: { + [PizzagnaLayer.Configuration]: { + [DEFAULT_PIZZAGNA_ROOT]: { + providers: { + [WellKnownProviders.DataSource]: { + // Setting the initially selected data source providerId + providerId: BeerVsReadingMockDataSource.providerId, + } as IProviderConfiguration, + }, + }, + header: { + properties: { + title: "Stacked Percentage Area Chart", + subtitle: "Survey of 1000 Solarians", + }, + }, + chart: { + providers: { + [WellKnownProviders.Adapter]: { + properties: { + // Setting the series and corresponding labels to initially display on the chart + series: [ + { + id: "series-1", + label: "Beer Tasting", + selectedSeriesId: "series-1", + }, + { + id: "series-2", + label: "Reading", + selectedSeriesId: "series-2", + }, + ] as ITimeseriesItemConfiguration[], + }, + } as Partial, + }, + properties: { + // Setting the general chart configuration + configuration: { + legendPlacement: LegendPlacement.Right, + enableZoom: true, + // Setting the preset to stacked percentage area + preset: TimeseriesChartPreset.StackedPercentageArea, + leftAxisLabel: "Solarians (%)", + // You can optionally define custom colors for the chart by setting the 'chartColors' configuration property + // "chartColors": [ + // "var(--nui-color-chart-eight)", + // "var(--nui-color-chart-nine)", + // "var(--nui-color-chart-ten)", + // ], + } as ITimeseriesWidgetConfig, + }, + }, + timeframeSelection: { + properties: { + // Setting the initial timeframe selected in the timeframe bar + timeframe: { + selectedPresetId: "last7Days", + } as ISerializableTimeframe, + minDate: moment().subtract(60, "days").format(), + maxDate: moment().format(), + }, + }, + }, + }, + }, + { + id: "stackedBarWidgetId", + type: "timeseries", + pizzagna: { + [PizzagnaLayer.Configuration]: { + [DEFAULT_PIZZAGNA_ROOT]: { + providers: { + [WellKnownProviders.DataSource]: { + // Setting the initially selected data source providerId + providerId: BeerVsReadingMockDataSource.providerId, + } as IProviderConfiguration, + }, + }, + header: { + properties: { + title: "Stacked Bar Chart", + subtitle: "Survey of 1000 Solarians", + }, + }, + chart: { + providers: { + [WellKnownProviders.Adapter]: { + properties: { + // Setting the series and corresponding labels to initially display on the chart + series: [ + { + id: "series-1", + label: "Beer Tasting", + selectedSeriesId: "series-1", + }, + { + id: "series-2", + label: "Reading", + selectedSeriesId: "series-2", + }, + ] as ITimeseriesItemConfiguration[], + }, + } as Partial, + }, + properties: { + configuration: { + legendPlacement: LegendPlacement.Right, + enableZoom: true, + leftAxisLabel: "Solarians (%)", + // Setting the preset to stacked bar + preset: TimeseriesChartPreset.StackedBar, + scales: { + x: { + type: TimeseriesScaleType.TimeInterval, + properties: { + interval: 24 * 60 * 60, + }, + } as ITimeseriesScaleConfig, + }, + } as ITimeseriesWidgetConfig, + }, + }, + timeframeSelection: { + properties: { + // Setting the initial timeframe selected in the timeframe bar + timeframe: { + selectedPresetId: "last7Days", + } as ISerializableTimeframe, + minDate: moment().subtract(60, "days").format(), + maxDate: moment().format(), + }, + }, + }, + }, + }, +]; + +// using startOf("day") so that each band for the bar chart corresponds to a calendar day +const now = moment().startOf("day"); + +export const getData = (): ITimeseriesWidgetData[] => [ + { + id: "series-1", + name: "Beer Tasting", + description: "Havin' some suds", + data: [ + { x: now.clone().subtract(20, "day").toDate(), y: 30 }, + { x: now.clone().subtract(19, "day").toDate(), y: 35 }, + { x: now.clone().subtract(18, "day").toDate(), y: 33 }, + { x: now.clone().subtract(17, "day").toDate(), y: 40 }, + { x: now.clone().subtract(16, "day").toDate(), y: 35 }, + { x: now.clone().subtract(15, "day").toDate(), y: 30 }, + { x: now.clone().subtract(14, "day").toDate(), y: 35 }, + { x: now.clone().subtract(13, "day").toDate(), y: 15 }, + { x: now.clone().subtract(12, "day").toDate(), y: 30 }, + { x: now.clone().subtract(11, "day").toDate(), y: 45 }, + { x: now.clone().subtract(10, "day").toDate(), y: 60 }, + { x: now.clone().subtract(9, "day").toDate(), y: 54 }, + { x: now.clone().subtract(8, "day").toDate(), y: 42 }, + { x: now.clone().subtract(7, "day").toDate(), y: 44 }, + { x: now.clone().subtract(6, "day").toDate(), y: 54 }, + { x: now.clone().subtract(5, "day").toDate(), y: 43 }, + { x: now.clone().subtract(4, "day").toDate(), y: 76 }, + { x: now.clone().subtract(3, "day").toDate(), y: 54 }, + { x: now.clone().subtract(2, "day").toDate(), y: 42 }, + { x: now.clone().subtract(1, "day").toDate(), y: 34 }, + ], + }, + { + id: "series-2", + name: "Reading", + description: "Hittin' the books", + data: [ + { x: now.clone().subtract(20, "day").toDate(), y: 60 }, + { x: now.clone().subtract(19, "day").toDate(), y: 64 }, + { x: now.clone().subtract(18, "day").toDate(), y: 70 }, + { x: now.clone().subtract(17, "day").toDate(), y: 55 }, + { x: now.clone().subtract(16, "day").toDate(), y: 55 }, + { x: now.clone().subtract(15, "day").toDate(), y: 45 }, + { x: now.clone().subtract(14, "day").toDate(), y: 60 }, + { x: now.clone().subtract(13, "day").toDate(), y: 65 }, + { x: now.clone().subtract(12, "day").toDate(), y: 63 }, + { x: now.clone().subtract(11, "day").toDate(), y: 60 }, + { x: now.clone().subtract(10, "day").toDate(), y: 61 }, + { x: now.clone().subtract(9, "day").toDate(), y: 65 }, + { x: now.clone().subtract(8, "day").toDate(), y: 63 }, + { x: now.clone().subtract(7, "day").toDate(), y: 58 }, + { x: now.clone().subtract(6, "day").toDate(), y: 64 }, + { x: now.clone().subtract(5, "day").toDate(), y: 63 }, + { x: now.clone().subtract(4, "day").toDate(), y: 60 }, + { x: now.clone().subtract(3, "day").toDate(), y: 62 }, + { x: now.clone().subtract(2, "day").toDate(), y: 61 }, + { x: now.clone().subtract(1, "day").toDate(), y: 62 }, + ], + }, +]; + +// Setting the widget dimensions and position (this is for gridster) +const positions: Record = { + [widgetConfigs[0].id]: { + cols: 6, + rows: 6, + y: 0, + x: 0, + }, + [widgetConfigs[1].id]: { + cols: 6, + rows: 6, + y: 0, + x: 6, + }, + [widgetConfigs[3].id]: { + cols: 6, + rows: 6, + y: 6, + x: 0, + }, + [widgetConfigs[2].id]: { + cols: 6, + rows: 6, + y: 6, + x: 6, + }, +}; +\\\\\\\\\\\\\\\`, + "widget-types/timeseries/timeseries-widget-interactive-example/timeseries-widget-interactive-example.component.ts": \\\\\\\\\\\\\\\`// © 2022 SolarWinds Worldwide, LLC. All rights reserved. +// +// Permission is hereby granted, free of charge, to any person obtaining a copy +// of this software and associated documentation files (the "Software"), to +// deal in the Software without restriction, including without limitation the +// rights to use, copy, modify, merge, publish, distribute, sublicense, and/or +// sell copies of the Software, and to permit persons to whom the Software is +// furnished to do so, subject to the following conditions: +// +// The above copyright notice and this permission notice shall be included in +// all copies or substantial portions of the Software. +// +// THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR +// IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, +// FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE +// AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER +// LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, +// OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN +// THE SOFTWARE. + +import { + ChangeDetectorRef, + Component, + Injectable, + OnInit, +} from "@angular/core"; +import { GridsterConfig, GridsterItem } from "angular-gridster2"; +import cloneDeep from "lodash/cloneDeep"; +import keyBy from "lodash/keyBy"; +import moment, { Moment } from "moment/moment"; +import { BehaviorSubject } from "rxjs"; + +import { + DataSourceService, + IDataSource, + INovaFilters, + ITimeframe, +} from "@nova-ui/bits"; +import { + DATA_SOURCE, + DEFAULT_PIZZAGNA_ROOT, + IDashboard, + IDataSourceOutput, + IProviderConfiguration, + ISerializableTimeframe, + ITimeseriesItemConfiguration, + ITimeseriesOutput, + ITimeseriesScaleConfig, + ITimeseriesWidgetConfig, + ITimeseriesWidgetData, + ITimeseriesWidgetSeriesData, + IWidget, + NOVA_URL_INTERACTION_HANDLER, + LegendPlacement, + PizzagnaLayer, + ProviderRegistryService, + TimeseriesChartPreset, + TimeseriesScaleType, + WellKnownPathKey, + WellKnownProviders, + WidgetTypesService, +} from "@nova-ui/dashboards"; + +/** + * A simple Timeseries data source implementation + */ +@Injectable() +export class TimeseriesMockDataSource + extends DataSourceService + implements IDataSource +{ + public static providerId = "TimeseriesMockDataSource"; + + public busy = new BehaviorSubject(false); + + public async getFilteredData( + filters: INovaFilters + ): Promise> { + // In this example we're using some static mock data located at the bottom of this file. In a real-world + // scenario, the data for the chart would likely be retrieved via an asynchronous backend call. + let filteredData = getData(); + + this.busy.next(true); + + // Filtering using the filter registered by the TimeFrameBar + const timeframeFilter = filters.timeframe?.value as ITimeframe; + if (timeframeFilter) { + filteredData = filteredData.map((item: ITimeseriesWidgetData) => ({ + id: item.id, + name: item.name, + description: item.description, + // the filtered data should return the provided links if they are set. + link: item?.link, + secondaryLink: item?.secondaryLink, + data: item.data.filter( + (seriesData: ITimeseriesWidgetSeriesData) => + filterDates( + seriesData.x, + timeframeFilter.startDatetime, + timeframeFilter.endDatetime + ) + ), + })); + } + + this.busy.next(false); + + return { result: { series: filteredData } }; + } +} + +function filterDates(dateToCheck: Date, startDate: Moment, endDate: Moment) { + const mom = moment(dateToCheck); + return ( + mom.isBetween(startDate, endDate) || + mom.isSame(startDate) || + mom.isSame(endDate) + ); +} + +/** + * A component that instantiates the dashboard + */ +@Component({ + selector: "timeseries-widget-interactive-example", + templateUrl: "./timeseries-widget-interactive-example.component.html", + styleUrls: ["./timeseries-widget-interactive-example.component.less"], + standalone: false, +}) +export class TimeseriesWidgetInteractiveExampleComponent implements OnInit { + // This variable will hold all the data needed to define the layout and behavior of the widgets. + // Pass this to the dashboard component's dashboard input in the template. + public dashboard: IDashboard | undefined; + + // Angular gridster requires a configuration object even if it's empty. + // Pass this to the dashboard component's gridsterConfig input in the template. + public gridsterConfig: GridsterConfig = {}; + + // Boolean passed as an input to the dashboard. When true, widgets can be moved, resized, removed, or edited + public editMode: boolean = false; + + constructor( + // WidgetTypesService provides the widget's necessary structure information + private widgetTypesService: WidgetTypesService, + + // In general, the ProviderRegistryService is used for making entities available for injection into dynamically loaded components. + private providerRegistry: ProviderRegistryService, + + // Angular's ChangeDetectorRef + private changeDetectorRef: ChangeDetectorRef + ) {} + + public ngOnInit(): void { + // Grabbing the widget's default template which will be needed as a parameter for setNode + const widgetTemplate = this.widgetTypesService.getWidgetType( + "timeseries", + 1 + ); + // Registering our data sources as dropdown options in the widget editor/configurator + // Note: This could also be done in the parent module's constructor so that + // multiple dashboards could have access to the same widget template modification. + this.widgetTypesService.setNode( + // This is the template we grabbed above with getWidgetType + widgetTemplate, + // We are setting the editor/configurator part of the widget template + "configurator", + // This indicates which node you are changing and we want to change + // the data source providers available for selection in the editor. + WellKnownPathKey.DataSourceProviders, + // We are setting the data sources available for selection in the editor + [TimeseriesMockDataSource.providerId] + ); + + // Registering the data source for injection into the widget. + this.providerRegistry.setProviders({ + [TimeseriesMockDataSource.providerId]: { + provide: DATA_SOURCE, + useClass: TimeseriesMockDataSource, + deps: [], + }, + }); + + this.initializeDashboard(); + } + + public initializeDashboard(): void { + // We're using a static configuration object for this example, but this is where + // the widget's configuration could potentially be populated from a database + const widgetsWithStructure = widgetConfigs.map((w) => + this.widgetTypesService.mergeWithWidgetType(w) + ); + const widgetsIndex = keyBy(widgetsWithStructure, (w: IWidget) => w.id); + + // Finally, assigning the variables we created above to the dashboard + this.dashboard = { + positions: cloneDeep(positions), + widgets: widgetsIndex, + }; + } + + /** Used for restoring widgets state */ + public reInitializeDashboard(): void { + // destroys the components and their providers so the dashboard can re init data + this.dashboard = undefined; + this.changeDetectorRef.detectChanges(); + + this.initializeDashboard(); + } +} + +const widgetConfigs: IWidget[] = [ + { + id: "lineWidgetId", + type: "timeseries", + pizzagna: { + [PizzagnaLayer.Configuration]: { + [DEFAULT_PIZZAGNA_ROOT]: { + providers: { + [WellKnownProviders.DataSource]: { + // Setting the initially selected data source providerId + providerId: TimeseriesMockDataSource.providerId, + } as IProviderConfiguration, + [WellKnownProviders.InteractionHandler]: { + // Setting the UrlInteractionHandler as an interactionHandler + providerId: NOVA_URL_INTERACTION_HANDLER, + properties: { + // the 'url' property tells the handler what link to use when interaction occurs on the series + url: "\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\${data.link || 'https://en.wikipedia.org/wiki/'+data.legendDescriptionPrimary}", + // by default the link is opened in the current window, set 'newWindow' to true to open in a new tab instead + // newWindow: true, + }, + }, + }, + }, + header: { + properties: { + title: "Line Chart", + subtitle: "Basic Timeseries with Interaction", + }, + }, + chart: { + providers: { + [WellKnownProviders.Adapter]: { + properties: { + // Setting the series and corresponding labels to initially display on the chart + series: [ + { + id: "series-1", + label: "Nur-Sultan", + selectedSeriesId: "series-1", + }, + { + id: "series-2", + label: "Brno", + selectedSeriesId: "series-2", + }, + { + id: "series-3", + label: "Lisbon", + selectedSeriesId: "series-3", + }, + { + id: "series-4", + label: "Austin", + selectedSeriesId: "series-4", + }, + ] as ITimeseriesItemConfiguration[], + }, + } as Partial, + }, + properties: { + // Setting the general chart configuration + configuration: { + // setting interaction to 'series' will make all series in the chart interactable + interaction: "series", + legendPlacement: LegendPlacement.Right, + enableZoom: true, + } as ITimeseriesWidgetConfig, + }, + }, + timeframeSelection: { + properties: { + timeframe: { + selectedPresetId: "last7Days", + } as ISerializableTimeframe, + minDate: moment().subtract(60, "days").format(), + maxDate: moment().format(), + }, + }, + }, + }, + }, + { + id: "stackedBarWidgetId", + type: "timeseries", + pizzagna: { + [PizzagnaLayer.Configuration]: { + [DEFAULT_PIZZAGNA_ROOT]: { + providers: { + [WellKnownProviders.DataSource]: { + // Setting the initially selected data source providerId + providerId: TimeseriesMockDataSource.providerId, + } as IProviderConfiguration, + }, + }, + header: { + properties: { + title: "Stacked Bar Chart", + subtitle: + "Basic Timeseries without Interaction Handler", + }, + }, + chart: { + providers: { + [WellKnownProviders.Adapter]: { + properties: { + // Setting the series and corresponding labels to initially display on the chart + series: [ + { + id: "series-1", + label: "Nur-Sultan", + selectedSeriesId: "series-1", + }, + { + id: "series-2", + label: "Brno", + selectedSeriesId: "series-2", + }, + { + id: "series-3", + label: "Lisbon", + selectedSeriesId: "series-3", + }, + { + id: "series-4", + label: "Austin", + selectedSeriesId: "series-4", + }, + ] as ITimeseriesItemConfiguration[], + }, + } as Partial, + }, + properties: { + // Setting the general chart configuration + configuration: { + legendPlacement: LegendPlacement.Right, + enableZoom: true, + // Setting the preset to stacked bar + preset: TimeseriesChartPreset.StackedBar, + scales: { + x: { + type: TimeseriesScaleType.TimeInterval, + properties: { + interval: 24 * 60 * 60, + }, + } as ITimeseriesScaleConfig, + }, + } as ITimeseriesWidgetConfig, + }, + }, + timeframeSelection: { + properties: { + // Setting the initial timeframe selected in the timeframe bar + timeframe: { + selectedPresetId: "last7Days", + } as ISerializableTimeframe, + minDate: moment().subtract(60, "days").format(), + maxDate: moment().format(), + }, + }, + }, + }, + }, +]; + +// using startOf("day") so that each band for the bar chart corresponds to a calendar day +const startOfToday = moment().startOf("day").toDate(); + +export const getData = (): ITimeseriesWidgetData[] => [ + { + id: "series-1", + name: "Nur-Sultan", + description: "'link' only", + link: "https://en.wikipedia.org/wiki/Nur-Sultan", + data: [ + { x: moment(startOfToday).subtract(59, "day").toDate(), y: 35 }, + { x: moment(startOfToday).subtract(58, "day").toDate(), y: 33 }, + { x: moment(startOfToday).subtract(57, "day").toDate(), y: 40 }, + { x: moment(startOfToday).subtract(56, "day").toDate(), y: 35 }, + { x: moment(startOfToday).subtract(55, "day").toDate(), y: 30 }, + { x: moment(startOfToday).subtract(54, "day").toDate(), y: 35 }, + { x: moment(startOfToday).subtract(53, "day").toDate(), y: 15 }, + { x: moment(startOfToday).subtract(52, "day").toDate(), y: 30 }, + { x: moment(startOfToday).subtract(51, "day").toDate(), y: 35 }, + { x: moment(startOfToday).subtract(50, "day").toDate(), y: 34 }, + { x: moment(startOfToday).subtract(49, "day").toDate(), y: 35 }, + { x: moment(startOfToday).subtract(48, "day").toDate(), y: 33 }, + { x: moment(startOfToday).subtract(47, "day").toDate(), y: 40 }, + { x: moment(startOfToday).subtract(46, "day").toDate(), y: 35 }, + { x: moment(startOfToday).subtract(45, "day").toDate(), y: 30 }, + { x: moment(startOfToday).subtract(44, "day").toDate(), y: 35 }, + { x: moment(startOfToday).subtract(43, "day").toDate(), y: 15 }, + { x: moment(startOfToday).subtract(42, "day").toDate(), y: 30 }, + { x: moment(startOfToday).subtract(41, "day").toDate(), y: 35 }, + { x: moment(startOfToday).subtract(40, "day").toDate(), y: 34 }, + { x: moment(startOfToday).subtract(39, "day").toDate(), y: 35 }, + { x: moment(startOfToday).subtract(38, "day").toDate(), y: 33 }, + { x: moment(startOfToday).subtract(37, "day").toDate(), y: 40 }, + { x: moment(startOfToday).subtract(36, "day").toDate(), y: 35 }, + { x: moment(startOfToday).subtract(35, "day").toDate(), y: 30 }, + { x: moment(startOfToday).subtract(34, "day").toDate(), y: 35 }, + { x: moment(startOfToday).subtract(33, "day").toDate(), y: 15 }, + { x: moment(startOfToday).subtract(32, "day").toDate(), y: 30 }, + { x: moment(startOfToday).subtract(31, "day").toDate(), y: 35 }, + { x: moment(startOfToday).subtract(30, "day").toDate(), y: 34 }, + { x: moment(startOfToday).subtract(29, "day").toDate(), y: 35 }, + { x: moment(startOfToday).subtract(28, "day").toDate(), y: 33 }, + { x: moment(startOfToday).subtract(27, "day").toDate(), y: 40 }, + { x: moment(startOfToday).subtract(26, "day").toDate(), y: 35 }, + { x: moment(startOfToday).subtract(25, "day").toDate(), y: 30 }, + { x: moment(startOfToday).subtract(24, "day").toDate(), y: 35 }, + { x: moment(startOfToday).subtract(23, "day").toDate(), y: 15 }, + { x: moment(startOfToday).subtract(22, "day").toDate(), y: 30 }, + { x: moment(startOfToday).subtract(21, "day").toDate(), y: 35 }, + { x: moment(startOfToday).subtract(20, "day").toDate(), y: 34 }, + { x: moment(startOfToday).subtract(19, "day").toDate(), y: 35 }, + { x: moment(startOfToday).subtract(18, "day").toDate(), y: 33 }, + { x: moment(startOfToday).subtract(17, "day").toDate(), y: 40 }, + { x: moment(startOfToday).subtract(16, "day").toDate(), y: 35 }, + { x: moment(startOfToday).subtract(15, "day").toDate(), y: 30 }, + { x: moment(startOfToday).subtract(14, "day").toDate(), y: 35 }, + { x: moment(startOfToday).subtract(13, "day").toDate(), y: 15 }, + { x: moment(startOfToday).subtract(12, "day").toDate(), y: 30 }, + { x: moment(startOfToday).subtract(11, "day").toDate(), y: 35 }, + { x: moment(startOfToday).subtract(10, "day").toDate(), y: 34 }, + { x: moment(startOfToday).subtract(9, "day").toDate(), y: 33 }, + { x: moment(startOfToday).subtract(8, "day").toDate(), y: 35 }, + { x: moment(startOfToday).subtract(7, "day").toDate(), y: 36 }, + { x: moment(startOfToday).subtract(6, "day").toDate(), y: 34 }, + { x: moment(startOfToday).subtract(5, "day").toDate(), y: 33 }, + { x: moment(startOfToday).subtract(4, "day").toDate(), y: 30 }, + { x: moment(startOfToday).subtract(3, "day").toDate(), y: 32 }, + { x: moment(startOfToday).subtract(2, "day").toDate(), y: 31 }, + { x: moment(startOfToday).subtract(1, "day").toDate(), y: 34 }, + { x: moment(startOfToday).toDate(), y: 25 }, + ], + }, + { + id: "series-2", + name: "Brno", + description: "'link' and 'secondaryLink'", + link: "https://en.wikipedia.org/wiki/Brno", + secondaryLink: "https://en.wikipedia.org/wiki/Europe", + data: [ + { x: moment(startOfToday).subtract(59, "day").toDate(), y: 35 }, + { x: moment(startOfToday).subtract(58, "day").toDate(), y: 33 }, + { x: moment(startOfToday).subtract(57, "day").toDate(), y: 40 }, + { x: moment(startOfToday).subtract(56, "day").toDate(), y: 35 }, + { x: moment(startOfToday).subtract(55, "day").toDate(), y: 30 }, + { x: moment(startOfToday).subtract(54, "day").toDate(), y: 35 }, + { x: moment(startOfToday).subtract(53, "day").toDate(), y: 15 }, + { x: moment(startOfToday).subtract(52, "day").toDate(), y: 30 }, + { x: moment(startOfToday).subtract(51, "day").toDate(), y: 35 }, + { x: moment(startOfToday).subtract(50, "day").toDate(), y: 34 }, + { x: moment(startOfToday).subtract(49, "day").toDate(), y: 35 }, + { x: moment(startOfToday).subtract(48, "day").toDate(), y: 33 }, + { x: moment(startOfToday).subtract(47, "day").toDate(), y: 40 }, + { x: moment(startOfToday).subtract(46, "day").toDate(), y: 35 }, + { x: moment(startOfToday).subtract(45, "day").toDate(), y: 30 }, + { x: moment(startOfToday).subtract(44, "day").toDate(), y: 35 }, + { x: moment(startOfToday).subtract(43, "day").toDate(), y: 15 }, + { x: moment(startOfToday).subtract(42, "day").toDate(), y: 30 }, + { x: moment(startOfToday).subtract(41, "day").toDate(), y: 35 }, + { x: moment(startOfToday).subtract(40, "day").toDate(), y: 34 }, + { x: moment(startOfToday).subtract(39, "day").toDate(), y: 35 }, + { x: moment(startOfToday).subtract(38, "day").toDate(), y: 33 }, + { x: moment(startOfToday).subtract(37, "day").toDate(), y: 40 }, + { x: moment(startOfToday).subtract(36, "day").toDate(), y: 35 }, + { x: moment(startOfToday).subtract(35, "day").toDate(), y: 30 }, + { x: moment(startOfToday).subtract(34, "day").toDate(), y: 35 }, + { x: moment(startOfToday).subtract(33, "day").toDate(), y: 15 }, + { x: moment(startOfToday).subtract(32, "day").toDate(), y: 30 }, + { x: moment(startOfToday).subtract(31, "day").toDate(), y: 35 }, + { x: moment(startOfToday).subtract(30, "day").toDate(), y: 34 }, + { x: moment(startOfToday).subtract(29, "day").toDate(), y: 35 }, + { x: moment(startOfToday).subtract(28, "day").toDate(), y: 33 }, + { x: moment(startOfToday).subtract(27, "day").toDate(), y: 40 }, + { x: moment(startOfToday).subtract(26, "day").toDate(), y: 35 }, + { x: moment(startOfToday).subtract(25, "day").toDate(), y: 30 }, + { x: moment(startOfToday).subtract(24, "day").toDate(), y: 35 }, + { x: moment(startOfToday).subtract(23, "day").toDate(), y: 15 }, + { x: moment(startOfToday).subtract(22, "day").toDate(), y: 30 }, + { x: moment(startOfToday).subtract(21, "day").toDate(), y: 35 }, + { x: moment(startOfToday).subtract(20, "day").toDate(), y: 34 }, + { x: moment(startOfToday).subtract(19, "day").toDate(), y: 64 }, + { x: moment(startOfToday).subtract(18, "day").toDate(), y: 70 }, + { x: moment(startOfToday).subtract(17, "day").toDate(), y: 55 }, + { x: moment(startOfToday).subtract(16, "day").toDate(), y: 55 }, + { x: moment(startOfToday).subtract(15, "day").toDate(), y: 45 }, + { x: moment(startOfToday).subtract(14, "day").toDate(), y: 10 }, + { x: moment(startOfToday).subtract(13, "day").toDate(), y: 65 }, + { x: moment(startOfToday).subtract(12, "day").toDate(), y: 35 }, + { x: moment(startOfToday).subtract(11, "day").toDate(), y: 60 }, + { x: moment(startOfToday).subtract(10, "day").toDate(), y: 61 }, + { x: moment(startOfToday).subtract(9, "day").toDate(), y: 65 }, + { x: moment(startOfToday).subtract(8, "day").toDate(), y: 63 }, + { x: moment(startOfToday).subtract(7, "day").toDate(), y: 58 }, + { x: moment(startOfToday).subtract(6, "day").toDate(), y: 64 }, + { x: moment(startOfToday).subtract(5, "day").toDate(), y: 63 }, + { x: moment(startOfToday).subtract(4, "day").toDate(), y: 60 }, + { x: moment(startOfToday).subtract(3, "day").toDate(), y: 62 }, + { x: moment(startOfToday).subtract(2, "day").toDate(), y: 61 }, + { x: moment(startOfToday).subtract(1, "day").toDate(), y: 62 }, + { x: moment(startOfToday).toDate(), y: 55 }, + ], + }, + { + id: "series-3", + name: "Lisbon", + description: "'secondaryLink' only", + secondaryLink: "https://en.wikipedia.org/wiki/Lisbon", + data: [ + { x: moment(startOfToday).subtract(59, "day").toDate(), y: 35 }, + { x: moment(startOfToday).subtract(58, "day").toDate(), y: 33 }, + { x: moment(startOfToday).subtract(57, "day").toDate(), y: 40 }, + { x: moment(startOfToday).subtract(56, "day").toDate(), y: 35 }, + { x: moment(startOfToday).subtract(55, "day").toDate(), y: 30 }, + { x: moment(startOfToday).subtract(54, "day").toDate(), y: 35 }, + { x: moment(startOfToday).subtract(53, "day").toDate(), y: 15 }, + { x: moment(startOfToday).subtract(52, "day").toDate(), y: 30 }, + { x: moment(startOfToday).subtract(51, "day").toDate(), y: 35 }, + { x: moment(startOfToday).subtract(50, "day").toDate(), y: 34 }, + { x: moment(startOfToday).subtract(49, "day").toDate(), y: 35 }, + { x: moment(startOfToday).subtract(48, "day").toDate(), y: 33 }, + { x: moment(startOfToday).subtract(47, "day").toDate(), y: 40 }, + { x: moment(startOfToday).subtract(46, "day").toDate(), y: 35 }, + { x: moment(startOfToday).subtract(45, "day").toDate(), y: 30 }, + { x: moment(startOfToday).subtract(44, "day").toDate(), y: 35 }, + { x: moment(startOfToday).subtract(43, "day").toDate(), y: 15 }, + { x: moment(startOfToday).subtract(42, "day").toDate(), y: 30 }, + { x: moment(startOfToday).subtract(41, "day").toDate(), y: 35 }, + { x: moment(startOfToday).subtract(40, "day").toDate(), y: 34 }, + { x: moment(startOfToday).subtract(39, "day").toDate(), y: 35 }, + { x: moment(startOfToday).subtract(38, "day").toDate(), y: 33 }, + { x: moment(startOfToday).subtract(37, "day").toDate(), y: 40 }, + { x: moment(startOfToday).subtract(36, "day").toDate(), y: 35 }, + { x: moment(startOfToday).subtract(35, "day").toDate(), y: 30 }, + { x: moment(startOfToday).subtract(34, "day").toDate(), y: 35 }, + { x: moment(startOfToday).subtract(33, "day").toDate(), y: 15 }, + { x: moment(startOfToday).subtract(32, "day").toDate(), y: 30 }, + { x: moment(startOfToday).subtract(31, "day").toDate(), y: 35 }, + { x: moment(startOfToday).subtract(30, "day").toDate(), y: 34 }, + { x: moment(startOfToday).subtract(29, "day").toDate(), y: 35 }, + { x: moment(startOfToday).subtract(28, "day").toDate(), y: 33 }, + { x: moment(startOfToday).subtract(27, "day").toDate(), y: 40 }, + { x: moment(startOfToday).subtract(26, "day").toDate(), y: 35 }, + { x: moment(startOfToday).subtract(25, "day").toDate(), y: 30 }, + { x: moment(startOfToday).subtract(24, "day").toDate(), y: 35 }, + { x: moment(startOfToday).subtract(23, "day").toDate(), y: 15 }, + { x: moment(startOfToday).subtract(22, "day").toDate(), y: 30 }, + { x: moment(startOfToday).subtract(21, "day").toDate(), y: 35 }, + { x: moment(startOfToday).subtract(20, "day").toDate(), y: 34 }, + { x: moment(startOfToday).subtract(19, "day").toDate(), y: 80 }, + { x: moment(startOfToday).subtract(18, "day").toDate(), y: 70 }, + { x: moment(startOfToday).subtract(17, "day").toDate(), y: 95 }, + { x: moment(startOfToday).subtract(16, "day").toDate(), y: 90 }, + { x: moment(startOfToday).subtract(15, "day").toDate(), y: 85 }, + { x: moment(startOfToday).subtract(14, "day").toDate(), y: 70 }, + { x: moment(startOfToday).subtract(13, "day").toDate(), y: 75 }, + { x: moment(startOfToday).subtract(12, "day").toDate(), y: 69 }, + { x: moment(startOfToday).subtract(11, "day").toDate(), y: 75 }, + { x: moment(startOfToday).subtract(10, "day").toDate(), y: 81 }, + { x: moment(startOfToday).subtract(9, "day").toDate(), y: 93 }, + { x: moment(startOfToday).subtract(8, "day").toDate(), y: 83 }, + { x: moment(startOfToday).subtract(7, "day").toDate(), y: 70 }, + { x: moment(startOfToday).subtract(6, "day").toDate(), y: 74 }, + { x: moment(startOfToday).subtract(5, "day").toDate(), y: 73 }, + { x: moment(startOfToday).subtract(4, "day").toDate(), y: 68 }, + { x: moment(startOfToday).subtract(3, "day").toDate(), y: 72 }, + { x: moment(startOfToday).subtract(2, "day").toDate(), y: 61 }, + { x: moment(startOfToday).subtract(1, "day").toDate(), y: 69 }, + { x: moment(startOfToday).toDate(), y: 60 }, + ], + }, + { + id: "series-4", + name: "Austin", + description: "No links", + data: [ + { x: moment(startOfToday).subtract(59, "day").toDate(), y: 25 }, + { x: moment(startOfToday).subtract(58, "day").toDate(), y: 43 }, + { x: moment(startOfToday).subtract(57, "day").toDate(), y: 40 }, + { x: moment(startOfToday).subtract(56, "day").toDate(), y: 65 }, + { x: moment(startOfToday).subtract(55, "day").toDate(), y: 30 }, + { x: moment(startOfToday).subtract(54, "day").toDate(), y: 25 }, + { x: moment(startOfToday).subtract(53, "day").toDate(), y: 45 }, + { x: moment(startOfToday).subtract(52, "day").toDate(), y: 30 }, + { x: moment(startOfToday).subtract(51, "day").toDate(), y: 85 }, + { x: moment(startOfToday).subtract(50, "day").toDate(), y: 74 }, + { x: moment(startOfToday).subtract(49, "day").toDate(), y: 55 }, + { x: moment(startOfToday).subtract(48, "day").toDate(), y: 23 }, + { x: moment(startOfToday).subtract(47, "day").toDate(), y: 40 }, + { x: moment(startOfToday).subtract(46, "day").toDate(), y: 15 }, + { x: moment(startOfToday).subtract(45, "day").toDate(), y: 20 }, + { x: moment(startOfToday).subtract(44, "day").toDate(), y: 65 }, + { x: moment(startOfToday).subtract(43, "day").toDate(), y: 25 }, + { x: moment(startOfToday).subtract(42, "day").toDate(), y: 40 }, + { x: moment(startOfToday).subtract(41, "day").toDate(), y: 25 }, + { x: moment(startOfToday).subtract(40, "day").toDate(), y: 54 }, + { x: moment(startOfToday).subtract(39, "day").toDate(), y: 65 }, + { x: moment(startOfToday).subtract(38, "day").toDate(), y: 33 }, + { x: moment(startOfToday).subtract(37, "day").toDate(), y: 50 }, + { x: moment(startOfToday).subtract(36, "day").toDate(), y: 45 }, + { x: moment(startOfToday).subtract(35, "day").toDate(), y: 20 }, + { x: moment(startOfToday).subtract(34, "day").toDate(), y: 25 }, + { x: moment(startOfToday).subtract(33, "day").toDate(), y: 35 }, + { x: moment(startOfToday).subtract(32, "day").toDate(), y: 20 }, + { x: moment(startOfToday).subtract(31, "day").toDate(), y: 35 }, + { x: moment(startOfToday).subtract(30, "day").toDate(), y: 14 }, + { x: moment(startOfToday).subtract(29, "day").toDate(), y: 55 }, + { x: moment(startOfToday).subtract(28, "day").toDate(), y: 23 }, + { x: moment(startOfToday).subtract(27, "day").toDate(), y: 10 }, + { x: moment(startOfToday).subtract(26, "day").toDate(), y: 5 }, + { x: moment(startOfToday).subtract(25, "day").toDate(), y: 20 }, + { x: moment(startOfToday).subtract(24, "day").toDate(), y: 35 }, + { x: moment(startOfToday).subtract(23, "day").toDate(), y: 15 }, + { x: moment(startOfToday).subtract(22, "day").toDate(), y: 30 }, + { x: moment(startOfToday).subtract(21, "day").toDate(), y: 35 }, + { x: moment(startOfToday).subtract(20, "day").toDate(), y: 34 }, + { x: moment(startOfToday).subtract(19, "day").toDate(), y: 50 }, + { x: moment(startOfToday).subtract(18, "day").toDate(), y: 60 }, + { x: moment(startOfToday).subtract(17, "day").toDate(), y: 95 }, + { x: moment(startOfToday).subtract(16, "day").toDate(), y: 80 }, + { x: moment(startOfToday).subtract(15, "day").toDate(), y: 65 }, + { x: moment(startOfToday).subtract(14, "day").toDate(), y: 80 }, + { x: moment(startOfToday).subtract(13, "day").toDate(), y: 85 }, + { x: moment(startOfToday).subtract(12, "day").toDate(), y: 69 }, + { x: moment(startOfToday).subtract(11, "day").toDate(), y: 65 }, + { x: moment(startOfToday).subtract(10, "day").toDate(), y: 71 }, + { x: moment(startOfToday).subtract(9, "day").toDate(), y: 73 }, + { x: moment(startOfToday).subtract(8, "day").toDate(), y: 43 }, + { x: moment(startOfToday).subtract(7, "day").toDate(), y: 70 }, + { x: moment(startOfToday).subtract(6, "day").toDate(), y: 84 }, + { x: moment(startOfToday).subtract(5, "day").toDate(), y: 73 }, + { x: moment(startOfToday).subtract(4, "day").toDate(), y: 38 }, + { x: moment(startOfToday).subtract(3, "day").toDate(), y: 72 }, + { x: moment(startOfToday).subtract(2, "day").toDate(), y: 81 }, + { x: moment(startOfToday).subtract(1, "day").toDate(), y: 59 }, + { x: moment(startOfToday).toDate(), y: 60 }, + ], + }, +]; +// Setting the widget dimensions and position (this is for gridster) +const positions: Record = { + [widgetConfigs[0].id]: { + cols: 6, + rows: 6, + y: 0, + x: 0, + }, + [widgetConfigs[1].id]: { + cols: 6, + rows: 6, + y: 0, + x: 6, + }, +}; +\\\\\\\\\\\\\\\`, + "widget-types/timeseries/timeseries-widget-status-bar-example/timeseries-widget-status-bar-example.component.ts": \\\\\\\\\\\\\\\`// © 2022 SolarWinds Worldwide, LLC. All rights reserved. +// +// Permission is hereby granted, free of charge, to any person obtaining a copy +// of this software and associated documentation files (the "Software"), to +// deal in the Software without restriction, including without limitation the +// rights to use, copy, modify, merge, publish, distribute, sublicense, and/or +// sell copies of the Software, and to permit persons to whom the Software is +// furnished to do so, subject to the following conditions: +// +// The above copyright notice and this permission notice shall be included in +// all copies or substantial portions of the Software. +// +// THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR +// IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, +// FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE +// AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER +// LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, +// OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN +// THE SOFTWARE. + +import { + ChangeDetectorRef, + Component, + Injectable, + OnInit, +} from "@angular/core"; +import { GridsterConfig, GridsterItem } from "angular-gridster2"; +import keyBy from "lodash/keyBy"; +import moment, { Moment } from "moment/moment"; +import { BehaviorSubject } from "rxjs"; + +import { + DataSourceService, + IDataSource, + IDataSourceOutput, + INovaFilters, + ITimeframe, +} from "@nova-ui/bits"; +import { CHART_PALETTE_CS_S_EXTENDED } from "@nova-ui/charts"; +import { + applyStatusEndpoints, + DATA_SOURCE, + DEFAULT_PIZZAGNA_ROOT, + IDashboard, + IProviderConfiguration, + ISerializableTimeframe, + ITimeseriesItemConfiguration, + ITimeseriesOutput, + ITimeseriesScaleConfig, + ITimeseriesWidgetConfig, + ITimeseriesWidgetData, + ITimeseriesWidgetSeriesData, + ITimeseriesWidgetStatusData, + IWidget, + LegendPlacement, + PizzagnaLayer, + ProviderRegistryService, + TimeseriesChartPreset, + TimeseriesScaleType, + WellKnownPathKey, + WellKnownProviders, + WidgetTypesService, +} from "@nova-ui/dashboards"; + +/** + * A simple Timeseries data source implementation with continuous (non-interval-based) output + */ +@Injectable() +export class TimeseriesStatusContinuousDataSource + extends DataSourceService + implements IDataSource> +{ + public static providerId = "TimeseriesStatusContinuousDataSource"; + + public busy = new BehaviorSubject(false); + + public async getFilteredData( + filters: INovaFilters + ): Promise< + IDataSourceOutput> + > { + // In this example we're using some static mock data located at the bottom of this file. In a real-world + // scenario, the data for the chart would likely be retrieved via an asynchronous backend call. + const data = getContinuousData(); + let filteredData = data; + + this.busy.next(true); + + // Filtering using the filter registered by the TimeFrameBar + const timeframeFilter = filters.timeframe?.value as ITimeframe; + if (timeframeFilter) { + filteredData = filteredData.map((item: ITimeseriesWidgetData) => ({ + id: item.id, + name: item.name, + description: item.description, + data: item.data.filter( + (seriesData: ITimeseriesWidgetSeriesData) => + filterDates( + seriesData.x, + timeframeFilter.startDatetime, + timeframeFilter.endDatetime + ) + ), + })); + + // apply endpoints on the filtered status data so that when the status chart is zoomed (filtered), + // each status visualizations is ensured to have valid start and end values + filteredData = applyStatusEndpoints( + timeframeFilter, + filteredData, + data + ); + } + + this.busy.next(false); + return { result: { series: filteredData } }; + } +} + +/** + * A simple Timeseries data source implementation with interval-based output + */ +@Injectable() +export class TimeseriesStatusIntervalDataSource + extends DataSourceService + implements IDataSource> +{ + public static providerId = "TimeseriesStatusIntervalDataSource"; + + public busy = new BehaviorSubject(false); + + public async getFilteredData( + filters: INovaFilters + ): Promise< + IDataSourceOutput> + > { + // In this example we're using some static mock data located at the bottom of this file. In a real-world + // scenario, the data for the chart would likely be retrieved via an asynchronous backend call. + const data = getIntervalData(); + let filteredData = data; + + this.busy.next(true); + + // Filtering using the filter registered by the TimeFrameBar + const timeframeFilter = filters.timeframe?.value as ITimeframe; + if (timeframeFilter) { + filteredData = filteredData.map((item: ITimeseriesWidgetData) => ({ + id: item.id, + name: item.name, + description: item.description, + data: item.data.filter( + (seriesData: ITimeseriesWidgetSeriesData) => + filterDates( + seriesData.x, + timeframeFilter.startDatetime, + timeframeFilter.endDatetime + ) + ), + })); + + // Note: There's no need to apply filter endpoints to the status data in this case since we know it's visualized in regular intervals + } + + this.busy.next(false); + return { result: { series: filteredData } }; + } +} + +function filterDates(dateToCheck: Date, startDate: Moment, endDate: Moment) { + const mom = moment(dateToCheck); + return ( + mom.isBetween(startDate, endDate) || + mom.isSame(startDate) || + mom.isSame(endDate) + ); +} + +/** + * A component that instantiates the dashboard + */ +@Component({ + selector: "timeseries-widget-status-bar-example", + templateUrl: "./timeseries-widget-status-bar-example.component.html", + styleUrls: ["./timeseries-widget-status-bar-example.component.less"], + standalone: false, +}) +export class TimeseriesWidgetStatusBarExampleComponent implements OnInit { + // This variable will hold all the data needed to define the layout and behavior of the widgets. + // Pass this to the dashboard component's dashboard input in the template. + public dashboard: IDashboard | undefined; + + // Angular gridster requires a configuration object even if it's empty. + // Pass this to the dashboard component's gridsterConfig input in the template. + public gridsterConfig: GridsterConfig = {}; + + // Boolean passed as an input to the dashboard. When true, widgets can be moved, resized, removed, or edited + public editMode: boolean = false; + + constructor( + // WidgetTypesService provides the widget's necessary structure information + private widgetTypesService: WidgetTypesService, + + // In general, the ProviderRegistryService is used for making entities available for injection into dynamically loaded components. + private providerRegistry: ProviderRegistryService, + private changeDetectorRef: ChangeDetectorRef + ) {} + + public ngOnInit(): void { + // Grabbing the widget's default template which will be needed as a parameter for setNode + const widgetTemplate = this.widgetTypesService.getWidgetType( + "timeseries", + 1 + ); + // Registering our data sources as dropdown options in the widget editor/configurator + // Note: This could also be done in the parent module's constructor so that + // multiple dashboards could have access to the same widget template modification. + this.widgetTypesService.setNode( + // This is the template we grabbed above with getWidgetType + widgetTemplate, + // We are setting the editor/configurator part of the widget template + "configurator", + // This indicates which node you are changing and we want to change + // the data source providers available for selection in the editor. + WellKnownPathKey.DataSourceProviders, + // We are setting the data sources available for selection in the editor + [ + TimeseriesStatusContinuousDataSource.providerId, + TimeseriesStatusIntervalDataSource.providerId, + ] + ); + + // Registering the data source for injection into the widget. + this.providerRegistry.setProviders({ + [TimeseriesStatusContinuousDataSource.providerId]: { + provide: DATA_SOURCE, + useClass: TimeseriesStatusContinuousDataSource, + deps: [], + }, + [TimeseriesStatusIntervalDataSource.providerId]: { + provide: DATA_SOURCE, + useClass: TimeseriesStatusIntervalDataSource, + deps: [], + }, + }); + + this.initializeDashboard(); + } + + /** Used for restoring widgets state */ + public reInitializeDashboard(): void { + // destroys the components and their providers so the dashboard can re init data + this.dashboard = undefined; + this.changeDetectorRef.detectChanges(); + + this.initializeDashboard(); + } + + public initializeDashboard(): void { + // We're using a static configuration object for this example, but this is where + // the widget's configuration could potentially be populated from a database + const widgetsWithStructure = widgetConfigs.map((w) => + this.widgetTypesService.mergeWithWidgetType(w) + ); + const widgetsIndex = keyBy(widgetsWithStructure, (w: IWidget) => w.id); + + // Finally, assigning the variables we created above to the dashboard + this.dashboard = { + positions, + widgets: widgetsIndex, + }; + } +} + +const widgetConfigs: IWidget[] = [ + { + id: "statusChartWidgetId", + type: "timeseries", + pizzagna: { + [PizzagnaLayer.Configuration]: { + [DEFAULT_PIZZAGNA_ROOT]: { + providers: { + [WellKnownProviders.DataSource]: { + // Setting the initially selected data source providerId + providerId: + TimeseriesStatusContinuousDataSource.providerId, + } as IProviderConfiguration, + }, + }, + header: { + properties: { + title: "Status Bar Chart with Continuous (Non-Interval) Scale", + subtitle: "Basic Timeseries Widget", + }, + }, + chart: { + providers: { + [WellKnownProviders.Adapter]: { + properties: { + // Setting the series and corresponding labels to initially display on the chart + series: [ + { + id: "series-1", + label: "Node Status", + selectedSeriesId: "series-1", + }, + { + id: "series-2", + label: "Node Status", + selectedSeriesId: "series-2", + }, + ] as ITimeseriesItemConfiguration[], + }, + } as Partial, + }, + properties: { + configuration: { + legendPlacement: LegendPlacement.Right, + enableZoom: true, + // Setting the preset to status bar + preset: TimeseriesChartPreset.StatusBar, + } as ITimeseriesWidgetConfig, + }, + }, + timeframeSelection: { + properties: { + // Setting the initial timeframe selected in the timeframe bar + timeframe: { + selectedPresetId: "last7Days", + } as ISerializableTimeframe, + maxDate: moment().format(), + }, + }, + }, + }, + }, + { + id: "statusIntervalChartWidgetId", + type: "timeseries", + pizzagna: { + [PizzagnaLayer.Configuration]: { + [DEFAULT_PIZZAGNA_ROOT]: { + providers: { + [WellKnownProviders.DataSource]: { + // Setting the initially selected data source providerId + providerId: + TimeseriesStatusIntervalDataSource.providerId, + } as IProviderConfiguration, + }, + }, + header: { + properties: { + title: "Status Bar Chart with Interval Scale", + subtitle: "Basic Timeseries Widget", + }, + }, + chart: { + providers: { + [WellKnownProviders.Adapter]: { + properties: { + // Setting the series and corresponding labels to initially display on the chart + series: [ + { + id: "series-1", + label: "Node Status", + selectedSeriesId: "series-1", + }, + { + id: "series-2", + label: "Node Status", + selectedSeriesId: "series-2", + }, + ] as ITimeseriesItemConfiguration[], + }, + } as Partial, + }, + properties: { + configuration: { + legendPlacement: LegendPlacement.Right, + enableZoom: true, + // Setting the preset to status bar + preset: TimeseriesChartPreset.StatusBar, + scales: { + x: { + type: TimeseriesScaleType.TimeInterval, + properties: { + // one-day interval in seconds + interval: 24 * 60 * 60, + }, + } as ITimeseriesScaleConfig, + }, + } as ITimeseriesWidgetConfig, + }, + }, + timeframeSelection: { + properties: { + // Setting the initial timeframe selected in the timeframe bar + timeframe: { + selectedPresetId: "last7Days", + } as ISerializableTimeframe, + maxDate: moment().format(), + }, + }, + }, + }, + }, +]; + +export const startOfToday = (): Moment => moment().startOf("day"); + +export const getContinuousData = + (): ITimeseriesWidgetData[] => { + const series: ITimeseriesWidgetData[] = [ + { + id: "series-1", + name: "Node Status", + description: "lastchance.demo.lab", + data: [ + // the 'x' value is set to the time and 'y' to the status at that given time + { + x: startOfToday().subtract(20, "day").toDate(), + y: Status.Warning, + }, + { + x: startOfToday().subtract(19, "day").toDate(), + y: Status.Down, + }, + { + x: startOfToday().subtract(17, "day").toDate(), + y: Status.Critical, + }, + { + x: startOfToday().subtract(16, "day").toDate(), + y: Status.Warning, + }, + { + x: startOfToday().subtract(15, "day").toDate(), + y: Status.Down, + }, + { + x: startOfToday().subtract(14, "day").toDate(), + y: Status.Critical, + }, + { + x: startOfToday().subtract(12, "day").toDate(), + y: Status.Unknown, + }, + { + x: startOfToday().subtract(10, "day").toDate(), + y: Status.Up, + }, + { + x: startOfToday().subtract(9, "day").toDate(), + y: Status.Critical, + }, + { + x: startOfToday().subtract(6, "day").toDate(), + y: Status.Up, + }, + { + x: startOfToday().subtract(3, "day").toDate(), + y: Status.Warning, + }, + { + x: startOfToday().subtract(2, "day").toDate(), + y: Status.Critical, + }, + { + x: startOfToday().subtract(1, "day").toDate(), + y: Status.Up, + }, + // This data point will be ignored and is only here to provide an endpoint for the previous status. + { x: moment().toDate(), y: Status.Up }, + ], + }, + { + id: "series-2", + name: "Node Status", + description: "newhope.demo.lab", + data: [ + { + x: startOfToday().subtract(19, "day").toDate(), + y: Status.Critical, + }, + { + x: startOfToday().subtract(18, "day").toDate(), + y: Status.Unknown, + }, + { + x: startOfToday().subtract(17, "day").toDate(), + y: Status.Warning, + }, + { + x: startOfToday().subtract(15, "day").toDate(), + y: Status.Down, + }, + { + x: startOfToday().subtract(8, "day").toDate(), + y: Status.Critical, + }, + { + x: startOfToday().subtract(7, "day").toDate(), + y: Status.Down, + }, + { + x: startOfToday().subtract(6, "day").toDate(), + y: Status.Up, + }, + { + x: startOfToday().subtract(5, "day").toDate(), + y: Status.Critical, + }, + { + x: startOfToday().subtract(4, "day").toDate(), + y: Status.Up, + }, + { + x: startOfToday().subtract(3, "day").toDate(), + y: Status.Warning, + }, + { + x: startOfToday().subtract(2, "day").toDate(), + y: Status.Up, + }, + { + x: startOfToday().subtract(1, "day").toDate(), + y: Status.Down, + }, + // This data point will be ignored and is only here to provide an endpoint for the previous status. + { x: moment().toDate(), y: Status.Down }, + ], + }, + ]; + + for (const s of series) { + // here are we setting the color and icon associated to the status for each data point + s.data = s.data.map((d: any, i: number) => ({ + ...d, + color: statusColors[d.y as Status], + // The thickness of the line is dependant on the status. If the status equals 'Up' then 'thick' is set to false. + thick: d.y !== Status.Up, + icon: "status_" + d.y, + })); + } + + return series; + }; + +// Note that the output of this function is spaced evenly at one-day intervals +export const getIntervalData = + (): ITimeseriesWidgetData[] => { + const series: ITimeseriesWidgetData[] = [ + { + id: "series-1", + name: "Node Status", + description: "lastchance.demo.lab", + data: [ + // the 'x' value is set to the time and 'y' to the status at that given time + { + x: startOfToday().subtract(20, "day").toDate(), + y: Status.Up, + }, + { + x: startOfToday().subtract(19, "day").toDate(), + y: Status.Critical, + }, + { + x: startOfToday().subtract(18, "day").toDate(), + y: Status.Warning, + }, + { + x: startOfToday().subtract(17, "day").toDate(), + y: Status.Down, + }, + { + x: startOfToday().subtract(16, "day").toDate(), + y: Status.Critical, + }, + { + x: startOfToday().subtract(15, "day").toDate(), + y: Status.Down, + }, + { + x: startOfToday().subtract(14, "day").toDate(), + y: Status.Up, + }, + { + x: startOfToday().subtract(13, "day").toDate(), + y: Status.Warning, + }, + { + x: startOfToday().subtract(12, "day").toDate(), + y: Status.Up, + }, + { + x: startOfToday().subtract(11, "day").toDate(), + y: Status.Critical, + }, + { + x: startOfToday().subtract(10, "day").toDate(), + y: Status.Up, + }, + { + x: startOfToday().subtract(9, "day").toDate(), + y: Status.Down, + }, + { + x: startOfToday().subtract(8, "day").toDate(), + y: Status.Critical, + }, + { + x: startOfToday().subtract(7, "day").toDate(), + y: Status.Warning, + }, + { + x: startOfToday().subtract(6, "day").toDate(), + y: Status.Down, + }, + { + x: startOfToday().subtract(5, "day").toDate(), + y: Status.Critical, + }, + { + x: startOfToday().subtract(4, "day").toDate(), + y: Status.Down, + }, + { + x: startOfToday().subtract(3, "day").toDate(), + y: Status.Up, + }, + { + x: startOfToday().subtract(2, "day").toDate(), + y: Status.Warning, + }, + { + x: startOfToday().subtract(1, "day").toDate(), + y: Status.Critical, + }, + { x: startOfToday().toDate(), y: Status.Up }, + ], + }, + { + id: "series-2", + name: "Node Status", + description: "newhope.demo.lab", + data: [ + { + x: startOfToday().subtract(20, "day").toDate(), + y: Status.Up, + }, + { + x: startOfToday().subtract(19, "day").toDate(), + y: Status.Up, + }, + { + x: startOfToday().subtract(18, "day").toDate(), + y: Status.Down, + }, + { + x: startOfToday().subtract(17, "day").toDate(), + y: Status.Critical, + }, + { + x: startOfToday().subtract(16, "day").toDate(), + y: Status.Down, + }, + { + x: startOfToday().subtract(15, "day").toDate(), + y: Status.Up, + }, + { + x: startOfToday().subtract(14, "day").toDate(), + y: Status.Critical, + }, + { + x: startOfToday().subtract(13, "day").toDate(), + y: Status.Up, + }, + { + x: startOfToday().subtract(12, "day").toDate(), + y: Status.Critical, + }, + { + x: startOfToday().subtract(11, "day").toDate(), + y: Status.Warning, + }, + { + x: startOfToday().subtract(10, "day").toDate(), + y: Status.Up, + }, + { + x: startOfToday().subtract(9, "day").toDate(), + y: Status.Down, + }, + { + x: startOfToday().subtract(8, "day").toDate(), + y: Status.Up, + }, + { + x: startOfToday().subtract(7, "day").toDate(), + y: Status.Down, + }, + { + x: startOfToday().subtract(6, "day").toDate(), + y: Status.Critical, + }, + { + x: startOfToday().subtract(5, "day").toDate(), + y: Status.Down, + }, + { + x: startOfToday().subtract(4, "day").toDate(), + y: Status.Up, + }, + { + x: startOfToday().subtract(3, "day").toDate(), + y: Status.Critical, + }, + { + x: startOfToday().subtract(2, "day").toDate(), + y: Status.Up, + }, + { + x: startOfToday().subtract(1, "day").toDate(), + y: Status.Warning, + }, + { x: startOfToday().toDate(), y: Status.Critical }, + ], + }, + ]; + + for (const s of series) { + // here are we setting the color and icon associated to the status for each data point + s.data = s.data.map((d: any, i: number) => ({ + ...d, + color: statusColors[d.y as Status], + // The thickness of the line is dependant on the status. If the status equals 'Up' then 'thick' is set to false. + thick: d.y !== Status.Up, + icon: "status_" + d.y, + })); + } + + return series; + }; + +// An enumeration of statuses +enum Status { + Unknown = "unknown", + Up = "up", + Warning = "warning", + Down = "down", + Critical = "critical", +} + +// This is the map used for setting the color of each status bar +const statusColors: Record = { + [Status.Unknown]: CHART_PALETTE_CS_S_EXTENDED[6], + [Status.Up]: CHART_PALETTE_CS_S_EXTENDED[8], + [Status.Warning]: CHART_PALETTE_CS_S_EXTENDED[4], + [Status.Down]: CHART_PALETTE_CS_S_EXTENDED[0], + [Status.Critical]: CHART_PALETTE_CS_S_EXTENDED[2], +}; + +// Setting the widget dimensions and position (this is for gridster) +const positions: Record = { + [widgetConfigs[0].id]: { + cols: 12, + rows: 4, + y: 0, + x: 0, + }, + [widgetConfigs[1].id]: { + cols: 12, + rows: 4, + y: 4, + x: 0, + }, +}; +\\\\\\\\\\\\\\\`, + "widget-types/view-components/kpi-tile-view-basic/kpi-tile-view-basic-example.component.ts": \\\\\\\\\\\\\\\`// © 2022 SolarWinds Worldwide, LLC. All rights reserved. +// +// Permission is hereby granted, free of charge, to any person obtaining a copy +// of this software and associated documentation files (the "Software"), to +// deal in the Software without restriction, including without limitation the +// rights to use, copy, modify, merge, publish, distribute, sublicense, and/or +// sell copies of the Software, and to permit persons to whom the Software is +// furnished to do so, subject to the following conditions: +// +// The above copyright notice and this permission notice shall be included in +// all copies or substantial portions of the Software. +// +// THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR +// IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, +// FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE +// AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER +// LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, +// OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN +// THE SOFTWARE. + +import { Component } from "@angular/core"; +import { FormControl } from "@angular/forms"; + +type KpiTileState = "normal" | "loading" | "empty"; + +/** + * KPI Tile View - Playground example. + * Switch between all visual states (normal / loading / empty) and toggle + * interactivity to explore every variant the standalone tile supports. + */ +@Component({ + selector: "kpi-tile-view-basic-example", + templateUrl: "./kpi-tile-view-basic-example.component.html", + standalone: false, +}) +export class KpiTileViewBasicExampleComponent { + public readonly stateControl = new FormControl("normal", { + nonNullable: true, + }); + public interactive = false; + public lastClicked = ""; + + public readonly stateOptions: KpiTileState[] = ["normal", "loading", "empty"]; + + public onTileClick(label: string): void { + this.lastClicked = label; + } +} +\\\\\\\\\\\\\\\`, + "widget-types/view-components/kpi-tile-view-interactive/kpi-tile-view-interactive-example.component.ts": \\\\\\\\\\\\\\\`// © 2022 SolarWinds Worldwide, LLC. All rights reserved. +// +// Permission is hereby granted, free of charge, to any person obtaining a copy +// of this software and associated documentation files (the "Software"), to +// deal in the Software without restriction, including without limitation the +// rights to use, copy, modify, merge, publish, distribute, sublicense, and/or +// sell copies of the Software, and to permit persons to whom the Software is +// furnished to do so, subject to the following conditions: +// +// The above copyright notice and this permission notice shall be included in +// all copies or substantial portions of the Software. +// +// THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR +// IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, +// FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE +// AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER +// LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, +// OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN +// THE SOFTWARE. + +import { Component, TemplateRef, ViewChild } from "@angular/core"; + +/** + * Interactive KPI Tile View example with custom value formatting + * and click event handling. + */ +@Component({ + selector: "kpi-tile-view-interactive-example", + templateUrl: "./kpi-tile-view-interactive-example.component.html", + standalone: false, +}) +export class KpiTileViewInteractiveExampleComponent { + public currentValue = 1_247; + public lastClickedTile = ""; + + @ViewChild("customValueTpl", { static: true }) + public customValueTpl: TemplateRef; + + public onTileClick(): void { + this.lastClickedTile = "Active Sessions"; + } + + public onUptimeClick(): void { + this.lastClickedTile = "Uptime"; + } +} +\\\\\\\\\\\\\\\`, + "widget-types/view-components/proportional-chart-view-playground/proportional-chart-view-playground-example.component.ts": \\\\\\\\\\\\\\\`// © 2022 SolarWinds Worldwide, LLC. All rights reserved. +// +// Permission is hereby granted, free of charge, to any person obtaining a copy +// of this software and associated documentation files (the "Software"), to +// deal in the Software without restriction, including without limitation the +// rights to use, copy, modify, merge, publish, distribute, sublicense, and/or +// sell copies of the Software, and to permit persons to whom the Software is +// furnished to do so, subject to the following conditions: +// +// The above copyright notice and this permission notice shall be included in +// all copies or substantial portions of the Software. +// +// THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR +// IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, +// FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE +// AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER +// LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, +// OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN +// THE SOFTWARE. + +import { Component } from "@angular/core"; +import { FormControl } from "@angular/forms"; + +import { IProportionalDataItem } from "@nova-ui/dashboards"; + +type ProportionalChartType = "donut" | "pie" | "verticalBar" | "horizontalBar"; +type LegendPlacement = "right" | "bottom" | "none"; + +/** + * Proportional Chart View - Playground example. + * Lets you switch between all supported chart types and legend placements + * to see every visual variant the standalone view component provides. + */ +@Component({ + selector: "proportional-chart-view-playground-example", + templateUrl: "./proportional-chart-view-playground-example.component.html", + standalone: false, +}) +export class ProportionalChartViewPlaygroundExampleComponent { + public readonly chartTypeControl = new FormControl( + "donut", + { nonNullable: true } + ); + public readonly legendPlacementControl = new FormControl( + "right", + { nonNullable: true } + ); + + public readonly chartTypeOptions: ProportionalChartType[] = [ + "donut", + "pie", + "verticalBar", + "horizontalBar", + ]; + public readonly legendPlacementOptions: LegendPlacement[] = [ + "right", + "bottom", + "none", + ]; + + public colors: Record = { + down: "#dc3545", + up: "#2cc079", + warning: "#f3a002", + unknown: "#707070", + }; + + public chartData: Array = [ + { id: "up", name: "Up", value: 78 }, + { id: "down", name: "Down", value: 8 }, + { id: "warning", name: "Warning", value: 12 }, + { id: "unknown", name: "Unknown", value: 2 }, + ]; + + public totalOf(data: IProportionalDataItem[] | undefined): number { + return (data ?? []).reduce((sum, d) => sum + (d?.value ?? 0), 0); + } +} +\\\\\\\\\\\\\\\`, + "widget-types/view-components/view-components-docs.component.ts": \\\\\\\\\\\\\\\`// © 2022 SolarWinds Worldwide, LLC. All rights reserved. +// +// Permission is hereby granted, free of charge, to any person obtaining a copy +// of this software and associated documentation files (the "Software"), to +// deal in the Software without restriction, including without limitation the +// rights to use, copy, modify, merge, publish, distribute, sublicense, and/or +// sell copies of the Software, and to permit persons to whom the Software is +// furnished to do so, subject to the following conditions: +// +// The above copyright notice and this permission notice shall be included in +// all copies or substantial portions of the Software. +// +// THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR +// IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, +// FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE +// AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER +// LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, +// OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN +// THE SOFTWARE. + +import { Component } from "@angular/core"; + +@Component({ + selector: "nui-view-components-docs", + templateUrl: "./view-components-docs.component.html", + standalone: false, +}) +export class ViewComponentsDocsComponent { + public readonly installationSnippet = \\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\`import { NuiDashboardViewsModule } from "@nova-ui/dashboards"; + +@NgModule({ + imports: [NuiDashboardViewsModule], +}) +export class MyFeatureModule {}\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\`; + + public readonly proportionalDataItemSnippet = \\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\`interface IProportionalDataItem { + id: string; // Unique segment identifier + name: string; // Display name in legend + value: number; // Numeric value determining segment size + color?: string; // Optional CSS color (hex or token) + icon?: string; // Optional icon name for legend + link?: string; // Optional drill-down URL +}\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\`; +} +\\\\\\\\\\\\\\\`, + "widget-types/view-components/view-components-docs.module.ts": \\\\\\\\\\\\\\\`// © 2022 SolarWinds Worldwide, LLC. All rights reserved. +// +// Permission is hereby granted, free of charge, to any person obtaining a copy +// of this software and associated documentation files (the "Software"), to +// deal in the Software without restriction, including without limitation the +// rights to use, copy, modify, merge, publish, distribute, sublicense, and/or +// sell copies of the Software, and to permit persons to whom the Software is +// furnished to do so, subject to the following conditions: +// +// The above copyright notice and this permission notice shall be included in +// all copies or substantial portions of the Software. +// +// THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR +// IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, +// FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE +// AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER +// LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, +// OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN +// THE SOFTWARE. + +import { CommonModule } from "@angular/common"; +import { NgModule } from "@angular/core"; +import { ReactiveFormsModule } from "@angular/forms"; +import { RouterModule, Routes } from "@angular/router"; + +import { + NuiDocsModule, + NuiIconModule, + NuiMessageModule, + NuiFormFieldModule, + NuiSelectV2Module, + NuiSwitchModule, + DEMO_PATH_TOKEN, +} from "@nova-ui/bits"; +import { NuiDashboardViewsModule } from "@nova-ui/dashboards"; + +import { getDemoFiles } from "../../../../demo-files-factory"; +import { KpiTileViewBasicExampleComponent } from "./kpi-tile-view-basic/kpi-tile-view-basic-example.component"; +import { KpiTileViewInteractiveExampleComponent } from "./kpi-tile-view-interactive/kpi-tile-view-interactive-example.component"; +import { ProportionalChartViewPlaygroundExampleComponent } from "./proportional-chart-view-playground/proportional-chart-view-playground-example.component"; +import { ViewComponentsDocsComponent } from "./view-components-docs.component"; + +const routes: Routes = [ + { + path: "", + component: ViewComponentsDocsComponent, + data: { + srlc: { + hideIndicator: true, + }, + showThemeSwitcher: true, + }, + }, + { + path: "kpi-tile-view-basic", + component: KpiTileViewBasicExampleComponent, + data: { + srlc: { + hideIndicator: true, + }, + }, + }, + { + path: "proportional-chart-view-playground", + component: ProportionalChartViewPlaygroundExampleComponent, + data: { + srlc: { + hideIndicator: true, + }, + }, + }, +]; + +@NgModule({ + imports: [ + CommonModule, + ReactiveFormsModule, + RouterModule.forChild(routes), + NuiDocsModule, + NuiMessageModule, + NuiIconModule, + NuiFormFieldModule, + NuiSelectV2Module, + NuiSwitchModule, + NuiDashboardViewsModule, + ], + declarations: [ + ViewComponentsDocsComponent, + KpiTileViewBasicExampleComponent, + KpiTileViewInteractiveExampleComponent, + ProportionalChartViewPlaygroundExampleComponent, + ], + providers: [ + { + provide: DEMO_PATH_TOKEN, + useValue: getDemoFiles("view-components"), + }, + ], +}) +export default class ViewComponentsDocsModule {} +\\\\\\\\\\\\\\\`, + "widget-types/widget-types.module.ts": \\\\\\\\\\\\\\\`// © 2022 SolarWinds Worldwide, LLC. All rights reserved. +// +// Permission is hereby granted, free of charge, to any person obtaining a copy +// of this software and associated documentation files (the "Software"), to +// deal in the Software without restriction, including without limitation the +// rights to use, copy, modify, merge, publish, distribute, sublicense, and/or +// sell copies of the Software, and to permit persons to whom the Software is +// furnished to do so, subject to the following conditions: +// +// The above copyright notice and this permission notice shall be included in +// all copies or substantial portions of the Software. +// +// THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR +// IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, +// FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE +// AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER +// LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, +// OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN +// THE SOFTWARE. + +import { NgModule, Type } from "@angular/core"; +import { RouterModule, Routes } from "@angular/router"; + +import { NuiDocsModule } from "@nova-ui/bits"; +import { + ConfiguratorHeadingService, + NuiDashboardsModule, +} from "@nova-ui/dashboards"; + +export enum WidgetTypesRoute { + kpi = "kpi", + riskScore = "risk-score", + timeseries = "timeseries", + table = "table", + proportional = "proportional", + embedded = "embedded", + drilldown = "drilldown", + viewComponents = "view-components", +} + +const routes: Routes = [ + { + path: WidgetTypesRoute.kpi, + loadChildren: async () => + import("./kpi/kpi-docs.module") as object as Promise>, + data: { + srlc: { + hideIndicator: true, + }, + }, + }, + { + path: WidgetTypesRoute.riskScore, + loadChildren: async () => + import("./risk-score/risk-score-docs.module") as object as Promise< + Type + >, + data: { + srlc: { + hideIndicator: true, + }, + }, + }, + { + path: WidgetTypesRoute.timeseries, + loadChildren: async () => + import("./timeseries/timeseries-docs.module") as object as Promise< + Type + >, + data: { + srlc: { + hideIndicator: true, + }, + }, + }, + { + path: WidgetTypesRoute.table, + loadChildren: async () => + import("./table/table-docs.module") as object as Promise>, + data: { + srlc: { + hideIndicator: true, + }, + }, + }, + { + path: WidgetTypesRoute.proportional, + loadChildren: async () => + import( + "./proportional/proportional-docs.module" + ) as object as Promise>, + data: { + srlc: { + hideIndicator: true, + }, + }, + }, + { + path: WidgetTypesRoute.embedded, + loadChildren: async () => + import( + "./embedded-content/embedded-content-docs.module" + ) as object as Promise>, + data: { + srlc: { + hideIndicator: true, + }, + }, + }, + { + path: WidgetTypesRoute.drilldown, + loadChildren: async () => + import( + "./drilldown/drilldown-widget-docs.module" + ) as object as Promise>, + data: { + srlc: { + hideIndicator: true, + }, + }, + }, + { + path: WidgetTypesRoute.viewComponents, + loadChildren: async () => + import( + "./view-components/view-components-docs.module" + ) as object as Promise>, + data: { + srlc: { + hideIndicator: true, + }, + }, + }, +]; + +@NgModule({ + imports: [ + RouterModule.forChild(routes), + NuiDocsModule, + NuiDashboardsModule, + ], + providers: [ConfiguratorHeadingService], +}) +export default class WidgetTypesModule {} +\\\\\\\\\\\\\\\`, +}; +\\\\\\\`, + "overview/hero/dashboard/hero-dashboard.component.ts": \\\\\\\`// © 2022 SolarWinds Worldwide, LLC. All rights reserved. +// +// Permission is hereby granted, free of charge, to any person obtaining a copy +// of this software and associated documentation files (the "Software"), to +// deal in the Software without restriction, including without limitation the +// rights to use, copy, modify, merge, publish, distribute, sublicense, and/or +// sell copies of the Software, and to permit persons to whom the Software is +// furnished to do so, subject to the following conditions: +// +// The above copyright notice and this permission notice shall be included in +// all copies or substantial portions of the Software. +// +// THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR +// IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, +// FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE +// AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER +// LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, +// OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN +// THE SOFTWARE. + +import { HttpClient } from "@angular/common/http"; +import { + ChangeDetectionStrategy, + Component, + OnInit, + ViewEncapsulation, +} from "@angular/core"; +import keyBy from "lodash/keyBy"; + +import { LoggerService, ThemeSwitchService } from "@nova-ui/bits"; +import { + DATA_SOURCE, + IDashboard, + IWidget, + ProviderRegistryService, + WidgetTypesService, +} from "@nova-ui/dashboards"; + +import { positions, widgets } from "./widget-configs"; +import { + HarryPotterAverageRatingDataSource, + HarryPotterRatingsCountDataSource, +} from "../data/kpi-datasources"; +import { + BeerReviewCountsByCityMockDataSource, + BeerReviewCountsByCityMockDataSource2, +} from "../data/proportional-datasources"; +import { BeerDataSource } from "../data/table/beer-data-source"; +import { RandomUserDataSource } from "../data/table/random-user-data-source"; +import { + BeerVsReadingMockDataSource, + LoungingVsFrisbeeGolfMockDataSource, +} from "../data/timeseries-data-sources"; + +/** + * A component that instantiates the dashboard + */ +@Component({ + selector: "hero-dashboard", + templateUrl: "./hero-dashboard.component.html", + styleUrls: ["./hero-dashboard.component.less"], + encapsulation: ViewEncapsulation.Emulated, + changeDetection: ChangeDetectionStrategy.Default, + standalone: false, +}) +export class HeroDashboardComponent implements OnInit { + public dashboard: IDashboard = { + positions: {}, + widgets: {}, + }; + + public gridsterConfig = {}; + public editMode = false; + + constructor( + private providerRegistry: ProviderRegistryService, + public themeSwitcherService: ThemeSwitchService, + private widgetTypesService: WidgetTypesService + ) { + this.providerRegistry.setProviders({ + [HarryPotterAverageRatingDataSource.providerId]: { + provide: DATA_SOURCE, + useClass: HarryPotterAverageRatingDataSource, + deps: [HttpClient], + }, + [HarryPotterRatingsCountDataSource.providerId]: { + provide: DATA_SOURCE, + useClass: HarryPotterRatingsCountDataSource, + deps: [HttpClient], + }, + [BeerReviewCountsByCityMockDataSource.providerId]: { + provide: DATA_SOURCE, + useClass: BeerReviewCountsByCityMockDataSource, + deps: [], + }, + [BeerReviewCountsByCityMockDataSource2.providerId]: { + provide: DATA_SOURCE, + useClass: BeerReviewCountsByCityMockDataSource2, + deps: [], + }, + [RandomUserDataSource.providerId]: { + provide: DATA_SOURCE, + useClass: RandomUserDataSource, + deps: [LoggerService], + }, + [BeerDataSource.providerId]: { + provide: DATA_SOURCE, + useClass: BeerDataSource, + deps: [LoggerService], + }, + [BeerVsReadingMockDataSource.providerId]: { + provide: DATA_SOURCE, + useClass: BeerVsReadingMockDataSource, + deps: [], + }, + [LoungingVsFrisbeeGolfMockDataSource.providerId]: { + provide: DATA_SOURCE, + useClass: LoungingVsFrisbeeGolfMockDataSource, + deps: [], + }, + }); + } + + public ngOnInit(): void { + const widgetsWithStructure = widgets.map((w) => ({ + ...w, + pizzagna: { + ...this.widgetTypesService.getWidgetType(w.type, w.version) + .widget, + ...w.pizzagna, + }, + })); + const widgetsIndex = keyBy(widgetsWithStructure, (w: IWidget) => w.id); + + this.dashboard = { + positions: positions, + widgets: widgetsIndex, + }; + } +} +\\\\\\\`, + "overview/hero/dashboard/widget-configs.ts": \\\\\\\`// © 2022 SolarWinds Worldwide, LLC. All rights reserved. +// +// Permission is hereby granted, free of charge, to any person obtaining a copy +// of this software and associated documentation files (the "Software"), to +// deal in the Software without restriction, including without limitation the +// rights to use, copy, modify, merge, publish, distribute, sublicense, and/or +// sell copies of the Software, and to permit persons to whom the Software is +// furnished to do so, subject to the following conditions: +// +// The above copyright notice and this permission notice shall be included in +// all copies or substantial portions of the Software. +// +// THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR +// IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, +// FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE +// AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER +// LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, +// OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN +// THE SOFTWARE. + +import { GridsterItem } from "angular-gridster2"; + +import { IWidget } from "@nova-ui/dashboards"; + +import { kpiConfig } from "../widget-configs/kpi"; +import { proportionalConfig } from "../widget-configs/proportional"; +import { tableConfig } from "../widget-configs/table"; +import { timeseriesConfig } from "../widget-configs/timeseries"; + +export const positions: Record = { + [tableConfig.id]: { + cols: 7, + rows: 7, + y: 0, + x: 0, + }, + [proportionalConfig.id]: { + cols: 5, + rows: 7, + y: 0, + x: 7, + }, + [kpiConfig.id]: { + cols: 6, + rows: 7, + y: 7, + x: 0, + }, + [timeseriesConfig.id]: { + cols: 6, + rows: 7, + y: 7, + x: 6, + }, +}; + +export const widgets: IWidget[] = [ + { + ...tableConfig, + }, + { + ...proportionalConfig, + }, + { + ...kpiConfig, + }, + { + ...timeseriesConfig, + }, +]; +\\\\\\\`, + "overview/hero/data/kpi-datasources.ts": \\\\\\\`// © 2022 SolarWinds Worldwide, LLC. All rights reserved. +// +// Permission is hereby granted, free of charge, to any person obtaining a copy +// of this software and associated documentation files (the "Software"), to +// deal in the Software without restriction, including without limitation the +// rights to use, copy, modify, merge, publish, distribute, sublicense, and/or +// sell copies of the Software, and to permit persons to whom the Software is +// furnished to do so, subject to the following conditions: +// +// The above copyright notice and this permission notice shall be included in +// all copies or substantial portions of the Software. +// +// THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR +// IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, +// FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE +// AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER +// LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, +// OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN +// THE SOFTWARE. + +import { HttpClient, HttpErrorResponse } from "@angular/common/http"; +import { Injectable, OnDestroy } from "@angular/core"; +import { BehaviorSubject } from "rxjs"; +import { finalize } from "rxjs/operators"; + +import { DataSourceService, IFilteringOutputs } from "@nova-ui/bits"; +import { IKpiData } from "@nova-ui/dashboards"; + +import { GOOGLE_BOOKS_URL } from "./table/constants"; + +@Injectable() +export class HarryPotterAverageRatingDataSource + extends DataSourceService + implements OnDestroy +{ + public static providerId = "HarryPotterAverageRatingDataSource"; + + public busy = new BehaviorSubject(false); + + constructor(private http: HttpClient) { + super(); + } + + public async getFilteredData(): Promise { + this.busy.next(true); + return new Promise((resolve) => { + // *** Make a resource request to an external API (if needed) + this.http + .get(\\\\\\\\\\\\\\\`\\\\\\\\\\\\\\\${GOOGLE_BOOKS_URL}/5MQFrgEACAAJ\\\\\\\\\\\\\\\`) + .pipe(finalize(() => this.busy.next(false))) + .subscribe({ + next: (data: any) => { + resolve({ + result: { + value: data.volumeInfo.averageRating, + }, + }); + }, + error: (error: HttpErrorResponse) => { + resolve({ + result: null, + error: { + type: error.status, + }, + }); + }, + }); + }); + } + + public ngOnDestroy(): void { + this.outputsSubject.complete(); + } +} + +@Injectable() +export class HarryPotterRatingsCountDataSource + extends DataSourceService + implements OnDestroy +{ + public static providerId = "HarryPotterRatingsCountDataSource"; + + public busy = new BehaviorSubject(false); + + constructor(private http: HttpClient) { + super(); + } + + public async getFilteredData(): Promise { + this.busy.next(true); + return new Promise((resolve) => { + this.http + .get(\\\\\\\\\\\\\\\`\\\\\\\\\\\\\\\${GOOGLE_BOOKS_URL}/5MQFrgEACAAJ\\\\\\\\\\\\\\\`) + .pipe(finalize(() => this.busy.next(false))) + .subscribe({ + next: (data: any) => { + resolve({ + result: { + value: data.volumeInfo.ratingsCount, + }, + }); + }, + error: (error: HttpErrorResponse) => { + resolve({ + result: null, + error: { + type: error.status, + }, + }); + }, + }); + }); + } + + public ngOnDestroy(): void { + this.outputsSubject.complete(); + } +} +\\\\\\\`, + "overview/hero/data/proportional-datasources.ts": \\\\\\\`// © 2022 SolarWinds Worldwide, LLC. All rights reserved. +// +// Permission is hereby granted, free of charge, to any person obtaining a copy +// of this software and associated documentation files (the "Software"), to +// deal in the Software without restriction, including without limitation the +// rights to use, copy, modify, merge, publish, distribute, sublicense, and/or +// sell copies of the Software, and to permit persons to whom the Software is +// furnished to do so, subject to the following conditions: +// +// The above copyright notice and this permission notice shall be included in +// all copies or substantial portions of the Software. +// +// THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR +// IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, +// FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE +// AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER +// LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, +// OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN +// THE SOFTWARE. + +import { Injectable, OnDestroy } from "@angular/core"; +import { BehaviorSubject } from "rxjs"; + +import { + DataSourceService, + IDataSource, + IFilteringOutputs, +} from "@nova-ui/bits"; + +import { + getMockBeerReviewCountsByCity, + getMockBeerReviewCountsByCity2, + IProportionalWidgetData, +} from "./widget-data"; + +@Injectable() +export class BeerReviewCountsByCityMockDataSource + extends DataSourceService + implements IDataSource, OnDestroy +{ + // This is the ID we'll use to identify the provider + public static providerId = "BeerReviewCountsByCityMockDataSource"; + public busy = new BehaviorSubject(false); + + public async getFilteredData(): Promise { + this.busy.next(true); + return new Promise((resolve) => { + setTimeout(() => { + this.outputsSubject.next({ + result: getMockBeerReviewCountsByCity(), + }); + this.busy.next(false); + }, 300); + }); + } + + public ngOnDestroy(): void { + this.outputsSubject.complete(); + } +} + +@Injectable() +export class BeerReviewCountsByCityMockDataSource2 + extends DataSourceService + implements IDataSource, OnDestroy +{ + // This is the ID we'll use to identify the provider + public static providerId = "BeerReviewCountsByCityMockDataSource2"; + public busy = new BehaviorSubject(false); + + public async getFilteredData(): Promise { + this.busy.next(true); + return new Promise((resolve) => { + setTimeout(() => { + this.outputsSubject.next({ + result: getMockBeerReviewCountsByCity2(), + }); + this.busy.next(false); + }, 1500); + }); + } + + public ngOnDestroy(): void { + this.outputsSubject.complete(); + } +} +\\\\\\\`, + "overview/hero/data/table/beer-data-source.ts": \\\\\\\`// © 2022 SolarWinds Worldwide, LLC. All rights reserved. +// +// Permission is hereby granted, free of charge, to any person obtaining a copy +// of this software and associated documentation files (the "Software"), to +// deal in the Software without restriction, including without limitation the +// rights to use, copy, modify, merge, publish, distribute, sublicense, and/or +// sell copies of the Software, and to permit persons to whom the Software is +// furnished to do so, subject to the following conditions: +// +// The above copyright notice and this permission notice shall be included in +// all copies or substantial portions of the Software. +// +// THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR +// IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, +// FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE +// AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER +// LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, +// OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN +// THE SOFTWARE. + +import { ListRange } from "@angular/cdk/collections"; +import { Injectable } from "@angular/core"; +import isEqual from "lodash/isEqual"; +import orderBy from "lodash/orderBy"; +import { BehaviorSubject } from "rxjs"; + +import { + DataSourceService, + IDataField, + INovaFilteringOutputs, + INovaFilters, + ISorterFilter, + LoggerService, +} from "@nova-ui/bits"; + +import { IBrewDatasourceResponse, IBrewInfo } from "../types"; +import { BREW_API_URL } from "./constants"; + +@Injectable() +export class BeerDataSource extends DataSourceService { + public static providerId = "BeerDataSource"; + + private cache = Array.from({ length: 0 }); + private lastSortValue?: ISorterFilter; + private lastVirtualScroll?: ListRange; + private totalItems: number = 325; + + public page: number = 1; + public busy = new BehaviorSubject(false); + + public dataFields: Array = [ + { id: "id", label: "No", dataType: "number" }, + { id: "name", label: "Name", dataType: "string" }, + { id: "tagline", label: "Tagline", dataType: "string" }, + { id: "first_brewed", label: "First Brewed", dataType: "string" }, + { id: "description", label: "Description", dataType: "string" }, + { id: "brewers_tips", label: "Brewer's Tips", dataType: "string" }, + ]; + + constructor(private logger: LoggerService) { + super(); + } + + public async getFilteredData( + filters: INovaFilters + ): Promise { + const start = filters.virtualScroll?.value?.start ?? 0; + const end = filters.virtualScroll?.value?.end ?? 0; + const delta = end - start; + + // This condition handles sorting. We want to sort columns without fetching another chunk of data. + // Since the data is being fetched when scrolled, we compare virtual scroll indexes here in the condition as well. + if (filters.sorter?.value) { + if ( + !isEqual(this.lastSortValue, filters.sorter.value) && + isEqual(this.lastVirtualScroll, filters.virtualScroll?.value) + ) { + const totalPages = Math.ceil( + delta ? this.totalItems / delta : 1 + ); + const itemsPerPage: number = Math.max( + delta < 80 ? delta : 80, + 1 + ); + let response: Array | null = null; + let map: IBrewDatasourceResponse; + + if (filters.sorter?.value?.direction === "desc") { + this.cache = []; + for (let i = 0; i < this.page; ++i) { + response = await ( + await fetch( + \\\\\\\\\\\\\\\`\\\\\\\\\\\\\\\${BREW_API_URL}/?page=\\\\\\\\\\\\\\\${ + totalPages - i || 1 + }&per_page=\\\\\\\\\\\\\\\${itemsPerPage}\\\\\\\\\\\\\\\` + ) + ).json(); + + // since the last page contains only 5 items we need to fetch another page to give virtual scroll enough space to work + if (response && response.length < itemsPerPage) { + this.page++; + } + map = { + brewInfo: response?.map((result: IBrewInfo) => ({ + id: result.id, + name: result.name, + tagline: result.tagline, + first_brewed: result.first_brewed, + description: result.description, + brewers_tips: result.brewers_tips, + })), + total: response?.length, + } as IBrewDatasourceResponse; + this.cache = + totalPages - i !== 0 + ? this.cache.concat(map.brewInfo) + : this.cache; + } + } + + if (filters.sorter?.value?.direction === "asc") { + this.cache = []; + for (let i = 0; i < this.page; i++) { + response = await ( + await fetch( + \\\\\\\\\\\\\\\`\\\\\\\\\\\\\\\${BREW_API_URL}/?page=\\\\\\\\\\\\\\\${ + i + 1 + }&per_page=\\\\\\\\\\\\\\\${itemsPerPage}\\\\\\\\\\\\\\\` + ) + ).json(); + map = { + brewInfo: response?.map((result: IBrewInfo) => ({ + id: result.id, + name: result.name, + tagline: result.tagline, + first_brewed: result.first_brewed, + description: result.description, + brewers_tips: result.brewers_tips, + })), + total: response?.length, + } as IBrewDatasourceResponse; + this.cache = this.cache.concat(map.brewInfo); + } + } + + this.lastSortValue = filters.sorter?.value; + this.lastVirtualScroll = filters.virtualScroll?.value; + + return { + repeat: { itemsSource: this.sortData(this.cache, filters) }, + paginator: { total: this.totalItems }, + dataFields: this.dataFields, + }; + } + } + + this.busy.next(true); + return new Promise((resolve) => { + setTimeout(() => { + this.getData(start, end, filters).then( + (response: INovaFilteringOutputs) => { + if (!response) { + return; + } + + this.cache = this.cache.concat(response.brewInfo); + + this.dataSubject.next(this.cache); + resolve({ + repeat: { + itemsSource: this.sortData(this.cache, filters), + }, + paginator: { total: this.totalItems }, + dataFields: this.dataFields, + }); + + this.lastSortValue = filters.sorter?.value; + this.lastVirtualScroll = filters.virtualScroll?.value; + this.busy.next(false); + } + ); + }, 500); + }); + } + + public async getData( + start: number = 0, + end: number = 20, + filters: INovaFilters + ): Promise { + const delta = end - start; + const totalPages = Math.ceil(delta ? this.totalItems / delta : 1); + let response: Array | null = null; + // The api.punk.com is able to return only 80 items per page + const itemsPerPage: number = Math.max(delta < 80 ? delta : 80, 1); + + if (filters.sorter?.value?.direction === "asc") { + response = await ( + await fetch( + \\\\\\\\\\\\\\\`\\\\\\\\\\\\\\\${BREW_API_URL}/?page=\\\\\\\\\\\\\\\${this.page}&per_page=\\\\\\\\\\\\\\\${itemsPerPage}\\\\\\\\\\\\\\\` + ) + ).json(); + } + + if (filters.sorter?.value?.direction === "desc") { + response = await ( + await fetch( + \\\\\\\\\\\\\\\`\\\\\\\\\\\\\\\${BREW_API_URL}/?page=\\\\\\\\\\\\\\\${ + totalPages - this.page + }&per_page=\\\\\\\\\\\\\\\${itemsPerPage}\\\\\\\\\\\\\\\` + ) + ).json(); + } + + if (!filters.sorter) { + response = await ( + await fetch( + \\\\\\\\\\\\\\\`\\\\\\\\\\\\\\\${BREW_API_URL}/?page=\\\\\\\\\\\\\\\${this.page}&per_page=\\\\\\\\\\\\\\\${itemsPerPage}\\\\\\\\\\\\\\\` + ) + ).json(); + } + return { + brewInfo: response?.map((result: IBrewInfo, i: number) => ({ + id: result.id, + name: result.name, + tagline: result.tagline, + first_brewed: result.first_brewed, + description: result.description, + brewers_tips: result.brewers_tips, + })), + total: response?.length, + } as IBrewDatasourceResponse; + } + + private sortData(data: IBrewInfo[], filters: INovaFilters) { + return orderBy( + data, + filters.sorter?.value?.sortBy, + filters.sorter?.value?.direction as "desc" | "asc" + ); + } +} +\\\\\\\`, + "overview/hero/data/table/constants.ts": \\\\\\\`// © 2022 SolarWinds Worldwide, LLC. All rights reserved. +// +// Permission is hereby granted, free of charge, to any person obtaining a copy +// of this software and associated documentation files (the "Software"), to +// deal in the Software without restriction, including without limitation the +// rights to use, copy, modify, merge, publish, distribute, sublicense, and/or +// sell copies of the Software, and to permit persons to whom the Software is +// furnished to do so, subject to the following conditions: +// +// The above copyright notice and this permission notice shall be included in +// all copies or substantial portions of the Software. +// +// THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR +// IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, +// FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE +// AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER +// LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, +// OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN +// THE SOFTWARE. + +export const corsProxy = "https://cors-anywhere.herokuapp.com"; +export const RANDOMUSER_API_URL = "https://randomuser.me"; +export const BREW_API_URL = "https://api.punkapi.com/v2/beers"; +export const GOOGLE_BOOKS_URL = "https://www.googleapis.com/books/v1/volumes"; +export const apiRoute = "api/1.3"; +export const responseError = \\\\\\\\\\\\\\\`Error responding from server. Please visit \\\\\\\\\\\\\\\${RANDOMUSER_API_URL} and \\\\\\\\\\\\\\\${corsProxy} to see if they're available\\\\\\\\\\\\\\\`; +\\\\\\\`, + "overview/hero/data/table/random-user-data-source.ts": \\\\\\\`// © 2022 SolarWinds Worldwide, LLC. All rights reserved. +// +// Permission is hereby granted, free of charge, to any person obtaining a copy +// of this software and associated documentation files (the "Software"), to +// deal in the Software without restriction, including without limitation the +// rights to use, copy, modify, merge, publish, distribute, sublicense, and/or +// sell copies of the Software, and to permit persons to whom the Software is +// furnished to do so, subject to the following conditions: +// +// The above copyright notice and this permission notice shall be included in +// all copies or substantial portions of the Software. +// +// THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR +// IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, +// FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE +// AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER +// LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, +// OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN +// THE SOFTWARE. + +import { ListRange } from "@angular/cdk/collections"; +import { Injectable } from "@angular/core"; +import isEqual from "lodash/isEqual"; +import orderBy from "lodash/orderBy"; +import { BehaviorSubject } from "rxjs"; + +import { + DataSourceService, + IDataField, + INovaFilteringOutputs, + INovaFilters, + ISorterFilter, + LoggerService, +} from "@nova-ui/bits"; + +import { + IRandomUserResponse, + IRandomUserResults, + IRandomUserTableModel, + UsersQueryResponse, +} from "../types"; +import { + apiRoute, + corsProxy, + RANDOMUSER_API_URL, + responseError, +} from "./constants"; + +@Injectable() +export class RandomUserDataSource extends DataSourceService { + public static providerId = "RandomUserDataSource"; + + private readonly seed = "sw"; + + private cache = Array.from({ length: 0 }); + private lastSortValue?: ISorterFilter; + private lastVirtualScroll?: ListRange; + + public page: number = 1; + public busy = new BehaviorSubject(false); + + public dataFields: Array = [ + { id: "no", label: $localize\\\\\\\\\\\\\\\`No\\\\\\\\\\\\\\\`, dataType: "number" }, + { id: "nameTitle", label: $localize\\\\\\\\\\\\\\\`Title\\\\\\\\\\\\\\\`, dataType: "string" }, + { id: "nameFirst", label: $localize\\\\\\\\\\\\\\\`First\\\\\\\\\\\\\\\`, dataType: "string" }, + { id: "nameLast", label: $localize\\\\\\\\\\\\\\\`Last\\\\\\\\\\\\\\\`, dataType: "string" }, + { id: "gender", label: $localize\\\\\\\\\\\\\\\`Gender\\\\\\\\\\\\\\\`, dataType: "string" }, + { id: "country", label: $localize\\\\\\\\\\\\\\\`Country\\\\\\\\\\\\\\\`, dataType: "string" }, + { id: "city", label: $localize\\\\\\\\\\\\\\\`City\\\\\\\\\\\\\\\`, dataType: "string" }, + { id: "postcode", label: $localize\\\\\\\\\\\\\\\`Postcode\\\\\\\\\\\\\\\`, dataType: "number" }, + { id: "email", label: $localize\\\\\\\\\\\\\\\`E-Mail\\\\\\\\\\\\\\\`, dataType: "string" }, + { id: "cell", label: $localize\\\\\\\\\\\\\\\`Cell\\\\\\\\\\\\\\\`, dataType: "string" }, + ]; + + constructor(private logger: LoggerService) { + super(); + } + + public async getFilteredData( + filters: INovaFilters + ): Promise { + // This condition handles sorting. We want to sort columns without fetching another chunk of data. + // Since the data is being fetched when scrolled, we compare virtual scroll indexes here in the condition as well. + if (filters.sorter?.value) { + if ( + !isEqual(this.lastSortValue, filters.sorter.value) && + isEqual(this.lastVirtualScroll, filters.virtualScroll?.value) + ) { + this.lastSortValue = filters.sorter?.value; + this.lastVirtualScroll = filters.virtualScroll?.value; + + return { + repeat: { itemsSource: this.sortData(this.cache, filters) }, + paginator: { total: 200 }, + dataFields: this.dataFields, + }; + } + } + this.busy.next(true); + + const virtualScrollFilter = + filters.virtualScroll && filters.virtualScroll.value; + const start = virtualScrollFilter + ? filters.virtualScroll?.value.start + : 0; + const end = virtualScrollFilter ? filters.virtualScroll?.value.end : 0; + + // We're returning Promise with setTimeout here to make the response from the server longer, as the API being used sends responses + // almost immediately. We need it longer to be able the show the spinner component on data load + return new Promise((resolve) => { + setTimeout(() => { + this.getData(start, end).then( + (response: INovaFilteringOutputs | undefined) => { + if (!response) { + return; + } + + this.cache = this.cache.concat(response.users); + + this.dataSubject.next(this.cache); + resolve({ + repeat: { + itemsSource: this.sortData(this.cache, filters), + }, + // This API can return thousands of results, however doesn't return the max number of results, + // so we set the max number of result manually here. + paginator: { total: 200 }, + dataFields: this.dataFields, + }); + + this.lastSortValue = filters.sorter?.value; + this.lastVirtualScroll = filters.virtualScroll?.value; + this.busy.next(false); + } + ); + }, 300); + }); + } + + public async getData( + start: number = 0, + end: number = 20 + ): Promise { + let response: IRandomUserResponse | null = null; + try { + response = await ( + await fetch( + \\\\\\\\\\\\\\\`\\\\\\\\\\\\\\\${corsProxy}/\\\\\\\\\\\\\\\${RANDOMUSER_API_URL}/\\\\\\\\\\\\\\\${apiRoute}/?page=\\\\\\\\\\\\\\\${ + this.page + }&results=\\\\\\\\\\\\\\\${end - start}&seed=\\\\\\\\\\\\\\\${this.seed}\\\\\\\\\\\\\\\` + ) + ).json(); + return { + users: response?.results.map( + (result: IRandomUserResults, i: number) => ({ + no: this.cache.length + i + 1, + nameTitle: result.name.title, + nameFirst: result.name.first, + nameLast: result.name.last, + gender: result.gender, + country: result.location.country, + city: result.location.city, + postcode: result.location.postcode, + email: result.email, + cell: result.cell, + }) + ), + total: response?.results.length, + start: start, + } as UsersQueryResponse; + } catch (e) { + this.logger.error(responseError); + } + } + + private sortData(data: IRandomUserTableModel[], filters: INovaFilters) { + return orderBy( + data, + filters.sorter?.value?.sortBy, + filters.sorter?.value?.direction as "desc" | "asc" + ); + } +} +\\\\\\\`, + "overview/hero/data/table/types.ts": \\\\\\\`// © 2022 SolarWinds Worldwide, LLC. All rights reserved. +// +// Permission is hereby granted, free of charge, to any person obtaining a copy +// of this software and associated documentation files (the "Software"), to +// deal in the Software without restriction, including without limitation the +// rights to use, copy, modify, merge, publish, distribute, sublicense, and/or +// sell copies of the Software, and to permit persons to whom the Software is +// furnished to do so, subject to the following conditions: +// +// The above copyright notice and this permission notice shall be included in +// all copies or substantial portions of the Software. +// +// THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR +// IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, +// FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE +// AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER +// LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, +// OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN +// THE SOFTWARE. + +import { IDataField, INovaFilteringOutputs } from "@nova-ui/bits"; +export interface BasicTableModel { + position: number; + name: string; + features: any; + status: string; + checks: any; + "cpu-load": number; + firstUrl: string; + firstUrlLabel: string; + secondUrl: string; + secondUrlLabel: string; +} + +export interface ITableDataSourceOutput extends INovaFilteringOutputs { + dataFields: IDataField[]; +} +\\\\\\\`, + "overview/hero/data/timeseries-data-sources.ts": \\\\\\\`// © 2022 SolarWinds Worldwide, LLC. All rights reserved. +// +// Permission is hereby granted, free of charge, to any person obtaining a copy +// of this software and associated documentation files (the "Software"), to +// deal in the Software without restriction, including without limitation the +// rights to use, copy, modify, merge, publish, distribute, sublicense, and/or +// sell copies of the Software, and to permit persons to whom the Software is +// furnished to do so, subject to the following conditions: +// +// The above copyright notice and this permission notice shall be included in +// all copies or substantial portions of the Software. +// +// THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR +// IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, +// FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE +// AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER +// LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, +// OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN +// THE SOFTWARE. + +import { Injectable } from "@angular/core"; +import { Moment } from "moment/moment"; +import { BehaviorSubject } from "rxjs"; + +import { DataSourceService, IDataSource, INovaFilters } from "@nova-ui/bits"; +import { + ITimeseriesOutput, + ITimeseriesWidgetData, + ITimeseriesWidgetSeriesData, +} from "@nova-ui/dashboards"; + +import { + BEER_VS_READING_DATA, + LOUNGING_VS_ULTIMATE_FRISBEE_DATA, +} from "./widget-data"; + +@Injectable() +export class BeerVsReadingMockDataSource + extends DataSourceService + implements IDataSource +{ + public static providerId = "BeerVsReadingMockDataSource"; + + public busy = new BehaviorSubject(false); + + constructor() { + super(); + } + + public async getFilteredData( + filters: INovaFilters + ): Promise { + this.busy.next(true); + const result = await delay( + { series: getData(filters, BEER_VS_READING_DATA) }, + 1000 + ); + this.busy.next(false); + return result; + } +} + +@Injectable() +export class LoungingVsFrisbeeGolfMockDataSource + extends DataSourceService + implements IDataSource +{ + public static providerId = "LoungingVsFrisbeeGolfMockDataSource"; + + public busy = new BehaviorSubject(false); + + constructor() { + super(); + } + + public async getFilteredData( + filters: INovaFilters + ): Promise { + this.busy.next(true); + const result = await delay( + { series: getData(filters, LOUNGING_VS_ULTIMATE_FRISBEE_DATA) }, + 1000 + ); + this.busy.next(false); + return result; + } +} + +function getData( + filters: INovaFilters, + data: ITimeseriesWidgetData[] +): ITimeseriesWidgetData[] { + const timeframeFilter = filters.timeframe; + let filteredData = data; + // TIME FRAME PICKER FILTERING + if (timeframeFilter) { + filteredData = filteredData.map((item: ITimeseriesWidgetData) => ({ + id: item.id, + name: item.name, + description: item.description, + data: item.data.filter((seriesData: ITimeseriesWidgetSeriesData) => + filterDates( + seriesData.x, + timeframeFilter.value.startDatetime, + timeframeFilter.value.endDatetime + ) + ), + })); + } + + return filteredData; +} + +function filterDates(dateToCheck: Moment, startDate: Moment, endDate: Moment) { + return ( + dateToCheck.isBetween(startDate, endDate) || + dateToCheck.isSame(startDate) || + dateToCheck.isSame(endDate) + ); +} + +async function delay( + value: ITimeseriesOutput, + ms: number +): Promise { + return new Promise((resolve) => setTimeout(() => resolve(value), ms)); +} +\\\\\\\`, + "overview/hero/data/types.ts": \\\\\\\`// © 2022 SolarWinds Worldwide, LLC. All rights reserved. +// +// Permission is hereby granted, free of charge, to any person obtaining a copy +// of this software and associated documentation files (the "Software"), to +// deal in the Software without restriction, including without limitation the +// rights to use, copy, modify, merge, publish, distribute, sublicense, and/or +// sell copies of the Software, and to permit persons to whom the Software is +// furnished to do so, subject to the following conditions: +// +// The above copyright notice and this permission notice shall be included in +// all copies or substantial portions of the Software. +// +// THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR +// IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, +// FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE +// AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER +// LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, +// OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN +// THE SOFTWARE. + +export interface UsersQueryResponse { + users: IRandomUserTableModel[]; + total: number; + start: number; +} + +export interface IRandomUserResponse { + info: Array; + results: Array; +} + +export interface IRandomUserInfo { + page: number; + results: number; + seed: string; + version: string; +} + +export interface IRandomUserResults { + cell: string; + dob: { + age: number; + date: string; + }; + email: string; + gender: string; + id: any; + location: IRandomUserLocation; + login: { + md5: string; + password: string; + salt: string; + sha1: string; + sha256: string; + username: string; + uuid: string; + }; + name: { + title: string; + first: string; + last: string; + }; + nat: string; + phone: string; + picture: { + large: string; + medium: string; + thumbnail: string; + }; + registered: { + date: string; + age: number; + }; +} + +export interface IRandomUserTableModel { + no: number; + nameTitle: string; + nameFirst: string; + nameLast: string; + gender: string; + country: string; + city: string; + postcode: number; + email: string; + cell: string; +} + +export interface IRandomUserLocation { + city: string; + coordinates: { latitude: string; longitude: string }; + country: string; + postcode: number; + state: string; + street: { number: number; name: string }; + timezone: any; +} + +export interface IBrewInfo { + id: number; + name: string; + tagline: string; + first_brewed: string; + description: string; + brewers_tips: string; +} + +export interface IBrewDatasourceResponse { + brewInfo: IBrewInfo[]; + total: number; +} +\\\\\\\`, + "overview/hero/data/widget-data.ts": \\\\\\\`// © 2022 SolarWinds Worldwide, LLC. All rights reserved. +// +// Permission is hereby granted, free of charge, to any person obtaining a copy +// of this software and associated documentation files (the "Software"), to +// deal in the Software without restriction, including without limitation the +// rights to use, copy, modify, merge, publish, distribute, sublicense, and/or +// sell copies of the Software, and to permit persons to whom the Software is +// furnished to do so, subject to the following conditions: +// +// The above copyright notice and this permission notice shall be included in +// all copies or substantial portions of the Software. +// +// THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR +// IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, +// FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE +// AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER +// LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, +// OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN +// THE SOFTWARE. + +import moment from "moment/moment"; + +import { ITimeseriesWidgetData } from "@nova-ui/dashboards"; + +import { BasicTableModel } from "./table/types"; + +export interface IProportionalWidgetData { + id: string; + name: string; + data: number[]; + link: string; + value: string; +} + +export function getMockBeerReviewCountsByCity(): IProportionalWidgetData[] { + return [ + { + id: "Brno", + name: "Brno", + data: [Math.round(Math.random() * 100000)], + link: "https://en.wikipedia.org/wiki/Brno", + value: "Brno", + }, + { + id: "kyiv", + name: "Kyiv", + data: [Math.round(Math.random() * 100000)], + link: "https://en.wikipedia.org/wiki/Kyiv", + value: "Kyiv", + }, + { + id: "austin", + name: "Austin", + data: [Math.round(Math.random() * 100000)], + link: "https://en.wikipedia.org/wiki/Austin", + value: "Austin", + }, + { + id: "lisbon", + name: "Lisbon", + data: [Math.round(Math.random() * 100000)], + link: "https://en.wikipedia.org/wiki/Lisbon", + value: "Lisbon", + }, + { + id: "sydney", + name: "Sydney", + data: [Math.round(Math.random() * 100000)], + link: "https://en.wikipedia.org/wiki/Sydney", + value: "Sydney", + }, + { + id: "nur-sultan", + name: "Nur-Sultan", + data: [Math.round(Math.random() * 100000)], + link: "https://en.wikipedia.org/wiki/Nur-Sultan", + value: "Nur-Sultan", + }, + ].sort((a, b) => a.data[0] - b.data[0]); +} + +export function getMockBeerReviewCountsByCity2(): IProportionalWidgetData[] { + return [ + { + id: "london", + name: "London", + data: [Math.round(Math.random() * 100000)], + link: "https://en.wikipedia.org/wiki/London", + value: "London", + }, + { + id: "paris", + name: "Paris", + data: [Math.round(Math.random() * 100000)], + link: "https://en.wikipedia.org/wiki/Paris", + value: "Paris", + }, + { + id: "rio", + name: "Rio", + data: [Math.round(Math.random() * 100000)], + link: "https://en.wikipedia.org/wiki/Rio_de_Janeiro", + value: "Rio", + }, + ].sort((a, b) => a.data[0] - b.data[0]); +} + +export const BEER_VS_READING_DATA: ITimeseriesWidgetData[] = [ + { + id: "series-1", + name: "Beer Tasting", + description: "Havin' some suds", + data: [ + { x: moment().subtract(10, "day"), y: 30 }, + { x: moment().subtract(9, "day"), y: 35 }, + { x: moment().subtract(8, "day"), y: 33 }, + { x: moment().subtract(7, "day"), y: 40 }, + { x: moment().subtract(6, "day"), y: 35 }, + { x: moment().subtract(5, "day"), y: 30 }, + { x: moment().subtract(4, "day"), y: 35 }, + { x: moment().subtract(3, "day"), y: 15 }, + { x: moment().subtract(2, "day"), y: 30 }, + { x: moment().subtract(1, "day"), y: 35 }, + { x: moment().subtract(24, "hour"), y: 34 }, + { x: moment().subtract(15, "hour"), y: 33 }, + { x: moment().subtract(10, "hour"), y: 35 }, + { x: moment().subtract(5, "hour"), y: 36 }, + { x: moment().subtract(1, "hour"), y: 34 }, + { x: moment().subtract(50, "minute"), y: 33 }, + { x: moment().subtract(40, "minute"), y: 30 }, + { x: moment().subtract(30, "minute"), y: 32 }, + { x: moment().subtract(20, "minute"), y: 31 }, + { x: moment().subtract(10, "minute"), y: 34 }, + ], + }, + { + id: "series-2", + name: "Reading", + description: "Hittin' the books", + data: [ + { x: moment().subtract(10, "day"), y: 60 }, + { x: moment().subtract(9, "day"), y: 64 }, + { x: moment().subtract(8, "day"), y: 70 }, + { x: moment().subtract(7, "day"), y: 55 }, + { x: moment().subtract(6, "day"), y: 55 }, + { x: moment().subtract(5, "day"), y: 45 }, + { x: moment().subtract(4, "day"), y: 10 }, + { x: moment().subtract(3, "day"), y: 65 }, + { x: moment().subtract(2, "day"), y: 35 }, + { x: moment().subtract(1, "day"), y: 60 }, + { x: moment().subtract(24, "hour"), y: 61 }, + { x: moment().subtract(15, "hour"), y: 65 }, + { x: moment().subtract(10, "hour"), y: 63 }, + { x: moment().subtract(5, "hour"), y: 58 }, + { x: moment().subtract(1, "hour"), y: 64 }, + { x: moment().subtract(50, "minute"), y: 63 }, + { x: moment().subtract(40, "minute"), y: 60 }, + { x: moment().subtract(30, "minute"), y: 62 }, + { x: moment().subtract(20, "minute"), y: 61 }, + { x: moment().subtract(10, "minute"), y: 62 }, + ], + }, +]; + +export const LOUNGING_VS_ULTIMATE_FRISBEE_DATA: ITimeseriesWidgetData[] = [ + { + id: "series-a", + name: "Lounging", + description: "Shootin' the Breeze", + data: [ + { x: moment().subtract(10, "day"), y: 10 }, + { x: moment().subtract(9, "day"), y: 15 }, + { x: moment().subtract(8, "day"), y: 13 }, + { x: moment().subtract(7, "day"), y: 20 }, + { x: moment().subtract(6, "day"), y: 15 }, + { x: moment().subtract(5, "day"), y: 10 }, + { x: moment().subtract(4, "day"), y: 15 }, + { x: moment().subtract(3, "day"), y: 5 }, + { x: moment().subtract(2, "day"), y: 10 }, + { x: moment().subtract(1, "day"), y: 15 }, + { x: moment().subtract(24, "hour"), y: 14 }, + { x: moment().subtract(15, "hour"), y: 13 }, + { x: moment().subtract(10, "hour"), y: 15 }, + { x: moment().subtract(5, "hour"), y: 16 }, + { x: moment().subtract(1, "hour"), y: 14 }, + { x: moment().subtract(50, "minute"), y: 13 }, + { x: moment().subtract(40, "minute"), y: 10 }, + { x: moment().subtract(30, "minute"), y: 12 }, + { x: moment().subtract(20, "minute"), y: 11 }, + { x: moment().subtract(10, "minute"), y: 14 }, + ], + }, + { + id: "series-b", + name: "Frisbee Golfing", + description: "Golfin' with a disc", + data: [ + { x: moment().subtract(10, "day"), y: 80 }, + { x: moment().subtract(9, "day"), y: 84 }, + { x: moment().subtract(8, "day"), y: 80 }, + { x: moment().subtract(7, "day"), y: 75 }, + { x: moment().subtract(6, "day"), y: 95 }, + { x: moment().subtract(5, "day"), y: 85 }, + { x: moment().subtract(4, "day"), y: 80 }, + { x: moment().subtract(3, "day"), y: 85 }, + { x: moment().subtract(2, "day"), y: 85 }, + { x: moment().subtract(1, "day"), y: 80 }, + { x: moment().subtract(24, "hour"), y: 81 }, + { x: moment().subtract(15, "hour"), y: 85 }, + { x: moment().subtract(10, "hour"), y: 83 }, + { x: moment().subtract(5, "hour"), y: 88 }, + { x: moment().subtract(1, "hour"), y: 84 }, + { x: moment().subtract(50, "minute"), y: 83 }, + { x: moment().subtract(40, "minute"), y: 80 }, + { x: moment().subtract(30, "minute"), y: 82 }, + { x: moment().subtract(20, "minute"), y: 81 }, + { x: moment().subtract(10, "minute"), y: 82 }, + ], + }, +]; + +export const TABLE_DATA: BasicTableModel[] = [ + { + position: 1, + name: "FOCUS-SVR-02258", + features: ["remote-access-vpn-tunnel", "patch-manager01"], + status: "Active", + checks: { + icon: "status_up", + num: 25, + }, + "cpu-load": 86, + firstUrl: "https://en.wikipedia.org/wiki/Brno", + firstUrlLabel: "Brno", + secondUrl: "https://en.wikipedia.org/wiki/VMware_Workstation", + secondUrlLabel: "Workstation", + }, + { + position: 2, + name: "FOCUS-SVR-03312", + features: ["tools", "database", "orion-ape-backup"], + status: "Active", + checks: { + icon: "status_critical", + num: 25, + }, + "cpu-load": 47, + firstUrl: "https://en.wikipedia.org/wiki/Brno", + firstUrlLabel: "Brno", + secondUrl: "https://en.wikipedia.org/wiki/VMware_Workstation", + secondUrlLabel: "Workstation", + }, + { + position: 3, + name: "FOCUS-SVR-02258", + features: [ + "remote-access-vpn-tunnel", + "database", + "orion-ape-backup", + "patch-manager01", + ], + status: "Active", + checks: { + icon: "status_down", + num: 25, + }, + "cpu-load": 53, + firstUrl: "https://en.wikipedia.org/wiki/Kyiv", + firstUrlLabel: "Kyiv", + secondUrl: "https://en.wikipedia.org/wiki/VMware_Workstation", + secondUrlLabel: "Workstation", + }, + { + position: 4, + name: "Man-LT-JYJ4AD5", + features: [ + "remote-access-vpn-tunnel", + "tools", + "database", + "orion-ape-backup", + ], + status: "Active", + checks: { + icon: "status_up", + num: 25, + }, + "cpu-load": 32, + firstUrl: "https://en.wikipedia.org/wiki/Kyiv", + firstUrlLabel: "Kyiv", + secondUrl: "https://en.wikipedia.org/wiki/VirtualBox", + secondUrlLabel: "VirtualBox", + }, + { + position: 5, + name: "Man-LT-JYJ425", + features: [ + "remote-access-vpn-tunnel", + "tools", + "database", + "orion-ape-backup", + ], + status: "Active", + checks: { + icon: "status_up", + num: 25, + }, + "cpu-load": 22, + firstUrl: "https://en.wikipedia.org/wiki/Kyiv", + firstUrlLabel: "Kyiv", + secondUrl: "https://en.wikipedia.org/wiki/VirtualBox", + secondUrlLabel: "VirtualBox", + }, + { + position: 6, + name: "Man-LT-JYJ4333", + features: [ + "remote-access-vpn-tunnel", + "tools", + "database", + "orion-ape-backup", + ], + status: "Active", + checks: { + icon: "status_up", + num: 25, + }, + "cpu-load": 12, + firstUrl: "https://en.wikipedia.org/wiki/Kyiv", + firstUrlLabel: "Kyiv", + secondUrl: "https://en.wikipedia.org/wiki/VirtualBox", + secondUrlLabel: "VirtualBox", + }, + { + position: 7, + name: "FOCUS-SVR-02258", + features: ["remote-access-vpn-tunnel", "patch-manager01"], + status: "Active", + checks: { + icon: "status_up", + num: 25, + }, + "cpu-load": 86, + firstUrl: "https://en.wikipedia.org/wiki/Austin", + firstUrlLabel: "Austin", + secondUrl: "https://en.wikipedia.org/wiki/VMware_Workstation", + secondUrlLabel: "Workstation", + }, + { + position: 8, + name: "Man-LT-JYJ4AD5", + features: [ + "remote-access-vpn-tunnel", + "tools", + "database", + "orion-ape-backup", + "patch-manager01", + ], + status: "Active", + checks: { + icon: "status_inactive", + num: 25, + }, + "cpu-load": 35, + firstUrl: "https://en.wikipedia.org/wiki/Austin", + firstUrlLabel: "Austin", + secondUrl: "https://en.wikipedia.org/wiki/VMware_Workstation", + secondUrlLabel: "Workstation", + }, + { + position: 9, + name: "Man-LT-JYJ4AD5", + features: [ + "remote-access-vpn-tunnel", + "tools", + "database", + "patch-manager01", + ], + status: "Active", + checks: { + icon: "status_up", + num: 25, + }, + "cpu-load": 32, + firstUrl: "https://en.wikipedia.org/wiki/Brno", + firstUrlLabel: "Brno", + secondUrl: "https://en.wikipedia.org/wiki/VMware_Workstation", + secondUrlLabel: "Workstation", + }, + { + position: 10, + name: "Man-LT-JYJ4AD5", + features: [ + "remote-access-vpn-tunnel", + "database", + "orion-ape-backup", + "patch-manager01", + ], + status: "Active", + checks: { + icon: "status_up", + num: 25, + }, + "cpu-load": 64, + firstUrl: "https://en.wikipedia.org/wiki/Kyiv", + firstUrlLabel: "Kyiv", + secondUrl: "https://en.wikipedia.org/wiki/VMware_Workstation", + secondUrlLabel: "Workstation", + }, + { + position: 11, + name: "Man-LT-111", + features: [], + status: "Active", + checks: { + icon: "status_external", + num: 25, + }, + "cpu-load": 55, + firstUrl: "https://en.wikipedia.org/wiki/Brno", + firstUrlLabel: "Brno", + secondUrl: "https://en.wikipedia.org/wiki/VMware_Workstation", + secondUrlLabel: "Workstation", + }, + { + position: 12, + name: "Man-LT-2222", + features: [ + "remote-access-vpn-tunnel", + "tools", + "database", + "orion-ape-backup", + "patch-manager01", + ], + status: "Active", + checks: { + icon: "status_inactive", + num: 25, + }, + "cpu-load": 34, + firstUrl: "https://en.wikipedia.org/wiki/Brno", + firstUrlLabel: "Brno", + secondUrl: "https://en.wikipedia.org/wiki/VMware_Workstation", + secondUrlLabel: "Workstation", + }, + { + position: 13, + name: "Man-LT-333333", + features: [ + "remote-access-vpn-tunnel", + "tools", + "database", + "patch-manager01", + ], + status: "Active", + checks: { + icon: "status_up", + num: 25, + }, + "cpu-load": 56, + firstUrl: "https://en.wikipedia.org/wiki/Kyiv", + firstUrlLabel: "Kyiv", + secondUrl: "https://en.wikipedia.org/wiki/VMware_Workstation", + secondUrlLabel: "Workstation", + }, + { + position: 14, + name: "Man-LT-444444", + features: [ + "remote-access-vpn-tunnel", + "database", + "orion-ape-backup", + "patch-manager01", + ], + status: "Active", + checks: { + icon: "status_up", + num: 25, + }, + "cpu-load": 26, + firstUrl: "https://en.wikipedia.org/wiki/Kyiv", + firstUrlLabel: "Kyiv", + secondUrl: "https://en.wikipedia.org/wiki/VMware_Workstation", + secondUrlLabel: "Workstation", + }, + { + position: 15, + name: "Man-LT-555555", + features: [ + "remote-access-vpn-tunnel", + "database", + "orion-ape-backup", + "patch-manager01", + ], + status: "Active", + checks: { + icon: "status_up", + num: 25, + }, + "cpu-load": 76, + firstUrl: "https://en.wikipedia.org/wiki/Austin", + firstUrlLabel: "Austin", + secondUrl: "https://en.wikipedia.org/wiki/VMware_Workstation", + secondUrlLabel: "Workstation", + }, + { + position: 16, + name: "FOCUS-SVR-02258", + features: ["remote-access-vpn-tunnel", "patch-manager01"], + status: "Active", + checks: { + icon: "status_up", + num: 25, + }, + "cpu-load": 86, + firstUrl: "https://en.wikipedia.org/wiki/Brno", + firstUrlLabel: "Brno", + secondUrl: "https://en.wikipedia.org/wiki/VMware_Workstation", + secondUrlLabel: "Workstation", + }, + { + position: 17, + name: "FOCUS-SVR-03312", + features: ["tools", "database", "orion-ape-backup"], + status: "Active", + checks: { + icon: "status_critical", + num: 25, + }, + "cpu-load": 47, + firstUrl: "https://en.wikipedia.org/wiki/Brno", + firstUrlLabel: "Brno", + secondUrl: "https://en.wikipedia.org/wiki/VMware_Workstation", + secondUrlLabel: "Workstation", + }, + { + position: 18, + name: "FOCUS-SVR-02258", + features: [ + "remote-access-vpn-tunnel", + "database", + "orion-ape-backup", + "patch-manager01", + ], + status: "Active", + checks: { + icon: "status_down", + num: 25, + }, + "cpu-load": 53, + firstUrl: "https://en.wikipedia.org/wiki/Kyiv", + firstUrlLabel: "Kyiv", + secondUrl: "https://en.wikipedia.org/wiki/VMware_Workstation", + secondUrlLabel: "Workstation", + }, + { + position: 19, + name: "Man-LT-JYJ4AD5", + features: [ + "remote-access-vpn-tunnel", + "tools", + "database", + "orion-ape-backup", + ], + status: "Active", + checks: { + icon: "status_up", + num: 25, + }, + "cpu-load": 32, + firstUrl: "https://en.wikipedia.org/wiki/Kyiv", + firstUrlLabel: "Kyiv", + secondUrl: "https://en.wikipedia.org/wiki/VirtualBox", + secondUrlLabel: "VirtualBox", + }, + { + position: 20, + name: "Man-LT-JYJ425", + features: [ + "remote-access-vpn-tunnel", + "tools", + "database", + "orion-ape-backup", + ], + status: "Active", + checks: { + icon: "status_up", + num: 25, + }, + "cpu-load": 22, + firstUrl: "https://en.wikipedia.org/wiki/Kyiv", + firstUrlLabel: "Kyiv", + secondUrl: "https://en.wikipedia.org/wiki/VirtualBox", + secondUrlLabel: "VirtualBox", + }, + { + position: 21, + name: "Man-LT-JYJ4333", + features: [ + "remote-access-vpn-tunnel", + "tools", + "database", + "orion-ape-backup", + ], + status: "Active", + checks: { + icon: "status_up", + num: 25, + }, + "cpu-load": 12, + firstUrl: "https://en.wikipedia.org/wiki/Kyiv", + firstUrlLabel: "Kyiv", + secondUrl: "https://en.wikipedia.org/wiki/VirtualBox", + secondUrlLabel: "VirtualBox", + }, + { + position: 22, + name: "FOCUS-SVR-02258", + features: ["remote-access-vpn-tunnel", "patch-manager01"], + status: "Active", + checks: { + icon: "status_up", + num: 25, + }, + "cpu-load": 86, + firstUrl: "https://en.wikipedia.org/wiki/Austin", + firstUrlLabel: "Austin", + secondUrl: "https://en.wikipedia.org/wiki/VMware_Workstation", + secondUrlLabel: "Workstation", + }, + { + position: 23, + name: "Man-LT-JYJ4AD5", + features: [ + "remote-access-vpn-tunnel", + "tools", + "database", + "orion-ape-backup", + "patch-manager01", + ], + status: "Active", + checks: { + icon: "status_inactive", + num: 25, + }, + "cpu-load": 35, + firstUrl: "https://en.wikipedia.org/wiki/Austin", + firstUrlLabel: "Austin", + secondUrl: "https://en.wikipedia.org/wiki/VMware_Workstation", + secondUrlLabel: "Workstation", + }, + { + position: 24, + name: "Man-LT-JYJ4AD5", + features: [ + "remote-access-vpn-tunnel", + "tools", + "database", + "patch-manager01", + ], + status: "Active", + checks: { + icon: "status_up", + num: 25, + }, + "cpu-load": 32, + firstUrl: "https://en.wikipedia.org/wiki/Brno", + firstUrlLabel: "Brno", + secondUrl: "https://en.wikipedia.org/wiki/VMware_Workstation", + secondUrlLabel: "Workstation", + }, + { + position: 25, + name: "Man-LT-JYJ4AD5", + features: [ + "remote-access-vpn-tunnel", + "database", + "orion-ape-backup", + "patch-manager01", + ], + status: "Active", + checks: { + icon: "status_up", + num: 25, + }, + "cpu-load": 64, + firstUrl: "https://en.wikipedia.org/wiki/Kyiv", + firstUrlLabel: "Kyiv", + secondUrl: "https://en.wikipedia.org/wiki/VMware_Workstation", + secondUrlLabel: "Workstation", + }, + { + position: 26, + name: "Man-LT-111", + features: [], + status: "Active", + checks: { + icon: "status_external", + num: 25, + }, + "cpu-load": 55, + firstUrl: "https://en.wikipedia.org/wiki/Brno", + firstUrlLabel: "Brno", + secondUrl: "https://en.wikipedia.org/wiki/VMware_Workstation", + secondUrlLabel: "Workstation", + }, + { + position: 27, + name: "Man-LT-2222", + features: [ + "remote-access-vpn-tunnel", + "tools", + "database", + "orion-ape-backup", + "patch-manager01", + ], + status: "Active", + checks: { + icon: "status_inactive", + num: 25, + }, + "cpu-load": 34, + firstUrl: "https://en.wikipedia.org/wiki/Brno", + firstUrlLabel: "Brno", + secondUrl: "https://en.wikipedia.org/wiki/VMware_Workstation", + secondUrlLabel: "Workstation", + }, + { + position: 28, + name: "Man-LT-333333", + features: [ + "remote-access-vpn-tunnel", + "tools", + "database", + "patch-manager01", + ], + status: "Active", + checks: { + icon: "status_up", + num: 25, + }, + "cpu-load": 56, + firstUrl: "https://en.wikipedia.org/wiki/Kyiv", + firstUrlLabel: "Kyiv", + secondUrl: "https://en.wikipedia.org/wiki/VMware_Workstation", + secondUrlLabel: "Workstation", + }, + { + position: 29, + name: "Man-LT-444444", + features: [ + "remote-access-vpn-tunnel", + "database", + "orion-ape-backup", + "patch-manager01", + ], + status: "Active", + checks: { + icon: "status_up", + num: 25, + }, + "cpu-load": 26, + firstUrl: "https://en.wikipedia.org/wiki/Kyiv", + firstUrlLabel: "Kyiv", + secondUrl: "https://en.wikipedia.org/wiki/VMware_Workstation", + secondUrlLabel: "Workstation", + }, + { + position: 30, + name: "Man-LT-555555", + features: [ + "remote-access-vpn-tunnel", + "database", + "orion-ape-backup", + "patch-manager01", + ], + status: "Active", + checks: { + icon: "status_up", + num: 25, + }, + "cpu-load": 76, + firstUrl: "https://en.wikipedia.org/wiki/Austin", + firstUrlLabel: "Austin", + secondUrl: "https://en.wikipedia.org/wiki/VMware_Workstation", + secondUrlLabel: "Workstation", + }, + { + position: 31, + name: "FOCUS-SVR-02258", + features: ["remote-access-vpn-tunnel", "patch-manager01"], + status: "Active", + checks: { + icon: "status_up", + num: 25, + }, + "cpu-load": 86, + firstUrl: "https://en.wikipedia.org/wiki/Brno", + firstUrlLabel: "Brno", + secondUrl: "https://en.wikipedia.org/wiki/VMware_Workstation", + secondUrlLabel: "Workstation", + }, + { + position: 32, + name: "FOCUS-SVR-03312", + features: ["tools", "database", "orion-ape-backup"], + status: "Active", + checks: { + icon: "status_critical", + num: 25, + }, + "cpu-load": 47, + firstUrl: "https://en.wikipedia.org/wiki/Brno", + firstUrlLabel: "Brno", + secondUrl: "https://en.wikipedia.org/wiki/VMware_Workstation", + secondUrlLabel: "Workstation", + }, + { + position: 33, + name: "FOCUS-SVR-02258", + features: [ + "remote-access-vpn-tunnel", + "database", + "orion-ape-backup", + "patch-manager01", + ], + status: "Active", + checks: { + icon: "status_down", + num: 25, + }, + "cpu-load": 53, + firstUrl: "https://en.wikipedia.org/wiki/Kyiv", + firstUrlLabel: "Kyiv", + secondUrl: "https://en.wikipedia.org/wiki/VMware_Workstation", + secondUrlLabel: "Workstation", + }, + { + position: 34, + name: "Man-LT-JYJ4AD5", + features: [ + "remote-access-vpn-tunnel", + "tools", + "database", + "orion-ape-backup", + ], + status: "Active", + checks: { + icon: "status_up", + num: 25, + }, + "cpu-load": 32, + firstUrl: "https://en.wikipedia.org/wiki/Kyiv", + firstUrlLabel: "Kyiv", + secondUrl: "https://en.wikipedia.org/wiki/VirtualBox", + secondUrlLabel: "VirtualBox", + }, + { + position: 35, + name: "Man-LT-JYJ425", + features: [ + "remote-access-vpn-tunnel", + "tools", + "database", + "orion-ape-backup", + ], + status: "Active", + checks: { + icon: "status_up", + num: 25, + }, + "cpu-load": 22, + firstUrl: "https://en.wikipedia.org/wiki/Kyiv", + firstUrlLabel: "Kyiv", + secondUrl: "https://en.wikipedia.org/wiki/VirtualBox", + secondUrlLabel: "VirtualBox", + }, + { + position: 36, + name: "Man-LT-JYJ4333", + features: [ + "remote-access-vpn-tunnel", + "tools", + "database", + "orion-ape-backup", + ], + status: "Active", + checks: { + icon: "status_up", + num: 25, + }, + "cpu-load": 12, + firstUrl: "https://en.wikipedia.org/wiki/Kyiv", + firstUrlLabel: "Kyiv", + secondUrl: "https://en.wikipedia.org/wiki/VirtualBox", + secondUrlLabel: "VirtualBox", + }, + { + position: 37, + name: "FOCUS-SVR-02258", + features: ["remote-access-vpn-tunnel", "patch-manager01"], + status: "Active", + checks: { + icon: "status_up", + num: 25, + }, + "cpu-load": 86, + firstUrl: "https://en.wikipedia.org/wiki/Austin", + firstUrlLabel: "Austin", + secondUrl: "https://en.wikipedia.org/wiki/VMware_Workstation", + secondUrlLabel: "Workstation", + }, + { + position: 38, + name: "Man-LT-JYJ4AD5", + features: [ + "remote-access-vpn-tunnel", + "tools", + "database", + "orion-ape-backup", + "patch-manager01", + ], + status: "Active", + checks: { + icon: "status_inactive", + num: 25, + }, + "cpu-load": 35, + firstUrl: "https://en.wikipedia.org/wiki/Austin", + firstUrlLabel: "Austin", + secondUrl: "https://en.wikipedia.org/wiki/VMware_Workstation", + secondUrlLabel: "Workstation", + }, + { + position: 39, + name: "Man-LT-JYJ4AD5", + features: [ + "remote-access-vpn-tunnel", + "tools", + "database", + "patch-manager01", + ], + status: "Active", + checks: { + icon: "status_up", + num: 25, + }, + "cpu-load": 32, + firstUrl: "https://en.wikipedia.org/wiki/Brno", + firstUrlLabel: "Brno", + secondUrl: "https://en.wikipedia.org/wiki/VMware_Workstation", + secondUrlLabel: "Workstation", + }, + { + position: 40, + name: "Man-LT-JYJ4AD5", + features: [ + "remote-access-vpn-tunnel", + "database", + "orion-ape-backup", + "patch-manager01", + ], + status: "Active", + checks: { + icon: "status_up", + num: 25, + }, + "cpu-load": 64, + firstUrl: "https://en.wikipedia.org/wiki/Kyiv", + firstUrlLabel: "Kyiv", + secondUrl: "https://en.wikipedia.org/wiki/VMware_Workstation", + secondUrlLabel: "Workstation", + }, + { + position: 41, + name: "Man-LT-111", + features: [], + status: "Active", + checks: { + icon: "status_external", + num: 25, + }, + "cpu-load": 55, + firstUrl: "https://en.wikipedia.org/wiki/Brno", + firstUrlLabel: "Brno", + secondUrl: "https://en.wikipedia.org/wiki/VMware_Workstation", + secondUrlLabel: "Workstation", + }, + { + position: 42, + name: "Man-LT-2222", + features: [ + "remote-access-vpn-tunnel", + "tools", + "database", + "orion-ape-backup", + "patch-manager01", + ], + status: "Active", + checks: { + icon: "status_inactive", + num: 25, + }, + "cpu-load": 34, + firstUrl: "https://en.wikipedia.org/wiki/Brno", + firstUrlLabel: "Brno", + secondUrl: "https://en.wikipedia.org/wiki/VMware_Workstation", + secondUrlLabel: "Workstation", + }, + { + position: 43, + name: "Man-LT-333333", + features: [ + "remote-access-vpn-tunnel", + "tools", + "database", + "patch-manager01", + ], + status: "Active", + checks: { + icon: "status_up", + num: 25, + }, + "cpu-load": 56, + firstUrl: "https://en.wikipedia.org/wiki/Kyiv", + firstUrlLabel: "Kyiv", + secondUrl: "https://en.wikipedia.org/wiki/VMware_Workstation", + secondUrlLabel: "Workstation", + }, + { + position: 44, + name: "Man-LT-444444", + features: [ + "remote-access-vpn-tunnel", + "database", + "orion-ape-backup", + "patch-manager01", + ], + status: "Active", + checks: { + icon: "status_up", + num: 25, + }, + "cpu-load": 26, + firstUrl: "https://en.wikipedia.org/wiki/Kyiv", + firstUrlLabel: "Kyiv", + secondUrl: "https://en.wikipedia.org/wiki/VMware_Workstation", + secondUrlLabel: "Workstation", + }, + { + position: 45, + name: "Man-LT-555555", + features: [ + "remote-access-vpn-tunnel", + "database", + "orion-ape-backup", + "patch-manager01", + ], + status: "Active", + checks: { + icon: "status_up", + num: 25, + }, + "cpu-load": 76, + firstUrl: "https://en.wikipedia.org/wiki/Austin", + firstUrlLabel: "Austin", + secondUrl: "https://en.wikipedia.org/wiki/VMware_Workstation", + secondUrlLabel: "Workstation", + }, +]; +\\\\\\\`, + "overview/hero/widget-configs/kpi.ts": \\\\\\\`// © 2022 SolarWinds Worldwide, LLC. All rights reserved. +// +// Permission is hereby granted, free of charge, to any person obtaining a copy +// of this software and associated documentation files (the "Software"), to +// deal in the Software without restriction, including without limitation the +// rights to use, copy, modify, merge, publish, distribute, sublicense, and/or +// sell copies of the Software, and to permit persons to whom the Software is +// furnished to do so, subject to the following conditions: +// +// The above copyright notice and this permission notice shall be included in +// all copies or substantial portions of the Software. +// +// THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR +// IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, +// FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE +// AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER +// LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, +// OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN +// THE SOFTWARE. + +import { + DEFAULT_PIZZAGNA_ROOT, + IProviderConfiguration, + IRefresherProperties, + IWidget, + KpiComponent, + NOVA_KPI_DATASOURCE_ADAPTER, + PizzagnaLayer, + WellKnownProviders, +} from "@nova-ui/dashboards"; + +import { + HarryPotterAverageRatingDataSource, + HarryPotterRatingsCountDataSource, +} from "../data/kpi-datasources"; + +export const kpiConfig: IWidget = { + id: "kpiWidgetId", + type: "kpi", + pizzagna: { + [PizzagnaLayer.Configuration]: { + [DEFAULT_PIZZAGNA_ROOT]: { + providers: { + [WellKnownProviders.Refresher]: { + properties: { + // Configuring the refresher interval so that our data source is invoked every ten minutes + interval: 60 * 10, + enabled: true, + } as IRefresherProperties, + } as Partial, + }, + }, + header: { + properties: { + title: "Harry Potter and the Sorcerer's Stone", + subtitle: "By J. K. Rowling", + }, + }, + tiles: { + properties: { + nodes: ["kpi1", "kpi2"], + }, + }, + kpi1: { + id: "kpi1", + componentType: KpiComponent.lateLoadKey, + properties: { + widgetData: { + label: "Average Rating", + backgroundColor: "var(--nui-color-chart-three)", + units: "out of 5 Stars", + }, + }, + providers: { + [WellKnownProviders.DataSource]: { + // Setting the data source providerId for the tile with id "kpi1" + providerId: + HarryPotterAverageRatingDataSource.providerId, + } as IProviderConfiguration, + [WellKnownProviders.Adapter]: { + providerId: NOVA_KPI_DATASOURCE_ADAPTER, + properties: { + componentId: "kpi1", + propertyPath: "widgetData", + }, + } as IProviderConfiguration, + }, + }, + kpi2: { + id: "kpi2", + componentType: KpiComponent.lateLoadKey, + properties: { + widgetData: { + label: "Reader Feedback", + units: "Ratings", + }, + }, + providers: { + [WellKnownProviders.DataSource]: { + // Setting the data source providerId for the tile with id "kpi" + providerId: + HarryPotterRatingsCountDataSource.providerId, + } as IProviderConfiguration, + [WellKnownProviders.Adapter]: { + providerId: NOVA_KPI_DATASOURCE_ADAPTER, + properties: { + componentId: "kpi2", + propertyPath: "widgetData", + }, + } as IProviderConfiguration, + }, + }, + }, + }, +}; +\\\\\\\`, + "overview/hero/widget-configs/proportional.ts": \\\\\\\`// © 2022 SolarWinds Worldwide, LLC. All rights reserved. +// +// Permission is hereby granted, free of charge, to any person obtaining a copy +// of this software and associated documentation files (the "Software"), to +// deal in the Software without restriction, including without limitation the +// rights to use, copy, modify, merge, publish, distribute, sublicense, and/or +// sell copies of the Software, and to permit persons to whom the Software is +// furnished to do so, subject to the following conditions: +// +// The above copyright notice and this permission notice shall be included in +// all copies or substantial portions of the Software. +// +// THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR +// IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, +// FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE +// AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER +// LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, +// OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN +// THE SOFTWARE. + +import { + DEFAULT_PIZZAGNA_ROOT, + IProportionalWidgetChartOptions, + IProviderConfiguration, + IWidget, + LegendPlacement, + PizzagnaLayer, + ProportionalWidgetChartTypes, + WellKnownProviders, +} from "@nova-ui/dashboards"; + +import { BeerReviewCountsByCityMockDataSource } from "../data/proportional-datasources"; + +export const proportionalConfig: IWidget = { + id: "proportionalWidgetId", + type: "proportional", + pizzagna: { + [PizzagnaLayer.Configuration]: { + [DEFAULT_PIZZAGNA_ROOT]: { + providers: { + [WellKnownProviders.Refresher]: { + properties: { + interval: 0, + }, + }, + }, + }, + header: { + properties: { + title: "Beer Review Tally by City", + subtitle: "These People Love Beer", + }, + }, + chart: { + providers: { + [WellKnownProviders.DataSource]: { + providerId: + BeerReviewCountsByCityMockDataSource.providerId, + } as IProviderConfiguration, + }, + properties: { + configuration: { + chartOptions: { + type: ProportionalWidgetChartTypes.DonutChart, + legendPlacement: LegendPlacement.Right, + } as IProportionalWidgetChartOptions, + }, + }, + }, + }, + }, +}; +\\\\\\\`, + "overview/hero/widget-configs/risk-score.ts": \\\\\\\`// © 2023 SolarWinds Worldwide, LLC. All rights reserved. +// +// Permission is hereby granted, free of charge, to any person obtaining a copy +// of this software and associated documentation files (the "Software"), to +// deal in the Software without restriction, including without limitation the +// rights to use, copy, modify, merge, publish, distribute, sublicense, and/or +// sell copies of the Software, and to permit persons to whom the Software is +// furnished to do so, subject to the following conditions: +// +// The above copyright notice and this permission notice shall be included in +// all copies or substantial portions of the Software. +// +// THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR +// IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, +// FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE +// AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER +// LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, +// OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN +// THE SOFTWARE. + +import { + DEFAULT_PIZZAGNA_ROOT, + IProviderConfiguration, + IRefresherProperties, + IWidget, + RiskScoreTileComponent, + NOVA_KPI_DATASOURCE_ADAPTER, + PizzagnaLayer, + WellKnownProviders, +} from "@nova-ui/dashboards"; + +import { HarryPotterAverageRatingDataSource } from "../data/kpi-datasources"; + +export const riskScoreConfig: IWidget = { + id: "riskScoreWidgetId", + type: "risk-score", + pizzagna: { + [PizzagnaLayer.Configuration]: { + [DEFAULT_PIZZAGNA_ROOT]: { + providers: { + [WellKnownProviders.Refresher]: { + properties: { + // Configuring the refresher interval so that our data source is invoked every ten minutes + interval: 60 * 10, + enabled: true, + } as IRefresherProperties, + } as Partial, + }, + }, + header: { + properties: { + title: "Harry Potter and the Sorcerer's Stone", + subtitle: "By J. K. Rowling", + }, + }, + tiles: { + properties: { + nodes: ["riskScore1"], + }, + }, + riskScore1: { + id: "riskScore1", + componentType: RiskScoreTileComponent.lateLoadKey, + properties: { + widgetData: { + minValue: 0, + maxValue: 5, + useStaticLabel: false, + staticLabel: undefined, + label: \\\\\\\\\\\\\\\`Average Rating\\\\\\\\\\\\\\\`, + description: \\\\\\\\\\\\\\\`Harry Potter and the Sorcerer's Stone By J. K. Rowling Average Rating Risk Score\\\\\\\\\\\\\\\`, + }, + }, + providers: { + [WellKnownProviders.DataSource]: { + // Setting the data source providerId for the tile with id "riskScore1" + providerId: + HarryPotterAverageRatingDataSource.providerId, + } as IProviderConfiguration, + [WellKnownProviders.Adapter]: { + providerId: NOVA_KPI_DATASOURCE_ADAPTER, + properties: { + componentId: "riskScore1", + propertyPath: "widgetData", + }, + } as IProviderConfiguration, + }, + }, + }, + }, +}; +\\\\\\\`, + "overview/hero/widget-configs/table.ts": \\\\\\\`// © 2022 SolarWinds Worldwide, LLC. All rights reserved. +// +// Permission is hereby granted, free of charge, to any person obtaining a copy +// of this software and associated documentation files (the "Software"), to +// deal in the Software without restriction, including without limitation the +// rights to use, copy, modify, merge, publish, distribute, sublicense, and/or +// sell copies of the Software, and to permit persons to whom the Software is +// furnished to do so, subject to the following conditions: +// +// The above copyright notice and this permission notice shall be included in +// all copies or substantial portions of the Software. +// +// THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR +// IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, +// FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE +// AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER +// LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, +// OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN +// THE SOFTWARE. + +import { + ITableWidgetColumnConfig, + ITableWidgetSorterConfig, + IWidget, + PizzagnaLayer, + RawFormatterComponent, + WellKnownProviders, +} from "@nova-ui/dashboards"; + +import { BeerDataSource } from "../data/table/beer-data-source"; + +export const tableConfig: IWidget = { + id: "tableWidgetId", + type: "table", + pizzagna: { + [PizzagnaLayer.Configuration]: { + header: { + properties: { + title: "Stupendous Suds", + subtitle: "Try These Brilliant Brews", + }, + }, + table: { + providers: { + [WellKnownProviders.DataSource]: { + providerId: BeerDataSource.providerId, + }, + }, + properties: { + configuration: { + columns: [ + { + id: "column1", + label: "Beer Name", + isActive: true, + width: 185, + formatter: { + componentType: + RawFormatterComponent.lateLoadKey, + properties: { + dataFieldIds: { + value: "name", + }, + }, + }, + }, + { + id: "column2", + label: "Tagline", + isActive: true, + width: 250, + formatter: { + componentType: + RawFormatterComponent.lateLoadKey, + properties: { + dataFieldIds: { + value: "tagline", + }, + }, + }, + }, + { + id: "column3", + label: "First Brewed", + isActive: true, + formatter: { + componentType: + RawFormatterComponent.lateLoadKey, + properties: { + dataFieldIds: { + value: "first_brewed", + }, + }, + }, + }, + { + id: "column4", + label: "Description", + isActive: true, + width: 275, + formatter: { + componentType: + RawFormatterComponent.lateLoadKey, + properties: { + dataFieldIds: { + value: "description", + }, + }, + }, + }, + ] as ITableWidgetColumnConfig[], + sorterConfiguration: { + descendantSorting: false, + sortBy: "", + } as ITableWidgetSorterConfig, + hasVirtualScroll: true, + }, + }, + }, + }, + }, +}; +\\\\\\\`, + "overview/hero/widget-configs/timeseries.ts": \\\\\\\`// © 2022 SolarWinds Worldwide, LLC. All rights reserved. +// +// Permission is hereby granted, free of charge, to any person obtaining a copy +// of this software and associated documentation files (the "Software"), to +// deal in the Software without restriction, including without limitation the +// rights to use, copy, modify, merge, publish, distribute, sublicense, and/or +// sell copies of the Software, and to permit persons to whom the Software is +// furnished to do so, subject to the following conditions: +// +// The above copyright notice and this permission notice shall be included in +// all copies or substantial portions of the Software. +// +// THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR +// IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, +// FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE +// AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER +// LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, +// OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN +// THE SOFTWARE. + +import moment from "moment/moment"; + +import { + DEFAULT_PIZZAGNA_ROOT, + IProviderConfiguration, + ISerializableTimeframe, + ITimeseriesItemConfiguration, + IWidget, + LegendPlacement, + WellKnownProviders, +} from "@nova-ui/dashboards"; + +import { BeerVsReadingMockDataSource } from "../data/timeseries-data-sources"; + +export const timeseriesConfig: IWidget = { + id: "timeseriesWidgetId", + type: "timeseries", + pizzagna: { + configuration: { + [DEFAULT_PIZZAGNA_ROOT]: { + providers: { + [WellKnownProviders.DataSource]: { + providerId: BeerVsReadingMockDataSource.providerId, + } as IProviderConfiguration, + }, + }, + header: { + properties: { + title: "Primary Leisure Activity Over Time", + subtitle: "Survey of 1000 Solarians", + }, + }, + chart: { + providers: { + [WellKnownProviders.Adapter]: { + properties: { + series: [ + { + id: "series-1", + label: "Beer Tasting", + selectedSeriesId: "series-1", + }, + { + id: "series-2", + label: "Reading", + selectedSeriesId: "series-2", + }, + ] as ITimeseriesItemConfiguration[], + }, + } as Partial, + }, + properties: { + configuration: { + legendPlacement: LegendPlacement.Right, + enableZoom: true, + leftAxisLabel: "Solarians (%)", + }, + }, + }, + timeframeSelection: { + properties: { + timeframe: { + selectedPresetId: "last7Days", + } as ISerializableTimeframe, + minDate: moment().subtract(10, "days").format(), + maxDate: moment().format(), + }, + }, + }, + }, +}; +\\\\\\\`, + "overview/overview-docs.component.ts": \\\\\\\`// © 2022 SolarWinds Worldwide, LLC. All rights reserved. +// +// Permission is hereby granted, free of charge, to any person obtaining a copy +// of this software and associated documentation files (the "Software"), to +// deal in the Software without restriction, including without limitation the +// rights to use, copy, modify, merge, publish, distribute, sublicense, and/or +// sell copies of the Software, and to permit persons to whom the Software is +// furnished to do so, subject to the following conditions: +// +// The above copyright notice and this permission notice shall be included in +// all copies or substantial portions of the Software. +// +// THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR +// IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, +// FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE +// AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER +// LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, +// OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN +// THE SOFTWARE. + +import { Component } from "@angular/core"; + +@Component({ + selector: "nui-dashboard-overview-docs", + templateUrl: "./overview-docs.component.html", + standalone: false, +}) +export class OverviewDocsComponent {} +\\\\\\\`, + "overview/overview.module.ts": \\\\\\\`// © 2022 SolarWinds Worldwide, LLC. All rights reserved. +// +// Permission is hereby granted, free of charge, to any person obtaining a copy +// of this software and associated documentation files (the "Software"), to +// deal in the Software without restriction, including without limitation the +// rights to use, copy, modify, merge, publish, distribute, sublicense, and/or +// sell copies of the Software, and to permit persons to whom the Software is +// furnished to do so, subject to the following conditions: +// +// The above copyright notice and this permission notice shall be included in +// all copies or substantial portions of the Software. +// +// THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR +// IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, +// FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE +// AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER +// LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, +// OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN +// THE SOFTWARE. + +import { CommonModule } from "@angular/common"; +import { NgModule } from "@angular/core"; +import { RouterModule } from "@angular/router"; + +import { + NuiBusyModule, + NuiButtonModule, + NuiDocsModule, + NuiIconModule, + NuiMessageModule, + NuiSwitchModule, +} from "@nova-ui/bits"; +import { + ConfiguratorHeadingService, + IFormatterDefinition, + LinkFormatterComponent, + NuiDashboardsModule, + WellKnownPathKey, + WidgetTypesService, +} from "@nova-ui/dashboards"; + +import { HeroDashboardComponent } from "./hero/dashboard/hero-dashboard.component"; +import { + HarryPotterAverageRatingDataSource, + HarryPotterRatingsCountDataSource, +} from "./hero/data/kpi-datasources"; +import { + BeerReviewCountsByCityMockDataSource, + BeerReviewCountsByCityMockDataSource2, +} from "./hero/data/proportional-datasources"; +import { BeerDataSource } from "./hero/data/table/beer-data-source"; +import { RandomUserDataSource } from "./hero/data/table/random-user-data-source"; +import { + BeerVsReadingMockDataSource, + LoungingVsFrisbeeGolfMockDataSource, +} from "./hero/data/timeseries-data-sources"; +import { OverviewDocsComponent } from "./overview-docs.component"; + +const routes = [ + { + path: "", + component: OverviewDocsComponent, + data: { + srlc: { + hideIndicator: true, + }, + showThemeSwitcher: true, + }, + }, + { + path: "hero", + component: HeroDashboardComponent, + data: { + srlc: { + hideIndicator: true, + }, + }, + }, +]; + +@NgModule({ + imports: [ + CommonModule, + NuiDashboardsModule, + NuiBusyModule, + NuiButtonModule, + NuiDocsModule, + NuiMessageModule, + NuiSwitchModule, + NuiIconModule, + RouterModule.forChild(routes), + ], + declarations: [OverviewDocsComponent, HeroDashboardComponent], + providers: [ConfiguratorHeadingService], +}) +export default class OverviewModule { + constructor(private widgetTypesService: WidgetTypesService) { + this.setupDataSourceProviders(); + this.setupProportionalLegendFormatters(); + } + + private setupDataSourceProviders() { + this.setDataSourceProviders("table", [ + RandomUserDataSource.providerId, + BeerDataSource.providerId, + ]); + this.setDataSourceProviders("kpi", [ + HarryPotterAverageRatingDataSource.providerId, + HarryPotterRatingsCountDataSource.providerId, + ]); + this.setDataSourceProviders("risk-score", [ + HarryPotterAverageRatingDataSource.providerId, + HarryPotterRatingsCountDataSource.providerId, + ]); + this.setDataSourceProviders("proportional", [ + BeerReviewCountsByCityMockDataSource.providerId, + BeerReviewCountsByCityMockDataSource2.providerId, + ]); + this.setDataSourceProviders("timeseries", [ + BeerVsReadingMockDataSource.providerId, + LoungingVsFrisbeeGolfMockDataSource.providerId, + ]); + } + + private setDataSourceProviders(type: string, providers: string[]) { + const widgetTemplate = this.widgetTypesService.getWidgetType(type, 1); + this.widgetTypesService.setNode( + widgetTemplate, + "configurator", + WellKnownPathKey.DataSourceProviders, + providers + ); + } + + private setupProportionalLegendFormatters() { + const formatters: IFormatterDefinition[] = [ + { + componentType: LinkFormatterComponent.lateLoadKey, + label: $localize\\\\\\\\\\\\\\\`Link\\\\\\\\\\\\\\\`, + dataTypes: { + value: "label", + link: "link", + }, + }, + ]; + + const widgetTemplate = this.widgetTypesService.getWidgetType( + "proportional", + 1 + ); + this.widgetTypesService.setNode( + widgetTemplate, + "configurator", + WellKnownPathKey.Formatters, + formatters + ); + } +} +\\\\\\\`, + "tutorials/customization/configurator-section/custom-configurator-section/custom-configurator-section.example.component.ts": \\\\\\\`// © 2022 SolarWinds Worldwide, LLC. All rights reserved. +// +// Permission is hereby granted, free of charge, to any person obtaining a copy +// of this software and associated documentation files (the "Software"), to +// deal in the Software without restriction, including without limitation the +// rights to use, copy, modify, merge, publish, distribute, sublicense, and/or +// sell copies of the Software, and to permit persons to whom the Software is +// furnished to do so, subject to the following conditions: +// +// The above copyright notice and this permission notice shall be included in +// all copies or substantial portions of the Software. +// +// THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR +// IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, +// FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE +// AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER +// LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, +// OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN +// THE SOFTWARE. + +import { HttpClient, HttpErrorResponse } from "@angular/common/http"; +import { + ChangeDetectionStrategy, + ChangeDetectorRef, + Component, + EventEmitter, + Injectable, + Input, + OnChanges, + OnDestroy, + OnInit, + Output, + SimpleChanges, +} from "@angular/core"; +import { FormBuilder, FormGroup, Validators } from "@angular/forms"; +import { GridsterConfig, GridsterItem } from "angular-gridster2"; +// eslint-disable-next-line import/no-deprecated +import { BehaviorSubject, combineLatest, Observable } from "rxjs"; +// eslint-disable-next-line import/no-deprecated +import { finalize, map, startWith } from "rxjs/operators"; + +import { DataSourceService, IFilteringOutputs } from "@nova-ui/bits"; +import { + ComponentRegistryService, + DATA_SOURCE, + DEFAULT_PIZZAGNA_ROOT, + IDashboard, + IHasChangeDetector, + IHasForm, + IKpiData, + IProviderConfiguration, + IRefresherProperties, + IWidget, + IWidgets, + KpiComponent, + NOVA_KPI_DATASOURCE_ADAPTER, + PizzagnaLayer, + ProviderRegistryService, + WellKnownPathKey, + WellKnownProviders, + WidgetTypesService, +} from "@nova-ui/dashboards"; + +/** + * A custom version of the KpiDescriptionConfigurationComponent provided by the dashboards framework. + * --- + * For this example, the existing background color selection functionality has been replaced by custom + * template content. + */ +@Component({ + selector: "custom-kpi-description-configuration", + template: \\\\\\\\\\\\\\\` + + +
+
+ + + +
+ + +
+
+ Custom Content +
+
+ The default version of this configurator section + displays a background color selector here. +
+
+ + +
+ + + +
+
+
+ \\\\\\\\\\\\\\\`, + styleUrls: ["./custom-configurator-section.example.component.less"], + changeDetection: ChangeDetectionStrategy.OnPush, + standalone: false, +}) +// Remember to declare this class in the parent module +export class CustomKpiDescriptionConfigurationComponent + implements OnInit, OnChanges, IHasChangeDetector, IHasForm +{ + // Ensure that the lateLoadKey value matches class name + public static lateLoadKey = "CustomKpiDescriptionConfigurationComponent"; + + @Input() componentId: string; + @Input() configurableUnits: boolean; + + @Input() label: string = ""; + @Input() units: string = ""; + + @Output() formReady = new EventEmitter(); + + public form: FormGroup; + public subtitle$: Observable; + + constructor( + public changeDetector: ChangeDetectorRef, + private formBuilder: FormBuilder + ) {} + + public ngOnInit(): void { + this.form = this.formBuilder.group({ + label: [this.label, [Validators.required]], + }); + + if (this.configurableUnits) { + this.form.addControl("units", this.formBuilder.control(this.units)); + } + + const label = this.form.get("label"); + // eslint-disable-next-line import/no-deprecated + const labelValue = label?.valueChanges.pipe(startWith(label?.value)); + + // eslint-disable-next-line import/no-deprecated + this.subtitle$ = combineLatest([ + labelValue?.pipe(map((t) => t || $localize\\\\\\\\\\\\\\\`no label\\\\\\\\\\\\\\\`)), + ]).pipe(map((labels) => labels.join(", "))); + + this.formReady.emit(this.form); + } + + public ngOnChanges(changes: SimpleChanges): void { + if (changes.label) { + this.form.patchValue({ label: changes.label.currentValue }); + } + if (changes.units) { + this.form.patchValue({ units: changes.units.currentValue }); + } + } +} + +/** + * A simple KPI data source to retrieve the average rating of Harry Potter and the Sorcerer's Stone (book) via googleapis + */ +@Injectable() +export class AverageRatingKpiDataSource + extends DataSourceService + implements OnDestroy +{ + // This is the ID we'll use to identify the provider + public static providerId = "AverageRatingKpiDataSource"; + + // Use this subject to communicate the data source's busy state + public busy = new BehaviorSubject(false); + + constructor(private http: HttpClient) { + super(); + } + + // In this example, getFilteredData is invoked every 10 minutes (Take a look at the refresher + // provider definition in the widget configuration below to see how the interval is set) + public async getFilteredData(): Promise { + this.busy.next(true); + return new Promise((resolve) => { + // *** Make a resource request to an external API (if needed) + this.http + .get("https://www.googleapis.com/books/v1/volumes/5MQFrgEACAAJ") + .pipe(finalize(() => this.busy.next(false))) + .subscribe({ + next: (data: any) => { + resolve({ + result: { + value: data.volumeInfo.averageRating, + }, + }); + }, + error: (error: HttpErrorResponse) => { + resolve({ + result: null, + error: { + type: error.status, + }, + }); + }, + }); + }); + } + + public ngOnDestroy(): void { + this.outputsSubject.complete(); + } +} + +/** + * A simple KPI data source to retrieve the ratings count of Harry Potter and the Sorcerer's Stone (book) via googleapis + */ +@Injectable() +export class RatingsCountKpiDataSource + extends DataSourceService + implements OnDestroy +{ + public static providerId = "RatingsCountKpiDataSource"; + + // Use this subject to communicate the data source's busy state + public busy = new BehaviorSubject(false); + + constructor(private http: HttpClient) { + super(); + } + + public async getFilteredData(): Promise { + this.busy.next(true); + return new Promise((resolve) => { + this.http + .get("https://www.googleapis.com/books/v1/volumes/5MQFrgEACAAJ") + .pipe(finalize(() => this.busy.next(false))) + .subscribe({ + next: (data: any) => { + resolve({ + result: { + value: data.volumeInfo.ratingsCount, + }, + }); + }, + error: (error: HttpErrorResponse) => { + resolve({ + result: null, + error: { + type: error.status, + }, + }); + }, + }); + }); + } + + public ngOnDestroy(): void { + this.outputsSubject.complete(); + } +} + +/** + * A component that instantiates the dashboard + */ +@Component({ + selector: "custom-configurator-section-example", + templateUrl: "./custom-configurator-section.example.component.html", + styleUrls: ["./custom-configurator-section.example.component.less"], + standalone: false, +}) +export class CustomConfiguratorSectionExampleComponent implements OnInit { + // This variable will hold all the data needed to define the layout and behavior of the widgets. + // Pass this to the dashboard component's dashboard input in the template. + public dashboard: IDashboard | undefined; + + // Angular gridster requires a configuration object even if it's empty. + // Pass this to the dashboard component's gridsterConfig input in the template. + public gridsterConfig: GridsterConfig = {}; + + // Boolean which dashboard takes in as an input if its true it allows you to move widgets around. + public editMode: boolean = false; + + constructor( + // WidgetTypesService provides the widget's necessary structure information + private widgetTypesService: WidgetTypesService, + + // In general, the ProviderRegistryService is used for making entities available for injection into dynamically loaded components. + private providerRegistry: ProviderRegistryService, + + // Inject the ComponentRegistryService to make our custom component available for late loading by the dashboards framework + private componentRegistry: ComponentRegistryService, + + private changeDetectorRef: ChangeDetectorRef + ) {} + + public ngOnInit(): void { + // Grab the widget's default template which will be needed as a parameter for setNode. + const widgetTemplate = this.widgetTypesService.getWidgetType("kpi", 1); + + // Replace the default KPI description configuration component with our custom one. + // Note: This could also be done in the parent module's constructor to give + // multiple dashboards access to the same custom configurator section. + this.widgetTypesService.setNode( + widgetTemplate, + "configurator", + WellKnownPathKey.TileDescriptionConfigComponentType, + CustomKpiDescriptionConfigurationComponent.lateLoadKey + ); + + // Register the custom configurator section with the component registry to make it available + // for late loading by the dashboards framework. + this.componentRegistry.registerByLateLoadKey( + CustomKpiDescriptionConfigurationComponent + ); + + // Register our data sources as dropdown options in the widget editor/configurator + // Note: This could also be done in the parent module's constructor so that + // multiple dashboards could have access to the same widget template modification. + this.widgetTypesService.setNode( + // This is the template we grabbed above with getWidgetType + widgetTemplate, + // We are setting the editor/configurator part of the widget template + "configurator", + // This indicates which node you are changing and we want to change + // the data source providers available for selection in the editor. + WellKnownPathKey.DataSourceProviders, + // We are setting the data sources available for selection in the editor + [ + AverageRatingKpiDataSource.providerId, + RatingsCountKpiDataSource.providerId, + ] + ); + + // Register the data sources available for injection into the KPI tiles. + // Note: Each tile of a KPI widget is assigned its own instance of a data source + this.providerRegistry.setProviders({ + [AverageRatingKpiDataSource.providerId]: { + provide: DATA_SOURCE, + useClass: AverageRatingKpiDataSource, + // Any dependencies that need to be injected into the provider must be listed here + deps: [HttpClient], + }, + [RatingsCountKpiDataSource.providerId]: { + provide: DATA_SOURCE, + useClass: RatingsCountKpiDataSource, + deps: [HttpClient], + }, + }); + + this.initializeDashboard(); + } + + public initializeDashboard(): void { + // We're using a static configuration object for this example (see widgetConfig at the bottom of the file), + // but this is where the widget's configuration could potentially be populated from a database + const kpiWidget = widgetConfig; + const widgetIndex: IWidgets = { + // Complete the KPI widget with information coming from its type definition + [kpiWidget.id]: + this.widgetTypesService.mergeWithWidgetType(kpiWidget), + }; + + // Setting the widget dimensions and position (this is for gridster) + const positions: Record = { + [kpiWidget.id]: { + cols: 4, + rows: 6, + y: 0, + x: 0, + }, + }; + + // Finally, assigning the variables we created above to the dashboard + this.dashboard = { + positions, + widgets: widgetIndex, + }; + } + + /** Used for restoring widgets state */ + public reInitializeDashboard(): void { + // destroys the components and their providers so the dashboard can re init data + this.dashboard = undefined; + this.changeDetectorRef.detectChanges(); + + this.initializeDashboard(); + } +} + +const widgetConfig: IWidget = { + id: "widget1", + type: "kpi", + pizzagna: { + [PizzagnaLayer.Configuration]: { + [DEFAULT_PIZZAGNA_ROOT]: { + providers: { + [WellKnownProviders.Refresher]: { + properties: { + // Configuring the refresher interval so that our data source is invoked every ten minutes + interval: 60 * 10, + enabled: true, + } as IRefresherProperties, + } as Partial, + }, + }, + header: { + properties: { + title: "Harry Potter and the Sorcerer's Stone", + subtitle: "By J. K. Rowling", + }, + }, + tiles: { + properties: { + nodes: ["kpi1"], + }, + }, + kpi1: { + id: "kpi1", + componentType: KpiComponent.lateLoadKey, + properties: { + widgetData: { + units: "out of 5 Stars", + label: "Average Rating", + }, + }, + providers: { + [WellKnownProviders.DataSource]: { + // Setting the data source providerId for the tile with id "kpi1" + providerId: AverageRatingKpiDataSource.providerId, + } as IProviderConfiguration, + [WellKnownProviders.Adapter]: { + providerId: NOVA_KPI_DATASOURCE_ADAPTER, + properties: { + componentId: "kpi1", + propertyPath: "widgetData", + }, + } as IProviderConfiguration, + }, + }, + }, + }, +}; +\\\\\\\`, + "tutorials/customization/configurator-section/custom-configurator-section-docs.component.ts": \\\\\\\`// © 2022 SolarWinds Worldwide, LLC. All rights reserved. +// +// Permission is hereby granted, free of charge, to any person obtaining a copy +// of this software and associated documentation files (the "Software"), to +// deal in the Software without restriction, including without limitation the +// rights to use, copy, modify, merge, publish, distribute, sublicense, and/or +// sell copies of the Software, and to permit persons to whom the Software is +// furnished to do so, subject to the following conditions: +// +// The above copyright notice and this permission notice shall be included in +// all copies or substantial portions of the Software. +// +// THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR +// IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, +// FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE +// AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER +// LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, +// OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN +// THE SOFTWARE. + +import { Component } from "@angular/core"; + +@Component({ + selector: "custom-configurator-section-docs", + templateUrl: "./custom-configurator-section-docs.component.html", + standalone: false, +}) +export class CustomConfiguratorSectionDocsComponent {} +\\\\\\\`, + "tutorials/customization/configurator-section/custom-configurator-section.module.ts": \\\\\\\`// © 2022 SolarWinds Worldwide, LLC. All rights reserved. +// +// Permission is hereby granted, free of charge, to any person obtaining a copy +// of this software and associated documentation files (the "Software"), to +// deal in the Software without restriction, including without limitation the +// rights to use, copy, modify, merge, publish, distribute, sublicense, and/or +// sell copies of the Software, and to permit persons to whom the Software is +// furnished to do so, subject to the following conditions: +// +// The above copyright notice and this permission notice shall be included in +// all copies or substantial portions of the Software. +// +// THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR +// IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, +// FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE +// AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER +// LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, +// OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN +// THE SOFTWARE. + +import { CommonModule } from "@angular/common"; +import { HttpClientModule } from "@angular/common/http"; +import { NgModule } from "@angular/core"; +import { ReactiveFormsModule } from "@angular/forms"; +import { RouterModule } from "@angular/router"; + +import { + NuiButtonModule, + NuiDocsModule, + NuiFormFieldModule, + NuiIconModule, + NuiMessageModule, + NuiSwitchModule, + NuiTextboxModule, + DEMO_PATH_TOKEN, +} from "@nova-ui/bits"; +import { + NuiDashboardConfiguratorModule, + NuiDashboardsModule, +} from "@nova-ui/dashboards"; + +import { + CustomConfiguratorSectionExampleComponent, + CustomKpiDescriptionConfigurationComponent, +} from "./custom-configurator-section/custom-configurator-section.example.component"; +import { CustomConfiguratorSectionDocsComponent } from "./custom-configurator-section-docs.component"; +import { getDemoFiles } from "../../../../../demo-files-factory"; + +const routes = [ + { + path: "", + component: CustomConfiguratorSectionDocsComponent, + data: { + srlc: { + hideIndicator: true, + }, + showThemeSwitcher: true, + }, + }, + { + path: "example", + component: CustomConfiguratorSectionExampleComponent, + data: { + srlc: { + hideIndicator: true, + }, + }, + }, +]; + +@NgModule({ + imports: [ + CommonModule, + ReactiveFormsModule, + HttpClientModule, + NuiDashboardsModule, + NuiDashboardConfiguratorModule, + NuiDocsModule, + NuiFormFieldModule, + NuiIconModule, + NuiMessageModule, + NuiSwitchModule, + NuiTextboxModule, + NuiButtonModule, + RouterModule.forChild(routes), + ], + declarations: [ + CustomConfiguratorSectionDocsComponent, + CustomKpiDescriptionConfigurationComponent, + CustomConfiguratorSectionExampleComponent, + ], + providers: [ + { + provide: DEMO_PATH_TOKEN, + useValue: getDemoFiles("configurator-section"), + }, + ], +}) +export default class CustomConfiguratorSectionModule {} +\\\\\\\`, + "tutorials/customization/customization.module.ts": \\\\\\\`// © 2022 SolarWinds Worldwide, LLC. All rights reserved. +// +// Permission is hereby granted, free of charge, to any person obtaining a copy +// of this software and associated documentation files (the "Software"), to +// deal in the Software without restriction, including without limitation the +// rights to use, copy, modify, merge, publish, distribute, sublicense, and/or +// sell copies of the Software, and to permit persons to whom the Software is +// furnished to do so, subject to the following conditions: +// +// The above copyright notice and this permission notice shall be included in +// all copies or substantial portions of the Software. +// +// THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR +// IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, +// FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE +// AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER +// LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, +// OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN +// THE SOFTWARE. + +import { NgModule, Type } from "@angular/core"; +import { RouterModule, Routes } from "@angular/router"; + +import { ConfiguratorHeadingService } from "@nova-ui/dashboards"; + +enum CustomizationModuleRoute { + ConfiguratorSection = "configurator-section", + Widget = "widget", + Formatter = "formatter", + DataSourceConfigurator = "data-source-configurator", +} + +const routes: Routes = [ + { + path: CustomizationModuleRoute.ConfiguratorSection, + loadChildren: async () => + import( + "./configurator-section/custom-configurator-section.module" + ) as object as Promise>, + }, + { + path: CustomizationModuleRoute.Widget, + loadChildren: async () => + import("./widget/custom-widget.module") as object as Promise< + Type + >, + }, + { + path: CustomizationModuleRoute.Formatter, + loadChildren: async () => + import("./formatter/custom-formatter.module") as object as Promise< + Type + >, + }, + { + path: CustomizationModuleRoute.DataSourceConfigurator, + loadChildren: async () => + import( + "./data-source-configurator/custom-data-source-configurator.module" + ) as object as Promise>, + }, +]; + +@NgModule({ + imports: [RouterModule.forChild(routes)], + providers: [ConfiguratorHeadingService], +}) +export default class CustomizationModule {} +\\\\\\\`, + "tutorials/customization/data-source-configurator/custom-data-source-configurator-docs.component.ts": \\\\\\\`// © 2022 SolarWinds Worldwide, LLC. All rights reserved. +// +// Permission is hereby granted, free of charge, to any person obtaining a copy +// of this software and associated documentation files (the "Software"), to +// deal in the Software without restriction, including without limitation the +// rights to use, copy, modify, merge, publish, distribute, sublicense, and/or +// sell copies of the Software, and to permit persons to whom the Software is +// furnished to do so, subject to the following conditions: +// +// The above copyright notice and this permission notice shall be included in +// all copies or substantial portions of the Software. +// +// THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR +// IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, +// FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE +// AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER +// LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, +// OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN +// THE SOFTWARE. + +import { Component } from "@angular/core"; + +@Component({ + selector: "nui-custom-data-source-configurator-docs", + templateUrl: "./custom-data-source-configurator-docs.component.html", + standalone: false, +}) +export class CustomDataSourceConfiguratorDocComponent {} +\\\\\\\`, + "tutorials/customization/data-source-configurator/custom-data-source-configurator.module.ts": \\\\\\\`// © 2022 SolarWinds Worldwide, LLC. All rights reserved. +// +// Permission is hereby granted, free of charge, to any person obtaining a copy +// of this software and associated documentation files (the "Software"), to +// deal in the Software without restriction, including without limitation the +// rights to use, copy, modify, merge, publish, distribute, sublicense, and/or +// sell copies of the Software, and to permit persons to whom the Software is +// furnished to do so, subject to the following conditions: +// +// The above copyright notice and this permission notice shall be included in +// all copies or substantial portions of the Software. +// +// THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR +// IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, +// FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE +// AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER +// LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, +// OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN +// THE SOFTWARE. + +import { NgModule } from "@angular/core"; +import { ReactiveFormsModule } from "@angular/forms"; +import { RouterModule, Routes } from "@angular/router"; + +// eslint-disable-next-line max-len +import { + NuiButtonModule, + NuiDocsModule, + NuiFormFieldModule, + NuiIconModule, + NuiMessageModule, + NuiSelectV2Module, + NuiSwitchModule, + NuiTextboxModule, + NuiValidationMessageModule, + DEMO_PATH_TOKEN, +} from "@nova-ui/bits"; +import { + NuiDashboardConfiguratorModule, + NuiDashboardsModule, +} from "@nova-ui/dashboards"; + +import { CustomDataSourceConfiguratorDocComponent } from "./custom-data-source-configurator-docs.component"; +import { + CustomDataSourceConfiguratorExampleComponent, + HarryPotterDataSourceConfiguratorComponent, +} from "./example/custom-data-source-configurator-example.component"; +import { getDemoFiles } from "../../../../../demo-files-factory"; + +const routes: Routes = [ + { + path: "", + component: CustomDataSourceConfiguratorDocComponent, + data: { + srlc: { + hideIndicator: true, + }, + showThemeSwitcher: true, + }, + }, +]; + +@NgModule({ + imports: [ + RouterModule.forChild(routes), + NuiDocsModule, + NuiButtonModule, + NuiMessageModule, + NuiDashboardConfiguratorModule, + NuiDashboardsModule, + NuiFormFieldModule, + NuiTextboxModule, + NuiSwitchModule, + NuiSelectV2Module, + NuiValidationMessageModule, + NuiIconModule, + ReactiveFormsModule, + ], + declarations: [ + CustomDataSourceConfiguratorDocComponent, + CustomDataSourceConfiguratorExampleComponent, + HarryPotterDataSourceConfiguratorComponent, + ], + providers: [ + { + provide: DEMO_PATH_TOKEN, + useValue: getDemoFiles("data-source-configurator"), + }, + ], +}) +export default class CustomDataSourceConfiguratorModuleRoute {} +\\\\\\\`, + "tutorials/customization/data-source-configurator/example/custom-data-source-configurator-example.component.ts": \\\\\\\`// © 2022 SolarWinds Worldwide, LLC. All rights reserved. +// +// Permission is hereby granted, free of charge, to any person obtaining a copy +// of this software and associated documentation files (the "Software"), to +// deal in the Software without restriction, including without limitation the +// rights to use, copy, modify, merge, publish, distribute, sublicense, and/or +// sell copies of the Software, and to permit persons to whom the Software is +// furnished to do so, subject to the following conditions: +// +// The above copyright notice and this permission notice shall be included in +// all copies or substantial portions of the Software. +// +// THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR +// IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, +// FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE +// AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER +// LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, +// OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN +// THE SOFTWARE. + +import { HttpClient, HttpErrorResponse } from "@angular/common/http"; +import { + ChangeDetectorRef, + Component, + Inject, + Injectable, + Injector, + OnDestroy, + OnInit, +} from "@angular/core"; +import { FormBuilder, Validators } from "@angular/forms"; +import { GridsterConfig, GridsterItem } from "angular-gridster2"; +import { BehaviorSubject } from "rxjs"; +import { finalize } from "rxjs/operators"; + +import { + DataSourceService, + EventBus, + IEvent, + IFilteringOutputs, + LoggerService, +} from "@nova-ui/bits"; +import { + ComponentRegistryService, + ConfiguratorHeadingService, + DataSourceConfigurationV2Component, + DATA_SOURCE, + DEFAULT_PIZZAGNA_ROOT, + IConfigurable, + IDashboard, + IKpiData, + IProperties, + IProviderConfiguration, + IRefresherProperties, + IWidget, + IWidgets, + KpiComponent, + NOVA_KPI_DATASOURCE_ADAPTER, + PizzagnaLayer, + PIZZAGNA_EVENT_BUS, + ProviderRegistryService, + WellKnownPathKey, + WellKnownProviders, + WidgetTypesService, +} from "@nova-ui/dashboards"; + +/** + * This component will serve as the data source accordion in the configurator. + */ +@Component({ + selector: "harry-potter-data-source-configurator", + styleUrls: ["./custom-data-source-configurator-example.component.less"], + template: \\\\\\\\\\\\\\\` + +
+ +
+ Data Source +
+ Harry Potter Books +
+
+
+
+ + + + {{ book.title }} + + + +
+
+ + + + {{ metric.label }} + + + +
+
+ \\\\\\\\\\\\\\\`, + standalone: false, +}) +@Injectable() +export class HarryPotterDataSourceConfiguratorComponent + extends DataSourceConfigurationV2Component + implements OnInit +{ + // This lateLoadKey allows the component to be able to be registered by the componentRegistry + public static lateLoadKey = "HarryPotterDataSourceConfiguratorComponent"; + + // Array of books that will populate the book select + public books = [ + { + id: "5MQFrgEACAAJ", + title: $localize\\\\\\\\\\\\\\\`Harry Potter and the Sorcerer's Stone\\\\\\\\\\\\\\\`, + }, + { + id: "5iTebBW-w7QC", + title: $localize\\\\\\\\\\\\\\\`Harry Potter and the Chamber of Secrets\\\\\\\\\\\\\\\`, + }, + ]; + + // Array of metrics that will populate the metric select + public metrics = [ + { + id: "averageRating", + label: $localize\\\\\\\\\\\\\\\`Average Rating\\\\\\\\\\\\\\\`, + }, + { + id: "ratingsCount", + label: $localize\\\\\\\\\\\\\\\`Ratings Count\\\\\\\\\\\\\\\`, + }, + ]; + + // These need to be injected because DataSourceConfigurationV2Component uses them + constructor( + changeDetector: ChangeDetectorRef, + configuratorHeading: ConfiguratorHeadingService, + formBuilder: FormBuilder, + providerRegistryService: ProviderRegistryService, + @Inject(PIZZAGNA_EVENT_BUS) eventBus: EventBus, + injector: Injector, + logger: LoggerService + ) { + super( + changeDetector, + configuratorHeading, + formBuilder, + providerRegistryService, + eventBus, + injector, + logger + ); + } + + // Overriding 'ngOnInit' to add custom controls to the 'properties' form group + public ngOnInit(): void { + super.ngOnInit(); + + // Overriding the 'properties' control on the form to create a form group that accommodates our custom properties + this.form.setControl( + "properties", + this.formBuilder.group({ + bookId: [this.properties?.bookId ?? "", Validators.required], + metric: [this.properties?.metric ?? "", Validators.required], + }) + ); + // The default data source control has a required validator we're removing that validator here since we aren't using it. + this.form.setControl("dataSource", this.formBuilder.control(null)); + // Here we set the providerId to our only data source so when a new tile gets created it will default to it. + this.form.get("providerId")?.setValue(AcmeKpiDataSource.providerId); + // Here we subscribe to the form and if there are any changes we invoke the data source + this.form.valueChanges.subscribe((value) => { + if (!value.providerId) { + return; + } + this.invokeDataSource(value); + }); + } +} + +/** + * A simple KPI data source to retrieve the average rating of Harry Potter and the Sorcerer's Stone (book) via googleapis + */ +@Injectable() +export class AcmeKpiDataSource + extends DataSourceService + implements OnDestroy, IConfigurable +{ + // This is the ID we'll use to identify the provider + public static providerId = "AcmeKpiDataSource"; + + // Use this subject to communicate the data source's busy state + public busy = new BehaviorSubject(false); + + public properties: IProperties; + + constructor(private http: HttpClient) { + super(); + } + + // This function MUST be implemented in order to receive property updates from our configurator + public updateConfiguration(properties: IProperties): void { + // Saving the properties because we will need it for this data source. + this.properties = properties; + } + + // In this example, getFilteredData is invoked every 10 minutes (Take a look at the refresher + // provider definition in the widget configuration below to see how the interval is set) + public async getFilteredData(): Promise { + // For loading indicator to show + this.busy.next(true); + return new Promise((resolve) => { + // *** Make a resource request to an external API (if needed) + this.http + .get( + \\\\\\\\\\\\\\\`https://www.googleapis.com/books/v1/volumes/\\\\\\\\\\\\\\\${this.properties?.bookId}\\\\\\\\\\\\\\\` + ) + // For loading indicator to be hidden + .pipe(finalize(() => this.busy.next(false))) + .subscribe({ + next: (data: any) => { + resolve({ + result: { + value: data.volumeInfo[this.properties?.metric], + }, + }); + }, + error: (error: HttpErrorResponse) => { + resolve({ + result: null, + error: { + type: error.status, + }, + }); + }, + }); + }); + } + + public ngOnDestroy(): void { + this.outputsSubject.complete(); + } +} + +/** + * A component that instantiates the dashboard + */ +@Component({ + selector: "custom-data-source-configurator-example", + templateUrl: "./custom-data-source-configurator-example.component.html", + styleUrls: ["./custom-data-source-configurator-example.component.less"], + standalone: false, +}) +export class CustomDataSourceConfiguratorExampleComponent implements OnInit { + // This variable will hold all the data needed to define the layout and behavior of the widgets. + // Pass this to the dashboard component's dashboard input in the template. + public dashboard: IDashboard; + + // Angular gridster requires a configuration object even if it's empty. + // Pass this to the dashboard component's gridsterConfig input in the template. + public gridsterConfig: GridsterConfig = {}; + + // Boolean which dashboard takes in as an input if its true it allows you to move widgets around. + public editMode: boolean = false; + + constructor( + // WidgetTypesService provides the widget's necessary structure information + private widgetTypesService: WidgetTypesService, + + // In general, the ProviderRegistryService is used for making entities available for injection into dynamically loaded components. + private providerRegistry: ProviderRegistryService, + + // Inject the ComponentRegistryService to make our custom component available for late loading by the dashboards framework + private componentRegistry: ComponentRegistryService + ) {} + + public ngOnInit(): void { + // Registering the new data source configurator so it can be used. + this.componentRegistry.registerByLateLoadKey( + HarryPotterDataSourceConfiguratorComponent + ); + // Registering the data source for injection into the KPI tile. + // Note: Each tile of a KPI widget is assigned its own instance of the data source + this.providerRegistry.setProviders({ + [AcmeKpiDataSource.providerId]: { + provide: DATA_SOURCE, + useClass: AcmeKpiDataSource, + // Any dependencies that need to be injected into the provider must be listed here + deps: [HttpClient], + }, + }); + + const kpiWidgetTemplate = this.widgetTypesService.getWidgetType( + "kpi", + 1 + ); + + this.widgetTypesService.setNode( + // This is the template we grabbed above with getWidgetType + kpiWidgetTemplate, + // We are setting the editor/configurator part of the widget template + "configurator", + // This is the path to go to the data source config component type. + WellKnownPathKey.DataSourceConfigComponentType, + // We are changing it to use the component we just created above instead of the default. + HarryPotterDataSourceConfiguratorComponent.lateLoadKey + ); + + // We're using a static configuration object for this example, but this is where + // the widget's configuration could potentially be populated from a database + const kpiWidget = widgetConfig; + const widgetIndex: IWidgets = { + // Complete the KPI widget with information coming from its type definition + [kpiWidget.id]: + this.widgetTypesService.mergeWithWidgetType(kpiWidget), + }; + + // Setting the widget dimensions and position (this is for gridster) + const positions: Record = { + [kpiWidget.id]: { + cols: 4, + rows: 6, + y: 0, + x: 0, + }, + }; + + // Finally, assigning the variables we created above to the dashboard + this.dashboard = { + positions, + widgets: widgetIndex, + }; + } +} + +const widgetConfig: IWidget = { + id: "widget1", + type: "kpi", + pizzagna: { + [PizzagnaLayer.Configuration]: { + [DEFAULT_PIZZAGNA_ROOT]: { + providers: { + [WellKnownProviders.Refresher]: { + properties: { + // Configuring the refresher interval so that our data source is invoked every ten minutes + interval: 60 * 10, + enabled: true, + } as IRefresherProperties, + } as Partial, + }, + }, + header: { + properties: { + title: "Harry Potter and the Sorcerer's Stone", + subtitle: "By J. K. Rowling", + }, + }, + tiles: { + properties: { + nodes: ["kpi1"], + }, + }, + kpi1: { + id: "kpi1", + componentType: KpiComponent.lateLoadKey, + properties: { + widgetData: { + units: "out of 5 Stars", + label: "Average Rating", + }, + }, + providers: { + [WellKnownProviders.DataSource]: { + // Setting the data source providerId for the tile with id "kpi1" + providerId: AcmeKpiDataSource.providerId, + properties: { + bookId: "5MQFrgEACAAJ", + metric: "averageRating", + }, + } as IProviderConfiguration, + [WellKnownProviders.Adapter]: { + providerId: NOVA_KPI_DATASOURCE_ADAPTER, + properties: { + componentId: "kpi1", + propertyPath: "widgetData", + }, + } as IProviderConfiguration, + }, + }, + }, + }, +}; +\\\\\\\`, + "tutorials/customization/formatter/custom-formatter.module.ts": \\\\\\\`// © 2022 SolarWinds Worldwide, LLC. All rights reserved. +// +// Permission is hereby granted, free of charge, to any person obtaining a copy +// of this software and associated documentation files (the "Software"), to +// deal in the Software without restriction, including without limitation the +// rights to use, copy, modify, merge, publish, distribute, sublicense, and/or +// sell copies of the Software, and to permit persons to whom the Software is +// furnished to do so, subject to the following conditions: +// +// The above copyright notice and this permission notice shall be included in +// all copies or substantial portions of the Software. +// +// THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR +// IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, +// FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE +// AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER +// LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, +// OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN +// THE SOFTWARE. + +import { NgModule } from "@angular/core"; +import { ReactiveFormsModule } from "@angular/forms"; +import { RouterModule, Routes } from "@angular/router"; + +// eslint-disable-next-line max-len +import { + NuiButtonModule, + NuiDocsModule, + NuiFormFieldModule, + NuiIconModule, + NuiMessageModule, + NuiSelectV2Module, + NuiSwitchModule, + NuiTextboxModule, + NuiValidationMessageModule, + DEMO_PATH_TOKEN, +} from "@nova-ui/bits"; +import { NuiDashboardsModule } from "@nova-ui/dashboards"; + +import { CustomDonutContentFormatterDocComponent } from "./donut-content-formatter-example/custom-donut-content-formatter-docs.component"; +import { + CustomDonutContentFormatterComponent, + CustomDonutContentFormatterConfiguratorComponent, + CustomDonutContentFormatterExampleComponent, +} from "./donut-content-formatter-example/custom-donut-content-formatter-example.component"; +import { CustomFormatterDocComponent } from "./formatter-example/custom-formatter-docs.component"; +import { + CustomFormatterComponent, + CustomFormatterConfiguratorComponent, + CustomFormatterExampleComponent, +} from "./formatter-example/custom-formatter-example.component"; +import { getDemoFiles } from "../../../../../demo-files-factory"; + +const routes: Routes = [ + { + path: "table-formatter", + component: CustomFormatterDocComponent, + data: { + srlc: { + hideIndicator: true, + }, + showThemeSwitcher: true, + }, + }, + { + path: "donut-content-formatter", + component: CustomDonutContentFormatterDocComponent, + data: { + srlc: { + hideIndicator: true, + }, + showThemeSwitcher: true, + }, + }, +]; + +@NgModule({ + imports: [ + RouterModule.forChild(routes), + NuiDocsModule, + NuiButtonModule, + NuiMessageModule, + NuiDashboardsModule, + NuiFormFieldModule, + NuiTextboxModule, + NuiSwitchModule, + NuiSelectV2Module, + NuiValidationMessageModule, + NuiIconModule, + ReactiveFormsModule, + ], + declarations: [ + CustomDonutContentFormatterComponent, + CustomDonutContentFormatterExampleComponent, + CustomDonutContentFormatterConfiguratorComponent, + CustomDonutContentFormatterDocComponent, + CustomFormatterDocComponent, + CustomFormatterExampleComponent, + CustomFormatterConfiguratorComponent, + CustomFormatterComponent, + ], + providers: [ + { + provide: DEMO_PATH_TOKEN, + useValue: getDemoFiles("formatter"), + }, + ], +}) +export default class CustomFormatterModuleRoute {} +\\\\\\\`, + "tutorials/customization/formatter/donut-content-formatter-example/custom-donut-content-formatter-docs.component.ts": \\\\\\\`// © 2022 SolarWinds Worldwide, LLC. All rights reserved. +// +// Permission is hereby granted, free of charge, to any person obtaining a copy +// of this software and associated documentation files (the "Software"), to +// deal in the Software without restriction, including without limitation the +// rights to use, copy, modify, merge, publish, distribute, sublicense, and/or +// sell copies of the Software, and to permit persons to whom the Software is +// furnished to do so, subject to the following conditions: +// +// The above copyright notice and this permission notice shall be included in +// all copies or substantial portions of the Software. +// +// THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR +// IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, +// FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE +// AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER +// LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, +// OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN +// THE SOFTWARE. + +import { Component } from "@angular/core"; + +@Component({ + selector: "nui-custom-donut-content-formatter-docs", + templateUrl: "./custom-donut-content-formatter-docs.component.html", + standalone: false, +}) +export class CustomDonutContentFormatterDocComponent {} +\\\\\\\`, + "tutorials/customization/formatter/donut-content-formatter-example/custom-donut-content-formatter-example.component.ts": \\\\\\\`// © 2022 SolarWinds Worldwide, LLC. All rights reserved. +// +// Permission is hereby granted, free of charge, to any person obtaining a copy +// of this software and associated documentation files (the "Software"), to +// deal in the Software without restriction, including without limitation the +// rights to use, copy, modify, merge, publish, distribute, sublicense, and/or +// sell copies of the Software, and to permit persons to whom the Software is +// furnished to do so, subject to the following conditions: +// +// The above copyright notice and this permission notice shall be included in +// all copies or substantial portions of the Software. +// +// THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR +// IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, +// FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE +// AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER +// LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, +// OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN +// THE SOFTWARE. + +import { HttpClient } from "@angular/common/http"; +import { + ChangeDetectorRef, + Component, + Injectable, + Input, + OnChanges, + OnDestroy, + OnInit, + SimpleChanges, +} from "@angular/core"; +import { FormBuilder, FormGroup, Validators } from "@angular/forms"; +import { GridsterConfig, GridsterItem } from "angular-gridster2"; +import { Subject } from "rxjs"; +import { takeUntil, tap } from "rxjs/operators"; + +import { + DataSourceService, + IconService, + IDataSource, + IFilteringOutputs, + LoggerService, +} from "@nova-ui/bits"; +import { + ChartAssist, + IAccessors, + IChartAssistEvent, + IChartAssistSeries, +} from "@nova-ui/charts"; +import { + ComponentRegistryService, + ConfiguratorHeadingService, + DATA_SOURCE, + DonutChartFormatterConfiguratorComponent, + DonutContentPercentageConfigurationComponent, + DonutContentPercentageFormatterComponent, + DonutContentSumFormatterComponent, + IDashboard, + IFormatterDefinition, + IHasChangeDetector, + IProperties, + IProportionalWidgetChartOptions, + IProportionalWidgetConfig, + IProviderConfiguration, + IWidget, + IWidgets, + LegendPlacement, + PizzagnaLayer, + ProportionalWidgetChartTypes, + ProviderRegistryService, + WellKnownPathKey, + WellKnownProviders, + WidgetTypesService, +} from "@nova-ui/dashboards"; + +export enum Units { + Days = "Day(s)", + Weeks = "Week(s)", + Hours = "Hour(s)", +} + +@Component({ + selector: "custom-donut-content-formatter", + host: { class: "d-flex flex-column align-items-center" }, + template: \\\\\\\\\\\\\\\` +
+ + {{ chartMetric || properties?.currentMetric || data[0].id }} +
+
+ {{ chartContent }} +
+
+ {{ units }} +
+
\\\\\\\\\\\\\\\`, + styleUrls: ["./custom-donut-content-formatter-example.component.less"], + standalone: false, +}) +export class CustomDonutContentFormatterComponent + implements IHasChangeDetector, OnInit, OnChanges +{ + public static lateLoadKey = "CustomDonutContentFormatterComponent"; + + // Used to emphasize the chart series when user interacts either with the chart legend, or chart segments. + public emphasizedSeriesData: IChartAssistSeries | undefined; + + // Current raw value of the metric to display + public currentMetricData: number; + + // Metric value rendered inside the template, when user selects a metric, and gets automatically recalculated depending on selected units + public chartContent: number; + + // Metric value rendered inside the template, when user interacts with either chart legend, or chart segments + public chartMetric: number; + + // Units which user can select from the configuration + public units: Units = Units.Days; + + private readonly destroy$ = new Subject(); + + constructor(public changeDetector: ChangeDetectorRef) {} + + // The data we receive from the chart, including metrics names and their values + @Input() data: IChartAssistSeries[]; + + // We use this chart assist instance to subscribe to the events triggered when an interaction with the chart occurs + @Input() chartAssist: ChartAssist; + + // These are the current properties from pizzagna. Used to use data set at the configuration layer + @Input() properties: IProperties; + + public ngOnChanges(changes: SimpleChanges): void { + if (changes.properties || !this.properties) { + // If current metric is not in the list of metrics any more we fall back to the very first one from the list we get from the datasource + this.currentMetricData = + this.data.find( + (item) => item.id === this.properties?.currentMetric + )?.data[0] || this.data[0].data[0]; + + // We either take the selected value, or fall back to the preselected default one + this.units = this.properties?.units || this.units; + } + + this.setContentValue(); + } + + public ngOnInit(): void { + // Here 'chartAssistSubject' is the entity that emits events every time user interacts with either chart legend, or chart segments. + // Subscribing to properly react on these kind of events + this.chartAssist.chartAssistSubject + .pipe( + tap( + (data: IChartAssistEvent) => + (this.emphasizedSeriesData = this.data.find( + (item) => item.id === data.payload.seriesId + )) + ), + tap(() => this.setContentValue()), + tap(() => this.setMetricValue()), + takeUntil(this.destroy$) + ) + .subscribe(); + } + + public getConvertedData(emphData: number): number { + // Recalculating data depending on the units user selected from the configuration view + switch (this.units) { + case Units.Weeks: + return this.emphasizedSeriesData + ? this.convertToWeeks(emphData) + : this.convertToWeeks(this.currentMetricData); + + case Units.Hours: + return this.emphasizedSeriesData + ? this.convertToHours(emphData) + : this.convertToHours(this.currentMetricData); + + default: + return this.emphasizedSeriesData + ? emphData + : this.currentMetricData; + } + } + + public setContentValue(): void { + this.chartContent = this.getConvertedData( + this.emphasizedSeriesData?.data[0] + ); + } + + public setMetricValue(): void { + this.chartMetric = this.emphasizedSeriesData + ? this.data.find( + (item) => + this.getConvertedData(item.data[0]) === + this.getConvertedData(this.emphasizedSeriesData?.data[0]) + )?.id + : // if metric was not initially selected we fall back to the very first one + this.properties?.currentMetric || this.data[0].id; + } + + private convertToWeeks(days: number | undefined): number { + return days ? Number((days / 7).toFixed(2)) : 0; + } + + private convertToHours(days: number | undefined): number { + return days ? Number((days * 24).toFixed(2)) : 0; + } +} + +@Component({ + selector: "custom-donut-content-formatter-configurator", + styleUrls: ["./custom-donut-content-formatter-example.component.less"], + template: \\\\\\\\\\\\\\\` +
+
+ + + + {{ itemValue?.name }} + + + + This field is required + + +
+
+ + + + {{ itemValue }} + + + + This field is required + + +
+
+ \\\\\\\\\\\\\\\`, + standalone: false, +}) +export class CustomDonutContentFormatterConfiguratorComponent + extends DonutChartFormatterConfiguratorComponent + implements OnChanges, OnInit, IHasChangeDetector +{ + public static lateLoadKey = "CustomFormatterConfiguratorComponent"; + + constructor( + changeDetector: ChangeDetectorRef, + formBuilder: FormBuilder, + logger: LoggerService, + public iconService: IconService, + public configuratorHeading: ConfiguratorHeadingService + ) { + super(changeDetector, formBuilder, logger); + } + + public availableUnits: Units[] = [Units.Days, Units.Hours, Units.Weeks]; + + protected addCustomFormControls(form: FormGroup): void { + form.addControl( + "units", + this.formBuilder.control(Units.Days, Validators.required) + ); + } +} + +/** + * A component that instantiates the dashboard + */ +@Component({ + selector: "custom-donut-content-formatter-example", + templateUrl: "./custom-donut-content-formatter-example.component.html", + styleUrls: ["./custom-donut-content-formatter-example.component.less"], + standalone: false, +}) +export class CustomDonutContentFormatterExampleComponent implements OnInit { + public editMode: boolean = false; + // This variable will hold all the data needed to define the layout and behavior of the widgets. + // Pass this to the dashboard component's dashboard input in the template. + public dashboard: IDashboard | undefined; + + // Angular gridster requires a configuration object even if it's empty. + // Pass this to the dashboard component's gridsterConfig input in the template. + public gridsterConfig: GridsterConfig = {}; + + constructor( + // WidgetTypesService provides the widget's necessary structure information + private widgetTypesService: WidgetTypesService, + // In general, the ProviderRegistryService is used for making entities available for injection into dynamically loaded components. + private providerRegistry: ProviderRegistryService, + // Inject the ComponentRegistryService to make our custom component available for late loading by the dashboards framework + private componentRegistry: ComponentRegistryService, + private changeDetectorRef: ChangeDetectorRef + ) { + // Register the custom configurator component with the component registry to make it available + // for late loading by the dashboard framework. + this.componentRegistry.registerByLateLoadKey( + CustomDonutContentFormatterConfiguratorComponent + ); + // Register the custom formatter component with the component registry to make it available + // for late loading by the dashboard framework. + this.componentRegistry.registerByLateLoadKey( + CustomDonutContentFormatterComponent + ); + + // Grab the widget's default template which will be needed as a parameter for setNode below. + const proportional = this.widgetTypesService.getWidgetType( + "proportional", + 1 + ); + + const donutFormatters: IFormatterDefinition[] = [ + { + componentType: DonutContentSumFormatterComponent.lateLoadKey, + label: $localize\\\\\\\\\\\\\\\`Sum\\\\\\\\\\\\\\\`, + } as IFormatterDefinition, + { + componentType: + DonutContentPercentageFormatterComponent.lateLoadKey, + label: $localize\\\\\\\\\\\\\\\`Percentage\\\\\\\\\\\\\\\`, + configurationComponent: + DonutContentPercentageConfigurationComponent.lateLoadKey, + } as IFormatterDefinition, + { + componentType: CustomDonutContentFormatterComponent.lateLoadKey, + label: $localize\\\\\\\\\\\\\\\`Custom\\\\\\\\\\\\\\\`, + // This is a custom configurator that will pop up below the formatter once it gets selected + configurationComponent: + CustomDonutContentFormatterConfiguratorComponent.lateLoadKey, + } as IFormatterDefinition, + ]; + + this.widgetTypesService.setNode( + // This is the template we grabbed above with getWidgetType + proportional, + // We are setting the editor/configurator part of the widget template + "configurator", + // This indicates which node you are changing and we want to change the formatters available for selection in the editor. + WellKnownPathKey.Formatters, + // We are setting the available formatters with the array we created above. + donutFormatters + ); + + // This sets the donut chart's datasource to have the StatusesExampleDatasource so the drop down is filled similar to the line above. + this.widgetTypesService.setNode( + proportional, + "configurator", + WellKnownPathKey.DataSourceProviders, + [StatusesExampleDatasource.providerId] + ); + + // Registering the data source for injection into the widget. + this.providerRegistry.setProviders({ + [StatusesExampleDatasource.providerId]: { + provide: DATA_SOURCE, + useClass: StatusesExampleDatasource, + // Any dependencies that need to be injected into the provider must be listed here + deps: [], + }, + }); + } + + public ngOnInit(): void { + this.initializeDashboard(); + } + + /** Used for restoring widgets state */ + public reInitializeDashboard(): void { + // destroys the components and their providers so the dashboard can re init data + this.dashboard = undefined; + this.changeDetectorRef.detectChanges(); + + this.initializeDashboard(); + } + + public initializeDashboard(): void { + // We're using a static configuration object for this example, but this is where + // the widget's configuration could potentially be populated from a database + const proportionalWidget = widgetConfig; + const widgetIndex: IWidgets = { + // Enhance the widget with information coming from it's type definition + [proportionalWidget.id]: + this.widgetTypesService.mergeWithWidgetType(proportionalWidget), + }; + + // Setting the widget dimensions and position (this is for gridster) + const positions: Record = { + [proportionalWidget.id]: { + cols: 12, + rows: 6, + y: 0, + x: 0, + }, + }; + + // Finally, assigning the variables we created above to the dashboard + this.dashboard = { + positions, + widgets: widgetIndex, + }; + } +} + +export interface IStatusesWidgetData { + id: string; + name: string; + data: number[]; +} + +export const randomStatusesWidgetData: IStatusesWidgetData[] = [ + { + id: "Down", + name: "Down", + data: [Math.round(Math.random() * 100)], + }, + { + id: "Critical", + name: "Critical", + data: [Math.round(Math.random() * 100)], + }, + { + id: "Warning", + name: "Warning", + data: [Math.round(Math.random() * 100)], + }, + { + id: "Unknown", + name: "Unknown", + data: [Math.round(Math.random() * 100)], + }, + { + id: "Up", + name: "Up", + data: [Math.round(Math.random() * 100)], + }, + { + id: "Unmanaged", + name: "Unmanaged", + data: [Math.round(Math.random() * 100)], + }, +]; + +@Injectable() +export class StatusesExampleDatasource + extends DataSourceService + implements IDataSource, OnDestroy +{ + public static providerId = "StatusesExampleDatasource"; + + public busy = new Subject(); + + constructor(private http: HttpClient) { + super(); + } + + public async getFilteredData(): Promise { + this.busy.next(true); + + return new Promise((resolve) => { + setTimeout(() => { + resolve({ + result: randomStatusesWidgetData, + }); + this.busy.next(false); + }, 1000); + }); + } + + public ngOnDestroy(): void { + this.outputsSubject.complete(); + } +} + +export const widgetConfig: IWidget = { + id: "proportionalWidgetId", + type: "proportional", + pizzagna: { + [PizzagnaLayer.Configuration]: { + header: { + properties: { + title: "Proportional Widget!", + subtitle: "Proportional widget with legend formatters", + }, + }, + chart: { + providers: { + [WellKnownProviders.DataSource]: { + providerId: StatusesExampleDatasource.providerId, + } as IProviderConfiguration, + }, + properties: { + configuration: { + interactive: true, + chartOptions: { + type: ProportionalWidgetChartTypes.DonutChart, + legendPlacement: LegendPlacement.Right, + contentFormatter: { + componentType: + CustomDonutContentFormatterComponent.lateLoadKey, + properties: { + // here you can set the default value for the metric you receive. If not set the first one from the list will be taken + currentMetric: "Down", + // here you set the default value for your custom controls. If not set the first one from the list will be taken + units: Units.Weeks, + }, + }, + } as IProportionalWidgetChartOptions, + chartColors: [ + "var(--nui-color-chart-eight)", + "var(--nui-color-chart-nine)", + "var(--nui-color-chart-ten)", + ], + } as IProportionalWidgetConfig, + }, + }, + }, + }, +}; +\\\\\\\`, + "tutorials/customization/formatter/formatter-example/custom-formatter-docs.component.ts": \\\\\\\`// © 2022 SolarWinds Worldwide, LLC. All rights reserved. +// +// Permission is hereby granted, free of charge, to any person obtaining a copy +// of this software and associated documentation files (the "Software"), to +// deal in the Software without restriction, including without limitation the +// rights to use, copy, modify, merge, publish, distribute, sublicense, and/or +// sell copies of the Software, and to permit persons to whom the Software is +// furnished to do so, subject to the following conditions: +// +// The above copyright notice and this permission notice shall be included in +// all copies or substantial portions of the Software. +// +// THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR +// IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, +// FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE +// AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER +// LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, +// OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN +// THE SOFTWARE. + +import { Component } from "@angular/core"; + +@Component({ + selector: "nui-custom-formatter-docs", + templateUrl: "./custom-formatter-docs.component.html", + standalone: false, +}) +export class CustomFormatterDocComponent {} +\\\\\\\`, + "tutorials/customization/formatter/formatter-example/custom-formatter-example.component.ts": \\\\\\\`// © 2022 SolarWinds Worldwide, LLC. All rights reserved. +// +// Permission is hereby granted, free of charge, to any person obtaining a copy +// of this software and associated documentation files (the "Software"), to +// deal in the Software without restriction, including without limitation the +// rights to use, copy, modify, merge, publish, distribute, sublicense, and/or +// sell copies of the Software, and to permit persons to whom the Software is +// furnished to do so, subject to the following conditions: +// +// The above copyright notice and this permission notice shall be included in +// all copies or substantial portions of the Software. +// +// THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR +// IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, +// FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE +// AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER +// LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, +// OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN +// THE SOFTWARE. + +import { ListRange } from "@angular/cdk/collections"; +import { ChangeDetectorRef, Component, Input, OnInit } from "@angular/core"; +import { FormBuilder, FormGroup, Validators } from "@angular/forms"; +import { GridsterConfig, GridsterItem } from "angular-gridster2"; +import isEqual from "lodash/isEqual"; +import orderBy from "lodash/orderBy"; +import { BehaviorSubject } from "rxjs"; + +import { + DataSourceService, + IconService, + IDataField, + INovaFilteringOutputs, + INovaFilters, + ISorterFilter, + LoggerService, +} from "@nova-ui/bits"; +import { + ComponentRegistryService, + ConfiguratorHeadingService, + DATA_SOURCE, + FormatterConfiguratorComponent, + IDashboard, + IDataSourceOutput, + IFormatterDefinition, + IHasChangeDetector, + ITableWidgetColumnConfig, + ITableWidgetSorterConfig, + IWidget, + IWidgets, + PizzagnaLayer, + ProviderRegistryService, + RawFormatterComponent, + TableFormatterRegistryService, + WellKnownPathKey, + WellKnownProviders, + WidgetTypesService, +} from "@nova-ui/dashboards"; + +export const BREW_API_URL = "https://api.punkapi.com/v2/beers"; + +@Component({ + selector: "custom-formatter", + host: { class: "d-flex" }, + template: \\\\\\\\\\\\\\\` +
+
+ +
+
+ {{ data.value }} +
+
+ \\\\\\\\\\\\\\\`, + styleUrls: ["./custom-formatter-example.component.less"], + standalone: false, +}) +export class CustomFormatterComponent implements IHasChangeDetector { + public static lateLoadKey = "CustomFormatterComponent"; + + constructor(public changeDetector: ChangeDetectorRef) {} + + @Input() public data: any; + @Input() public icon: string; + @Input() public threshold: string; + + public isAboveThreshold(): boolean { + return parseFloat(this.threshold) <= this.data.value; + } +} + +@Component({ + selector: "custom-formatter-configurator", + styleUrls: ["./custom-formatter-example.component.less"], + template: \\\\\\\\\\\\\\\` +
+
+ + + + {{ item.label }} + + + + This field is required + + +
+
+ + + + + + + + This field is required + + +
+
+ + + + + This field is required + + +
+
+ +
+
+ +
+ + +
+ + + Select Item + +
+ \\\\\\\\\\\\\\\`, + standalone: false, +}) +export class CustomFormatterConfiguratorComponent + extends FormatterConfiguratorComponent + implements OnInit, IHasChangeDetector +{ + public static lateLoadKey = "CustomFormatterConfiguratorComponent"; + + constructor( + changeDetector: ChangeDetectorRef, + configuratorHeading: ConfiguratorHeadingService, + formBuilder: FormBuilder, + logger: LoggerService, + public iconService: IconService + ) { + super(changeDetector, configuratorHeading, formBuilder, logger); + } + + public formatterFormGroup: FormGroup; + // This array is where the icon names will be stored + public options: string[] = []; + + public ngOnInit(): void { + for (const icon of this.iconService.icons) { + if (icon.category === "severity") { + this.options.push(icon.name); + } + } + } + + protected addCustomFormControls(form: FormGroup): void { + form.addControl( + "icon", + this.formBuilder.control("", Validators.required) + ); + form.addControl( + "threshold", + this.formBuilder.control(null, Validators.required) + ); + } +} + +/** + * A component that instantiates the dashboard + */ +@Component({ + selector: "custom-formatter-example", + templateUrl: "./custom-formatter-example.component.html", + styleUrls: ["./custom-formatter-example.component.less"], + standalone: false, +}) +export class CustomFormatterExampleComponent implements OnInit { + public editMode: boolean = false; + // This variable will hold all the data needed to define the layout and behavior of the widgets. + // Pass this to the dashboard component's dashboard input in the template. + public dashboard: IDashboard | undefined; + + // Angular gridster requires a configuration object even if it's empty. + // Pass this to the dashboard component's gridsterConfig input in the template. + public gridsterConfig: GridsterConfig = {}; + + constructor( + // WidgetTypesService provides the widget's necessary structure information + private widgetTypesService: WidgetTypesService, + // In general, the ProviderRegistryService is used for making entities available for injection into dynamically loaded components. + private providerRegistry: ProviderRegistryService, + // Inject the ComponentRegistryService to make our custom component available for late loading by the dashboards framework + private componentRegistry: ComponentRegistryService, + private tableFormatterRegistryService: TableFormatterRegistryService, + private changeDetectorRef: ChangeDetectorRef + ) { + // Register the custom configurator component with the component registry to make it available + // for late loading by the dashboard framework. + this.componentRegistry.registerByLateLoadKey( + CustomFormatterConfiguratorComponent + ); + // Register the custom formatter component with the component registry to make it available + // for late loading by the dashboard framework. + this.componentRegistry.registerByLateLoadKey(CustomFormatterComponent); + + // Grab the widget's default template which will be needed as a parameter for setNode below. + const table = this.widgetTypesService.getWidgetType("table", 1); + + const tableFormatters: IFormatterDefinition[] = [ + { + // This will be the component that will format the data + componentType: RawFormatterComponent.lateLoadKey, + // This is the label for what the formatter is selected in the drop down + label: $localize\\\\\\\\\\\\\\\`:table formatter|:No formatter\\\\\\\\\\\\\\\`, + // This says what datatype the formatter supports. If the value node is null, it accepts any data type. + dataTypes: { + // @ts-ignore: Ignoring compiler error to keep the same flow + value: null, + }, + }, + { + componentType: CustomFormatterComponent.lateLoadKey, + label: $localize\\\\\\\\\\\\\\\`:table formatter|:Custom formatter\\\\\\\\\\\\\\\`, + // This is a custom configurator that will pop up below the formatter once it gets selected + configurationComponent: + CustomFormatterConfiguratorComponent.lateLoadKey, + // This says what data types the formatter supports. + // In this case, it supports abv values only. + // If you look below in the table data source you'll see where we define our column's data types. + dataTypes: { + value: ["abv"], + }, + }, + ]; + + // Registering the formatters + this.tableFormatterRegistryService.addItems(tableFormatters); + + // This sets the table's datasource to have the BeerDataSource so the drop down is filled similar to the line above. + this.widgetTypesService.setNode( + table, + "configurator", + WellKnownPathKey.DataSourceProviders, + [BeerDataSource.providerId] + ); + + // Registering the data source for injection into the widget. + this.providerRegistry.setProviders({ + [BeerDataSource.providerId]: { + provide: DATA_SOURCE, + useClass: BeerDataSource, + // Any dependencies that need to be injected into the provider must be listed here + deps: [], + }, + }); + } + + public ngOnInit(): void { + this.initializeDashboard(); + } + + /** Used for restoring widgets state */ + public reInitializeDashboard(): void { + // destroys the components and their providers so the dashboard can re init data + this.dashboard = undefined; + this.changeDetectorRef.detectChanges(); + + this.initializeDashboard(); + } + + public initializeDashboard(): void { + // We're using a static configuration object for this example, but this is where + // the widget's configuration could potentially be populated from a database + const tableWidget = widgetConfig; + const widgetIndex: IWidgets = { + // Enhance the widget with information coming from it's type definition + [tableWidget.id]: + this.widgetTypesService.mergeWithWidgetType(tableWidget), + }; + + // Setting the widget dimensions and position (this is for gridster) + const positions: Record = { + [tableWidget.id]: { + cols: 12, + rows: 6, + y: 0, + x: 0, + }, + }; + + // Finally, assigning the variables we created above to the dashboard + this.dashboard = { + positions, + widgets: widgetIndex, + }; + } +} + +export interface IBrewInfo { + id: number; + name: string; + tagline: string; + first_brewed: string; + description: string; + brewers_tips: string; + abv: number; +} + +export interface IBrewDatasourceResponse { + brewInfo: IBrewInfo[]; + total: number; +} + +export class BeerDataSource extends DataSourceService { + public static providerId = "BeerDataSource"; + + private cache = Array.from({ length: 0 }); + private lastSortValue?: ISorterFilter; + private lastVirtualScroll?: ListRange; + // For simplicity, the totalItems value is hard-coded here, but in a real-world scenario the value would likely be retrieved via an async backend call + private totalItems: number = 325; + + public page: number = 1; + public busy = new BehaviorSubject(false); + + public dataFields: Array = [ + { id: "id", label: "No", dataType: "number" }, + { id: "name", label: "Name", dataType: "string" }, + { id: "tagline", label: "Tagline", dataType: "string" }, + { id: "first_brewed", label: "First Brewed", dataType: "string" }, + { id: "description", label: "Description", dataType: "string" }, + { id: "brewers_tips", label: "Brewer's Tips", dataType: "string" }, + // We are giving this field a custom data type of 'abv' so the dropdown in the custom formatter configurator can use it to filter out other data types + { id: "abv", label: "Alcohol By Volume", dataType: "abv" }, + ]; + + constructor(private logger: LoggerService) { + super(); + } + + public async getFilteredData( + filters: INovaFilters + ): Promise> { + const start = filters.virtualScroll?.value?.start ?? 0; + const end = filters.virtualScroll?.value?.end ?? 0; + const delta = end - start; + + // Note: We should start with a clean cache every time first page is requested + if (start === 0) { + this.cache = []; + } + + // This condition handles sorting. We want to sort columns without fetching another chunk of data. + // Since the data is being fetched when scrolled, we compare virtual scroll indexes here in the condition as well. + if (filters.sorter?.value) { + if ( + !isEqual(this.lastSortValue, filters.sorter.value) && + filters.virtualScroll?.value.start === 0 && + !!this.lastVirtualScroll + ) { + const totalPages = Math.ceil( + delta ? this.totalItems / delta : 1 + ); + const itemsPerPage: number = Math.max( + delta < 80 ? delta : 80, + 1 + ); + let response: Array | null = null; + let map: IBrewDatasourceResponse; + + if (filters.sorter?.value?.direction === "desc") { + this.cache = []; + for (let i = 0; i < this.page; ++i) { + response = await ( + await fetch( + \\\\\\\\\\\\\\\`\\\\\\\\\\\\\\\${BREW_API_URL}/?page=\\\\\\\\\\\\\\\${ + totalPages - i || 1 + }&per_page=\\\\\\\\\\\\\\\${itemsPerPage}\\\\\\\\\\\\\\\` + ) + ).json(); + + // since the last page contains only 5 items we need to fetch another page to give virtual scroll enough space to work + if (response && response.length < itemsPerPage) { + this.page++; + } + map = { + brewInfo: response?.map((result: IBrewInfo) => ({ + id: result.id, + name: result.name, + tagline: result.tagline, + first_brewed: result.first_brewed, + description: result.description, + brewers_tips: result.brewers_tips, + })), + total: response?.length, + } as IBrewDatasourceResponse; + this.cache = + totalPages - i !== 0 + ? this.cache.concat(map.brewInfo) + : this.cache; + } + } + + if (filters.sorter?.value?.direction === "asc") { + this.cache = []; + for (let i = 0; i < this.page; i++) { + response = await ( + await fetch( + \\\\\\\\\\\\\\\`\\\\\\\\\\\\\\\${BREW_API_URL}/?page=\\\\\\\\\\\\\\\${ + i + 1 + }&per_page=\\\\\\\\\\\\\\\${itemsPerPage}\\\\\\\\\\\\\\\` + ) + ).json(); + map = { + brewInfo: response?.map((result: IBrewInfo) => ({ + id: result.id, + name: result.name, + tagline: result.tagline, + first_brewed: result.first_brewed, + description: result.description, + brewers_tips: result.brewers_tips, + })), + total: response?.length, + } as IBrewDatasourceResponse; + this.cache = this.cache.concat(map.brewInfo); + } + } + + this.lastSortValue = filters.sorter?.value; + this.lastVirtualScroll = filters.virtualScroll?.value; + + return { + result: { + repeat: { + itemsSource: this.sortData(this.cache, filters), + }, + paginator: { total: this.totalItems }, + dataFields: this.dataFields, + }, + }; + } + } + + this.busy.next(true); + return new Promise((resolve) => { + setTimeout(() => { + this.getData(start, end, filters).then( + (response: INovaFilteringOutputs) => { + if (!response) { + return; + } + + this.cache = this.cache.concat(response.brewInfo); + + this.dataSubject.next(this.cache); + resolve({ + result: { + repeat: { + itemsSource: this.sortData( + this.cache, + filters + ), + }, + paginator: { total: this.totalItems }, + dataFields: this.dataFields, + }, + }); + + this.lastSortValue = filters.sorter?.value; + this.lastVirtualScroll = filters.virtualScroll?.value; + this.busy.next(false); + } + ); + }, 500); + }); + } + + public async getData( + start: number = 0, + end: number = 20, + filters: INovaFilters + ): Promise { + const delta = end - start; + const totalPages = Math.ceil(delta ? this.totalItems / delta : 1); + let response: Array | null = null; + // The api.punk.com is able to return only 80 items per page + const itemsPerPage: number = Math.max(delta < 80 ? delta : 80, 1); + + if (filters.sorter?.value?.direction === "asc") { + response = await ( + await fetch( + \\\\\\\\\\\\\\\`\\\\\\\\\\\\\\\${BREW_API_URL}/?page=\\\\\\\\\\\\\\\${this.page}&per_page=\\\\\\\\\\\\\\\${itemsPerPage}\\\\\\\\\\\\\\\` + ) + ).json(); + } + + if (filters.sorter?.value?.direction === "desc") { + response = await ( + await fetch( + \\\\\\\\\\\\\\\`\\\\\\\\\\\\\\\${BREW_API_URL}/?page=\\\\\\\\\\\\\\\${ + totalPages - this.page + }&per_page=\\\\\\\\\\\\\\\${itemsPerPage}\\\\\\\\\\\\\\\` + ) + ).json(); + } + + if (!filters.sorter) { + response = await ( + await fetch( + \\\\\\\\\\\\\\\`\\\\\\\\\\\\\\\${BREW_API_URL}/?page=\\\\\\\\\\\\\\\${this.page}&per_page=\\\\\\\\\\\\\\\${itemsPerPage}\\\\\\\\\\\\\\\` + ) + ).json(); + } + return { + brewInfo: response?.map((result: IBrewInfo, i: number) => ({ + id: result.id, + abv: result.abv, + name: result.name, + tagline: result.tagline, + first_brewed: result.first_brewed, + description: result.description, + brewers_tips: result.brewers_tips, + })), + total: response?.length, + } as IBrewDatasourceResponse; + } + + private sortData(data: IBrewInfo[], filters: INovaFilters) { + return orderBy( + data, + filters.sorter?.value?.sortBy, + filters.sorter?.value?.direction as "desc" | "asc" + ); + } +} + +export const widgetConfig: IWidget = { + id: "tableWidgetId", + type: "table", + pizzagna: { + [PizzagnaLayer.Configuration]: { + header: { + properties: { + title: "Stupendous Suds", + subtitle: "Try These Brilliant Brews", + }, + }, + table: { + providers: { + [WellKnownProviders.DataSource]: { + providerId: BeerDataSource.providerId, + }, + }, + properties: { + configuration: { + columns: [ + { + id: "column1", + label: "Beer Name", + isActive: true, + width: 185, + formatter: { + componentType: + RawFormatterComponent.lateLoadKey, + properties: { + dataFieldIds: { + value: "name", + }, + }, + }, + }, + { + id: "column2", + label: "Tagline", + isActive: true, + width: 250, + formatter: { + componentType: + RawFormatterComponent.lateLoadKey, + properties: { + dataFieldIds: { + value: "tagline", + }, + }, + }, + }, + { + id: "column3", + label: "Alcohol By Volume", + isActive: true, + width: 150, + formatter: { + componentType: + CustomFormatterComponent.lateLoadKey, + properties: { + dataFieldIds: { + value: "abv", + }, + icon: "severity_error", + threshold: "5", + }, + }, + }, + { + id: "column4", + label: "Description", + isActive: true, + formatter: { + componentType: + RawFormatterComponent.lateLoadKey, + properties: { + dataFieldIds: { + value: "description", + }, + }, + }, + }, + ] as ITableWidgetColumnConfig[], + sorterConfiguration: { + descendantSorting: false, + sortBy: "", + } as ITableWidgetSorterConfig, + hasVirtualScroll: true, + }, + }, + }, + }, + }, +}; +\\\\\\\`, + "tutorials/customization/widget/custom-widget-docs.component.ts": \\\\\\\`// © 2022 SolarWinds Worldwide, LLC. All rights reserved. +// +// Permission is hereby granted, free of charge, to any person obtaining a copy +// of this software and associated documentation files (the "Software"), to +// deal in the Software without restriction, including without limitation the +// rights to use, copy, modify, merge, publish, distribute, sublicense, and/or +// sell copies of the Software, and to permit persons to whom the Software is +// furnished to do so, subject to the following conditions: +// +// The above copyright notice and this permission notice shall be included in +// all copies or substantial portions of the Software. +// +// THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR +// IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, +// FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE +// AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER +// LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, +// OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN +// THE SOFTWARE. + +import { Component } from "@angular/core"; + +@Component({ + selector: "custom-widget-docs", + templateUrl: "./custom-widget-docs.component.html", + standalone: false, +}) +export class CustomWidgetDocsComponent {} +\\\\\\\`, + "tutorials/customization/widget/custom-widget.component.ts": \\\\\\\`// © 2022 SolarWinds Worldwide, LLC. All rights reserved. +// +// Permission is hereby granted, free of charge, to any person obtaining a copy +// of this software and associated documentation files (the "Software"), to +// deal in the Software without restriction, including without limitation the +// rights to use, copy, modify, merge, publish, distribute, sublicense, and/or +// sell copies of the Software, and to permit persons to whom the Software is +// furnished to do so, subject to the following conditions: +// +// The above copyright notice and this permission notice shall be included in +// all copies or substantial portions of the Software. +// +// THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR +// IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, +// FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE +// AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER +// LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, +// OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN +// THE SOFTWARE. + +import { + ChangeDetectionStrategy, + ChangeDetectorRef, + Component, + EventEmitter, + HostBinding, + Input, + OnChanges, + OnInit, + Output, + SimpleChanges, +} from "@angular/core"; +import { FormBuilder, FormGroup, Validators } from "@angular/forms"; +import { GridsterConfig, GridsterItem } from "angular-gridster2"; + +import { IMenuItem } from "@nova-ui/bits"; +import { + ComponentRegistryService, + ConfiguratorHeadingService, + DEFAULT_PIZZAGNA_ROOT, + EVENT_PROXY, + FormStackComponent, + IConverterFormPartsProperties, + IDashboard, + IHasChangeDetector, + IHasForm, + IProviderConfiguration, + IWidget, + IWidgets, + IWidgetTypeDefinition, + NOVA_GENERIC_CONVERTER, + NOVA_TITLE_AND_DESCRIPTION_CONVERTER, + PizzagnaLayer, + refresher, + StackComponent, + TitleAndDescriptionConfigurationComponent, + WellKnownPathKey, + WellKnownProviders, + widgetBodyContentNodes, + WidgetConfiguratorSectionComponent, + WidgetTypesService, + WIDGET_BODY, + WIDGET_HEADER, + WIDGET_LOADING, +} from "@nova-ui/dashboards"; + +// The custom widget type name we'll use +const CUSTOM_WIDGET_TYPENAME = "example-custom-widget"; +// The path key we'll use for image selection in the configurator definition +const IMAGE_SELECTION_CONFIGURATOR_PATH_KEY = "imageSelection"; + +@Component({ + selector: "custom-widget-body", + // A simple template for our custom widget + template: \\\\\\\\\\\\\\\`\\\\\\\\\\\\\\\`, + styleUrls: ["./custom-widget.component.less"], + changeDetection: ChangeDetectionStrategy.OnPush, + standalone: false, +}) +// Remember to declare this class in the parent module +export class CustomWidgetBodyContentComponent implements IHasChangeDetector { + // Ensure that the lateLoadKey value matches class name + public static lateLoadKey = "CustomWidgetBodyContentComponent"; + + // Optionally, providing an input for styling of the host element + @Input() @HostBinding("class") public elementClass = ""; + + // We'll map this input with the configurator form using the NOVA_GENERIC_CONVERTER. + // See the customWidget definition at the bottom of the file. + @Input() public imageSource: string; + + // Injecting the ChangeDetectorRef to implement IHasChangeDetector. + // This allows the dashboard framework to reliably propagate component property changes to the DOM. + constructor(public changeDetector: ChangeDetectorRef) {} +} + +/** + * A custom configurator section component for selecting the image source for the custom widget + */ +@Component({ + selector: "custom-configurator-section", + template: \\\\\\\\\\\\\\\` + + + + +
+ + +
+ Image Selection +
+ {{ imageDisplayValue }} +
+
+
+
+ + + + + {{ item.title }} + + + +
+
+ \\\\\\\\\\\\\\\`, + styleUrls: ["./custom-widget.component.less"], + changeDetection: ChangeDetectionStrategy.OnPush, + standalone: false, +}) +// Remember to declare this class in the parent module +export class CustomConfiguratorSectionComponent + implements OnInit, OnChanges, IHasChangeDetector, IHasForm +{ + // Ensure that the lateLoadKey value matches the class name + public static lateLoadKey = "CustomConfiguratorSectionComponent"; + + /** + * This input serves as the itemsSource a user can select an image from. + */ + @Input() imageItems: IMenuItem[] = []; + /** + * This property holds the currently selected image source string. + */ + @Input() imageSource: string; + + /** + * An output for emitting formReady to allow the immediate parent formGroup component to register us as a form control + * in the larger form. In this case, the immediate parent would be the WidgetConfiguratorSectionComponent as specified + * in the customWidget configurator definition at the bottom of this file. + */ + @Output() formReady = new EventEmitter(); + + public form: FormGroup; + public imageDisplayValue: string; + + constructor( + public changeDetector: ChangeDetectorRef, + private formBuilder: FormBuilder, + public configuratorHeading: ConfiguratorHeadingService + ) {} + + public ngOnInit(): void { + // Initializing the form + this.form = this.formBuilder.group({ + // Note: When using the NOVA_GENERIC_CONVERTER, the form control name, in this case 'imageSource', must match the input name on + // this component as well as that of the corresponding property on the custom widget body component. + imageSource: [{}, [Validators.required]], + }); + + // Emitting the formReady as described above. + this.formReady.emit(this.form); + } + + public ngOnChanges(changes: SimpleChanges): void { + if (changes.imageSource && !changes.imageSource.isFirstChange()) { + const previousValue: string = changes.imageSource.previousValue; + if (previousValue !== this.imageSource) { + // Setting the display value according to the current imageSource value + this.imageDisplayValue = this.imageItems.find( + (item: IMenuItem) => item.url === this.imageSource + )?.title; + + // Updating the form when the imageSource input gets updated + this.form.get("imageSource")?.setValue(this.imageSource); + } + } + } + + public onChanged(newValue: string): void { + // Keeping the display value updated as the user changes the dropdown selection + this.imageDisplayValue = this.imageItems.find( + (item: IMenuItem) => item.url === newValue + )?.title; + } +} + +/** + * The component that instantiates the dashboard + */ +@Component({ + selector: "custom-widget", + templateUrl: "./custom-widget.component.html", + styleUrls: ["./custom-widget.component.less"], + standalone: false, +}) +export class CustomWidgetComponent implements OnInit { + // This variable will hold all the data needed to define the layout and behavior of the widgets. + // Pass this to the dashboard component's dashboard input in the template. + public dashboard: IDashboard | undefined; + + // Angular gridster requires a configuration object even if it's empty. + // Pass this to the dashboard component's gridsterConfig input in the template. + public gridsterConfig: GridsterConfig = {}; + + // Boolean which dashboard takes in as an input if its true it allows you to move widgets around. + public editMode: boolean = false; + + constructor( + // WidgetTypesService provides the widget's necessary structure information + private widgetTypesService: WidgetTypesService, + + // Inject the ComponentRegistryService to make our custom component available for late loading by the dashboards framework + private componentRegistry: ComponentRegistryService, + private changeDetectorRef: ChangeDetectorRef + ) {} + + public ngOnInit(): void { + // Register the custom widget type and custom components + // Note: This could also be done in the parent module's constructor so that + // multiple dashboards could have access to the same registrations. + this.prepareNovaDashboards(); + + // Register some image items as dropdown options in the widget editor/configurator + // Note: This could also be done in the parent module's constructor so that + // multiple dashboards could have access to the same dropdown options. + this.registerImageOptions(); + + // Initialize our current instance of a dashboard with an instance of our custom widget + this.initializeDashboard(); + } + + /** Used for restoring widgets state */ + public reInitializeDashboard(): void { + // destroys the components and their providers so the dashboard can re init data + this.dashboard = undefined; + this.changeDetectorRef.detectChanges(); + + this.initializeDashboard(); + } + + public initializeDashboard(): void { + // We're using a static configuration object for this example (see widgetConfig at the bottom of the file), + // but this is where the widget's configuration could potentially be populated from a database + const widget = widgetConfig; + + // Create an index of widgets complete with structure and configuration to assign to the dashboard + const widgets: IWidgets = { + // Complete the custom widget with structure information coming from its type definition + [widget.id]: this.widgetTypesService.mergeWithWidgetType(widget), + }; + + // Setting the widget dimensions and position (this is for gridster) + const positions: Record = { + [widget.id]: { + cols: 4, + rows: 11, + y: 0, + x: 0, + }, + }; + + // Finally, assigning the variables we created above to the dashboard + this.dashboard = { positions, widgets }; + } + + private prepareNovaDashboards() { + // Register the custom widget type + this.widgetTypesService.registerWidgetType( + CUSTOM_WIDGET_TYPENAME, + 1, + customWidget + ); + + // Register the custom widget body component with the component registry to make it available + // for late loading by the dashboard framework. + this.componentRegistry.registerByLateLoadKey( + CustomWidgetBodyContentComponent + ); + + // Register the custom configurator section with the component registry to make it available + // for late loading by the dashboard framework. + this.componentRegistry.registerByLateLoadKey( + CustomConfiguratorSectionComponent + ); + } + + private registerImageOptions() { + // Grab the widget's default template which will be needed as a parameter for setNode below. + const widgetTemplate = this.widgetTypesService.getWidgetType( + CUSTOM_WIDGET_TYPENAME, + 1 + ); + + // Register some image items as dropdown options in the widget editor/configurator + this.widgetTypesService.setNode( + // This is the template we grabbed above with getWidgetType + widgetTemplate, + // We are setting the editor/configurator part of the widget template + "configurator", + // This indicates which node you are changing and we want to change the image items available for selection in the editor. + // For reference, see the 'paths' property of the custom widget's IWidgetTypeDefinition at the bottom of this file. + IMAGE_SELECTION_CONFIGURATOR_PATH_KEY, + // We are setting the image items available for selection in the editor. 'imageItems' is defined + // at the bottom of this file. + imageItems + ); + } +} + +/*************************************************************************************************** + * This is the type definition of our custom widget + ***************************************************************************************************/ +const customWidget: IWidgetTypeDefinition = { + /*************************************************************************************************** + * Paths to important settings in this type definition + ***************************************************************************************************/ + paths: { + widget: { + [WellKnownPathKey.Root]: DEFAULT_PIZZAGNA_ROOT, + }, + configurator: { + [WellKnownPathKey.Root]: DEFAULT_PIZZAGNA_ROOT, + // for the custom configuration component, this is the path for the list of image items available for selection + [IMAGE_SELECTION_CONFIGURATOR_PATH_KEY]: + "imageSelection.properties.imageItems", + }, + }, + /*************************************************************************************************** + * Widget section describes the structural part of the custom widget + ***************************************************************************************************/ + widget: { + [PizzagnaLayer.Structure]: { + [DEFAULT_PIZZAGNA_ROOT]: { + id: DEFAULT_PIZZAGNA_ROOT, + // base layout of the widget - all components referenced herein will be stacked in a column + componentType: StackComponent.lateLoadKey, + providers: { + // When enabled, this provider emits the REFRESH event on the pizzagna event bus every X seconds + [WellKnownProviders.Refresher]: refresher(), + // event proxy manages the transmission of events between widget and dashboard such as the WIDGET_EDIT and WIDGET_REMOVE events + [WellKnownProviders.EventProxy]: EVENT_PROXY, + }, + properties: { + // these values reference child components in the widget structure defined below + nodes: ["header", "loading", "body"], + }, + }, + // standard widget header + header: WIDGET_HEADER, + // this is the loading bar below the header + loading: WIDGET_LOADING, + // the body node + body: WIDGET_BODY, + + // retrieving the definitions for the body content nodes. the argument corresponds to the main content node key + ...widgetBodyContentNodes("mainContent"), + + // the component that supplies the content of our custom widget + mainContent: { + id: "mainContent", + componentType: CustomWidgetBodyContentComponent.lateLoadKey, + properties: { + elementClass: "d-flex w-100 justify-content-center", + }, + }, + }, + [PizzagnaLayer.Configuration]: { + [DEFAULT_PIZZAGNA_ROOT]: { + id: DEFAULT_PIZZAGNA_ROOT, + providers: { + // default refresher configuration + [WellKnownProviders.Refresher]: refresher(false, 60), + }, + }, + // default header configuration + header: { + properties: { + title: $localize\\\\\\\\\\\\\\\`Empty Custom Widget\\\\\\\\\\\\\\\`, + }, + }, + }, + }, + /*************************************************************************************************** + * Configurator section describes the form that's used to configure the widget + ***************************************************************************************************/ + configurator: { + [PizzagnaLayer.Structure]: { + [DEFAULT_PIZZAGNA_ROOT]: { + id: DEFAULT_PIZZAGNA_ROOT, + // base layout of the configurator - all form components referenced herein will be stacked in a column + componentType: FormStackComponent.lateLoadKey, + properties: { + elementClass: + "flex-grow-1 overflow-auto nui-scroll-shadows", + // these values reference child components laid out in this form (defined below) + nodes: ["presentation", "customConfig"], + }, + }, + // /presentation + presentation: { + id: "presentation", + componentType: WidgetConfiguratorSectionComponent.lateLoadKey, + properties: { + headerText: $localize\\\\\\\\\\\\\\\`Presentation\\\\\\\\\\\\\\\`, + nodes: ["titleAndDescription"], + }, + }, + // /presentation/titleAndDescription + titleAndDescription: { + id: "titleAndDescription", + componentType: + TitleAndDescriptionConfigurationComponent.lateLoadKey, + providers: { + converter: { + providerId: NOVA_TITLE_AND_DESCRIPTION_CONVERTER, + } as IProviderConfiguration, + }, + }, + // /customConfig + customConfig: { + id: "customConfig", + componentType: WidgetConfiguratorSectionComponent.lateLoadKey, + properties: { + headerText: $localize\\\\\\\\\\\\\\\`Custom Widget Configuration\\\\\\\\\\\\\\\`, + nodes: ["imageSelection"], + }, + }, + // /customConfig/imageSelection + imageSelection: { + id: "imageSelection", + // Here's where we set the configurator to use our custom configurator section + componentType: CustomConfiguratorSectionComponent.lateLoadKey, + properties: { + // This corresponds to the 'imageItems' input on the custom configurator section component + // which defines the list of image items to pick from. The empty value shown here is overridden + // in the 'registerImageOptions' method above. + imageItems: [] as IMenuItem[], + }, + providers: { + // Using the generic converter to map the selected image source between the widget and the form + [WellKnownProviders.Converter]: { + providerId: NOVA_GENERIC_CONVERTER, + properties: { + formParts: [ + { + // Setting up the generic converter to update the 'imageSource' property of the custom widget 'mainContent' component + previewPath: "mainContent.properties", + // Note: To use the NOVA_GENERIC_CONVERTER, the linked properties must have the same name between the configurator + // section component and the widget 'mainContent' component. Additionally, the property name must match the formControl + // name used in the configurator section component. In this case, the common name among all three is 'imageSource'. + keys: ["imageSource"], + }, + ] as IConverterFormPartsProperties[], + }, + } as IProviderConfiguration, + }, + }, + }, + }, +}; + +// For this example, we're using static items for the image selection dropdown. In a more realistic scenario, +// the items available for selection might come from a backend database. +const imageItems = [ + { + title: "Harry Potter Book Cover", + url: "https://imgc.allpostersimages.com/img/print/u-g-F8PQ9I0.jpg?w=550&h=550&p=0", + }, + { + title: "Harry Potter Movie Poster", + url: "https://images-na.ssl-images-amazon.com/images/I/81gpmMdKOHL._AC_SY741_.jpg", + }, +] as IMenuItem[]; + +// We're using a static configuration object for this example. In a more realistic scenario, +// a widget's configuration would likely be stored in a database. +const widgetConfig: IWidget = { + id: "widget1", + // This custom type is registered in the 'prepareNovaDashboards' method above. + type: CUSTOM_WIDGET_TYPENAME, + pizzagna: { + [PizzagnaLayer.Configuration]: { + header: { + // Setting the initial property values for the WidgetHeaderComponent + properties: { + title: "Harry Potter and the Sorcerer's Stone", + subtitle: "By J. K. Rowling", + }, + }, + mainContent: { + properties: { + // Setting the initial value for the 'imageSource' property on our custom widget body + imageSource: imageItems[0].url, + }, + }, + }, + }, +}; +\\\\\\\`, + "tutorials/customization/widget/custom-widget.module.ts": \\\\\\\`// © 2022 SolarWinds Worldwide, LLC. All rights reserved. +// +// Permission is hereby granted, free of charge, to any person obtaining a copy +// of this software and associated documentation files (the "Software"), to +// deal in the Software without restriction, including without limitation the +// rights to use, copy, modify, merge, publish, distribute, sublicense, and/or +// sell copies of the Software, and to permit persons to whom the Software is +// furnished to do so, subject to the following conditions: +// +// The above copyright notice and this permission notice shall be included in +// all copies or substantial portions of the Software. +// +// THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR +// IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, +// FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE +// AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER +// LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, +// OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN +// THE SOFTWARE. + +import { CommonModule } from "@angular/common"; +import { HttpClientModule } from "@angular/common/http"; +import { NgModule } from "@angular/core"; +import { ReactiveFormsModule } from "@angular/forms"; +import { RouterModule } from "@angular/router"; + +import { + NuiButtonModule, + NuiDocsModule, + NuiFormFieldModule, + NuiIconModule, + NuiImageModule, + NuiMessageModule, + NuiSelectV2Module, + NuiSwitchModule, + DEMO_PATH_TOKEN, +} from "@nova-ui/bits"; +import { + NuiDashboardConfiguratorModule, + NuiDashboardsModule, +} from "@nova-ui/dashboards"; + +import { CustomWidgetDocsComponent } from "./custom-widget-docs.component"; +import { + CustomConfiguratorSectionComponent, + CustomWidgetBodyContentComponent, + CustomWidgetComponent, +} from "./custom-widget.component"; +import { getDemoFiles } from "../../../../../demo-files-factory"; + +const routes = [ + { + path: "", + component: CustomWidgetDocsComponent, + data: { + srlc: { + hideIndicator: true, + }, + showThemeSwitcher: true, + }, + }, + { + path: "example", + component: CustomWidgetComponent, + data: { + srlc: { + hideIndicator: true, + }, + }, + }, +]; + +@NgModule({ + imports: [ + CommonModule, + ReactiveFormsModule, + HttpClientModule, + NuiDashboardsModule, + NuiDashboardConfiguratorModule, + NuiDocsModule, + NuiFormFieldModule, + NuiIconModule, + NuiImageModule, + NuiMessageModule, + NuiSelectV2Module, + NuiSwitchModule, + NuiButtonModule, + RouterModule.forChild(routes), + ], + declarations: [ + CustomWidgetDocsComponent, + CustomConfiguratorSectionComponent, + CustomWidgetBodyContentComponent, + CustomWidgetComponent, + ], + providers: [ + { + provide: DEMO_PATH_TOKEN, + useValue: getDemoFiles("widget"), + }, + ], +}) +export default class CustomWidgetModule {} +\\\\\\\`, + "tutorials/data-source-setup/data-source-setup-docs.component.ts": \\\\\\\`// © 2022 SolarWinds Worldwide, LLC. All rights reserved. +// +// Permission is hereby granted, free of charge, to any person obtaining a copy +// of this software and associated documentation files (the "Software"), to +// deal in the Software without restriction, including without limitation the +// rights to use, copy, modify, merge, publish, distribute, sublicense, and/or +// sell copies of the Software, and to permit persons to whom the Software is +// furnished to do so, subject to the following conditions: +// +// The above copyright notice and this permission notice shall be included in +// all copies or substantial portions of the Software. +// +// THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR +// IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, +// FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE +// AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER +// LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, +// OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN +// THE SOFTWARE. + +import { Component } from "@angular/core"; + +@Component({ + selector: "nui-dashboard-data-source-docs", + templateUrl: "./data-source-setup-docs.component.html", + standalone: false, +}) +export class DataSourceDocsComponent {} +\\\\\\\`, + "tutorials/data-source-setup/data-source-setup.component.ts": \\\\\\\`// © 2022 SolarWinds Worldwide, LLC. All rights reserved. +// +// Permission is hereby granted, free of charge, to any person obtaining a copy +// of this software and associated documentation files (the "Software"), to +// deal in the Software without restriction, including without limitation the +// rights to use, copy, modify, merge, publish, distribute, sublicense, and/or +// sell copies of the Software, and to permit persons to whom the Software is +// furnished to do so, subject to the following conditions: +// +// The above copyright notice and this permission notice shall be included in +// all copies or substantial portions of the Software. +// +// THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR +// IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, +// FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE +// AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER +// LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, +// OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN +// THE SOFTWARE. + +import { HttpClient, HttpErrorResponse } from "@angular/common/http"; +import { Component, Injectable, OnDestroy, OnInit } from "@angular/core"; +import { GridsterConfig, GridsterItem } from "angular-gridster2"; +import { BehaviorSubject } from "rxjs"; +import { finalize } from "rxjs/operators"; + +import { DataSourceService, IFilteringOutputs } from "@nova-ui/bits"; +import { + DATA_SOURCE, + DEFAULT_PIZZAGNA_ROOT, + IDashboard, + IKpiData, + IProviderConfiguration, + IRefresherProperties, + IWidget, + IWidgets, + KpiComponent, + NOVA_KPI_DATASOURCE_ADAPTER, + PizzagnaLayer, + ProviderRegistryService, + WellKnownProviders, + WidgetTypesService, +} from "@nova-ui/dashboards"; + +/** + * A simple KPI data source to retrieve the average rating of Harry Potter and the Sorcerer's Stone (book) via googleapis + */ +@Injectable() +export class AverageRatingKpiDataSource + extends DataSourceService + implements OnDestroy +{ + // This is the ID we'll use to identify the provider + public static providerId = "AverageRatingKpiDataSource"; + + // Use this subject to communicate the data source's busy state + public busy = new BehaviorSubject(false); + + constructor(private http: HttpClient) { + super(); + } + + // In this example, getFilteredData is invoked every 10 minutes (Take a look at the refresher + // provider definition in the widget configuration below to see how the interval is set) + public async getFilteredData(): Promise { + this.busy.next(true); + return new Promise((resolve) => { + // *** Make a resource request to an external API (if needed) + this.http + .get("https://www.googleapis.com/books/v1/volumes/5MQFrgEACAAJ") + .pipe(finalize(() => this.busy.next(false))) + .subscribe({ + next: (data: any) => { + resolve({ + result: { + value: data.volumeInfo.averageRating, + }, + }); + }, + error: (error: HttpErrorResponse) => { + resolve({ + result: null, + error: { + type: error.status, + }, + }); + }, + }); + }); + } + + public ngOnDestroy(): void { + this.outputsSubject.complete(); + } +} + +/** + * A component that instantiates the dashboard + */ +@Component({ + selector: "data-source-setup", + templateUrl: "./data-source-setup.component.html", + styleUrls: ["./data-source-setup.component.less"], + standalone: false, +}) +export class DataSourceSetupComponent implements OnInit { + // This variable will hold all the data needed to define the layout and behavior of the widgets. + // Pass this to the dashboard component's dashboard input in the template. + public dashboard: IDashboard; + + // Angular gridster requires a configuration object even if it's empty. + // Pass this to the dashboard component's gridsterConfig input in the template. + public gridsterConfig: GridsterConfig = {}; + + constructor( + // WidgetTypesService provides the widget's necessary structure information + private widgetTypesService: WidgetTypesService, + + // In general, the ProviderRegistryService is used for making entities available for injection into dynamically loaded components. + private providerRegistry: ProviderRegistryService + ) {} + + public ngOnInit(): void { + // Registering the data source for injection into the KPI tile. + // Note: Each tile of a KPI widget is assigned its own instance of the data source + this.providerRegistry.setProviders({ + [AverageRatingKpiDataSource.providerId]: { + provide: DATA_SOURCE, + useClass: AverageRatingKpiDataSource, + // Any dependencies that need to be injected into the provider must be listed here + deps: [HttpClient], + }, + }); + // We're using a static configuration object for this example, but this is where + // the widget's configuration could potentially be populated from a database + const kpiWidget = widgetConfig; + const widgetIndex: IWidgets = { + // Complete the KPI widget with information coming from its type definition + [kpiWidget.id]: + this.widgetTypesService.mergeWithWidgetType(kpiWidget), + }; + + // Setting the widget dimensions and position (this is for gridster) + const positions: Record = { + [kpiWidget.id]: { + cols: 4, + rows: 6, + y: 0, + x: 0, + }, + }; + + // Finally, assigning the variables we created above to the dashboard + this.dashboard = { + positions, + widgets: widgetIndex, + }; + } +} + +const widgetConfig: IWidget = { + id: "widget1", + type: "kpi", + pizzagna: { + [PizzagnaLayer.Configuration]: { + [DEFAULT_PIZZAGNA_ROOT]: { + providers: { + [WellKnownProviders.Refresher]: { + properties: { + // Configuring the refresher interval so that our data source is invoked every ten minutes + interval: 60 * 10, + enabled: true, + } as IRefresherProperties, + } as Partial, + }, + }, + header: { + properties: { + title: "Harry Potter and the Sorcerer's Stone", + subtitle: "By J. K. Rowling", + }, + }, + tiles: { + properties: { + nodes: ["kpi1"], + }, + }, + kpi1: { + id: "kpi1", + componentType: KpiComponent.lateLoadKey, + properties: { + widgetData: { + units: "out of 5 Stars", + label: "Average Rating", + }, + }, + providers: { + [WellKnownProviders.DataSource]: { + // Setting the data source providerId for the tile with id "kpi1" + providerId: AverageRatingKpiDataSource.providerId, + } as IProviderConfiguration, + [WellKnownProviders.Adapter]: { + providerId: NOVA_KPI_DATASOURCE_ADAPTER, + properties: { + componentId: "kpi1", + propertyPath: "widgetData", + }, + } as IProviderConfiguration, + }, + }, + }, + }, +}; +\\\\\\\`, + "tutorials/data-source-setup/data-source-setup.module.ts": \\\\\\\`// © 2022 SolarWinds Worldwide, LLC. All rights reserved. +// +// Permission is hereby granted, free of charge, to any person obtaining a copy +// of this software and associated documentation files (the "Software"), to +// deal in the Software without restriction, including without limitation the +// rights to use, copy, modify, merge, publish, distribute, sublicense, and/or +// sell copies of the Software, and to permit persons to whom the Software is +// furnished to do so, subject to the following conditions: +// +// The above copyright notice and this permission notice shall be included in +// all copies or substantial portions of the Software. +// +// THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR +// IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, +// FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE +// AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER +// LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, +// OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN +// THE SOFTWARE. + +import { CommonModule } from "@angular/common"; +import { HttpClientModule } from "@angular/common/http"; +import { NgModule } from "@angular/core"; +import { RouterModule } from "@angular/router"; + +import { + NuiDocsModule, + NuiMessageModule, + DEMO_PATH_TOKEN, +} from "@nova-ui/bits"; +import { NuiDashboardsModule } from "@nova-ui/dashboards"; + +import { DataSourceDocsComponent } from "./data-source-setup-docs.component"; +import { DataSourceSetupComponent } from "./data-source-setup.component"; +import { getDemoFiles } from "../../../../demo-files-factory"; + +const routes = [ + { + path: "", + component: DataSourceDocsComponent, + data: { + srlc: { + hideIndicator: true, + }, + showThemeSwitcher: true, + }, + }, + { + path: "example", + component: DataSourceSetupComponent, + data: { + srlc: { + hideIndicator: true, + }, + }, + }, +]; + +@NgModule({ + imports: [ + CommonModule, + HttpClientModule, + NuiDashboardsModule, + NuiDocsModule, + NuiMessageModule, + RouterModule.forChild(routes), + ], + declarations: [DataSourceDocsComponent, DataSourceSetupComponent], + providers: [ + { + provide: DEMO_PATH_TOKEN, + useValue: getDemoFiles("data-source-setup"), + }, + ], +}) +export default class DataSourceSetupModule {} +\\\\\\\`, + "tutorials/dynamic-header-links/dynamic-header-links-docs.component.ts": \\\\\\\`// © 2022 SolarWinds Worldwide, LLC. All rights reserved. +// +// Permission is hereby granted, free of charge, to any person obtaining a copy +// of this software and associated documentation files (the "Software"), to +// deal in the Software without restriction, including without limitation the +// rights to use, copy, modify, merge, publish, distribute, sublicense, and/or +// sell copies of the Software, and to permit persons to whom the Software is +// furnished to do so, subject to the following conditions: +// +// The above copyright notice and this permission notice shall be included in +// all copies or substantial portions of the Software. +// +// THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR +// IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, +// FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE +// AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER +// LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, +// OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN +// THE SOFTWARE. + +import { Component } from "@angular/core"; + +@Component({ + selector: "nui-dynamic-header-links-docs", + templateUrl: "./dynamic-header-links-docs.component.html", + standalone: false, +}) +export class DynamicHeaderLinksDocsComponent {} +\\\\\\\`, + "tutorials/dynamic-header-links/dynamic-header-links-docs.module.ts": \\\\\\\`// © 2022 SolarWinds Worldwide, LLC. All rights reserved. +// +// Permission is hereby granted, free of charge, to any person obtaining a copy +// of this software and associated documentation files (the "Software"), to +// deal in the Software without restriction, including without limitation the +// rights to use, copy, modify, merge, publish, distribute, sublicense, and/or +// sell copies of the Software, and to permit persons to whom the Software is +// furnished to do so, subject to the following conditions: +// +// The above copyright notice and this permission notice shall be included in +// all copies or substantial portions of the Software. +// +// THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR +// IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, +// FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE +// AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER +// LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, +// OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN +// THE SOFTWARE. + +import { NgModule } from "@angular/core"; +import { RouterModule } from "@angular/router"; + +import { DynamicHeaderLinksDocsComponent } from "./dynamic-header-links-docs.component"; + +const routes = [ + { + path: "", + component: DynamicHeaderLinksDocsComponent, + data: { + srlc: { + hideIndicator: true, + }, + showThemeSwitcher: true, + }, + }, +]; + +@NgModule({ + imports: [RouterModule.forChild(routes)], + declarations: [DynamicHeaderLinksDocsComponent], +}) +export default class DynamicHeaderLinksDocsModule {} +\\\\\\\`, + "tutorials/hello-dashboards/hello-dashboards-docs.component.ts": \\\\\\\`// © 2022 SolarWinds Worldwide, LLC. All rights reserved. +// +// Permission is hereby granted, free of charge, to any person obtaining a copy +// of this software and associated documentation files (the "Software"), to +// deal in the Software without restriction, including without limitation the +// rights to use, copy, modify, merge, publish, distribute, sublicense, and/or +// sell copies of the Software, and to permit persons to whom the Software is +// furnished to do so, subject to the following conditions: +// +// The above copyright notice and this permission notice shall be included in +// all copies or substantial portions of the Software. +// +// THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR +// IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, +// FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE +// AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER +// LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, +// OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN +// THE SOFTWARE. + +import { Component } from "@angular/core"; + +@Component({ + selector: "nui-dashboard-hello-dashboards-docs", + templateUrl: "./hello-dashboards-docs.component.html", + standalone: false, +}) +export class HelloDashboardsDocsComponent {} +\\\\\\\`, + "tutorials/hello-dashboards/hello-dashboards-example/hello-dashboards-example.component.ts": \\\\\\\`// © 2022 SolarWinds Worldwide, LLC. All rights reserved. +// +// Permission is hereby granted, free of charge, to any person obtaining a copy +// of this software and associated documentation files (the "Software"), to +// deal in the Software without restriction, including without limitation the +// rights to use, copy, modify, merge, publish, distribute, sublicense, and/or +// sell copies of the Software, and to permit persons to whom the Software is +// furnished to do so, subject to the following conditions: +// +// The above copyright notice and this permission notice shall be included in +// all copies or substantial portions of the Software. +// +// THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR +// IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, +// FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE +// AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER +// LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, +// OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN +// THE SOFTWARE. + +import { Component, OnInit } from "@angular/core"; +import { GridsterConfig, GridsterItem } from "angular-gridster2"; + +import { + IDashboard, + IWidget, + IWidgets, + KpiComponent, + PizzagnaLayer, + WidgetTypesService, +} from "@nova-ui/dashboards"; + +/** + * A component that instantiates the dashboard + */ +@Component({ + selector: "hello-dashboards-example", + templateUrl: "./hello-dashboards-example.component.html", + styleUrls: ["./hello-dashboards-example.component.less"], + standalone: false, +}) +export class HelloDashboardsExampleComponent implements OnInit { + // This variable will have all the data needed to render the widgets widgets. + // Pass this to the dashboard component's dashboard input. + public dashboard: IDashboard; + // Angular gridster requires a configuration object even if its empty. + // Pass this to the dashboard component's gridsterConfig input. + public gridsterConfig: GridsterConfig = {}; + + // WidgetTypesService provides the widget's necessary structure information + constructor(private widgetTypesService: WidgetTypesService) {} + + public ngOnInit(): void { + // Here we are hard-coding the widget config for this example, but this is where you + // could potentially populate the widget's configuration from a database + const kpiWidget = widgetConfig; + const widgetIndex: IWidgets = { + // Complete the KPI widget with information coming from its type definition + [kpiWidget.id]: + this.widgetTypesService.mergeWithWidgetType(kpiWidget), + }; + // Setting widget position and dimensions (this is for gridster) + const positions: Record = { + [kpiWidget.id]: { + cols: 4, + rows: 6, + y: 0, + x: 0, + }, + }; + // Finally, assigning the variables we created above to the dashboard + this.dashboard = { + positions, + widgets: widgetIndex, + }; + } +} + +// In a real-world scenario, this configuration would typically be fetched from a database or at least live in another file +const widgetConfig: IWidget = { + id: "widget1", + type: "kpi", + pizzagna: { + [PizzagnaLayer.Configuration]: { + header: { + properties: { + title: "Hello, KPI Widget!", + subtitle: "A Venue for Meaningful Values", + }, + }, + tiles: { + properties: { + nodes: ["kpi1"], + }, + }, + kpi1: { + id: "kpi1", + componentType: KpiComponent.lateLoadKey, + properties: { + widgetData: { + id: "totalStorage", + value: 1, + label: "Total storage", + units: "TB", + }, + }, + }, + }, + }, +}; +\\\\\\\`, + "tutorials/hello-dashboards/hello-dashboards.module.ts": \\\\\\\`// © 2022 SolarWinds Worldwide, LLC. All rights reserved. +// +// Permission is hereby granted, free of charge, to any person obtaining a copy +// of this software and associated documentation files (the "Software"), to +// deal in the Software without restriction, including without limitation the +// rights to use, copy, modify, merge, publish, distribute, sublicense, and/or +// sell copies of the Software, and to permit persons to whom the Software is +// furnished to do so, subject to the following conditions: +// +// The above copyright notice and this permission notice shall be included in +// all copies or substantial portions of the Software. +// +// THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR +// IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, +// FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE +// AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER +// LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, +// OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN +// THE SOFTWARE. + +import { CommonModule } from "@angular/common"; +import { NgModule } from "@angular/core"; +import { RouterModule } from "@angular/router"; + +import { + NuiDocsModule, + NuiMessageModule, + DEMO_PATH_TOKEN, +} from "@nova-ui/bits"; +import { NuiDashboardsModule } from "@nova-ui/dashboards"; + +import { HelloDashboardsDocsComponent } from "./hello-dashboards-docs.component"; +import { HelloDashboardsExampleComponent } from "./hello-dashboards-example/hello-dashboards-example.component"; +import { getDemoFiles } from "../../../../demo-files-factory"; + +const routes = [ + { + path: "", + component: HelloDashboardsDocsComponent, + data: { + srlc: { + hideIndicator: true, + }, + showThemeSwitcher: true, + }, + }, + { + path: "example", + component: HelloDashboardsExampleComponent, + data: { + srlc: { + hideIndicator: true, + }, + }, + }, +]; + +@NgModule({ + imports: [ + CommonModule, + NuiDashboardsModule, + NuiDocsModule, + NuiMessageModule, + RouterModule.forChild(routes), + ], + declarations: [ + HelloDashboardsDocsComponent, + HelloDashboardsExampleComponent, + ], + providers: [ + { + provide: DEMO_PATH_TOKEN, + useValue: getDemoFiles("hello-dashboards"), + }, + ], +}) +export default class HelloDashboardsModule {} +\\\\\\\`, + "tutorials/persistence-handler-setup/persistence-handler-setup-docs.component.ts": \\\\\\\`// © 2022 SolarWinds Worldwide, LLC. All rights reserved. +// +// Permission is hereby granted, free of charge, to any person obtaining a copy +// of this software and associated documentation files (the "Software"), to +// deal in the Software without restriction, including without limitation the +// rights to use, copy, modify, merge, publish, distribute, sublicense, and/or +// sell copies of the Software, and to permit persons to whom the Software is +// furnished to do so, subject to the following conditions: +// +// The above copyright notice and this permission notice shall be included in +// all copies or substantial portions of the Software. +// +// THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR +// IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, +// FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE +// AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER +// LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, +// OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN +// THE SOFTWARE. + +import { Component } from "@angular/core"; + +@Component({ + selector: "nui-dashboard-persistence-handler-setup-docs", + templateUrl: "./persistence-handler-setup-docs.component.html", + standalone: false, +}) +export class PersistenceHandlerSetupDocsComponent {} +\\\\\\\`, + "tutorials/persistence-handler-setup/persistence-handler-setup.component.ts": \\\\\\\`// © 2022 SolarWinds Worldwide, LLC. All rights reserved. +// +// Permission is hereby granted, free of charge, to any person obtaining a copy +// of this software and associated documentation files (the "Software"), to +// deal in the Software without restriction, including without limitation the +// rights to use, copy, modify, merge, publish, distribute, sublicense, and/or +// sell copies of the Software, and to permit persons to whom the Software is +// furnished to do so, subject to the following conditions: +// +// The above copyright notice and this permission notice shall be included in +// all copies or substantial portions of the Software. +// +// THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR +// IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, +// FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE +// AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER +// LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, +// OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN +// THE SOFTWARE. + +import { HttpClient, HttpErrorResponse } from "@angular/common/http"; +import { + ChangeDetectorRef, + Component, + Injectable, + OnDestroy, + OnInit, +} from "@angular/core"; +import { GridsterConfig, GridsterItem } from "angular-gridster2"; +import { BehaviorSubject, Observable, Subject } from "rxjs"; +import { finalize } from "rxjs/operators"; + +import { + DataSourceService, + IFilteringOutputs, + ToastService, + uuid, +} from "@nova-ui/bits"; +import { + DATA_SOURCE, + DEFAULT_PIZZAGNA_ROOT, + IDashboard, + IDashboardPersistenceHandler, + IKpiData, + IProviderConfiguration, + IRefresherProperties, + IWidget, + IWidgets, + KpiComponent, + NOVA_KPI_DATASOURCE_ADAPTER, + PizzagnaLayer, + ProviderRegistryService, + WellKnownPathKey, + WellKnownProviders, + WidgetTypesService, +} from "@nova-ui/dashboards"; + +/** + * A simple persistence handler that is tied to the widget editor directive + */ +@Injectable() +// The realizer of IDashboardPersistenceHandler may implement a trySubmit and/or a tryRemove method. +export class PersistenceHandler implements IDashboardPersistenceHandler { + // This variable is just to show how to handle error handling. + private persistenceSucceeded: boolean = true; + + // The example uses the toast service to demonstrate the + // invocation of each of the persistence handler callbacks + constructor(private toastService: ToastService) { + // toastService options to let it sit on the page for 2 seconds. + this.toastService.setConfig({ + timeOut: 2000, + }); + } + + // This method will be invoked anytime the widget editor form gets submitted. + public trySubmit = (widget: IWidget): Observable => { + // Since we are working asynchronously, we'll return a subject. So, after the submit attempt + // succeeds or fails, we can let the subscriber know the result. + const subject = new Subject(); + + if (!widget.id) { + // Creates an id if the widget has no id. + // (This step will make more sense in the context of the widget cloning tutorial + // in which we handle the persistence of a newly created widget.) + widget.id = uuid(); + } + + // For this example, we're using a setTimeout to mock an asynchronous persistence request to a backend + setTimeout(() => { + if (this.persistenceSucceeded) { + // Passes along the new widget after one second. + subject.next(widget); + // Toast on the page on success. + this.toastService.success({ + title: $localize\\\\\\\\\\\\\\\`Submit succeeded.\\\\\\\\\\\\\\\`, + }); + } else { + const errorText = $localize\\\\\\\\\\\\\\\`Submit failed.\\\\\\\\\\\\\\\`; + // Toast on the page on failure. + this.toastService.error({ title: errorText }); + // Makes the subject say there is an error. + subject.error(errorText); + } + // Completes the subject so whoever subscribes to it knows its finished. + subject.complete(); + }, 1000); + + // Returns the subject as an observable. + return subject.asObservable(); + }; + + // This method will be invoked anytime there's a widget removal attempt. + public tryRemove = (widgetId: string): Observable => { + const subject = new Subject(); + + setTimeout(() => { + if (this.persistenceSucceeded) { + // Pass through the id of the widget that was removed. + subject.next(widgetId); + this.toastService.success({ + title: $localize\\\\\\\\\\\\\\\`Removal succeeded.\\\\\\\\\\\\\\\`, + }); + } else { + const errorText = $localize\\\\\\\\\\\\\\\`Removal failed.\\\\\\\\\\\\\\\`; + this.toastService.error({ title: errorText }); + subject.error(errorText); + } + subject.complete(); + }, 1000); + + return subject.asObservable(); + }; +} + +/** + * A component that instantiates the dashboard + */ +@Component({ + selector: "persistence-handler-setup", + templateUrl: "./persistence-handler-setup.component.html", + styleUrls: ["./persistence-handler-setup.component.less"], + // Here we provide our persistence handler at the component level; this can also be done in the module. + providers: [PersistenceHandler], + standalone: false, +}) +export class PersistenceHandlerSetupComponent implements OnInit { + // This variable will hold all the data needed to define the layout and behavior of the widgets. + // Pass this to the dashboard component's dashboard input in the template. + public dashboard: IDashboard | undefined; + + // Angular gridster requires a configuration object even if it's empty. + // Pass this to the dashboard component's gridsterConfig input in the template. + public gridsterConfig: GridsterConfig = {}; + + // Boolean which dashboard takes in as an input if its true it allows you to move widgets around. + public editMode: boolean = false; + + constructor( + // WidgetTypesService provides the widget's necessary structure information + private widgetTypesService: WidgetTypesService, + + // In general, the ProviderRegistryService is used for making entities available for injection into dynamically loaded components. + private providerRegistry: ProviderRegistryService, + + // We are injecting the PersistenceHandler we created and assigning it to a property we use in the template. + public persistenceHandler: PersistenceHandler, + private changeDetectorRef: ChangeDetectorRef + ) {} + + public ngOnInit(): void { + // Grabbing the widget's default template which will be needed as a parameter for setNode + const widgetTemplate = this.widgetTypesService.getWidgetType("kpi", 1); + // Registering our data sources as dropdown options in the widget editor/configurator + // Note: This could also be done in the parent module's constructor so that + // multiple dashboards could have access to the same widget template modification. + this.widgetTypesService.setNode( + // This is the template we grabbed above with getWidgetType + widgetTemplate, + // We are setting the editor/configurator part of the widget template + "configurator", + // This indicates which node you are changing and we want to change + // the data source providers available for selection in the editor. + WellKnownPathKey.DataSourceProviders, + // We are setting the data sources available for selection in the editor + [ + AverageRatingKpiDataSource.providerId, + RatingsCountKpiDataSource.providerId, + ] + ); + + // Registering the data sources available for injection into the KPI tiles. + // Note: Each tile of a KPI widget is assigned its own instance of a data source + this.providerRegistry.setProviders({ + [AverageRatingKpiDataSource.providerId]: { + provide: DATA_SOURCE, + useClass: AverageRatingKpiDataSource, + // Any dependencies that need to be injected into the provider must be listed here + deps: [HttpClient], + }, + [RatingsCountKpiDataSource.providerId]: { + provide: DATA_SOURCE, + useClass: RatingsCountKpiDataSource, + deps: [HttpClient], + }, + }); + + this.initializeDashboard(); + } + + /** Used for restoring widgets state */ + public reInitializeDashboard(): void { + // destroys the components and their providers so the dashboard can re init data + this.dashboard = undefined; + this.changeDetectorRef.detectChanges(); + + this.initializeDashboard(); + } + + public initializeDashboard(): void { + // We're using a static configuration object for this example (see widgetConfig at the bottom of the file), + // but this is where the widget's configuration could potentially be populated from a database + const kpiWidget = widgetConfig; + const widgetIndex: IWidgets = { + // Complete the KPI widget with information coming from its type definition + [kpiWidget.id]: + this.widgetTypesService.mergeWithWidgetType(kpiWidget), + }; + + // Setting the widget dimensions and position (this is for gridster) + const positions: Record = { + [kpiWidget.id]: { + cols: 4, + rows: 6, + y: 0, + x: 0, + }, + }; + + // Finally, assigning the variables we created above to the dashboard + this.dashboard = { + positions, + widgets: widgetIndex, + }; + } +} + +/** + * A simple KPI data source to retrieve the average rating of Harry Potter and the Sorcerer's Stone (book) via googleapis + */ +@Injectable() +export class AverageRatingKpiDataSource + extends DataSourceService + implements OnDestroy +{ + // This is the ID we'll use to identify the provider + public static providerId = "AverageRatingKpiDataSource"; + + // Use this subject to communicate the data source's busy state + public busy = new BehaviorSubject(false); + + constructor(private http: HttpClient) { + super(); + } + + // In this example, getFilteredData is invoked every 10 minutes (Take a look at the refresher + // provider definition in the widget configuration below to see how the interval is set) + public async getFilteredData(): Promise { + this.busy.next(true); + return new Promise((resolve) => { + // *** Make a resource request to an external API (if needed) + this.http + .get("https://www.googleapis.com/books/v1/volumes/5MQFrgEACAAJ") + .pipe(finalize(() => this.busy.next(false))) + .subscribe({ + next: (data: any) => { + resolve({ + result: { + value: data.volumeInfo.averageRating, + }, + }); + }, + error: (error: HttpErrorResponse) => { + resolve({ + result: null, + error: { + type: error.status, + }, + }); + }, + }); + }); + } + + public ngOnDestroy(): void { + this.outputsSubject.complete(); + } +} + +/** + * A simple KPI data source to retrieve the ratings count of Harry Potter and the Sorcerer's Stone (book) via googleapis + */ +@Injectable() +export class RatingsCountKpiDataSource + extends DataSourceService + implements OnDestroy +{ + public static providerId = "RatingsCountKpiDataSource"; + + // Use this subject to communicate the data source's busy state + public busy = new BehaviorSubject(false); + + constructor(private http: HttpClient) { + super(); + } + + public async getFilteredData(): Promise { + this.busy.next(true); + return new Promise((resolve) => { + this.http + .get("https://www.googleapis.com/books/v1/volumes/5MQFrgEACAAJ") + .pipe(finalize(() => this.busy.next(false))) + .subscribe({ + next: (data: any) => { + resolve({ + result: { + value: data.volumeInfo.ratingsCount, + }, + }); + }, + error: (error: HttpErrorResponse) => { + resolve({ + result: null, + error: { + type: error.status, + }, + }); + }, + }); + }); + } + + public ngOnDestroy(): void { + this.outputsSubject.complete(); + } +} + +const widgetConfig: IWidget = { + id: "widget1", + type: "kpi", + pizzagna: { + [PizzagnaLayer.Configuration]: { + [DEFAULT_PIZZAGNA_ROOT]: { + providers: { + [WellKnownProviders.Refresher]: { + properties: { + // Configuring the refresher interval so that our data source is invoked every ten minutes + interval: 60 * 10, + enabled: true, + } as IRefresherProperties, + } as Partial, + }, + }, + header: { + properties: { + title: "Harry Potter and the Sorcerer's Stone", + subtitle: "By J. K. Rowling", + }, + }, + tiles: { + properties: { + nodes: ["kpi1"], + }, + }, + kpi1: { + id: "kpi1", + componentType: KpiComponent.lateLoadKey, + properties: { + widgetData: { + units: "out of 5 Stars", + label: "Average Rating", + }, + }, + providers: { + [WellKnownProviders.DataSource]: { + // Setting the data source providerId for the tile with id "kpi1" + providerId: AverageRatingKpiDataSource.providerId, + } as IProviderConfiguration, + [WellKnownProviders.Adapter]: { + providerId: NOVA_KPI_DATASOURCE_ADAPTER, + properties: { + componentId: "kpi1", + propertyPath: "widgetData", + }, + } as IProviderConfiguration, + }, + }, + }, + }, +}; +\\\\\\\`, + "tutorials/persistence-handler-setup/persistence-handler-setup.module.ts": \\\\\\\`// © 2022 SolarWinds Worldwide, LLC. All rights reserved. +// +// Permission is hereby granted, free of charge, to any person obtaining a copy +// of this software and associated documentation files (the "Software"), to +// deal in the Software without restriction, including without limitation the +// rights to use, copy, modify, merge, publish, distribute, sublicense, and/or +// sell copies of the Software, and to permit persons to whom the Software is +// furnished to do so, subject to the following conditions: +// +// The above copyright notice and this permission notice shall be included in +// all copies or substantial portions of the Software. +// +// THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR +// IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, +// FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE +// AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER +// LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, +// OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN +// THE SOFTWARE. + +import { CommonModule } from "@angular/common"; +import { HttpClientModule } from "@angular/common/http"; +import { NgModule } from "@angular/core"; +import { RouterModule } from "@angular/router"; + +import { + NuiButtonModule, + NuiDocsModule, + NuiMessageModule, + NuiSwitchModule, + NuiToastModule, + DEMO_PATH_TOKEN, +} from "@nova-ui/bits"; +import { NuiDashboardsModule } from "@nova-ui/dashboards"; + +import { PersistenceHandlerSetupDocsComponent } from "./persistence-handler-setup-docs.component"; +import { PersistenceHandlerSetupComponent } from "./persistence-handler-setup.component"; +import { getDemoFiles } from "../../../../demo-files-factory"; + +const routes = [ + { + path: "", + component: PersistenceHandlerSetupDocsComponent, + data: { + srlc: { + hideIndicator: true, + }, + showThemeSwitcher: true, + }, + }, + { + path: "example", + component: PersistenceHandlerSetupComponent, + data: { + srlc: { + hideIndicator: true, + }, + }, + }, +]; + +@NgModule({ + imports: [ + CommonModule, + HttpClientModule, + NuiDashboardsModule, + NuiDocsModule, + NuiMessageModule, + NuiSwitchModule, + NuiToastModule, + NuiButtonModule, + RouterModule.forChild(routes), + ], + declarations: [ + PersistenceHandlerSetupDocsComponent, + PersistenceHandlerSetupComponent, + ], + providers: [ + { + provide: DEMO_PATH_TOKEN, + useValue: getDemoFiles("persistence-handler-setup"), + }, + ], +}) +export default class PersistenceHandlerSetupModule {} +\\\\\\\`, + "tutorials/tutorials.module.ts": \\\\\\\`// © 2022 SolarWinds Worldwide, LLC. All rights reserved. +// +// Permission is hereby granted, free of charge, to any person obtaining a copy +// of this software and associated documentation files (the "Software"), to +// deal in the Software without restriction, including without limitation the +// rights to use, copy, modify, merge, publish, distribute, sublicense, and/or +// sell copies of the Software, and to permit persons to whom the Software is +// furnished to do so, subject to the following conditions: +// +// The above copyright notice and this permission notice shall be included in +// all copies or substantial portions of the Software. +// +// THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR +// IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, +// FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE +// AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER +// LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, +// OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN +// THE SOFTWARE. + +import { NgModule, Type } from "@angular/core"; +import { RouterModule, Routes } from "@angular/router"; + +import { ConfiguratorHeadingService } from "@nova-ui/dashboards"; + +export enum TutorialsModuleRoute { + HelloDashboards = "hello-dashboards", + DataSource = "data-source-setup", + WidgetEditor = "widget-editor-setup", + SubmitHandler = "persistence-handler-setup", + WidgetCreation = "widget-creation", + Customization = "customization", + WidgetErrorHandling = "widget-error-handling", + DynamicHeaderLinks = "dynamic-header-links", +} + +const routes: Routes = [ + { + path: TutorialsModuleRoute.HelloDashboards, + loadChildren: async () => + import( + "./hello-dashboards/hello-dashboards.module" + ) as object as Promise>, + }, + { + path: TutorialsModuleRoute.DataSource, + loadChildren: async () => + import( + "./data-source-setup/data-source-setup.module" + ) as object as Promise>, + }, + { + path: TutorialsModuleRoute.WidgetEditor, + loadChildren: async () => + import( + "./widget-editor-setup/widget-editor-setup.module" + ) as object as Promise>, + }, + { + path: TutorialsModuleRoute.SubmitHandler, + loadChildren: async () => + import( + "./persistence-handler-setup/persistence-handler-setup.module" + ) as object as Promise>, + }, + { + path: TutorialsModuleRoute.WidgetCreation, + loadChildren: async () => + import( + "./widget-creation/widget-creation.module" + ) as object as Promise>, + }, + { + path: TutorialsModuleRoute.Customization, + loadChildren: async () => + import("./customization/customization.module") as object as Promise< + Type + >, + }, + { + path: TutorialsModuleRoute.WidgetErrorHandling, + loadChildren: async () => + import( + "./widget-error-handling/widget-error-handling.module" + ) as object as Promise>, + }, + { + path: TutorialsModuleRoute.DynamicHeaderLinks, + loadChildren: async () => + import( + "./dynamic-header-links/dynamic-header-links-docs.module" + ) as object as Promise>, + }, +]; + +@NgModule({ + imports: [RouterModule.forChild(routes)], + providers: [ConfiguratorHeadingService], +}) +export default class TutorialsModule {} +\\\\\\\`, + "tutorials/widget-creation/widget-creation-docs.component.ts": \\\\\\\`// © 2022 SolarWinds Worldwide, LLC. All rights reserved. +// +// Permission is hereby granted, free of charge, to any person obtaining a copy +// of this software and associated documentation files (the "Software"), to +// deal in the Software without restriction, including without limitation the +// rights to use, copy, modify, merge, publish, distribute, sublicense, and/or +// sell copies of the Software, and to permit persons to whom the Software is +// furnished to do so, subject to the following conditions: +// +// The above copyright notice and this permission notice shall be included in +// all copies or substantial portions of the Software. +// +// THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR +// IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, +// FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE +// AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER +// LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, +// OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN +// THE SOFTWARE. + +import { Component } from "@angular/core"; + +@Component({ + selector: "nui-dashboard-widget-creation-docs", + templateUrl: "./widget-creation-docs.component.html", + standalone: false, +}) +export class WidgetCreationDocsComponent {} +\\\\\\\`, + "tutorials/widget-creation/widget-creation.component.ts": \\\\\\\`// © 2022 SolarWinds Worldwide, LLC. All rights reserved. +// +// Permission is hereby granted, free of charge, to any person obtaining a copy +// of this software and associated documentation files (the "Software"), to +// deal in the Software without restriction, including without limitation the +// rights to use, copy, modify, merge, publish, distribute, sublicense, and/or +// sell copies of the Software, and to permit persons to whom the Software is +// furnished to do so, subject to the following conditions: +// +// The above copyright notice and this permission notice shall be included in +// all copies or substantial portions of the Software. +// +// THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR +// IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, +// FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE +// AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER +// LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, +// OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN +// THE SOFTWARE. + +import { HttpClient, HttpErrorResponse } from "@angular/common/http"; +import { + Component, + EventEmitter, + Injectable, + OnDestroy, + OnInit, + Output, + ViewChild, +} from "@angular/core"; +import { GridsterConfig, GridsterItem } from "angular-gridster2"; +import { BehaviorSubject, Observable, Subject } from "rxjs"; +import { finalize, take, takeUntil } from "rxjs/operators"; + +import { + DataSourceService, + IFilteringOutputs, + ToastService, + uuid, +} from "@nova-ui/bits"; +import { + DashboardComponent, + DATA_SOURCE, + DEFAULT_PIZZAGNA_ROOT, + IDashboard, + IDashboardPersistenceHandler, + IDataSourceOutput, + IKpiData, + IProviderConfiguration, + IRefresherProperties, + IWidget, + IWidgets, + IWidgetSelector, + IWidgetTemplateSelector, + KpiComponent, + NOVA_KPI_DATASOURCE_ADAPTER, + PizzagnaLayer, + ProviderRegistryService, + WellKnownPathKey, + WellKnownProviders, + WidgetClonerService, + WidgetTypesService, +} from "@nova-ui/dashboards"; + +// Interface of a widget item +interface IWidgetItem { + name: string; + widget: IWidget; +} + +// This component acts as the first step, or page, in the wizard where the user selects a wizard type to create. +// It's recommended to have this component in a different file. For this tutorial, it's included in the same +// file for simplicity. +@Component({ + selector: "widget-template-selection", + styleUrls: ["./widget-creation.component.less"], + template: \\\\\\\\\\\\\\\` +
+ + +
+ + +
+
{{ item.name }}
+
+
+ \\\\\\\\\\\\\\\`, + standalone: false, +}) +export class WidgetTemplateSelectionComponent + implements IWidgetTemplateSelector, OnInit +{ + // This output will notify the wizard that a widget has been selected. + @Output() public widgetSelected = new EventEmitter(); + + public widgetItems: IWidgetItem[] = []; + public widgetSelection: IWidgetItem[]; + + constructor(private widgetTypesService: WidgetTypesService) {} + + public ngOnInit(): void { + // Here we combine the widget structure from the WidgetTypesService with the corresponding widget + // configuration to create an array of widget objects for the itemSource on the repeat component. + this.widgetItems = [ + { + name: "Fully Configured KPI Widget", + widget: this.widgetTypesService.mergeWithWidgetType( + fullKpiWidgetConfig + ), + }, + { + name: "Unconfigured Proportional Widget", + // Note that 'partialPropWidgetConfig' sets 'metadata.needsConfiguration' to true. + // When this widget is selected in the wizard, the 'Create Widget' button will be hidden + // to guide the user to the second step where they can complete the configuration. + widget: this.widgetTypesService.mergeWithWidgetType( + partialPropWidgetConfig + ), + }, + ]; + + // You can optionally auto-select a widget by doing the following + // this.onSelect([this.widgetItems[0]]); + } + + public onSelect(selectedItems: any[]): void { + // We emit the selected widget to communicate the selection to the configurator + this.widgetSelected.emit(selectedItems[0].widget); + this.widgetSelection = selectedItems; + } +} + +/** + * A simple persistence handler that is tied to the widget editor directive + */ +@Injectable() +// The realizer of IDashboardPersistenceHandler may implement a trySubmit and/or a tryRemove method. +export class PersistenceHandler implements IDashboardPersistenceHandler { + // This variable is just to show how to handle error handling. + private persistenceSucceeded: boolean = true; + + // The example uses the toast service to demonstrate the + // invocation of each of the persistence handler callbacks + constructor(private toastService: ToastService) { + // toastService options to let it sit on the page for 2 seconds. + this.toastService.setConfig({ + timeOut: 2000, + }); + } + + // This method will be invoked anytime the widget editor form gets submitted. + public trySubmit = (widget: IWidget): Observable => { + // Since we are working asynchronously, we'll return a subject. So, after the submit attempt + // succeeds or fails, we can let the subscriber know the result. + const subject = new Subject(); + + if (!widget.id) { + // Creates an id if the widget has no id. + // (This step will make more sense in the context of the widget cloning tutorial + // in which we handle the persistence of a newly created widget.) + widget.id = uuid(); + } + + // For this example, we're using a setTimeout to mock an asynchronous persistence request to a backend + setTimeout(() => { + if (this.persistenceSucceeded) { + // Passes along the new widget after one second. + subject.next(widget); + // Toast on the page on success. + this.toastService.success({ + title: $localize\\\\\\\\\\\\\\\`Submit succeeded.\\\\\\\\\\\\\\\`, + }); + } else { + const errorText = $localize\\\\\\\\\\\\\\\`Submit failed.\\\\\\\\\\\\\\\`; + // Toast on the page on failure. + this.toastService.error({ title: errorText }); + // Makes the subject say there is an error. + subject.error(errorText); + } + // Completes the subject so whoever subscribes to it knows its finished. + subject.complete(); + }, 1000); + + // Returns the subject as an observable. + return subject.asObservable(); + }; + + // This method will be invoked anytime there's a widget removal attempt. + public tryRemove = (widgetId: string): Observable => { + const subject = new Subject(); + + setTimeout(() => { + if (this.persistenceSucceeded) { + // Pass through the id of the widget that was removed. + subject.next(widgetId); + this.toastService.success({ + title: $localize\\\\\\\\\\\\\\\`Removal success\\\\\\\\\\\\\\\`, + }); + } else { + const errorText = $localize\\\\\\\\\\\\\\\`Removal failed.\\\\\\\\\\\\\\\`; + this.toastService.error({ title: errorText }); + subject.error(errorText); + } + subject.complete(); + }, 1000); + + return subject.asObservable(); + }; +} + +/** + * A component that instantiates the dashboard + */ +@Component({ + selector: "widget-creation", + templateUrl: "./widget-creation.component.html", + styleUrls: ["./widget-creation.component.less"], + // Here we provide our persistence handler at the component level; this can also be done in the module. + providers: [PersistenceHandler], + standalone: false, +}) +export class WidgetCreationComponent implements OnInit { + // The WidgetClonerService will need this for updating the dashboard + @ViewChild(DashboardComponent, { static: true }) + dashboardComponent: DashboardComponent; + // This variable will hold all the data needed to define the layout and behavior of the widgets. + // Pass this to the dashboard component's dashboard input in the template. + public dashboard: IDashboard; + + // Angular gridster requires a configuration object even if it's empty. + // Pass this to the dashboard component's gridsterConfig input in the template. + public gridsterConfig: GridsterConfig = { + // These values will be used to set the initial widget dimensions on creation. + // If not set, they each default to 6. + defaultItemCols: 3, + defaultItemRows: 5, + }; + + // Boolean the dashboard takes in as an input; if it's set to true + // the dashboard allows you to resize widgets and move them around. + public editMode: boolean = false; + + // Subject used for auto-unsubscribing from subscriptions on component destruction + private readonly destroy$ = new Subject(); + + constructor( + // WidgetTypesService provides the widget's necessary structure information + private widgetTypesService: WidgetTypesService, + + // In general, the ProviderRegistryService is used for making entities available for injection into dynamically loaded components. + private providerRegistry: ProviderRegistryService, + + // Injecting the PersistenceHandler we created and assigning it to a property we use in the template. + public persistenceHandler: PersistenceHandler, + + // Injecting the cloner service which is needed for opening up the cloner wizard. + private widgetClonerService: WidgetClonerService + ) {} + + public ngOnInit(): void { + // Grabbing the widget's default template which will be needed as a parameter for setNode + const kpiTemplate = this.widgetTypesService.getWidgetType("kpi", 1); + const proportionalTemplate = this.widgetTypesService.getWidgetType( + "proportional", + 1 + ); + + // Registering our data sources as dropdown options in the widget editor/configurator + // Note: This could also be done in the parent module's constructor so that + // multiple dashboards could have access to the same widget template modification. + this.widgetTypesService.setNode( + // This is the template we grabbed above with getWidgetType + proportionalTemplate, + // Setting the editor/configurator part of the widget template + "configurator", + // This indicates which node you are changing and we want to change + // the data source providers available for selection in the editor. + WellKnownPathKey.DataSourceProviders, + // Setting the data sources available for selection in the editor + [RandomCitiesProportionalDataSource.providerId] + ); + + // Same as above, but for the KPI data sources + this.widgetTypesService.setNode( + kpiTemplate, + "configurator", + WellKnownPathKey.DataSourceProviders, + [ + AverageRatingKpiDataSource.providerId, + RatingsCountKpiDataSource.providerId, + ] + ); + + // Registering the data sources available for injection into the KPI tiles and proportional widget. + // Note: Each tile of a KPI widget is assigned its own instance of a data source. + this.providerRegistry.setProviders({ + [AverageRatingKpiDataSource.providerId]: { + provide: DATA_SOURCE, + useClass: AverageRatingKpiDataSource, + // Any dependencies that need to be injected into the provider must be listed here + deps: [HttpClient], + }, + [RatingsCountKpiDataSource.providerId]: { + provide: DATA_SOURCE, + useClass: RatingsCountKpiDataSource, + deps: [HttpClient], + }, + [RandomCitiesProportionalDataSource.providerId]: { + provide: DATA_SOURCE, + useClass: RandomCitiesProportionalDataSource, + deps: [], + }, + }); + + this.initializeDashboard(); + } + + public onCreateWidget(): void { + const widgetSelector: IWidgetSelector = { + // Template ref of the dashboard component. + dashboardComponent: this.dashboardComponent, + // A trySubmit function; in this case, we use the trySubmit from the PersistenceHandler created in the previous tutorial. + trySubmit: this.persistenceHandler.trySubmit, + // WidgetTemplateSelectionComponent will act as step one of the wizard to allow the user to select which widget will be cloned. + widgetSelectionComponentType: WidgetTemplateSelectionComponent, + }; + this.widgetClonerService + .open(widgetSelector) + .pipe( + // Auto-unsubscribe after one emission or on component destruction + take(1), + takeUntil(this.destroy$) + ) + .subscribe(); + } + + public initializeDashboard(): void { + // We're using a static configuration object for this example (see widgetConfig at the bottom of the file), + // but this is where the widget's configuration could potentially be populated from a database + const kpiWidget = fullKpiWidgetConfig; + const widgetIndex: IWidgets = { + // Complete the KPI widget with information coming from its type definition + [kpiWidget.id]: + this.widgetTypesService.mergeWithWidgetType(kpiWidget), + }; + + // Setting the widget dimensions and position (this is for gridster) + // Note: If no position is given for a widget the 'defaultItemCols' and 'defaultItemRows' properties + // from the gridsterConfig will be used for the dimensions + const positions: Record = { + [kpiWidget.id]: { + cols: 3, + rows: 5, + y: 0, + x: 0, + }, + }; + + // Finally, assigning the variables we created above to the dashboard + this.dashboard = { + positions, + widgets: widgetIndex, + }; + } +} + +// Interface for each data point in a proportional widget. +interface IProportionalWidgetData { + id: string; + name: string; + data: number[]; + icon: string; + link: string; + value: string; +} + +@Injectable() +export class RandomCitiesProportionalDataSource implements OnDestroy { + public static providerId = "RandomCitiesProportionalDataSource"; + + public outputsSubject = new Subject< + IDataSourceOutput + >(); + + // Every time applyFilters gets ran we are changing the data source. + public applyFilters(): void { + setTimeout(() => { + this.outputsSubject.next({ + result: this.getRandomProportionalWidgetData(), + }); + }, 1000); + } + + public ngOnDestroy(): void { + this.outputsSubject.complete(); + } + + private getRandomProportionalWidgetData(): IProportionalWidgetData[] { + return [ + { + id: "Down", + name: "Down", + data: [Math.round(Math.random() * 100)], + icon: "status_down", + link: "https://en.wikipedia.org/wiki/Brno", + value: "Brno", + }, + { + id: "Critical", + name: "Critical", + data: [Math.round(Math.random() * 100)], + icon: "status_critical", + link: "https://en.wikipedia.org/wiki/Kyiv", + value: "Kyiv", + }, + { + id: "Warning", + name: "Warning", + data: [Math.round(Math.random() * 100)], + icon: "status_warning", + link: "https://en.wikipedia.org/wiki/Austin", + value: "Austin", + }, + { + id: "Unknown", + name: "Unknown", + data: [Math.round(Math.random() * 100)], + icon: "status_unknown", + link: "https://en.wikipedia.org/wiki/Lisbon", + value: "Lisbon", + }, + { + id: "Up", + name: "Up", + data: [Math.round(Math.random() * 100)], + icon: "status_up", + link: "https://en.wikipedia.org/wiki/Sydney", + value: "Sydney", + }, + { + id: "Unmanaged", + name: "Unmanaged", + data: [Math.round(Math.random() * 100)], + icon: "status_unmanaged", + link: "https://en.wikipedia.org/wiki/Nur-Sultan", + value: "Nur-Sultan", + }, + ]; + } +} + +/** + * A simple KPI data source to retrieve the average rating of Harry Potter and the Sorcerer's Stone (book) via googleapis + */ +@Injectable() +export class AverageRatingKpiDataSource + extends DataSourceService + implements OnDestroy +{ + // This is the ID we'll use to identify the provider + public static providerId = "AverageRatingKpiDataSource"; + + // Use this subject to communicate the data source's busy state + public busy = new BehaviorSubject(false); + + constructor(private http: HttpClient) { + super(); + } + + // In this example, getFilteredData is invoked every 10 minutes (Take a look at the refresher + // provider definition in the widget configuration below to see how the interval is set) + public async getFilteredData(): Promise { + this.busy.next(true); + return new Promise((resolve) => { + // *** Make a resource request to an external API (if needed) + this.http + .get("https://www.googleapis.com/books/v1/volumes/5MQFrgEACAAJ") + .pipe(finalize(() => this.busy.next(false))) + .subscribe({ + next: (data: any) => { + resolve({ + result: { + value: data.volumeInfo.averageRating, + }, + }); + }, + error: (error: HttpErrorResponse) => { + resolve({ + result: null, + error: { + type: error.status, + }, + }); + }, + }); + }); + } + + public ngOnDestroy(): void { + this.outputsSubject.complete(); + } +} + +/** + * A simple KPI data source to retrieve the ratings count of Harry Potter and the Sorcerer's Stone (book) via googleapis + */ +@Injectable() +export class RatingsCountKpiDataSource + extends DataSourceService + implements OnDestroy +{ + public static providerId = "RatingsCountKpiDataSource"; + + // Use this subject to communicate the data source's busy state + public busy = new BehaviorSubject(false); + + constructor(private http: HttpClient) { + super(); + } + + public async getFilteredData(): Promise { + this.busy.next(true); + return new Promise((resolve) => { + this.http + .get("https://www.googleapis.com/books/v1/volumes/5MQFrgEACAAJ") + .pipe(finalize(() => this.busy.next(false))) + .subscribe({ + next: (data: any) => { + resolve({ + result: { + value: data.volumeInfo.ratingsCount, + }, + }); + }, + error: (error: HttpErrorResponse) => { + resolve({ + result: null, + error: { + type: error.status, + }, + }); + }, + }); + }); + } + + public ngOnDestroy(): void { + this.outputsSubject.complete(); + } +} + +const fullKpiWidgetConfig: IWidget = { + id: "widget1", + type: "kpi", + pizzagna: { + [PizzagnaLayer.Configuration]: { + [DEFAULT_PIZZAGNA_ROOT]: { + providers: { + [WellKnownProviders.Refresher]: { + properties: { + // Configuring the refresher interval so that our data source is invoked every ten minutes + interval: 60 * 10, + enabled: true, + } as IRefresherProperties, + } as Partial, + }, + }, + header: { + properties: { + title: "Harry Potter and the Sorcerer's Stone", + subtitle: "By J. K. Rowling", + }, + }, + tiles: { + properties: { + nodes: ["kpi1"], + }, + }, + kpi1: { + id: "kpi1", + componentType: KpiComponent.lateLoadKey, + properties: { + widgetData: { + units: \\\\\\\\\\\\\\\`out of 5 Stars\\\\\\\\\\\\\\\`, + label: \\\\\\\\\\\\\\\`Average Rating\\\\\\\\\\\\\\\`, + }, + }, + providers: { + [WellKnownProviders.DataSource]: { + // Setting the data source providerId for the tile with id "kpi1" + providerId: AverageRatingKpiDataSource.providerId, + } as IProviderConfiguration, + [WellKnownProviders.Adapter]: { + providerId: NOVA_KPI_DATASOURCE_ADAPTER, + properties: { + componentId: "kpi1", + propertyPath: "widgetData", + }, + } as IProviderConfiguration, + }, + }, + }, + }, +}; + +const partialPropWidgetConfig: IWidget = { + id: "widget2", + type: "proportional", + metadata: { + // Set 'needsConfiguration' to true if the widget needs further configuration before it can be + // placed on the dashboard. The "Create Widget" button will be hidden in the wizard when this + // widget is selected. + needsConfiguration: true, + }, + pizzagna: { + [PizzagnaLayer.Configuration]: { + header: { + properties: { + title: "*New Proportional Widget*", + }, + }, + }, + }, +}; +\\\\\\\`, + "tutorials/widget-creation/widget-creation.module.ts": \\\\\\\`// © 2022 SolarWinds Worldwide, LLC. All rights reserved. +// +// Permission is hereby granted, free of charge, to any person obtaining a copy +// of this software and associated documentation files (the "Software"), to +// deal in the Software without restriction, including without limitation the +// rights to use, copy, modify, merge, publish, distribute, sublicense, and/or +// sell copies of the Software, and to permit persons to whom the Software is +// furnished to do so, subject to the following conditions: +// +// The above copyright notice and this permission notice shall be included in +// all copies or substantial portions of the Software. +// +// THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR +// IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, +// FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE +// AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER +// LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, +// OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN +// THE SOFTWARE. + +import { CommonModule } from "@angular/common"; +import { HttpClientModule } from "@angular/common/http"; +import { NgModule } from "@angular/core"; +import { RouterModule } from "@angular/router"; + +import { + NuiButtonModule, + NuiDocsModule, + NuiImageModule, + NuiMessageModule, + NuiRepeatModule, + NuiSwitchModule, + NuiToastModule, + DEMO_PATH_TOKEN, +} from "@nova-ui/bits"; +import { NuiDashboardsModule } from "@nova-ui/dashboards"; + +import { WidgetCreationDocsComponent } from "./widget-creation-docs.component"; +import { + WidgetCreationComponent, + WidgetTemplateSelectionComponent, +} from "./widget-creation.component"; +import { getDemoFiles } from "../../../../demo-files-factory"; + +const routes = [ + { + path: "", + component: WidgetCreationDocsComponent, + data: { + srlc: { + hideIndicator: true, + }, + showThemeSwitcher: true, + }, + }, + { + path: "example", + component: WidgetCreationComponent, + data: { + srlc: { + hideIndicator: true, + }, + }, + }, +]; + +@NgModule({ + imports: [ + CommonModule, + HttpClientModule, + NuiDashboardsModule, + NuiDocsModule, + NuiMessageModule, + NuiSwitchModule, + NuiToastModule, + NuiButtonModule, + NuiRepeatModule, + NuiImageModule, + RouterModule.forChild(routes), + ], + declarations: [ + WidgetCreationDocsComponent, + WidgetCreationComponent, + WidgetTemplateSelectionComponent, + ], + providers: [ + { + provide: DEMO_PATH_TOKEN, + useValue: getDemoFiles("widget-creation"), + }, + ], +}) +export default class WidgetCreationModule {} +\\\\\\\`, + "tutorials/widget-editor-setup/widget-editor-setup-docs.component.ts": \\\\\\\`// © 2022 SolarWinds Worldwide, LLC. All rights reserved. +// +// Permission is hereby granted, free of charge, to any person obtaining a copy +// of this software and associated documentation files (the "Software"), to +// deal in the Software without restriction, including without limitation the +// rights to use, copy, modify, merge, publish, distribute, sublicense, and/or +// sell copies of the Software, and to permit persons to whom the Software is +// furnished to do so, subject to the following conditions: +// +// The above copyright notice and this permission notice shall be included in +// all copies or substantial portions of the Software. +// +// THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR +// IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, +// FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE +// AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER +// LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, +// OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN +// THE SOFTWARE. + +import { Component } from "@angular/core"; + +@Component({ + selector: "nui-dashboard-widget-editor-docs", + templateUrl: "./widget-editor-setup-docs.component.html", + standalone: false, +}) +export class WidgetEditorDocsComponent {} +\\\\\\\`, + "tutorials/widget-editor-setup/widget-editor-setup.component.ts": \\\\\\\`// © 2022 SolarWinds Worldwide, LLC. All rights reserved. +// +// Permission is hereby granted, free of charge, to any person obtaining a copy +// of this software and associated documentation files (the "Software"), to +// deal in the Software without restriction, including without limitation the +// rights to use, copy, modify, merge, publish, distribute, sublicense, and/or +// sell copies of the Software, and to permit persons to whom the Software is +// furnished to do so, subject to the following conditions: +// +// The above copyright notice and this permission notice shall be included in +// all copies or substantial portions of the Software. +// +// THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR +// IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, +// FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE +// AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER +// LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, +// OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN +// THE SOFTWARE. + +import { HttpClient, HttpErrorResponse } from "@angular/common/http"; +import { + ChangeDetectorRef, + Component, + Injectable, + OnDestroy, + OnInit, +} from "@angular/core"; +import { GridsterConfig, GridsterItem } from "angular-gridster2"; +import { BehaviorSubject } from "rxjs"; +import { finalize } from "rxjs/operators"; + +import { DataSourceService, IFilteringOutputs } from "@nova-ui/bits"; +import { + DATA_SOURCE, + DEFAULT_PIZZAGNA_ROOT, + IDashboard, + IKpiData, + IProviderConfiguration, + IRefresherProperties, + IWidget, + IWidgets, + KpiComponent, + NOVA_KPI_DATASOURCE_ADAPTER, + PizzagnaLayer, + ProviderRegistryService, + WellKnownPathKey, + WellKnownProviders, + WidgetTypesService, +} from "@nova-ui/dashboards"; + +/** + * A simple KPI data source to retrieve the average rating of Harry Potter and the Sorcerer's Stone (book) via googleapis + */ +@Injectable() +export class AverageRatingKpiDataSource + extends DataSourceService + implements OnDestroy +{ + // This is the ID we'll use to identify the provider + public static providerId = "AverageRatingKpiDataSource"; + + // Use this subject to communicate the data source's busy state + public busy = new BehaviorSubject(false); + + constructor(private http: HttpClient) { + super(); + } + + // In this example, getFilteredData is invoked every 10 minutes (Take a look at the refresher + // provider definition in the widget configuration below to see how the interval is set) + public async getFilteredData(): Promise { + this.busy.next(true); + return new Promise((resolve) => { + // *** Make a resource request to an external API (if needed) + this.http + .get("https://www.googleapis.com/books/v1/volumes/5MQFrgEACAAJ") + .pipe(finalize(() => this.busy.next(false))) + .subscribe({ + next: (data: any) => { + resolve({ + result: { + value: data.volumeInfo.averageRating, + }, + }); + }, + error: (error: HttpErrorResponse) => { + resolve({ + result: null, + error: { + type: error.status, + }, + }); + }, + }); + }); + } + + public ngOnDestroy(): void { + this.outputsSubject.complete(); + } +} + +/** + * A simple KPI data source to retrieve the ratings count of Harry Potter and the Sorcerer's Stone (book) via googleapis + */ +@Injectable() +export class RatingsCountKpiDataSource + extends DataSourceService + implements OnDestroy +{ + public static providerId = "RatingsCountKpiDataSource"; + + // Use this subject to communicate the data source's busy state + public busy = new BehaviorSubject(false); + + constructor(private http: HttpClient) { + super(); + } + + public async getFilteredData(): Promise { + this.busy.next(true); + return new Promise((resolve) => { + this.http + .get("https://www.googleapis.com/books/v1/volumes/5MQFrgEACAAJ") + .pipe(finalize(() => this.busy.next(false))) + .subscribe({ + next: (data: any) => { + resolve({ + result: { + value: data.volumeInfo.ratingsCount, + }, + }); + }, + error: (error: HttpErrorResponse) => { + resolve({ + result: null, + error: { + type: error.status, + }, + }); + }, + }); + }); + } + + public ngOnDestroy(): void { + this.outputsSubject.complete(); + } +} + +/** + * A component that instantiates the dashboard + */ +@Component({ + selector: "widget-editor-setup", + templateUrl: "./widget-editor-setup.component.html", + styleUrls: ["./widget-editor-setup.component.less"], + standalone: false, +}) +export class WidgetEditorSetupComponent implements OnInit { + // This variable will hold all the data needed to define the layout and behavior of the widgets. + // Pass this to the dashboard component's dashboard input in the template. + public dashboard: IDashboard | undefined; + + // Angular gridster requires a configuration object even if it's empty. + // Pass this to the dashboard component's gridsterConfig input in the template. + public gridsterConfig: GridsterConfig = {}; + + // Boolean which dashboard takes in as an input if its true it allows you to move widgets around. + public editMode: boolean = false; + + constructor( + // WidgetTypesService provides the widget's necessary structure information + private widgetTypesService: WidgetTypesService, + + // In general, the ProviderRegistryService is used for making entities available for injection into dynamically loaded components. + private providerRegistry: ProviderRegistryService, + private changeDetectorRef: ChangeDetectorRef + ) {} + + public ngOnInit(): void { + // Grabbing the widget's default template which will be needed as a parameter for setNode + const widgetTemplate = this.widgetTypesService.getWidgetType("kpi", 1); + // Registering our data sources as dropdown options in the widget editor/configurator + // Note: This could also be done in the parent module's constructor so that + // multiple dashboards could have access to the same widget template modification. + this.widgetTypesService.setNode( + // This is the template we grabbed above with getWidgetType + widgetTemplate, + // We are setting the editor/configurator part of the widget template + "configurator", + // This indicates which node you are changing and we want to change + // the data source providers available for selection in the editor. + WellKnownPathKey.DataSourceProviders, + // We are setting the data sources available for selection in the editor + [ + AverageRatingKpiDataSource.providerId, + RatingsCountKpiDataSource.providerId, + ] + ); + + // Registering the data sources available for injection into the KPI tiles. + // Note: Each tile of a KPI widget is assigned its own instance of a data source + this.providerRegistry.setProviders({ + [AverageRatingKpiDataSource.providerId]: { + provide: DATA_SOURCE, + useClass: AverageRatingKpiDataSource, + // Any dependencies that need to be injected into the provider must be listed here + deps: [HttpClient], + }, + [RatingsCountKpiDataSource.providerId]: { + provide: DATA_SOURCE, + useClass: RatingsCountKpiDataSource, + deps: [HttpClient], + }, + }); + + this.initializeDashboard(); + } + + /** Used for restoring widgets state */ + public reInitializeDashboard(): void { + // destroys the components and their providers so the dashboard can re init data + this.dashboard = undefined; + this.changeDetectorRef.detectChanges(); + + this.initializeDashboard(); + } + + public initializeDashboard(): void { + // We're using a static configuration object for this example (see widgetConfig at the bottom of the file), + // but this is where the widget's configuration could potentially be populated from a database + const kpiWidget = widgetConfig; + const widgetIndex: IWidgets = { + // Complete the KPI widget with information coming from its type definition + [kpiWidget.id]: + this.widgetTypesService.mergeWithWidgetType(kpiWidget), + }; + + // Setting the widget dimensions and position (this is for gridster) + const positions: Record = { + [kpiWidget.id]: { + cols: 4, + rows: 6, + y: 0, + x: 0, + }, + }; + + // Finally, assigning the variables we created above to the dashboard + this.dashboard = { + positions, + widgets: widgetIndex, + }; + } +} + +const widgetConfig: IWidget = { + id: "widget1", + type: "kpi", + pizzagna: { + [PizzagnaLayer.Configuration]: { + [DEFAULT_PIZZAGNA_ROOT]: { + providers: { + [WellKnownProviders.Refresher]: { + properties: { + // Configuring the refresher interval so that our data source is invoked every ten minutes + interval: 60 * 10, + enabled: true, + } as IRefresherProperties, + } as Partial, + }, + }, + header: { + properties: { + title: "Harry Potter and the Sorcerer's Stone", + subtitle: "By J. K. Rowling", + }, + }, + tiles: { + properties: { + nodes: ["kpi1"], + }, + }, + kpi1: { + id: "kpi1", + componentType: KpiComponent.lateLoadKey, + properties: { + widgetData: { + units: "out of 5 Stars", + label: "Average Rating", + }, + }, + providers: { + [WellKnownProviders.DataSource]: { + // Setting the data source providerId for the tile with id "kpi1" + providerId: AverageRatingKpiDataSource.providerId, + } as IProviderConfiguration, + [WellKnownProviders.Adapter]: { + providerId: NOVA_KPI_DATASOURCE_ADAPTER, + properties: { + componentId: "kpi1", + propertyPath: "widgetData", + }, + } as IProviderConfiguration, + }, + }, + }, + }, +}; +\\\\\\\`, + "tutorials/widget-editor-setup/widget-editor-setup.module.ts": \\\\\\\`// © 2022 SolarWinds Worldwide, LLC. All rights reserved. +// +// Permission is hereby granted, free of charge, to any person obtaining a copy +// of this software and associated documentation files (the "Software"), to +// deal in the Software without restriction, including without limitation the +// rights to use, copy, modify, merge, publish, distribute, sublicense, and/or +// sell copies of the Software, and to permit persons to whom the Software is +// furnished to do so, subject to the following conditions: +// +// The above copyright notice and this permission notice shall be included in +// all copies or substantial portions of the Software. +// +// THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR +// IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, +// FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE +// AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER +// LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, +// OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN +// THE SOFTWARE. + +import { CommonModule } from "@angular/common"; +import { HttpClientModule } from "@angular/common/http"; +import { NgModule } from "@angular/core"; +import { RouterModule } from "@angular/router"; + +import { + NuiButtonModule, + NuiDocsModule, + NuiMessageModule, + NuiSwitchModule, + DEMO_PATH_TOKEN, +} from "@nova-ui/bits"; +import { NuiDashboardsModule } from "@nova-ui/dashboards"; + +import { WidgetEditorDocsComponent } from "./widget-editor-setup-docs.component"; +import { WidgetEditorSetupComponent } from "./widget-editor-setup.component"; +import { getDemoFiles } from "../../../../demo-files-factory"; + +const routes = [ + { + path: "", + component: WidgetEditorDocsComponent, + data: { + srlc: { + hideIndicator: true, + }, + showThemeSwitcher: true, + }, + }, + { + path: "example", + component: WidgetEditorSetupComponent, + data: { + srlc: { + hideIndicator: true, + }, + }, + }, +]; + +@NgModule({ + imports: [ + CommonModule, + HttpClientModule, + NuiDashboardsModule, + NuiDocsModule, + NuiMessageModule, + NuiSwitchModule, + NuiButtonModule, + RouterModule.forChild(routes), + ], + declarations: [WidgetEditorDocsComponent, WidgetEditorSetupComponent], + providers: [ + { + provide: DEMO_PATH_TOKEN, + useValue: getDemoFiles("widget-editor-setup"), + }, + ], +}) +export default class WidgetEditorSetupModule {} +\\\\\\\`, + "tutorials/widget-error-handling/widget-error-handling-docs.component.ts": \\\\\\\`// © 2022 SolarWinds Worldwide, LLC. All rights reserved. +// +// Permission is hereby granted, free of charge, to any person obtaining a copy +// of this software and associated documentation files (the "Software"), to +// deal in the Software without restriction, including without limitation the +// rights to use, copy, modify, merge, publish, distribute, sublicense, and/or +// sell copies of the Software, and to permit persons to whom the Software is +// furnished to do so, subject to the following conditions: +// +// The above copyright notice and this permission notice shall be included in +// all copies or substantial portions of the Software. +// +// THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR +// IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, +// FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE +// AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER +// LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, +// OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN +// THE SOFTWARE. + +import { Component } from "@angular/core"; + +@Component({ + selector: "nui-widget-error-handling-docs", + templateUrl: "./widget-error-handling-docs.component.html", + standalone: false, +}) +export class WidgetErrorHandlingDocsComponent { + public fallbackAdapter = \\\\\\\\\\\\\\\` +@Injectable() +export class StatusContentFallbackAdapter implements OnDestroy, IHasComponent { + + protected readonly destroy$ = new Subject(); + protected componentId: string; + + constructor(@Inject(PIZZAGNA_EVENT_BUS) protected eventBus: EventBus, + protected pizzagnaService: PizzagnaService) { + this.eventBus.getStream(DATA_SOURCE_OUTPUT) + .pipe(takeUntil(this.destroy$)).subscribe((event: IEvent>) => { + this.handleDataSourceOutput(event); + }); + } + + public ngOnDestroy(): void { + this.destroy$.next(); + this.destroy$.complete(); + } + + public setComponent(component: any, componentId: string) { + this.componentId = componentId; + } + + protected handleDataSourceOutput(event: IEvent>) { + this.pizzagnaService.setProperty({ + componentId: this.componentId, + propertyPath: ["fallbackKey"], + pizzagnaKey: PizzagnaLayer.Data, + }, typeof event.payload?.error?.type !== "undefined" ? event.payload?.error?.type.toString() : undefined); + } +}\\\\\\\\\\\\\\\`; + public errorsMap = \\\\\\\\\\\\\\\` +export const ERROR_FALLBACK_MAP: Record = { + [HttpStatusCode.Unknown]: ErrorNodeKey.ErrorUnknown, + [HttpStatusCode.Forbidden]: ErrorNodeKey.ErrorForbidden, + [HttpStatusCode.NotFound]: ErrorNodeKey.ErrorNotFound, +}; +\\\\\\\\\\\\\\\`; + public errorNodes = \\\\\\\\\\\\\\\` +export const ERROR_NODES: Record = { + [ErrorNodeKey.ErrorUnknown]: { + id: ErrorNodeKey.ErrorUnknown, + componentType: WidgetErrorComponent.lateLoadKey, + properties: { + image: "no-data-to-show", + title: $localize\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\`Whoops, something went wrong\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\`, + description: $localize\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\`There was an unexpected error.\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\`, + } as IWidgetErrorDisplayProperties, + }, + [ErrorNodeKey.ErrorForbidden]: { + id: ErrorNodeKey.ErrorForbidden, + componentType: WidgetErrorComponent.lateLoadKey, + properties: { + image: "no-data-to-show", + title: $localize\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\`403 - Forbidden\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\`, + description: $localize\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\`The requested action was forbidden.\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\`, + } as IWidgetErrorDisplayProperties, + }, + [ErrorNodeKey.ErrorNotFound]: { + id: ErrorNodeKey.ErrorNotFound, + componentType: WidgetErrorComponent.lateLoadKey, + properties: { + image: "no-data-to-show", + title: $localize\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\`404 - Not Found\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\`, + description: $localize\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\`The requested resource could not be found.\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\`, + } as IWidgetErrorDisplayProperties, + }, +};\\\\\\\\\\\\\\\`; + public widgetBodyContentNodesSignature = \\\\\\\\\\\\\\\` +/** + * Retrieves an index of the basic widget body content nodes including fallback nodes + * + * @param mainContentNodeKey The key corresponding to the main body content node + * @param fallbackAdapterId The id for the adapter responsible for activating fallback content in case of an error + * @param fallbackMap A map of node keys to fallback content definitions + * @param fallbackNodes An index of fallback content definitions + * + * @returns An index of component configurations + */ +export function widgetBodyContentNodes( + mainContentNodeKey: string, + fallbackAdapterId = NOVA_STATUS_CONTENT_FALLBACK_ADAPTER, + fallbackMap: Record = ERROR_FALLBACK_MAP, + fallbackNodes: Record = ERROR_NODES +): Record { ... } +\\\\\\\\\\\\\\\`; +} +\\\\\\\`, + "tutorials/widget-error-handling/widget-error-handling.component.ts": \\\\\\\`// © 2022 SolarWinds Worldwide, LLC. All rights reserved. +// +// Permission is hereby granted, free of charge, to any person obtaining a copy +// of this software and associated documentation files (the "Software"), to +// deal in the Software without restriction, including without limitation the +// rights to use, copy, modify, merge, publish, distribute, sublicense, and/or +// sell copies of the Software, and to permit persons to whom the Software is +// furnished to do so, subject to the following conditions: +// +// The above copyright notice and this permission notice shall be included in +// all copies or substantial portions of the Software. +// +// THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR +// IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, +// FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE +// AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER +// LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, +// OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN +// THE SOFTWARE. + +import { HttpClient, HttpErrorResponse } from "@angular/common/http"; +import { + ChangeDetectorRef, + Component, + Injectable, + OnDestroy, + OnInit, +} from "@angular/core"; +import { GridsterConfig, GridsterItem } from "angular-gridster2"; +import { BehaviorSubject } from "rxjs"; +import { finalize } from "rxjs/operators"; + +import { DataSourceService, IFilteringOutputs } from "@nova-ui/bits"; +import { + DATA_SOURCE, + DEFAULT_PIZZAGNA_ROOT, + HttpStatusCode, + IDashboard, + IKpiData, + IProviderConfiguration, + IRefresherProperties, + IWidget, + IWidgets, + KpiComponent, + NOVA_KPI_DATASOURCE_ADAPTER, + PizzagnaLayer, + ProviderRegistryService, + WellKnownPathKey, + WellKnownProviders, + WidgetTypesService, +} from "@nova-ui/dashboards"; + +/** + * A simple KPI data source to retrieve the average rating of Harry Potter and the Sorcerer's Stone (book) via googleapis + */ +@Injectable() +export class AverageRatingKpiDataSource + extends DataSourceService + implements OnDestroy +{ + // This is the ID we'll use to identify the provider + public static providerId = "AverageRatingKpiDataSource"; + + // Use this subject to communicate the data source's busy state + public busy = new BehaviorSubject(false); + + constructor(private http: HttpClient) { + super(); + } + + // In this example, getFilteredData is invoked every 10 minutes (Take a look at the refresher + // provider definition in the widget configuration below to see how the interval is set) + public async getFilteredData(): Promise { + this.busy.next(true); + return new Promise((resolve) => { + // *** Make a resource request to an external API (if needed) + this.http + .get("https://www.googleapis.com/books/v1/volumes/5MQFrgEACAAJ") + .pipe(finalize(() => this.busy.next(false))) + .subscribe({ + next: (data: any) => { + resolve({ + result: { + value: data.volumeInfo.averageRating, + }, + }); + }, + error: (error: HttpErrorResponse) => { + resolve({ + result: null, + error: { + type: error.status, + }, + }); + }, + }); + }); + } + + public ngOnDestroy(): void { + this.outputsSubject.complete(); + } +} + +/** + * A simple KPI data source to retrieve the average rating of Harry Potter and the Sorcerer's Stone (book) via googleapis + */ +@Injectable() +export class ErrorUnknownDataSource + extends DataSourceService + implements OnDestroy +{ + // This is the ID we'll use to identify the provider + public static providerId = "ErrorUnknownDataSource"; + + // Use this subject to communicate the data source's busy state + public busy = new BehaviorSubject(false); + + // In this example, getFilteredData is invoked every 10 minutes (Take a look at the refresher + // provider definition in the widget configuration below to see how the interval is set) + public async getFilteredData(): Promise { + this.busy.next(true); + const mockError = { + result: null, + error: { type: HttpStatusCode.Unknown }, + }; + this.busy.next(false); + return mockError; + } + + public ngOnDestroy(): void { + this.outputsSubject.complete(); + } +} + +/** + * A simple KPI data source to retrieve the ratings count of Harry Potter and the Sorcerer's Stone (book) via googleapis + */ +@Injectable() +export class ErrorForbiddenDataSource + extends DataSourceService + implements OnDestroy +{ + public static providerId = "ErrorForbiddenDataSource"; + + // Use this subject to communicate the data source's busy state + public busy = new BehaviorSubject(false); + + constructor(private http: HttpClient) { + super(); + } + + public async getFilteredData(): Promise { + this.busy.next(true); + // generate a 403 + return new Promise((resolve) => { + this.http + .get( + "http://www.mocky.io/v2/5ecc724a3200000f0023614a?mocky-delay=4000ms" + ) + .pipe(finalize(() => this.busy.next(false))) + .subscribe({ + error: (error: HttpErrorResponse) => { + resolve({ + result: null, + error: { + type: error.status, + }, + }); + }, + }); + }); + } + + public ngOnDestroy(): void { + this.outputsSubject.complete(); + } +} + +/** + * A simple KPI data source to retrieve the ratings count of Harry Potter and the Sorcerer's Stone (book) via googleapis + */ +@Injectable() +export class ErrorNotFoundDataSource + extends DataSourceService + implements OnDestroy +{ + public static providerId = "ErrorNotFoundDataSource"; + + // Use this subject to communicate the data source's busy state + public busy = new BehaviorSubject(false); + + constructor(private http: HttpClient) { + super(); + } + + public async getFilteredData(): Promise { + this.busy.next(true); + // generate a 404 + return new Promise((resolve) => { + this.http + .get( + "http://www.mocky.io/v2/5ec6bfd93200007800d75100?mocky-delay=1000ms" + ) + .pipe(finalize(() => this.busy.next(false))) + .subscribe({ + error: (error: HttpErrorResponse) => { + resolve({ + result: null, + error: { + type: error.status, + }, + }); + }, + }); + }); + } + + public ngOnDestroy(): void { + this.outputsSubject.complete(); + } +} + +/** + * A component that instantiates the dashboard + */ +@Component({ + selector: "widget-error-handling", + templateUrl: "./widget-error-handling.component.html", + styleUrls: ["./widget-error-handling.component.less"], + standalone: false, +}) +export class WidgetErrorHandlingComponent implements OnInit { + // This variable will hold all the data needed to define the layout and behavior of the widgets. + // Pass this to the dashboard component's dashboard input in the template. + public dashboard: IDashboard | undefined; + + // Angular gridster requires a configuration object even if it's empty. + // Pass this to the dashboard component's gridsterConfig input in the template. + public gridsterConfig: GridsterConfig = {}; + + // Boolean which dashboard takes in as an input if its true it allows you to move widgets around. + public editMode: boolean = false; + + constructor( + // WidgetTypesService provides the widget's necessary structure information + private widgetTypesService: WidgetTypesService, + + // In general, the ProviderRegistryService is used for making entities available for injection into dynamically loaded components. + private providerRegistry: ProviderRegistryService, + private changeDetectorRef: ChangeDetectorRef + ) {} + + public ngOnInit(): void { + // Grab the widget's default template which will be needed as a parameter for setNode. + const widgetTemplate = this.widgetTypesService.getWidgetType("kpi", 1); + // Register our data sources as dropdown options in the widget editor/configurator + // Note: This could also be done in the parent module's constructor so that + // multiple dashboards could have access to the same widget template modification. + this.widgetTypesService.setNode( + // This is the template we grabbed above with getWidgetType + widgetTemplate, + // We are setting the editor/configurator part of the widget template + "configurator", + // This indicates which node you are changing and we want to change + // the data source providers available for selection in the editor. + WellKnownPathKey.DataSourceProviders, + // We are setting the data sources available for selection in the editor + [ + ErrorUnknownDataSource.providerId, + ErrorForbiddenDataSource.providerId, + ErrorNotFoundDataSource.providerId, + AverageRatingKpiDataSource.providerId, + ] + ); + + // Register the data sources available for injection into the KPI tiles. + // Note: Each tile of a KPI widget is assigned its own instance of a data source + this.providerRegistry.setProviders({ + [ErrorUnknownDataSource.providerId]: { + provide: DATA_SOURCE, + useClass: ErrorUnknownDataSource, + // Any dependencies that need to be injected into the provider must be listed here + deps: [HttpClient], + }, + [ErrorForbiddenDataSource.providerId]: { + provide: DATA_SOURCE, + useClass: ErrorForbiddenDataSource, + deps: [HttpClient], + }, + [ErrorNotFoundDataSource.providerId]: { + provide: DATA_SOURCE, + useClass: ErrorNotFoundDataSource, + deps: [HttpClient], + }, + [AverageRatingKpiDataSource.providerId]: { + provide: DATA_SOURCE, + useClass: AverageRatingKpiDataSource, + // Any dependencies that need to be injected into the provider must be listed here + deps: [HttpClient], + }, + }); + + this.initializeDashboard(); + } + + /** Used for restoring widgets state */ + public reInitializeDashboard(): void { + // destroys the components and their providers so the dashboard can re init data + this.dashboard = undefined; + this.changeDetectorRef.detectChanges(); + + this.initializeDashboard(); + } + + public initializeDashboard(): void { + // We're using a static configuration object for this example (see widgetConfig at the bottom of the file), + // but this is where the widget's configuration could potentially be populated from a database + const kpiWidget = widgetConfig; + const widgetIndex: IWidgets = { + // Complete the KPI widget with information coming from its type definition + [kpiWidget.id]: + this.widgetTypesService.mergeWithWidgetType(kpiWidget), + }; + + // Setting the widget dimensions and position (this is for gridster) + const positions: Record = { + [kpiWidget.id]: { + cols: 4, + rows: 6, + y: 0, + x: 0, + }, + }; + + // Finally, assigning the variables we created above to the dashboard + this.dashboard = { + positions, + widgets: widgetIndex, + }; + } +} + +const widgetConfig: IWidget = { + id: "widget1", + type: "kpi", + pizzagna: { + [PizzagnaLayer.Configuration]: { + [DEFAULT_PIZZAGNA_ROOT]: { + providers: { + [WellKnownProviders.Refresher]: { + properties: { + // Configuring the refresher interval so that our data source is invoked every ten minutes + interval: 60 * 10, + enabled: true, + } as IRefresherProperties, + } as Partial, + }, + }, + header: { + properties: { + title: "Harry Potter and the Sorcerer's Stone", + subtitle: "By J. K. Rowling", + }, + }, + tiles: { + properties: { + nodes: ["kpi1"], + }, + }, + kpi1: { + id: "kpi1", + componentType: KpiComponent.lateLoadKey, + properties: { + widgetData: { + units: "out of 5 Stars", + label: "Average Rating", + }, + }, + providers: { + [WellKnownProviders.DataSource]: { + // Setting the data source providerId for the tile with id "kpi1" + providerId: ErrorUnknownDataSource.providerId, + } as IProviderConfiguration, + [WellKnownProviders.Adapter]: { + providerId: NOVA_KPI_DATASOURCE_ADAPTER, + properties: { + componentId: "kpi1", + propertyPath: "widgetData", + }, + } as IProviderConfiguration, + }, + }, + }, + }, +}; +\\\\\\\`, + "tutorials/widget-error-handling/widget-error-handling.module.ts": \\\\\\\`// © 2022 SolarWinds Worldwide, LLC. All rights reserved. +// +// Permission is hereby granted, free of charge, to any person obtaining a copy +// of this software and associated documentation files (the "Software"), to +// deal in the Software without restriction, including without limitation the +// rights to use, copy, modify, merge, publish, distribute, sublicense, and/or +// sell copies of the Software, and to permit persons to whom the Software is +// furnished to do so, subject to the following conditions: +// +// The above copyright notice and this permission notice shall be included in +// all copies or substantial portions of the Software. +// +// THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR +// IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, +// FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE +// AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER +// LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, +// OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN +// THE SOFTWARE. + +import { CommonModule } from "@angular/common"; +import { HttpClientModule } from "@angular/common/http"; +import { NgModule } from "@angular/core"; +import { ReactiveFormsModule } from "@angular/forms"; +import { RouterModule } from "@angular/router"; + +import { + NuiButtonModule, + NuiDocsModule, + NuiFormFieldModule, + NuiIconModule, + NuiMessageModule, + NuiSwitchModule, + NuiTextboxModule, + DEMO_PATH_TOKEN, +} from "@nova-ui/bits"; +import { + NuiDashboardConfiguratorModule, + NuiDashboardsModule, +} from "@nova-ui/dashboards"; + +import { WidgetErrorHandlingDocsComponent } from "./widget-error-handling-docs.component"; +import { WidgetErrorHandlingComponent } from "./widget-error-handling.component"; +import { getDemoFiles } from "../../../../demo-files-factory"; + +const routes = [ + { + path: "", + component: WidgetErrorHandlingDocsComponent, + data: { + srlc: { + hideIndicator: true, + }, + showThemeSwitcher: true, + }, + }, + { + path: "example", + component: WidgetErrorHandlingComponent, + data: { + srlc: { + hideIndicator: true, + }, + }, + }, +]; + +@NgModule({ + imports: [ + CommonModule, + ReactiveFormsModule, + HttpClientModule, + NuiButtonModule, + NuiDashboardsModule, + NuiDashboardConfiguratorModule, + NuiDocsModule, + NuiFormFieldModule, + NuiIconModule, + NuiMessageModule, + NuiIconModule, + NuiTextboxModule, + NuiIconModule, + NuiSwitchModule, + RouterModule.forChild(routes), + ], + declarations: [ + WidgetErrorHandlingDocsComponent, + WidgetErrorHandlingComponent, + ], + providers: [ + { + provide: DEMO_PATH_TOKEN, + useValue: getDemoFiles("widget-error-handling"), + }, + ], +}) +export default class WidgetErrorHandlingModule {} +\\\\\\\`, + "types.ts": \\\\\\\`// © 2022 SolarWinds Worldwide, LLC. All rights reserved. +// +// Permission is hereby granted, free of charge, to any person obtaining a copy +// of this software and associated documentation files (the "Software"), to +// deal in the Software without restriction, including without limitation the +// rights to use, copy, modify, merge, publish, distribute, sublicense, and/or +// sell copies of the Software, and to permit persons to whom the Software is +// furnished to do so, subject to the following conditions: +// +// The above copyright notice and this permission notice shall be included in +// all copies or substantial portions of the Software. +// +// THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR +// IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, +// FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE +// AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER +// LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, +// OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN +// THE SOFTWARE. + +export enum APOLLO_API_NAMESPACE { + COUNTRIES = "countries", +} +\\\\\\\`, + "widget-types/drilldown/drilldown-multi-request-widget/drilldown-multi-request-widget-example.component.ts": \\\\\\\`// © 2022 SolarWinds Worldwide, LLC. All rights reserved. +// +// Permission is hereby granted, free of charge, to any person obtaining a copy +// of this software and associated documentation files (the "Software"), to +// deal in the Software without restriction, including without limitation the +// rights to use, copy, modify, merge, publish, distribute, sublicense, and/or +// sell copies of the Software, and to permit persons to whom the Software is +// furnished to do so, subject to the following conditions: +// +// The above copyright notice and this permission notice shall be included in +// all copies or substantial portions of the Software. +// +// THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR +// IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, +// FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE +// AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER +// LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, +// OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN +// THE SOFTWARE. + +import { HttpClient } from "@angular/common/http"; +import { + ChangeDetectorRef, + Component, + Injectable, + OnDestroy, + OnInit, +} from "@angular/core"; +import { GridsterConfig, GridsterItem } from "angular-gridster2"; +import { Apollo, gql } from "apollo-angular"; +import { BehaviorSubject, Observable, of, Subject } from "rxjs"; +// eslint-disable-next-line import/no-deprecated +import { finalize, map, switchMap, tap } from "rxjs/operators"; + +import { + DataSourceService, + IconStatus, + IDataField, + IFilters, + INovaFilters, +} from "@nova-ui/bits"; +import { + DATA_SOURCE, + DEFAULT_PIZZAGNA_ROOT, + IDashboard, + IDrilldownComponentsConfiguration, + IListWidgetConfiguration, + IProviderConfiguration, + IWidget, + IWidgets, + ListGroupItemComponent, + ListLeafItemComponent, + NOVA_DRILLDOWN_DATASOURCE_ADAPTER, + PizzagnaLayer, + ProviderRegistryService, + WellKnownPathKey, + WellKnownProviders, + WidgetTypesService, +} from "@nova-ui/dashboards"; + +import { APOLLO_API_NAMESPACE } from "../../../types"; + +/** + * A simple KPI data source to retrieve the average rating of Harry Potter and the Sorcerer's Stone (book) via googleapis + */ +@Injectable() +export class DrilldownDataSource + extends DataSourceService + implements OnDestroy +{ + // This is the ID we'll use to identify the provider + public static providerId = "DrilldownDataSource"; + + // Use this subject to communicate the data source's busy state + public busy = new BehaviorSubject(false); + + public dataFields: Partial[] = [ + { id: "Region", label: "Region name" }, + { id: "Subregion", label: "Subregion name" }, + ]; + + private drillState: string[] = []; + private groupBy: string[]; + private cache: any; + private lastDrillState: string[] = []; + private leafGroup: string = "country"; + private applyFilters$ = new Subject(); + + constructor(private http: HttpClient, private apollo: Apollo) { + super(); + + // TODO: remove Partial in vNext after marking dataType field as optional - NUI-5838 + ( + this.dataFieldsConfig.dataFields$ as BehaviorSubject< + Partial[] + > + ).next(this.dataFields); + + this.applyFilters$ + // eslint-disable-next-line import/no-deprecated + .pipe(switchMap((filters) => this.getData(filters))) + .subscribe(async (res) => { + this.outputsSubject.next(await this.getFilteredData(res)); + }); + } + + private groupedDataHistory: any[] = []; + + // In this example, getFilteredData is invoked every 10 minutes (Take a look at the refresher + // provider definition in the widget configuration below to see how the interval is set) + public async getFilteredData(data: any): Promise { + return of(data) + .pipe( + map((entries) => { + if (this.isDrillDown()) { + const activeDrillLvl = this.drillState.length; + const group = this.groupBy[activeDrillLvl]; + const lastGroupedValue = + this.getTransformedDataForGroup( + entries, + group, + getLast(this.drillState) + ); + + this.groupedDataHistory.push(lastGroupedValue); + + return lastGroupedValue; + } + + const mapIconsToEntries = entries.map((item: any) => ({ + ...item, + icon: "virtual-host", + icon_status: IconStatus.Up, + })); + this.groupedDataHistory.push(mapIconsToEntries); + const widgetInput = this.getOutput(entries); + + return widgetInput; + }) + ) + .toPromise(); + } + + public ngOnDestroy(): void { + this.outputsSubject.complete(); + } + + // redefine parent method + public async applyFilters(): Promise { + this.applyFilters$.next(this.getFilters()); + } + + private getQuery(key: string, value: string) { + const groupToRequestMap: Record = { + Region: \\\\\\\\\\\\\\\`{ Region { name } }\\\\\\\\\\\\\\\`, + Subregion: \\\\\\\\\\\\\\\`{ Subregion(filter: { region: { name: "\\\\\\\\\\\\\\\${value}" } } ) { name } }\\\\\\\\\\\\\\\`, + Country: \\\\\\\\\\\\\\\`{ Country(filter: { subregion: { name: "\\\\\\\\\\\\\\\${value}" } } ) { name capital } }\\\\\\\\\\\\\\\`, + }; + + return gql\\\\\\\\\\\\\\\` + \\\\\\\\\\\\\\\${groupToRequestMap[key]} + \\\\\\\\\\\\\\\`; + } + + private getData(filters: INovaFilters): Observable { + this.drillState = filters.drillstate?.value; + this.groupBy = filters.group?.value; + const group = this.groupBy[this.drillState.length]; + const isDrillUp = this.drillState.length < this.lastDrillState.length; + + this.lastDrillState = [...this.drillState]; + + if (!this.drillState.length) { + this.groupedDataHistory.length = 0; + } + + this.busy.next(true); + + if (this.cache && (isDrillUp || this.isHome())) { + return of(this.cache).pipe( + map((data) => data.data[group]), + finalize(() => this.busy.next(false)) + ); + } else { + return this.apollo + .use(APOLLO_API_NAMESPACE.COUNTRIES) + .query({ + query: this.getQuery( + group || this.leafGroup, + getLast(this.drillState) + ), + }) + .pipe( + tap( + (data) => + (this.cache = { + data: { ...this.cache?.data, ...data?.data }, + }) + ), + map((data) => data.data[group || this.leafGroup]), + finalize(() => this.busy.next(false)) + ); + } + } + + private getTransformedDataForGroup( + data: any, + group: string, + drillStateValue: string + ) { + const fallback: string = \\\\\\\\\\\\\\\`No \\\\\\\\\\\\\\\${group} for \\\\\\\\\\\\\\\${drillStateValue}\\\\\\\\\\\\\\\`; + const dataArr = Object.values(data).map((val: any) => ({ + id: val.name || fallback, + label: val.name || fallback, + statuses: [ + { key: "state_ok", value: val.name?.length }, + { + key: "status_unreachable", + value: generateNumberUpTo(100000), + }, + { key: "status_warning", value: generateNumberUpTo(10000) }, + { key: "status_unknown", value: generateNumberUpTo(1000) }, + ], + })); + + return dataArr; + } + + private isHome(): boolean { + return this.drillState.length === 0; + } + + private isDrillDown(): boolean { + return this.drillState.length !== this.groupBy.length; + } + + private getOutput(data: any) { + if (this.isHome()) { + this.groupedDataHistory.length = 0; + } + + const lastHistoryValue = getLast(this.groupedDataHistory); + + if (!lastHistoryValue) { + return data; + } + + return lastHistoryValue[getLast(this.drillState)] || lastHistoryValue; + } +} + +@Component({ + selector: "drilldown-multi-request-widget-example", + templateUrl: "./drilldown-multi-request-widget-example.component.html", + styleUrls: ["./drilldown-multi-request-widget-example.component.less"], + standalone: false, +}) +export class DrilldownMultiRequestWidgetExampleComponent implements OnInit { + // This variable will hold all the data needed to define the layout and behavior of the widgets. + // Pass this to the dashboard component's dashboard input in the template. + public dashboard: IDashboard | undefined; + + // Angular gridster requires a configuration object even if it's empty. + // Pass this to the dashboard component's gridsterConfig input in the template. + public gridsterConfig: GridsterConfig = {}; + + // Boolean passed as an input to the dashboard. When true, widgets can be moved, resized, removed, or edited + public editMode: boolean = false; + + constructor( + // WidgetTypesService provides the widget's necessary structure information + private widgetTypesService: WidgetTypesService, + private providerRegistry: ProviderRegistryService, + private changeDetectorRef: ChangeDetectorRef + ) {} + + public ngOnInit(): void { + // this.prepareNovaDashboards(); + this.initializeDashboard(); + const widgetTemplate = this.widgetTypesService.getWidgetType( + "drilldown", + 1 + ); + this.widgetTypesService.setNode( + widgetTemplate, + "configurator", + WellKnownPathKey.DataSourceProviders, + [DrilldownDataSource.providerId] + ); + + // Registering the data source for injection into the KPI tile. + // Note: Each tile of a KPI widget is assigned its own instance of the data source + this.providerRegistry.setProviders({ + [DrilldownDataSource.providerId]: { + provide: DATA_SOURCE, + useClass: DrilldownDataSource, + // Any dependencies that need to be injected into the provider must be listed here + deps: [HttpClient, Apollo], + }, + }); + } + + /** Used for restoring widgets state */ + public reInitializeDashboard(): void { + // destroys the components and their providers so the dashboard can re init data + this.dashboard = undefined; + this.changeDetectorRef.detectChanges(); + + this.initializeDashboard(); + } + + public initializeDashboard(): void { + // We're using a static configuration object for this example, but this is where + // the widget's configuration could potentially be populated from a database + const drilldownWidget = widgetConfig; + const widgets: IWidgets = { + // Complete the widget with information coming from its type definition + [drilldownWidget.id]: + this.widgetTypesService.mergeWithWidgetType(drilldownWidget), + }; + + // Setting the widget dimensions and position (this is for gridster) + const positions: Record = { + [drilldownWidget.id]: { + cols: 10, + rows: 10, + y: 0, + x: 0, + }, + }; + + // Finally, assigning the variables we created above to the dashboard + this.dashboard = { positions, widgets }; + } +} + +const widgetConfig: IWidget = { + id: "drilldown", + type: "drilldown", + pizzagna: { + [PizzagnaLayer.Configuration]: { + header: { + properties: { + title: "Drilldown Widget", + subtitle: "Countries BY continent THEN currency", + }, + }, + [DEFAULT_PIZZAGNA_ROOT]: { + providers: { + [WellKnownProviders.DataSource]: { + // Setting the data source providerId for the tile with id "kpi1" + providerId: DrilldownDataSource.providerId, + properties: {}, + } as IProviderConfiguration, + }, + }, + listWidget: { + providers: { + [WellKnownProviders.Adapter]: { + providerId: NOVA_DRILLDOWN_DATASOURCE_ADAPTER, + properties: { + // widget + navigationBarId: "navigationBar", + componentId: "listWidget", + dataPath: "data", + + // adapter props + drillstate: [], + groups: ["Region", "Subregion"], + groupBy: ["Region", "Subregion"], + + // components + componentsConfig: { + group: { + componentType: + ListGroupItemComponent.lateLoadKey, + properties: { + dataFieldIds: { + id: "id", + label: "label", + statuses: "statuses", + }, + }, + itemProperties: { + canNavigate: true, + }, + }, + leaf: { + componentType: + ListLeafItemComponent.lateLoadKey, + properties: { + dataFieldIds: { + icon: "icon", + status: "icon_status", + detailedUrl: "capital", + label: "name", + }, + }, + itemProperties: { + canNavigate: false, + }, + }, + } as IDrilldownComponentsConfiguration, + }, + }, + }, + properties: { + configuration: { + // FORMAT: + // componentType: ListLeafItemComponent.lateLoadKey, + // properties: { + // dataFieldIds: { + // icon: "", + // status: "code", + // detailedUrl: "capital", + // label: "name", + // }, + // }, + // + } as IListWidgetConfiguration, + }, + }, + }, + }, +}; + +const getLast = (arr: any[]) => arr[arr.length - 1]; + +const generateNumberUpTo = (upperLimit: number): number => + Math.floor(Math.random() * upperLimit + 1); +\\\\\\\`, + "widget-types/drilldown/drilldown-widget/data-mock.ts": \\\\\\\`// © 2022 SolarWinds Worldwide, LLC. All rights reserved. +// +// Permission is hereby granted, free of charge, to any person obtaining a copy +// of this software and associated documentation files (the "Software"), to +// deal in the Software without restriction, including without limitation the +// rights to use, copy, modify, merge, publish, distribute, sublicense, and/or +// sell copies of the Software, and to permit persons to whom the Software is +// furnished to do so, subject to the following conditions: +// +// The above copyright notice and this permission notice shall be included in +// all copies or substantial portions of the Software. +// +// THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR +// IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, +// FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE +// AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER +// LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, +// OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN +// THE SOFTWARE. + +import { IconStatus } from "@nova-ui/bits"; + +export const GRAPH_DATA_MOCK = { + data: { + countries: [ + { + name: "Andorra", + code: "AD", + icon: "virtual-host", + icon_status: IconStatus.Up, + capital: "Andorra la Vella", + continent: { + name: "Europe", + }, + currency: "EUR", + languages: [ + { + name: "Catalan", + }, + ], + url: "https://en.wikipedia.org/wiki/Andorra", + }, + { + name: "United Arab Emirates", + code: "AE", + icon: "virtual-host", + icon_status: IconStatus.Up, + capital: "Abu Dhabi", + continent: { + name: "Asia", + }, + currency: "AED", + languages: [ + { + name: "Arabic", + }, + ], + url: "https://en.wikipedia.org/wiki/United_Arab_Emirates", + }, + { + name: "Afghanistan", + code: "AF", + icon: "virtual-host", + icon_status: IconStatus.Up, + capital: "Kabul", + continent: { + name: "Asia", + }, + currency: "AFN", + languages: [ + { + name: "Pashto", + }, + { + name: "Uzbek", + }, + { + name: "Turkmen", + }, + ], + url: "https://en.wikipedia.org/wiki/Afghanistan", + }, + { + name: "Antigua and Barbuda", + code: "AG", + icon: "virtual-host", + icon_status: IconStatus.Up, + capital: "Saint John's", + continent: { + name: "North America", + }, + currency: "XCD", + languages: [ + { + name: "English", + }, + ], + url: "https://en.wikipedia.org/wiki/Antigua_and_Barbuda", + }, + { + name: "Anguilla", + code: "AI", + icon: "virtual-host", + icon_status: IconStatus.Up, + capital: "The Valley", + continent: { + name: "North America", + }, + currency: "XCD", + languages: [ + { + name: "English", + }, + ], + url: "https://en.wikipedia.org/wiki/Anguilla", + }, + { + name: "Albania", + code: "AL", + icon: "virtual-host", + icon_status: IconStatus.Up, + capital: "Tirana", + continent: { + name: "Europe", + }, + currency: "ALL", + languages: [ + { + name: "Albanian", + }, + ], + url: "https://en.wikipedia.org/wiki/Albania", + }, + { + name: "Armenia", + code: "AM", + icon: "virtual-host", + icon_status: IconStatus.Up, + capital: "Yerevan", + continent: { + name: "Asia", + }, + currency: "AMD", + languages: [ + { + name: "Armenian", + }, + { + name: "Russian", + }, + ], + url: "https://en.wikipedia.org/wiki/Armenia", + }, + { + name: "Angola", + code: "AO", + icon: "virtual-host", + icon_status: IconStatus.Up, + capital: "Luanda", + continent: { + name: "Africa", + }, + currency: "AOA", + languages: [ + { + name: "Portuguese", + }, + ], + }, + { + name: "Antarctica", + code: "AQ", + icon: "virtual-host", + icon_status: IconStatus.Up, + capital: null, + continent: { + name: "Antarctica", + }, + currency: null, + languages: [], + }, + { + name: "Argentina", + code: "AR", + icon: "virtual-host", + icon_status: IconStatus.Up, + capital: "Buenos Aires", + continent: { + name: "South America", + }, + currency: "ARS", + languages: [ + { + name: "Spanish", + }, + { + name: "Guarani", + }, + ], + }, + { + name: "American Samoa", + code: "AS", + icon: "virtual-host", + icon_status: IconStatus.Up, + capital: "Pago Pago", + continent: { + name: "Oceania", + }, + currency: "USD", + languages: [ + { + name: "English", + }, + { + name: "Samoan", + }, + ], + }, + { + name: "Austria", + code: "AT", + icon: "virtual-host", + icon_status: IconStatus.Up, + capital: "Vienna", + continent: { + name: "Europe", + }, + currency: "EUR", + languages: [ + { + name: "German", + }, + ], + }, + { + name: "Australia", + code: "AU", + icon: "virtual-host", + icon_status: IconStatus.Up, + capital: "Canberra", + continent: { + name: "Oceania", + }, + currency: "AUD", + languages: [ + { + name: "English", + }, + ], + }, + { + name: "Aruba", + code: "AW", + icon: "virtual-host", + icon_status: IconStatus.Up, + capital: "Oranjestad", + continent: { + name: "North America", + }, + currency: "AWG", + languages: [ + { + name: "Dutch", + }, + { + name: "Panjabi / Punjabi", + }, + ], + }, + { + name: "Åland", + code: "AX", + icon: "virtual-host", + icon_status: IconStatus.Up, + capital: "Mariehamn", + continent: { + name: "Europe", + }, + currency: "EUR", + languages: [ + { + name: "Swedish", + }, + ], + }, + { + name: "Azerbaijan", + code: "AZ", + icon: "virtual-host", + icon_status: IconStatus.Up, + capital: "Baku", + continent: { + name: "Asia", + }, + currency: "AZN", + languages: [ + { + name: "Azerbaijani", + }, + ], + }, + { + name: "Bosnia and Herzegovina", + code: "BA", + icon: "virtual-host", + icon_status: IconStatus.Up, + capital: "Sarajevo", + continent: { + name: "Europe", + }, + currency: "BAM", + languages: [ + { + name: "Bosnian", + }, + { + name: "Croatian", + }, + { + name: "Serbian", + }, + ], + }, + { + name: "Barbados", + code: "BB", + icon: "virtual-host", + icon_status: IconStatus.Up, + capital: "Bridgetown", + continent: { + name: "North America", + }, + currency: "BBD", + languages: [ + { + name: "English", + }, + ], + }, + { + name: "Bangladesh", + code: "BD", + icon: "virtual-host", + icon_status: IconStatus.Up, + capital: "Dhaka", + continent: { + name: "Asia", + }, + currency: "BDT", + languages: [ + { + name: "Bengali", + }, + ], + }, + { + name: "Belgium", + code: "BE", + icon: "virtual-host", + icon_status: IconStatus.Up, + capital: "Brussels", + continent: { + name: "Europe", + }, + currency: "EUR", + languages: [ + { + name: "Dutch", + }, + { + name: "French", + }, + { + name: "German", + }, + ], + }, + { + name: "Burkina Faso", + code: "BF", + icon: "virtual-host", + icon_status: IconStatus.Up, + capital: "Ouagadougou", + continent: { + name: "Africa", + }, + currency: "XOF", + languages: [ + { + name: "French", + }, + { + name: "Peul", + }, + ], + }, + { + name: "Bulgaria", + code: "BG", + icon: "virtual-host", + icon_status: IconStatus.Up, + capital: "Sofia", + continent: { + name: "Europe", + }, + currency: "BGN", + languages: [ + { + name: "Bulgarian", + }, + ], + }, + { + name: "Bahrain", + code: "BH", + icon: "virtual-host", + icon_status: IconStatus.Up, + capital: "Manama", + continent: { + name: "Asia", + }, + currency: "BHD", + languages: [ + { + name: "Arabic", + }, + ], + }, + { + name: "Burundi", + code: "BI", + icon: "virtual-host", + icon_status: IconStatus.Up, + capital: "Bujumbura", + continent: { + name: "Africa", + }, + currency: "BIF", + languages: [ + { + name: "French", + }, + { + name: "Kirundi", + }, + ], + }, + { + name: "Benin", + code: "BJ", + icon: "virtual-host", + icon_status: IconStatus.Up, + capital: "Porto-Novo", + continent: { + name: "Africa", + }, + currency: "XOF", + languages: [ + { + name: "French", + }, + ], + }, + { + name: "Saint Barthélemy", + code: "BL", + icon: "virtual-host", + icon_status: IconStatus.Up, + capital: "Gustavia", + continent: { + name: "North America", + }, + currency: "EUR", + languages: [ + { + name: "French", + }, + ], + }, + { + name: "Bermuda", + code: "BM", + icon: "virtual-host", + icon_status: IconStatus.Up, + capital: "Hamilton", + continent: { + name: "North America", + }, + currency: "BMD", + languages: [ + { + name: "English", + }, + ], + }, + { + name: "Brunei", + code: "BN", + icon: "virtual-host", + icon_status: IconStatus.Up, + capital: "Bandar Seri Begawan", + continent: { + name: "Asia", + }, + currency: "BND", + languages: [ + { + name: "Malay", + }, + ], + }, + { + name: "Bolivia", + code: "BO", + icon: "virtual-host", + icon_status: IconStatus.Up, + capital: "Sucre", + continent: { + name: "South America", + }, + currency: "BOB,BOV", + languages: [ + { + name: "Spanish", + }, + { + name: "Aymara", + }, + { + name: "Quechua", + }, + ], + }, + { + name: "Bonaire", + code: "BQ", + icon: "virtual-host", + icon_status: IconStatus.Up, + capital: "Kralendijk", + continent: { + name: "North America", + }, + currency: "USD", + languages: [ + { + name: "Dutch", + }, + ], + }, + { + name: "Brazil", + code: "BR", + icon: "virtual-host", + icon_status: IconStatus.Up, + capital: "Brasília", + continent: { + name: "South America", + }, + currency: "BRL", + languages: [ + { + name: "Portuguese", + }, + ], + }, + { + name: "Bahamas", + code: "BS", + icon: "virtual-host", + icon_status: IconStatus.Up, + capital: "Nassau", + continent: { + name: "North America", + }, + currency: "BSD", + languages: [ + { + name: "English", + }, + ], + }, + { + name: "Bhutan", + code: "BT", + icon: "virtual-host", + icon_status: IconStatus.Up, + capital: "Thimphu", + continent: { + name: "Asia", + }, + currency: "BTN,INR", + languages: [ + { + name: "Dzongkha", + }, + ], + }, + { + name: "Bouvet Island", + code: "BV", + icon: "virtual-host", + icon_status: IconStatus.Up, + capital: null, + continent: { + name: "Antarctica", + }, + currency: "NOK", + languages: [ + { + name: "Norwegian", + }, + { + name: "Norwegian Bokmål", + }, + { + name: "Norwegian Nynorsk", + }, + ], + }, + { + name: "Botswana", + code: "BW", + icon: "virtual-host", + icon_status: IconStatus.Up, + capital: "Gaborone", + continent: { + name: "Africa", + }, + currency: "BWP", + languages: [ + { + name: "English", + }, + { + name: "Tswana", + }, + ], + }, + { + name: "Belarus", + code: "BY", + icon: "virtual-host", + icon_status: IconStatus.Up, + capital: "Minsk", + continent: { + name: "Europe", + }, + currency: "BYN", + languages: [ + { + name: "Belarusian", + }, + { + name: "Russian", + }, + ], + }, + { + name: "Belize", + code: "BZ", + icon: "virtual-host", + icon_status: IconStatus.Up, + capital: "Belmopan", + continent: { + name: "North America", + }, + currency: "BZD", + languages: [ + { + name: "English", + }, + { + name: "Spanish", + }, + ], + }, + { + name: "Canada", + code: "CA", + icon: "virtual-host", + icon_status: IconStatus.Up, + capital: "Ottawa", + continent: { + name: "North America", + }, + currency: "CAD", + languages: [ + { + name: "English", + }, + { + name: "French", + }, + ], + }, + { + name: "Cocos [Keeling] Islands", + code: "CC", + icon: "virtual-host", + icon_status: IconStatus.Up, + capital: "West Island", + continent: { + name: "Asia", + }, + currency: "AUD", + languages: [ + { + name: "English", + }, + ], + }, + { + name: "Democratic Republic of the Congo", + code: "CD", + icon: "virtual-host", + icon_status: IconStatus.Up, + capital: "Kinshasa", + continent: { + name: "Africa", + }, + currency: "CDF", + languages: [ + { + name: "French", + }, + { + name: "Lingala", + }, + { + name: "Kongo", + }, + { + name: "Swahili", + }, + { + name: "Luba-Katanga", + }, + ], + }, + { + name: "Central African Republic", + code: "CF", + icon: "virtual-host", + icon_status: IconStatus.Up, + capital: "Bangui", + continent: { + name: "Africa", + }, + currency: "XAF", + languages: [ + { + name: "French", + }, + { + name: "Sango", + }, + ], + }, + { + name: "Republic of the Congo", + code: "CG", + icon: "virtual-host", + icon_status: IconStatus.Up, + capital: "Brazzaville", + continent: { + name: "Africa", + }, + currency: "XAF", + languages: [ + { + name: "French", + }, + { + name: "Lingala", + }, + ], + }, + { + name: "Switzerland", + code: "CH", + icon: "virtual-host", + icon_status: IconStatus.Up, + capital: "Bern", + continent: { + name: "Europe", + }, + currency: "CHE,CHF,CHW", + languages: [ + { + name: "German", + }, + { + name: "French", + }, + { + name: "Italian", + }, + ], + }, + { + name: "Ivory Coast", + code: "CI", + icon: "virtual-host", + icon_status: IconStatus.Up, + capital: "Yamoussoukro", + continent: { + name: "Africa", + }, + currency: "XOF", + languages: [ + { + name: "French", + }, + ], + }, + { + name: "Cook Islands", + code: "CK", + icon: "virtual-host", + icon_status: IconStatus.Up, + capital: "Avarua", + continent: { + name: "Oceania", + }, + currency: "NZD", + languages: [ + { + name: "English", + }, + ], + }, + { + name: "Chile", + code: "CL", + icon: "virtual-host", + icon_status: IconStatus.Up, + capital: "Santiago", + continent: { + name: "South America", + }, + currency: "CLF,CLP", + languages: [ + { + name: "Spanish", + }, + ], + }, + { + name: "Cameroon", + code: "CM", + icon: "virtual-host", + icon_status: IconStatus.Up, + capital: "Yaoundé", + continent: { + name: "Africa", + }, + currency: "XAF", + languages: [ + { + name: "English", + }, + { + name: "French", + }, + ], + }, + { + name: "China", + code: "CN", + icon: "virtual-host", + icon_status: IconStatus.Up, + capital: "Beijing", + continent: { + name: "Asia", + }, + currency: "CNY", + languages: [ + { + name: "Chinese", + }, + ], + }, + { + name: "Colombia", + code: "CO", + icon: "virtual-host", + icon_status: IconStatus.Up, + capital: "Bogotá", + continent: { + name: "South America", + }, + currency: "COP", + languages: [ + { + name: "Spanish", + }, + ], + }, + { + name: "Costa Rica", + code: "CR", + icon: "virtual-host", + icon_status: IconStatus.Up, + capital: "San José", + continent: { + name: "North America", + }, + currency: "CRC", + languages: [ + { + name: "Spanish", + }, + ], + }, + { + name: "Cuba", + code: "CU", + icon: "virtual-host", + icon_status: IconStatus.Up, + capital: "Havana", + continent: { + name: "North America", + }, + currency: "CUC,CUP", + languages: [ + { + name: "Spanish", + }, + ], + }, + { + name: "Cape Verde", + code: "CV", + icon: "virtual-host", + icon_status: IconStatus.Up, + capital: "Praia", + continent: { + name: "Africa", + }, + currency: "CVE", + languages: [ + { + name: "Portuguese", + }, + ], + }, + { + name: "Curacao", + code: "CW", + icon: "virtual-host", + icon_status: IconStatus.Up, + capital: "Willemstad", + continent: { + name: "North America", + }, + currency: "ANG", + languages: [ + { + name: "Dutch", + }, + { + name: "Panjabi / Punjabi", + }, + { + name: "English", + }, + ], + }, + { + name: "Christmas Island", + code: "CX", + icon: "virtual-host", + icon_status: IconStatus.Up, + capital: "Flying Fish Cove", + continent: { + name: "Asia", + }, + currency: "AUD", + languages: [ + { + name: "English", + }, + ], + }, + { + name: "Cyprus", + code: "CY", + icon: "virtual-host", + icon_status: IconStatus.Up, + capital: "Nicosia", + continent: { + name: "Europe", + }, + currency: "EUR", + languages: [ + { + name: "Greek", + }, + { + name: "Turkish", + }, + { + name: "Armenian", + }, + ], + }, + { + name: "Czech Republic", + code: "CZ", + icon: "virtual-host", + icon_status: IconStatus.Up, + capital: "Prague", + continent: { + name: "Europe", + }, + currency: "CZK", + languages: [ + { + name: "Czech", + }, + { + name: "Slovak", + }, + ], + }, + { + name: "Germany", + code: "DE", + icon: "virtual-host", + icon_status: IconStatus.Up, + capital: "Berlin", + continent: { + name: "Europe", + }, + currency: "EUR", + languages: [ + { + name: "German", + }, + ], + }, + { + name: "Djibouti", + code: "DJ", + icon: "virtual-host", + icon_status: IconStatus.Up, + capital: "Djibouti", + continent: { + name: "Africa", + }, + currency: "DJF", + languages: [ + { + name: "French", + }, + { + name: "Arabic", + }, + ], + }, + { + name: "Denmark", + code: "DK", + icon: "virtual-host", + icon_status: IconStatus.Up, + capital: "Copenhagen", + continent: { + name: "Europe", + }, + currency: "DKK", + languages: [ + { + name: "Danish", + }, + ], + }, + { + name: "Dominica", + code: "DM", + icon: "virtual-host", + icon_status: IconStatus.Up, + capital: "Roseau", + continent: { + name: "North America", + }, + currency: "XCD", + languages: [ + { + name: "English", + }, + ], + }, + { + name: "Dominican Republic", + code: "DO", + icon: "virtual-host", + icon_status: IconStatus.Up, + capital: "Santo Domingo", + continent: { + name: "North America", + }, + currency: "DOP", + languages: [ + { + name: "Spanish", + }, + ], + }, + { + name: "Algeria", + code: "DZ", + icon: "virtual-host", + icon_status: IconStatus.Up, + capital: "Algiers", + continent: { + name: "Africa", + }, + currency: "DZD", + languages: [ + { + name: "Arabic", + }, + ], + }, + { + name: "Ecuador", + code: "EC", + icon: "virtual-host", + icon_status: IconStatus.Up, + capital: "Quito", + continent: { + name: "South America", + }, + currency: "USD", + languages: [ + { + name: "Spanish", + }, + ], + }, + { + name: "Estonia", + code: "EE", + icon: "virtual-host", + icon_status: IconStatus.Up, + capital: "Tallinn", + continent: { + name: "Europe", + }, + currency: "EUR", + languages: [ + { + name: "Estonian", + }, + ], + }, + { + name: "Egypt", + code: "EG", + icon: "virtual-host", + icon_status: IconStatus.Up, + capital: "Cairo", + continent: { + name: "Africa", + }, + currency: "EGP", + languages: [ + { + name: "Arabic", + }, + ], + }, + { + name: "Western Sahara", + code: "EH", + icon: "virtual-host", + icon_status: IconStatus.Up, + capital: "El Aaiún", + continent: { + name: "Africa", + }, + currency: "MAD,DZD,MRU", + languages: [ + { + name: "Spanish", + }, + ], + }, + { + name: "Eritrea", + code: "ER", + icon: "virtual-host", + icon_status: IconStatus.Up, + capital: "Asmara", + continent: { + name: "Africa", + }, + currency: "ERN", + languages: [ + { + name: "Tigrinya", + }, + { + name: "Arabic", + }, + { + name: "English", + }, + ], + }, + { + name: "Spain", + code: "ES", + icon: "virtual-host", + icon_status: IconStatus.Up, + capital: "Madrid", + continent: { + name: "Europe", + }, + currency: "EUR", + languages: [ + { + name: "Spanish", + }, + { + name: "Basque", + }, + { + name: "Catalan", + }, + { + name: "Galician", + }, + { + name: "Occitan", + }, + ], + }, + { + name: "Ethiopia", + code: "ET", + icon: "virtual-host", + icon_status: IconStatus.Up, + capital: "Addis Ababa", + continent: { + name: "Africa", + }, + currency: "ETB", + languages: [ + { + name: "Amharic", + }, + ], + }, + { + name: "Finland", + code: "FI", + icon: "virtual-host", + icon_status: IconStatus.Up, + capital: "Helsinki", + continent: { + name: "Europe", + }, + currency: "EUR", + languages: [ + { + name: "Finnish", + }, + { + name: "Swedish", + }, + ], + }, + { + name: "Fiji", + code: "FJ", + icon: "virtual-host", + icon_status: IconStatus.Up, + capital: "Suva", + continent: { + name: "Oceania", + }, + currency: "FJD", + languages: [ + { + name: "English", + }, + { + name: "Fijian", + }, + { + name: "Hindi", + }, + { + name: "Urdu", + }, + ], + }, + { + name: "Falkland Islands", + code: "FK", + icon: "virtual-host", + icon_status: IconStatus.Up, + capital: "Stanley", + continent: { + name: "South America", + }, + currency: "FKP", + languages: [ + { + name: "English", + }, + ], + }, + { + name: "Micronesia", + code: "FM", + icon: "virtual-host", + icon_status: IconStatus.Up, + capital: "Palikir", + continent: { + name: "Oceania", + }, + currency: "USD", + languages: [ + { + name: "English", + }, + ], + }, + { + name: "Faroe Islands", + code: "FO", + icon: "virtual-host", + icon_status: IconStatus.Up, + capital: "Tórshavn", + continent: { + name: "Europe", + }, + currency: "DKK", + languages: [ + { + name: "Faroese", + }, + ], + }, + { + name: "France", + code: "FR", + icon: "virtual-host", + icon_status: IconStatus.Up, + capital: "Paris", + continent: { + name: "Europe", + }, + currency: "EUR", + languages: [ + { + name: "French", + }, + ], + }, + { + name: "Gabon", + code: "GA", + icon: "virtual-host", + icon_status: IconStatus.Up, + capital: "Libreville", + continent: { + name: "Africa", + }, + currency: "XAF", + languages: [ + { + name: "French", + }, + ], + }, + { + name: "United Kingdom", + code: "GB", + icon: "virtual-host", + icon_status: IconStatus.Up, + capital: "London", + continent: { + name: "Europe", + }, + currency: "GBP", + languages: [ + { + name: "English", + }, + ], + }, + { + name: "Grenada", + code: "GD", + icon: "virtual-host", + icon_status: IconStatus.Up, + capital: "St. George's", + continent: { + name: "North America", + }, + currency: "XCD", + languages: [ + { + name: "English", + }, + ], + }, + { + name: "Georgia", + code: "GE", + icon: "virtual-host", + icon_status: IconStatus.Up, + capital: "Tbilisi", + continent: { + name: "Asia", + }, + currency: "GEL", + languages: [ + { + name: "Georgian", + }, + ], + }, + { + name: "French Guiana", + code: "GF", + icon: "virtual-host", + icon_status: IconStatus.Up, + capital: "Cayenne", + continent: { + name: "South America", + }, + currency: "EUR", + languages: [ + { + name: "French", + }, + ], + }, + { + name: "Guernsey", + code: "GG", + icon: "virtual-host", + icon_status: IconStatus.Up, + capital: "St. Peter Port", + continent: { + name: "Europe", + }, + currency: "GBP", + languages: [ + { + name: "English", + }, + { + name: "French", + }, + ], + }, + { + name: "Ghana", + code: "GH", + icon: "virtual-host", + icon_status: IconStatus.Up, + capital: "Accra", + continent: { + name: "Africa", + }, + currency: "GHS", + languages: [ + { + name: "English", + }, + ], + }, + { + name: "Gibraltar", + code: "GI", + icon: "virtual-host", + icon_status: IconStatus.Up, + capital: "Gibraltar", + continent: { + name: "Europe", + }, + currency: "GIP", + languages: [ + { + name: "English", + }, + ], + }, + { + name: "Greenland", + code: "GL", + icon: "virtual-host", + icon_status: IconStatus.Up, + capital: "Nuuk", + continent: { + name: "North America", + }, + currency: "DKK", + languages: [ + { + name: "Greenlandic", + }, + ], + }, + { + name: "Gambia", + code: "GM", + icon: "virtual-host", + icon_status: IconStatus.Up, + capital: "Banjul", + continent: { + name: "Africa", + }, + currency: "GMD", + languages: [ + { + name: "English", + }, + ], + }, + { + name: "Guinea", + code: "GN", + icon: "virtual-host", + icon_status: IconStatus.Up, + capital: "Conakry", + continent: { + name: "Africa", + }, + currency: "GNF", + languages: [ + { + name: "French", + }, + { + name: "Peul", + }, + ], + }, + { + name: "Guadeloupe", + code: "GP", + icon: "virtual-host", + icon_status: IconStatus.Up, + capital: "Basse-Terre", + continent: { + name: "North America", + }, + currency: "EUR", + languages: [ + { + name: "French", + }, + ], + }, + { + name: "Equatorial Guinea", + code: "GQ", + icon: "virtual-host", + icon_status: IconStatus.Up, + capital: "Malabo", + continent: { + name: "Africa", + }, + currency: "XAF", + languages: [ + { + name: "Spanish", + }, + { + name: "French", + }, + ], + }, + { + name: "Greece", + code: "GR", + icon: "virtual-host", + icon_status: IconStatus.Up, + capital: "Athens", + continent: { + name: "Europe", + }, + currency: "EUR", + languages: [ + { + name: "Greek", + }, + ], + }, + { + name: "South Georgia and the South Sandwich Islands", + code: "GS", + icon: "virtual-host", + icon_status: IconStatus.Up, + capital: "King Edward Point", + continent: { + name: "Antarctica", + }, + currency: "GBP", + languages: [ + { + name: "English", + }, + ], + }, + { + name: "Guatemala", + code: "GT", + icon: "virtual-host", + icon_status: IconStatus.Up, + capital: "Guatemala City", + continent: { + name: "North America", + }, + currency: "GTQ", + languages: [ + { + name: "Spanish", + }, + ], + }, + { + name: "Guam", + code: "GU", + icon: "virtual-host", + icon_status: IconStatus.Up, + capital: "Hagåtña", + continent: { + name: "Oceania", + }, + currency: "USD", + languages: [ + { + name: "English", + }, + { + name: "Chamorro", + }, + { + name: "Spanish", + }, + ], + }, + { + name: "Guinea-Bissau", + code: "GW", + icon: "virtual-host", + icon_status: IconStatus.Up, + capital: "Bissau", + continent: { + name: "Africa", + }, + currency: "XOF", + languages: [ + { + name: "Portuguese", + }, + ], + }, + { + name: "Guyana", + code: "GY", + icon: "virtual-host", + icon_status: IconStatus.Up, + capital: "Georgetown", + continent: { + name: "South America", + }, + currency: "GYD", + languages: [ + { + name: "English", + }, + ], + }, + { + name: "Hong Kong", + code: "HK", + icon: "virtual-host", + icon_status: IconStatus.Up, + capital: "City of Victoria", + continent: { + name: "Asia", + }, + currency: "HKD", + languages: [ + { + name: "Chinese", + }, + { + name: "English", + }, + ], + }, + { + name: "Heard Island and McDonald Islands", + code: "HM", + icon: "virtual-host", + icon_status: IconStatus.Up, + capital: null, + continent: { + name: "Antarctica", + }, + currency: "AUD", + languages: [ + { + name: "English", + }, + ], + }, + { + name: "Honduras", + code: "HN", + icon: "virtual-host", + icon_status: IconStatus.Up, + capital: "Tegucigalpa", + continent: { + name: "North America", + }, + currency: "HNL", + languages: [ + { + name: "Spanish", + }, + ], + }, + { + name: "Croatia", + code: "HR", + icon: "virtual-host", + icon_status: IconStatus.Up, + capital: "Zagreb", + continent: { + name: "Europe", + }, + currency: "HRK", + languages: [ + { + name: "Croatian", + }, + ], + }, + { + name: "Haiti", + code: "HT", + icon: "virtual-host", + icon_status: IconStatus.Up, + capital: "Port-au-Prince", + continent: { + name: "North America", + }, + currency: "HTG,USD", + languages: [ + { + name: "French", + }, + { + name: "Haitian", + }, + ], + }, + { + name: "Hungary", + code: "HU", + icon: "virtual-host", + icon_status: IconStatus.Up, + capital: "Budapest", + continent: { + name: "Europe", + }, + currency: "HUF", + languages: [ + { + name: "Hungarian", + }, + ], + }, + { + name: "Indonesia", + code: "ID", + icon: "virtual-host", + icon_status: IconStatus.Up, + capital: "Jakarta", + continent: { + name: "Asia", + }, + currency: "IDR", + languages: [ + { + name: "Indonesian", + }, + ], + }, + { + name: "Ireland", + code: "IE", + icon: "virtual-host", + icon_status: IconStatus.Up, + capital: "Dublin", + continent: { + name: "Europe", + }, + currency: "EUR", + languages: [ + { + name: "Irish", + }, + { + name: "English", + }, + ], + }, + { + name: "Israel", + code: "IL", + icon: "virtual-host", + icon_status: IconStatus.Up, + capital: "Jerusalem", + continent: { + name: "Asia", + }, + currency: "ILS", + languages: [ + { + name: "Hebrew", + }, + { + name: "Arabic", + }, + ], + }, + { + name: "Isle of Man", + code: "IM", + icon: "virtual-host", + icon_status: IconStatus.Up, + capital: "Douglas", + continent: { + name: "Europe", + }, + currency: "GBP", + languages: [ + { + name: "English", + }, + { + name: "Manx", + }, + ], + }, + { + name: "India", + code: "IN", + icon: "virtual-host", + icon_status: IconStatus.Up, + capital: "New Delhi", + continent: { + name: "Asia", + }, + currency: "INR", + languages: [ + { + name: "Hindi", + }, + { + name: "English", + }, + ], + }, + { + name: "British Indian Ocean Territory", + code: "IO", + icon: "virtual-host", + icon_status: IconStatus.Up, + capital: "Diego Garcia", + continent: { + name: "Asia", + }, + currency: "USD", + languages: [ + { + name: "English", + }, + ], + }, + { + name: "Iraq", + code: "IQ", + icon: "virtual-host", + icon_status: IconStatus.Up, + capital: "Baghdad", + continent: { + name: "Asia", + }, + currency: "IQD", + languages: [ + { + name: "Arabic", + }, + { + name: "Kurdish", + }, + ], + }, + { + name: "Iran", + code: "IR", + icon: "virtual-host", + icon_status: IconStatus.Up, + capital: "Tehran", + continent: { + name: "Asia", + }, + currency: "IRR", + languages: [ + { + name: "Persian", + }, + ], + }, + { + name: "Iceland", + code: "IS", + icon: "virtual-host", + icon_status: IconStatus.Up, + capital: "Reykjavik", + continent: { + name: "Europe", + }, + currency: "ISK", + languages: [ + { + name: "Icelandic", + }, + ], + }, + { + name: "Italy", + code: "IT", + icon: "virtual-host", + icon_status: IconStatus.Up, + capital: "Rome", + continent: { + name: "Europe", + }, + currency: "EUR", + languages: [ + { + name: "Italian", + }, + ], + }, + { + name: "Jersey", + code: "JE", + icon: "virtual-host", + icon_status: IconStatus.Up, + capital: "Saint Helier", + continent: { + name: "Europe", + }, + currency: "GBP", + languages: [ + { + name: "English", + }, + { + name: "French", + }, + ], + }, + { + name: "Jamaica", + code: "JM", + icon: "virtual-host", + icon_status: IconStatus.Up, + capital: "Kingston", + continent: { + name: "North America", + }, + currency: "JMD", + languages: [ + { + name: "English", + }, + ], + }, + { + name: "Jordan", + code: "JO", + icon: "virtual-host", + icon_status: IconStatus.Up, + capital: "Amman", + continent: { + name: "Asia", + }, + currency: "JOD", + languages: [ + { + name: "Arabic", + }, + ], + }, + { + name: "Japan", + code: "JP", + icon: "virtual-host", + icon_status: IconStatus.Up, + capital: "Tokyo", + continent: { + name: "Asia", + }, + currency: "JPY", + languages: [ + { + name: "Japanese", + }, + ], + }, + { + name: "Kenya", + code: "KE", + icon: "virtual-host", + icon_status: IconStatus.Up, + capital: "Nairobi", + continent: { + name: "Africa", + }, + currency: "KES", + languages: [ + { + name: "English", + }, + { + name: "Swahili", + }, + ], + }, + { + name: "Kyrgyzstan", + code: "KG", + icon: "virtual-host", + icon_status: IconStatus.Up, + capital: "Bishkek", + continent: { + name: "Asia", + }, + currency: "KGS", + languages: [ + { + name: "Kirghiz", + }, + { + name: "Russian", + }, + ], + }, + { + name: "Cambodia", + code: "KH", + icon: "virtual-host", + icon_status: IconStatus.Up, + capital: "Phnom Penh", + continent: { + name: "Asia", + }, + currency: "KHR", + languages: [ + { + name: "Cambodian", + }, + ], + }, + { + name: "Kiribati", + code: "KI", + icon: "virtual-host", + icon_status: IconStatus.Up, + capital: "South Tarawa", + continent: { + name: "Oceania", + }, + currency: "AUD", + languages: [ + { + name: "English", + }, + ], + }, + { + name: "Comoros", + code: "KM", + icon: "virtual-host", + icon_status: IconStatus.Up, + capital: "Moroni", + continent: { + name: "Africa", + }, + currency: "KMF", + languages: [ + { + name: "Arabic", + }, + { + name: "French", + }, + ], + }, + { + name: "Saint Kitts and Nevis", + code: "KN", + icon: "virtual-host", + icon_status: IconStatus.Up, + capital: "Basseterre", + continent: { + name: "North America", + }, + currency: "XCD", + languages: [ + { + name: "English", + }, + ], + }, + { + name: "North Korea", + code: "KP", + icon: "virtual-host", + icon_status: IconStatus.Up, + capital: "Pyongyang", + continent: { + name: "Asia", + }, + currency: "KPW", + languages: [ + { + name: "Korean", + }, + ], + }, + { + name: "South Korea", + code: "KR", + icon: "virtual-host", + icon_status: IconStatus.Up, + capital: "Seoul", + continent: { + name: "Asia", + }, + currency: "KRW", + languages: [ + { + name: "Korean", + }, + ], + }, + { + name: "Kuwait", + code: "KW", + icon: "virtual-host", + icon_status: IconStatus.Up, + capital: "Kuwait City", + continent: { + name: "Asia", + }, + currency: "KWD", + languages: [ + { + name: "Arabic", + }, + ], + }, + { + name: "Cayman Islands", + code: "KY", + icon: "virtual-host", + icon_status: IconStatus.Up, + capital: "George Town", + continent: { + name: "North America", + }, + currency: "KYD", + languages: [ + { + name: "English", + }, + ], + }, + { + name: "Kazakhstan", + code: "KZ", + icon: "virtual-host", + icon_status: IconStatus.Up, + capital: "Astana", + continent: { + name: "Asia", + }, + currency: "KZT", + languages: [ + { + name: "Kazakh", + }, + { + name: "Russian", + }, + ], + }, + { + name: "Laos", + code: "LA", + icon: "virtual-host", + icon_status: IconStatus.Up, + capital: "Vientiane", + continent: { + name: "Asia", + }, + currency: "LAK", + languages: [ + { + name: "Laotian", + }, + ], + }, + { + name: "Lebanon", + code: "LB", + icon: "virtual-host", + icon_status: IconStatus.Up, + capital: "Beirut", + continent: { + name: "Asia", + }, + currency: "LBP", + languages: [ + { + name: "Arabic", + }, + { + name: "French", + }, + ], + }, + { + name: "Saint Lucia", + code: "LC", + icon: "virtual-host", + icon_status: IconStatus.Up, + capital: "Castries", + continent: { + name: "North America", + }, + currency: "XCD", + languages: [ + { + name: "English", + }, + ], + }, + { + name: "Liechtenstein", + code: "LI", + icon: "virtual-host", + icon_status: IconStatus.Up, + capital: "Vaduz", + continent: { + name: "Europe", + }, + currency: "CHF", + languages: [ + { + name: "German", + }, + ], + }, + { + name: "Sri Lanka", + code: "LK", + icon: "virtual-host", + icon_status: IconStatus.Up, + capital: "Colombo", + continent: { + name: "Asia", + }, + currency: "LKR", + languages: [ + { + name: "Sinhalese", + }, + { + name: "Tamil", + }, + ], + }, + { + name: "Liberia", + code: "LR", + icon: "virtual-host", + icon_status: IconStatus.Up, + capital: "Monrovia", + continent: { + name: "Africa", + }, + currency: "LRD", + languages: [ + { + name: "English", + }, + ], + }, + { + name: "Lesotho", + code: "LS", + icon: "virtual-host", + icon_status: IconStatus.Up, + capital: "Maseru", + continent: { + name: "Africa", + }, + currency: "LSL,ZAR", + languages: [ + { + name: "English", + }, + { + name: "Southern Sotho", + }, + ], + }, + { + name: "Lithuania", + code: "LT", + icon: "virtual-host", + icon_status: IconStatus.Up, + capital: "Vilnius", + continent: { + name: "Europe", + }, + currency: "EUR", + languages: [ + { + name: "Lithuanian", + }, + ], + }, + { + name: "Luxembourg", + code: "LU", + icon: "virtual-host", + icon_status: IconStatus.Up, + capital: "Luxembourg", + continent: { + name: "Europe", + }, + currency: "EUR", + languages: [ + { + name: "French", + }, + { + name: "German", + }, + { + name: "Luxembourgish", + }, + ], + }, + { + name: "Latvia", + code: "LV", + icon: "virtual-host", + icon_status: IconStatus.Up, + capital: "Riga", + continent: { + name: "Europe", + }, + currency: "EUR", + languages: [ + { + name: "Latvian", + }, + ], + }, + { + name: "Libya", + code: "LY", + icon: "virtual-host", + icon_status: IconStatus.Up, + capital: "Tripoli", + continent: { + name: "Africa", + }, + currency: "LYD", + languages: [ + { + name: "Arabic", + }, + ], + }, + { + name: "Morocco", + code: "MA", + icon: "virtual-host", + icon_status: IconStatus.Up, + capital: "Rabat", + continent: { + name: "Africa", + }, + currency: "MAD", + languages: [ + { + name: "Arabic", + }, + ], + }, + { + name: "Monaco", + code: "MC", + icon: "virtual-host", + icon_status: IconStatus.Up, + capital: "Monaco", + continent: { + name: "Europe", + }, + currency: "EUR", + languages: [ + { + name: "French", + }, + ], + }, + { + name: "Moldova", + code: "MD", + icon: "virtual-host", + icon_status: IconStatus.Up, + capital: "Chișinău", + continent: { + name: "Europe", + }, + currency: "MDL", + languages: [ + { + name: "Romanian", + }, + ], + }, + { + name: "Montenegro", + code: "ME", + icon: "virtual-host", + icon_status: IconStatus.Up, + capital: "Podgorica", + continent: { + name: "Europe", + }, + currency: "EUR", + languages: [ + { + name: "Serbian", + }, + { + name: "Bosnian", + }, + { + name: "Albanian", + }, + { + name: "Croatian", + }, + ], + }, + { + name: "Saint Martin", + code: "MF", + icon: "virtual-host", + icon_status: IconStatus.Up, + capital: "Marigot", + continent: { + name: "North America", + }, + currency: "EUR", + languages: [ + { + name: "English", + }, + { + name: "French", + }, + { + name: "Dutch", + }, + ], + }, + { + name: "Madagascar", + code: "MG", + icon: "virtual-host", + icon_status: IconStatus.Up, + capital: "Antananarivo", + continent: { + name: "Africa", + }, + currency: "MGA", + languages: [ + { + name: "French", + }, + { + name: "Malagasy", + }, + ], + }, + { + name: "Marshall Islands", + code: "MH", + icon: "virtual-host", + icon_status: IconStatus.Up, + capital: "Majuro", + continent: { + name: "Oceania", + }, + currency: "USD", + languages: [ + { + name: "English", + }, + { + name: "Marshallese", + }, + ], + }, + { + name: "North Macedonia", + code: "MK", + icon: "virtual-host", + icon_status: IconStatus.Up, + capital: "Skopje", + continent: { + name: "Europe", + }, + currency: "MKD", + languages: [ + { + name: "Macedonian", + }, + ], + }, + { + name: "Mali", + code: "ML", + icon: "virtual-host", + icon_status: IconStatus.Up, + capital: "Bamako", + continent: { + name: "Africa", + }, + currency: "XOF", + languages: [ + { + name: "French", + }, + ], + }, + { + name: "Myanmar [Burma]", + code: "MM", + icon: "virtual-host", + icon_status: IconStatus.Up, + capital: "Naypyidaw", + continent: { + name: "Asia", + }, + currency: "MMK", + languages: [ + { + name: "Burmese", + }, + ], + }, + { + name: "Mongolia", + code: "MN", + icon: "virtual-host", + icon_status: IconStatus.Up, + capital: "Ulan Bator", + continent: { + name: "Asia", + }, + currency: "MNT", + languages: [ + { + name: "Mongolian", + }, + ], + }, + { + name: "Macao", + code: "MO", + icon: "virtual-host", + icon_status: IconStatus.Up, + capital: null, + continent: { + name: "Asia", + }, + currency: "MOP", + languages: [ + { + name: "Chinese", + }, + { + name: "Portuguese", + }, + ], + }, + { + name: "Northern Mariana Islands", + code: "MP", + icon: "virtual-host", + icon_status: IconStatus.Up, + capital: "Saipan", + continent: { + name: "Oceania", + }, + currency: "USD", + languages: [ + { + name: "English", + }, + { + name: "Chamorro", + }, + ], + }, + { + name: "Martinique", + code: "MQ", + icon: "virtual-host", + icon_status: IconStatus.Up, + capital: "Fort-de-France", + continent: { + name: "North America", + }, + currency: "EUR", + languages: [ + { + name: "French", + }, + ], + }, + { + name: "Mauritania", + code: "MR", + icon: "virtual-host", + icon_status: IconStatus.Up, + capital: "Nouakchott", + continent: { + name: "Africa", + }, + currency: "MRU", + languages: [ + { + name: "Arabic", + }, + ], + }, + { + name: "Montserrat", + code: "MS", + icon: "virtual-host", + icon_status: IconStatus.Up, + capital: "Plymouth", + continent: { + name: "North America", + }, + currency: "XCD", + languages: [ + { + name: "English", + }, + ], + }, + { + name: "Malta", + code: "MT", + icon: "virtual-host", + icon_status: IconStatus.Up, + capital: "Valletta", + continent: { + name: "Europe", + }, + currency: "EUR", + languages: [ + { + name: "Maltese", + }, + { + name: "English", + }, + ], + }, + { + name: "Mauritius", + code: "MU", + icon: "virtual-host", + icon_status: IconStatus.Up, + capital: "Port Louis", + continent: { + name: "Africa", + }, + currency: "MUR", + languages: [ + { + name: "English", + }, + ], + }, + { + name: "Maldives", + code: "MV", + icon: "virtual-host", + icon_status: IconStatus.Up, + capital: "Malé", + continent: { + name: "Asia", + }, + currency: "MVR", + languages: [ + { + name: "Divehi", + }, + ], + }, + { + name: "Malawi", + code: "MW", + icon: "virtual-host", + icon_status: IconStatus.Up, + capital: "Lilongwe", + continent: { + name: "Africa", + }, + currency: "MWK", + languages: [ + { + name: "English", + }, + { + name: "Chichewa", + }, + ], + }, + { + name: "Mexico", + code: "MX", + icon: "virtual-host", + icon_status: IconStatus.Up, + capital: "Mexico City", + continent: { + name: "North America", + }, + currency: "MXN", + languages: [ + { + name: "Spanish", + }, + ], + }, + { + name: "Malaysia", + code: "MY", + icon: "virtual-host", + icon_status: IconStatus.Up, + capital: "Kuala Lumpur", + continent: { + name: "Asia", + }, + currency: "MYR", + languages: [ + { + name: "Malay", + }, + ], + }, + { + name: "Mozambique", + code: "MZ", + icon: "virtual-host", + icon_status: IconStatus.Up, + capital: "Maputo", + continent: { + name: "Africa", + }, + currency: "MZN", + languages: [ + { + name: "Portuguese", + }, + ], + }, + { + name: "Namibia", + code: "NA", + icon: "virtual-host", + icon_status: IconStatus.Up, + capital: "Windhoek", + continent: { + name: "Africa", + }, + currency: "NAD,ZAR", + languages: [ + { + name: "English", + }, + { + name: "Afrikaans", + }, + ], + }, + { + name: "New Caledonia", + code: "NC", + icon: "virtual-host", + icon_status: IconStatus.Up, + capital: "Nouméa", + continent: { + name: "Oceania", + }, + currency: "XPF", + languages: [ + { + name: "French", + }, + ], + }, + { + name: "Niger", + code: "NE", + icon: "virtual-host", + icon_status: IconStatus.Up, + capital: "Niamey", + continent: { + name: "Africa", + }, + currency: "XOF", + languages: [ + { + name: "French", + }, + ], + }, + { + name: "Norfolk Island", + code: "NF", + icon: "virtual-host", + icon_status: IconStatus.Up, + capital: "Kingston", + continent: { + name: "Oceania", + }, + currency: "AUD", + languages: [ + { + name: "English", + }, + ], + }, + { + name: "Nigeria", + code: "NG", + icon: "virtual-host", + icon_status: IconStatus.Up, + capital: "Abuja", + continent: { + name: "Africa", + }, + currency: "NGN", + languages: [ + { + name: "English", + }, + ], + }, + { + name: "Nicaragua", + code: "NI", + icon: "virtual-host", + icon_status: IconStatus.Up, + capital: "Managua", + continent: { + name: "North America", + }, + currency: "NIO", + languages: [ + { + name: "Spanish", + }, + ], + }, + { + name: "Netherlands", + code: "NL", + icon: "virtual-host", + icon_status: IconStatus.Up, + capital: "Amsterdam", + continent: { + name: "Europe", + }, + currency: "EUR", + languages: [ + { + name: "Dutch", + }, + ], + }, + { + name: "Norway", + code: "NO", + icon: "virtual-host", + icon_status: IconStatus.Up, + capital: "Oslo", + continent: { + name: "Europe", + }, + currency: "NOK", + languages: [ + { + name: "Norwegian", + }, + { + name: "Norwegian Bokmål", + }, + { + name: "Norwegian Nynorsk", + }, + ], + }, + { + name: "Nepal", + code: "NP", + icon: "virtual-host", + icon_status: IconStatus.Up, + capital: "Kathmandu", + continent: { + name: "Asia", + }, + currency: "NPR", + languages: [ + { + name: "Nepali", + }, + ], + }, + { + name: "Nauru", + code: "NR", + icon: "virtual-host", + icon_status: IconStatus.Up, + capital: "Yaren", + continent: { + name: "Oceania", + }, + currency: "AUD", + languages: [ + { + name: "English", + }, + { + name: "Nauruan", + }, + ], + }, + { + name: "Niue", + code: "NU", + icon: "virtual-host", + icon_status: IconStatus.Up, + capital: "Alofi", + continent: { + name: "Oceania", + }, + currency: "NZD", + languages: [ + { + name: "English", + }, + ], + }, + { + name: "New Zealand", + code: "NZ", + icon: "virtual-host", + icon_status: IconStatus.Up, + capital: "Wellington", + continent: { + name: "Oceania", + }, + currency: "NZD", + languages: [ + { + name: "English", + }, + { + name: "Maori", + }, + ], + }, + { + name: "Oman", + code: "OM", + icon: "virtual-host", + icon_status: IconStatus.Up, + capital: "Muscat", + continent: { + name: "Asia", + }, + currency: "OMR", + languages: [ + { + name: "Arabic", + }, + ], + }, + { + name: "Panama", + code: "PA", + icon: "virtual-host", + icon_status: IconStatus.Up, + capital: "Panama City", + continent: { + name: "North America", + }, + currency: "PAB,USD", + languages: [ + { + name: "Spanish", + }, + ], + }, + { + name: "Peru", + code: "PE", + icon: "virtual-host", + icon_status: IconStatus.Up, + capital: "Lima", + continent: { + name: "South America", + }, + currency: "PEN", + languages: [ + { + name: "Spanish", + }, + ], + }, + { + name: "French Polynesia", + code: "PF", + icon: "virtual-host", + icon_status: IconStatus.Up, + capital: "Papeetē", + continent: { + name: "Oceania", + }, + currency: "XPF", + languages: [ + { + name: "French", + }, + ], + }, + { + name: "Papua New Guinea", + code: "PG", + icon: "virtual-host", + icon_status: IconStatus.Up, + capital: "Port Moresby", + continent: { + name: "Oceania", + }, + currency: "PGK", + languages: [ + { + name: "English", + }, + ], + }, + { + name: "Philippines", + code: "PH", + icon: "virtual-host", + icon_status: IconStatus.Up, + capital: "Manila", + continent: { + name: "Asia", + }, + currency: "PHP", + languages: [ + { + name: "English", + }, + ], + }, + { + name: "Pakistan", + code: "PK", + icon: "virtual-host", + icon_status: IconStatus.Up, + capital: "Islamabad", + continent: { + name: "Asia", + }, + currency: "PKR", + languages: [ + { + name: "English", + }, + { + name: "Urdu", + }, + ], + }, + { + name: "Poland", + code: "PL", + icon: "virtual-host", + icon_status: IconStatus.Up, + capital: "Warsaw", + continent: { + name: "Europe", + }, + currency: "PLN", + languages: [ + { + name: "Polish", + }, + ], + }, + { + name: "Saint Pierre and Miquelon", + code: "PM", + icon: "virtual-host", + icon_status: IconStatus.Up, + capital: "Saint-Pierre", + continent: { + name: "North America", + }, + currency: "EUR", + languages: [ + { + name: "French", + }, + ], + }, + { + name: "Pitcairn Islands", + code: "PN", + icon: "virtual-host", + icon_status: IconStatus.Up, + capital: "Adamstown", + continent: { + name: "Oceania", + }, + currency: "NZD", + languages: [ + { + name: "English", + }, + ], + }, + { + name: "Puerto Rico", + code: "PR", + icon: "virtual-host", + icon_status: IconStatus.Up, + capital: "San Juan", + continent: { + name: "North America", + }, + currency: "USD", + languages: [ + { + name: "Spanish", + }, + { + name: "English", + }, + ], + }, + { + name: "Palestine", + code: "PS", + icon: "virtual-host", + icon_status: IconStatus.Up, + capital: "Ramallah", + continent: { + name: "Asia", + }, + currency: "ILS", + languages: [ + { + name: "Arabic", + }, + ], + }, + { + name: "Portugal", + code: "PT", + icon: "virtual-host", + icon_status: IconStatus.Up, + capital: "Lisbon", + continent: { + name: "Europe", + }, + currency: "EUR", + languages: [ + { + name: "Portuguese", + }, + ], + }, + { + name: "Palau", + code: "PW", + icon: "virtual-host", + icon_status: IconStatus.Up, + capital: "Ngerulmud", + continent: { + name: "Oceania", + }, + currency: "USD", + languages: [ + { + name: "English", + }, + ], + }, + { + name: "Paraguay", + code: "PY", + icon: "virtual-host", + icon_status: IconStatus.Up, + capital: "Asunción", + continent: { + name: "South America", + }, + currency: "PYG", + languages: [ + { + name: "Spanish", + }, + { + name: "Guarani", + }, + ], + }, + { + name: "Qatar", + code: "QA", + icon: "virtual-host", + icon_status: IconStatus.Up, + capital: "Doha", + continent: { + name: "Asia", + }, + currency: "QAR", + languages: [ + { + name: "Arabic", + }, + ], + }, + { + name: "Réunion", + code: "RE", + icon: "virtual-host", + icon_status: IconStatus.Up, + capital: "Saint-Denis", + continent: { + name: "Africa", + }, + currency: "EUR", + languages: [ + { + name: "French", + }, + ], + }, + { + name: "Romania", + code: "RO", + icon: "virtual-host", + icon_status: IconStatus.Up, + capital: "Bucharest", + continent: { + name: "Europe", + }, + currency: "RON", + languages: [ + { + name: "Romanian", + }, + ], + }, + { + name: "Serbia", + code: "RS", + icon: "virtual-host", + icon_status: IconStatus.Up, + capital: "Belgrade", + continent: { + name: "Europe", + }, + currency: "RSD", + languages: [ + { + name: "Serbian", + }, + ], + }, + { + name: "Russia", + code: "RU", + icon: "virtual-host", + icon_status: IconStatus.Up, + capital: "Moscow", + continent: { + name: "Europe", + }, + currency: "RUB", + languages: [ + { + name: "Russian", + }, + ], + }, + { + name: "Rwanda", + code: "RW", + icon: "virtual-host", + icon_status: IconStatus.Up, + capital: "Kigali", + continent: { + name: "Africa", + }, + currency: "RWF", + languages: [ + { + name: "Rwandi", + }, + { + name: "English", + }, + { + name: "French", + }, + ], + }, + { + name: "Saudi Arabia", + code: "SA", + icon: "virtual-host", + icon_status: IconStatus.Up, + capital: "Riyadh", + continent: { + name: "Asia", + }, + currency: "SAR", + languages: [ + { + name: "Arabic", + }, + ], + }, + { + name: "Solomon Islands", + code: "SB", + icon: "virtual-host", + icon_status: IconStatus.Up, + capital: "Honiara", + continent: { + name: "Oceania", + }, + currency: "SBD", + languages: [ + { + name: "English", + }, + ], + }, + { + name: "Seychelles", + code: "SC", + icon: "virtual-host", + icon_status: IconStatus.Up, + capital: "Victoria", + continent: { + name: "Africa", + }, + currency: "SCR", + languages: [ + { + name: "French", + }, + { + name: "English", + }, + ], + }, + { + name: "Sudan", + code: "SD", + icon: "virtual-host", + icon_status: IconStatus.Up, + capital: "Khartoum", + continent: { + name: "Africa", + }, + currency: "SDG", + languages: [ + { + name: "Arabic", + }, + { + name: "English", + }, + ], + }, + { + name: "Sweden", + code: "SE", + icon: "virtual-host", + icon_status: IconStatus.Up, + capital: "Stockholm", + continent: { + name: "Europe", + }, + currency: "SEK", + languages: [ + { + name: "Swedish", + }, + ], + }, + { + name: "Singapore", + code: "SG", + icon: "virtual-host", + icon_status: IconStatus.Up, + capital: "Singapore", + continent: { + name: "Asia", + }, + currency: "SGD", + languages: [ + { + name: "English", + }, + { + name: "Malay", + }, + { + name: "Tamil", + }, + { + name: "Chinese", + }, + ], + }, + { + name: "Saint Helena", + code: "SH", + icon: "virtual-host", + icon_status: IconStatus.Up, + capital: "Jamestown", + continent: { + name: "Africa", + }, + currency: "SHP", + languages: [ + { + name: "English", + }, + ], + }, + { + name: "Slovenia", + code: "SI", + icon: "virtual-host", + icon_status: IconStatus.Up, + capital: "Ljubljana", + continent: { + name: "Europe", + }, + currency: "EUR", + languages: [ + { + name: "Slovenian", + }, + ], + }, + { + name: "Svalbard and Jan Mayen", + code: "SJ", + icon: "virtual-host", + icon_status: IconStatus.Up, + capital: "Longyearbyen", + continent: { + name: "Europe", + }, + currency: "NOK", + languages: [ + { + name: "Norwegian", + }, + ], + }, + { + name: "Slovakia", + code: "SK", + icon: "virtual-host", + icon_status: IconStatus.Up, + capital: "Bratislava", + continent: { + name: "Europe", + }, + currency: "EUR", + languages: [ + { + name: "Slovak", + }, + ], + }, + { + name: "Sierra Leone", + code: "SL", + icon: "virtual-host", + icon_status: IconStatus.Up, + capital: "Freetown", + continent: { + name: "Africa", + }, + currency: "SLL", + languages: [ + { + name: "English", + }, + ], + }, + { + name: "San Marino", + code: "SM", + icon: "virtual-host", + icon_status: IconStatus.Up, + capital: "City of San Marino", + continent: { + name: "Europe", + }, + currency: "EUR", + languages: [ + { + name: "Italian", + }, + ], + }, + { + name: "Senegal", + code: "SN", + icon: "virtual-host", + icon_status: IconStatus.Up, + capital: "Dakar", + continent: { + name: "Africa", + }, + currency: "XOF", + languages: [ + { + name: "French", + }, + ], + }, + { + name: "Somalia", + code: "SO", + icon: "virtual-host", + icon_status: IconStatus.Up, + capital: "Mogadishu", + continent: { + name: "Africa", + }, + currency: "SOS", + languages: [ + { + name: "Somalia", + }, + { + name: "Arabic", + }, + ], + }, + { + name: "Suriname", + code: "SR", + icon: "virtual-host", + icon_status: IconStatus.Up, + capital: "Paramaribo", + continent: { + name: "South America", + }, + currency: "SRD", + languages: [ + { + name: "Dutch", + }, + ], + }, + { + name: "South Sudan", + code: "SS", + icon: "virtual-host", + icon_status: IconStatus.Up, + capital: "Juba", + continent: { + name: "Africa", + }, + currency: "SSP", + languages: [ + { + name: "English", + }, + ], + }, + { + name: "São Tomé and Príncipe", + code: "ST", + icon: "virtual-host", + icon_status: IconStatus.Up, + capital: "São Tomé", + continent: { + name: "Africa", + }, + currency: "STN", + languages: [ + { + name: "Portuguese", + }, + ], + }, + { + name: "El Salvador", + code: "SV", + icon: "virtual-host", + icon_status: IconStatus.Up, + capital: "San Salvador", + continent: { + name: "North America", + }, + currency: "SVC,USD", + languages: [ + { + name: "Spanish", + }, + ], + }, + { + name: "Sint Maarten", + code: "SX", + icon: "virtual-host", + icon_status: IconStatus.Up, + capital: "Philipsburg", + continent: { + name: "North America", + }, + currency: "ANG", + languages: [ + { + name: "Dutch", + }, + { + name: "English", + }, + ], + }, + { + name: "Syria", + code: "SY", + icon: "virtual-host", + icon_status: IconStatus.Up, + capital: "Damascus", + continent: { + name: "Asia", + }, + currency: "SYP", + languages: [ + { + name: "Arabic", + }, + ], + }, + { + name: "Swaziland", + code: "SZ", + icon: "virtual-host", + icon_status: IconStatus.Up, + capital: "Lobamba", + continent: { + name: "Africa", + }, + currency: "SZL", + languages: [ + { + name: "English", + }, + { + name: "Swati", + }, + ], + }, + { + name: "Turks and Caicos Islands", + code: "TC", + icon: "virtual-host", + icon_status: IconStatus.Up, + capital: "Cockburn Town", + continent: { + name: "North America", + }, + currency: "USD", + languages: [ + { + name: "English", + }, + ], + }, + { + name: "Chad", + code: "TD", + icon: "virtual-host", + icon_status: IconStatus.Up, + capital: "N'Djamena", + continent: { + name: "Africa", + }, + currency: "XAF", + languages: [ + { + name: "French", + }, + { + name: "Arabic", + }, + ], + }, + { + name: "French Southern Territories", + code: "TF", + icon: "virtual-host", + icon_status: IconStatus.Up, + capital: "Port-aux-Français", + continent: { + name: "Antarctica", + }, + currency: "EUR", + languages: [ + { + name: "French", + }, + ], + }, + { + name: "Togo", + code: "TG", + icon: "virtual-host", + icon_status: IconStatus.Up, + capital: "Lomé", + continent: { + name: "Africa", + }, + currency: "XOF", + languages: [ + { + name: "French", + }, + ], + }, + { + name: "Thailand", + code: "TH", + icon: "virtual-host", + icon_status: IconStatus.Up, + capital: "Bangkok", + continent: { + name: "Asia", + }, + currency: "THB", + languages: [ + { + name: "Thai", + }, + ], + }, + { + name: "Tajikistan", + code: "TJ", + icon: "virtual-host", + icon_status: IconStatus.Up, + capital: "Dushanbe", + continent: { + name: "Asia", + }, + currency: "TJS", + languages: [ + { + name: "Tajik", + }, + { + name: "Russian", + }, + ], + }, + { + name: "Tokelau", + code: "TK", + icon: "virtual-host", + icon_status: IconStatus.Up, + capital: "Fakaofo", + continent: { + name: "Oceania", + }, + currency: "NZD", + languages: [ + { + name: "English", + }, + ], + }, + { + name: "East Timor", + code: "TL", + icon: "virtual-host", + icon_status: IconStatus.Up, + capital: "Dili", + continent: { + name: "Oceania", + }, + currency: "USD", + languages: [ + { + name: "Portuguese", + }, + ], + }, + { + name: "Turkmenistan", + code: "TM", + icon: "virtual-host", + icon_status: IconStatus.Up, + capital: "Ashgabat", + continent: { + name: "Asia", + }, + currency: "TMT", + languages: [ + { + name: "Turkmen", + }, + { + name: "Russian", + }, + ], + }, + { + name: "Tunisia", + code: "TN", + icon: "virtual-host", + icon_status: IconStatus.Up, + capital: "Tunis", + continent: { + name: "Africa", + }, + currency: "TND", + languages: [ + { + name: "Arabic", + }, + ], + }, + { + name: "Tonga", + code: "TO", + icon: "virtual-host", + icon_status: IconStatus.Up, + capital: "Nuku'alofa", + continent: { + name: "Oceania", + }, + currency: "TOP", + languages: [ + { + name: "English", + }, + { + name: "Tonga", + }, + ], + }, + { + name: "Turkey", + code: "TR", + icon: "virtual-host", + icon_status: IconStatus.Up, + capital: "Ankara", + continent: { + name: "Asia", + }, + currency: "TRY", + languages: [ + { + name: "Turkish", + }, + ], + }, + { + name: "Trinidad and Tobago", + code: "TT", + icon: "virtual-host", + icon_status: IconStatus.Up, + capital: "Port of Spain", + continent: { + name: "North America", + }, + currency: "TTD", + languages: [ + { + name: "English", + }, + ], + }, + { + name: "Tuvalu", + code: "TV", + icon: "virtual-host", + icon_status: IconStatus.Up, + capital: "Funafuti", + continent: { + name: "Oceania", + }, + currency: "AUD", + languages: [ + { + name: "English", + }, + ], + }, + { + name: "Taiwan", + code: "TW", + icon: "virtual-host", + icon_status: IconStatus.Up, + capital: "Taipei", + continent: { + name: "Asia", + }, + currency: "TWD", + languages: [ + { + name: "Chinese", + }, + ], + }, + { + name: "Tanzania", + code: "TZ", + icon: "virtual-host", + icon_status: IconStatus.Up, + capital: "Dodoma", + continent: { + name: "Africa", + }, + currency: "TZS", + languages: [ + { + name: "Swahili", + }, + { + name: "English", + }, + ], + }, + { + name: "Ukraine", + code: "UA", + icon: "virtual-host", + icon_status: IconStatus.Up, + capital: "Kyiv", + continent: { + name: "Europe", + }, + currency: "UAH", + languages: [ + { + name: "Ukrainian", + }, + ], + }, + { + name: "Uganda", + code: "UG", + icon: "virtual-host", + icon_status: IconStatus.Up, + capital: "Kampala", + continent: { + name: "Africa", + }, + currency: "UGX", + languages: [ + { + name: "English", + }, + { + name: "Swahili", + }, + ], + }, + { + name: "U.S. Minor Outlying Islands", + code: "UM", + icon: "virtual-host", + icon_status: IconStatus.Up, + capital: null, + continent: { + name: "Oceania", + }, + currency: "USD", + languages: [ + { + name: "English", + }, + ], + }, + { + name: "United States", + code: "US", + icon: "virtual-host", + icon_status: IconStatus.Up, + capital: "Washington D.C.", + continent: { + name: "North America", + }, + currency: "USD,USN,USS", + languages: [ + { + name: "English", + }, + ], + }, + { + name: "Uruguay", + code: "UY", + icon: "virtual-host", + icon_status: IconStatus.Up, + capital: "Montevideo", + continent: { + name: "South America", + }, + currency: "UYI,UYU", + languages: [ + { + name: "Spanish", + }, + ], + }, + { + name: "Uzbekistan", + code: "UZ", + icon: "virtual-host", + icon_status: IconStatus.Up, + capital: "Tashkent", + continent: { + name: "Asia", + }, + currency: "UZS", + languages: [ + { + name: "Uzbek", + }, + { + name: "Russian", + }, + ], + }, + { + name: "Vatican City", + code: "VA", + icon: "virtual-host", + icon_status: IconStatus.Up, + capital: "Vatican City", + continent: { + name: "Europe", + }, + currency: "EUR", + languages: [ + { + name: "Italian", + }, + { + name: "Latin", + }, + ], + }, + { + name: "Saint Vincent and the Grenadines", + code: "VC", + icon: "virtual-host", + icon_status: IconStatus.Up, + capital: "Kingstown", + continent: { + name: "North America", + }, + currency: "XCD", + languages: [ + { + name: "English", + }, + ], + }, + { + name: "Venezuela", + code: "VE", + icon: "virtual-host", + icon_status: IconStatus.Up, + capital: "Caracas", + continent: { + name: "South America", + }, + currency: "VES", + languages: [ + { + name: "Spanish", + }, + ], + }, + { + name: "British Virgin Islands", + code: "VG", + icon: "virtual-host", + icon_status: IconStatus.Up, + capital: "Road Town", + continent: { + name: "North America", + }, + currency: "USD", + languages: [ + { + name: "English", + }, + ], + }, + { + name: "U.S. Virgin Islands", + code: "VI", + icon: "virtual-host", + icon_status: IconStatus.Up, + capital: "Charlotte Amalie", + continent: { + name: "North America", + }, + currency: "USD", + languages: [ + { + name: "English", + }, + ], + }, + { + name: "Vietnam", + code: "VN", + icon: "virtual-host", + icon_status: IconStatus.Up, + capital: "Hanoi", + continent: { + name: "Asia", + }, + currency: "VND", + languages: [ + { + name: "Vietnamese", + }, + ], + }, + { + name: "Vanuatu", + code: "VU", + icon: "virtual-host", + icon_status: IconStatus.Up, + capital: "Port Vila", + continent: { + name: "Oceania", + }, + currency: "VUV", + languages: [ + { + name: "Bislama", + }, + { + name: "English", + }, + { + name: "French", + }, + ], + }, + { + name: "Wallis and Futuna", + code: "WF", + icon: "virtual-host", + icon_status: IconStatus.Up, + capital: "Mata-Utu", + continent: { + name: "Oceania", + }, + currency: "XPF", + languages: [ + { + name: "French", + }, + ], + }, + { + name: "Samoa", + code: "WS", + icon: "virtual-host", + icon_status: IconStatus.Up, + capital: "Apia", + continent: { + name: "Oceania", + }, + currency: "WST", + languages: [ + { + name: "Samoan", + }, + { + name: "English", + }, + ], + }, + { + name: "Kosovo", + code: "XK", + icon: "virtual-host", + icon_status: IconStatus.Up, + capital: "Pristina", + continent: { + name: "Europe", + }, + currency: "EUR", + languages: [ + { + name: "Albanian", + }, + { + name: "Serbian", + }, + ], + }, + { + name: "Yemen", + code: "YE", + icon: "virtual-host", + icon_status: IconStatus.Up, + capital: "Sana'a", + continent: { + name: "Asia", + }, + currency: "YER", + languages: [ + { + name: "Arabic", + }, + ], + }, + { + name: "Mayotte", + code: "YT", + icon: "virtual-host", + icon_status: IconStatus.Up, + capital: "Mamoudzou", + continent: { + name: "Africa", + }, + currency: "EUR", + languages: [ + { + name: "French", + }, + ], + }, + { + name: "South Africa", + code: "ZA", + icon: "virtual-host", + icon_status: IconStatus.Up, + capital: "Pretoria", + continent: { + name: "Africa", + }, + currency: "ZAR", + languages: [ + { + name: "Afrikaans", + }, + { + name: "English", + }, + { + name: "South Ndebele", + }, + { + name: "Southern Sotho", + }, + { + name: "Swati", + }, + { + name: "Tswana", + }, + { + name: "Tsonga", + }, + { + name: "Venda", + }, + { + name: "Xhosa", + }, + { + name: "Zulu", + }, + ], + }, + { + name: "Zambia", + code: "ZM", + icon: "virtual-host", + icon_status: IconStatus.Up, + capital: "Lusaka", + continent: { + name: "Africa", + }, + currency: "ZMW", + languages: [ + { + name: "English", + }, + ], + }, + { + name: "Zimbabwe", + code: "ZW", + icon: "virtual-host", + icon_status: IconStatus.Up, + capital: "Harare", + continent: { + name: "Africa", + }, + currency: "USD,ZAR,BWP,GBP,AUD,CNY,INR,JPY", + languages: [ + { + name: "English", + }, + { + name: "Shona", + }, + { + name: "North Ndebele", + }, + ], + }, + ], + }, +}; +\\\\\\\`, + "widget-types/drilldown/drilldown-widget/drilldown-widget-example.component.ts": \\\\\\\`// © 2022 SolarWinds Worldwide, LLC. All rights reserved. +// +// Permission is hereby granted, free of charge, to any person obtaining a copy +// of this software and associated documentation files (the "Software"), to +// deal in the Software without restriction, including without limitation the +// rights to use, copy, modify, merge, publish, distribute, sublicense, and/or +// sell copies of the Software, and to permit persons to whom the Software is +// furnished to do so, subject to the following conditions: +// +// The above copyright notice and this permission notice shall be included in +// all copies or substantial portions of the Software. +// +// THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR +// IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, +// FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE +// AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER +// LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, +// OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN +// THE SOFTWARE. + +import { HttpClient } from "@angular/common/http"; +import { + ChangeDetectorRef, + Component, + Injectable, + OnDestroy, + OnInit, +} from "@angular/core"; +import { GridsterConfig, GridsterItem } from "angular-gridster2"; +import { Apollo, gql } from "apollo-angular"; +import groupBy from "lodash/groupBy"; +import { BehaviorSubject, Observable, of } from "rxjs"; +import { catchError, delay, filter, map } from "rxjs/operators"; + +import { + DataSourceFeatures, + IconStatus, + IDataField, + IDataSource, + IDataSourceFeatures, + IDataSourceFeaturesConfiguration, + INovaFilters, + LoggerService, + ServerSideDataSource, + IFilters, +} from "@nova-ui/bits"; +import { + DATA_SOURCE, + DEFAULT_PIZZAGNA_ROOT, + IDashboard, + IDrilldownComponentsConfiguration, + IListWidgetConfiguration, + IProviderConfiguration, + IWidget, + IWidgets, + ListGroupItemComponent, + ListLeafItemComponent, + NOVA_DRILLDOWN_DATASOURCE_ADAPTER, + PizzagnaLayer, + ProviderRegistryService, + WellKnownPathKey, + WellKnownProviders, + WidgetTypesService, +} from "@nova-ui/dashboards"; + +import { DrilldownDataSource } from "./mock-data-source"; + +/** + * A simple KPI data source to retrieve the average rating of Harry Potter and the Sorcerer's Stone (book) via googleapis + */ +@Injectable() +export class DrilldownDataSourceRealApi + extends ServerSideDataSource + implements OnDestroy, IDataSource +{ + // This is the ID we'll use to identify the provider + public static providerId = "DrilldownDataSourceRealApi"; + + // Use this subject to communicate the data source's busy state + public busy = new BehaviorSubject(false); + public dataFields: Partial[] = [ + { id: "regionName", label: "Region name" }, + { id: "subregionName", label: "Subregion name" }, + ]; + + public features: IDataSourceFeaturesConfiguration; + private supportedFeatures: IDataSourceFeatures = { + search: { enabled: true }, + }; + + private drillState: string[] = []; + private groupBy: string[]; + + constructor( + private logger: LoggerService, + private http: HttpClient, + private apollo: Apollo + ) { + super(); + this.features = new DataSourceFeatures(this.supportedFeatures); + // TODO: remove Partial in vNext after marking dataType field as optional - NUI-5838 + ( + this.dataFieldsConfig.dataFields$ as BehaviorSubject< + Partial[] + > + ).next(this.dataFields); + } + + private groupedDataHistory: Array> = []; + + // In this example, getFilteredData is invoked every 10 minutes (Take a look at the refresher + // provider definition in the widget configuration below to see how the interval is set) + public async getFilteredData(data: IFilters): Promise { + return of(data) + .pipe( + filter(() => !!this.drillState), + map((countries) => { + const lastHistory = () => getLast(this.groupedDataHistory); + + if (!this.drillState.length && !this.groupBy.length) { + return countries; + } + + // adding "ROOT" as a root level for drilling + const fullDrillState = ["ROOT", ...this.drillState]; + const activeDrillLvl = fullDrillState.length; + const historyLvl = this.groupedDataHistory.length; + + // checking how many lvls we have to group for drilling, in case some are missed + const drillLvlDiff = activeDrillLvl - historyLvl; + + if (!drillLvlDiff) { + return lastHistory() || countries; + } + + const drillToGroup = fullDrillState.slice( + fullDrillState.length - drillLvlDiff + ); + + for (const drill of drillToGroup) { + const drillIdx = fullDrillState.findIndex( + (v) => v === drill + ); + const group = this.groupBy[drillIdx]; + + if (group) { + const dataToGroup = lastHistory() + ? lastHistory()[drill] + : countries; + const lastGroupedValue = groupBy( + dataToGroup, + group + ); + + this.groupedDataHistory.push(lastGroupedValue); + } + } + + // take last if we have all data grouped + if (this.groupBy.length === this.drillState.length) { + return lastHistory()[getLast(this.drillState)]; + } + + // get groping and transform to raw data format + return this.getGroupsWidgetData(lastHistory()); + }) + ) + .toPromise(); + } + + public ngOnDestroy(): void { + this.outputsSubject.complete(); + } + + // This method is expected to return all data needed for repeat/paginator/filterGroups in order to work. + // In case of custom filtering participants feel free to extend INovaFilteringOutputs. + protected getBackendData(filters: INovaFilters): Observable { + const mainRequest = this.apollo.watchQuery<{ countries: any }>({ + query: this.generateQuery(filters), + }); + + return mainRequest.valueChanges.pipe( + // mock delay + delay(300), + // data mapping, !DS specific! + map((res) => res.data.countries), + // adds mock icons to be displayed on leaf nodes !DS specific! + map((res: any[]) => + res.map((v) => ({ + ...v, + icon: "virtual-host", + icon_status: IconStatus.Up, + subregionName: + v.subregion?.name || "No Subregion Specified", + regionName: + v.subregion?.region?.name || "No Region Specified", + })) + ), + catchError((e) => { + this.logger.error(e); + return of({} as any); + }) + ); + } + + private generateQuery(filters: INovaFilters) { + const { search } = filters; + const searchValue = search?.value ? \\\\\\\\\\\\\\\`^[\\\\\\\\\\\\\\\${search.value}]*\\\\\\\\\\\\\\\` : ""; + + const queryString = \\\\\\\\\\\\\\\` + query { + countries(filter: {name: {regex: "\\\\\\\\\\\\\\\${searchValue}"} }) { + name + native + capital + languages { + name + } + currencies + subdivisions { + name + } + } + } + \\\\\\\\\\\\\\\`; + + return gql\\\\\\\\\\\\\\\` + \\\\\\\\\\\\\\\${queryString} + \\\\\\\\\\\\\\\`; + } + + // Overrides default ServerSideDataSource.beforeApplyFilters implementation + // to save some filters that are used internally + // -- !DS specific + protected beforeApplyFilters(filters: INovaFilters): void { + this.busy.next(true); + + this.drillState = filters.drillstate?.value; + this.groupBy = filters.group?.value; + + if (this.isHome()) { + this.groupedDataHistory.length = 0; + } + + if (this.isBack()) { + this.groupedDataHistory.length = this.groupedDataHistory.length - 1; + } + + if (this.getFilters()["search"] && this.filterChanged("search")) { + this.groupedDataHistory.length = 0; + } + } + + private getGroupsWidgetData(groupByObj: Record) { + return Object.keys(groupByObj).map((property) => ({ + id: property, + label: property, + // statuses that will be displayed on group item + statuses: [ + { key: "virtual-host", value: groupByObj[property].length }, + { + key: "acknowledge", + value: this.getPopulation(groupByObj[property]), + }, + ], + })); + } + + private isHome(): boolean { + return this.drillState?.length === 0; + } + + private isBack(): boolean { + return ( + this.groupedDataHistory?.length > this.drillState?.length && + !this.isHome() + ); + } + + /** + * Gets population for the country(ies) + */ + private getPopulation(countries: any[]) { + const totalPopulation = countries.reduce( + (acc, next) => (acc += next.population), + 0 + ); + return \\\\\\\\\\\\\\\`\\\\\\\\\\\\\\\${totalPopulation * Math.pow(10, -3)} k\\\\\\\\\\\\\\\`; + } +} + +@Component({ + selector: "drilldown-widget-example", + templateUrl: "./drilldown-widget-example.component.html", + styleUrls: ["./drilldown-widget-example.component.less"], + standalone: false, +}) +export class DrilldownWidgetExampleComponent implements OnInit { + // This variable will hold all the data needed to define the layout and behavior of the widgets. + // Pass this to the dashboard component's dashboard input in the template. + public dashboard: IDashboard | undefined; + + // Angular gridster requires a configuration object even if it's empty. + // Pass this to the dashboard component's gridsterConfig input in the template. + public gridsterConfig: GridsterConfig = {}; + + // Boolean passed as an input to the dashboard. When true, widgets can be moved, resized, removed, or edited + public editMode: boolean = false; + + constructor( + // WidgetTypesService provides the widget's necessary structure information + private widgetTypesService: WidgetTypesService, + private providerRegistry: ProviderRegistryService, + private changeDetectorRef: ChangeDetectorRef + ) {} + + public ngOnInit(): void { + // Registering the data source for injection into the KPI tile. + // Note: Each tile of a KPI widget is assigned its own instance of the data source + this.providerRegistry.setProviders({ + [DrilldownDataSource.providerId]: { + provide: DATA_SOURCE, + useClass: DrilldownDataSource, + // Any dependencies that need to be injected into the provider must be listed here + deps: [HttpClient], + }, + [DrilldownDataSourceRealApi.providerId]: { + provide: DATA_SOURCE, + useClass: DrilldownDataSourceRealApi, + // Any dependencies that need to be injected into the provider must be listed here + deps: [LoggerService, HttpClient, Apollo], + }, + }); + + this.initializeDashboard(); + const widgetTemplate = this.widgetTypesService.getWidgetType( + "drilldown", + 1 + ); + this.widgetTypesService.setNode( + widgetTemplate, + "configurator", + WellKnownPathKey.DataSourceProviders, + [ + DrilldownDataSourceRealApi.providerId, + DrilldownDataSource.providerId, + ] + ); + } + + public initializeDashboard(): void { + // We're using a static configuration object for this example, but this is where + // the widget's configuration could potentially be populated from a database + const drilldownWidget = widgetConfig; + const widgets: IWidgets = { + // Complete the widget with information coming from its type definition + [drilldownWidget.id]: + this.widgetTypesService.mergeWithWidgetType(drilldownWidget), + }; + + // Setting the widget dimensions and position (this is for gridster) + const positions: Record = { + [drilldownWidget.id]: { + cols: 10, + rows: 10, + y: 0, + x: 0, + }, + }; + + // Finally, assigning the variables we created above to the dashboard + this.dashboard = { positions, widgets }; + } + + public reInitializeDashboard(): void { + // destroys the components and their providers so the dashboard can re init data + this.dashboard = undefined; + this.changeDetectorRef.detectChanges(); + + const adapterProperties = + widgetConfig.pizzagna[PizzagnaLayer.Configuration].listWidget + .providers?.adapter?.properties; + + if (adapterProperties) { + adapterProperties.drillstate = []; + } + + this.initializeDashboard(); + } +} + +const widgetConfig: IWidget = { + id: "drilldown", + type: "drilldown", + pizzagna: { + [PizzagnaLayer.Configuration]: { + [DEFAULT_PIZZAGNA_ROOT]: { + providers: { + [WellKnownProviders.DataSource]: { + // Setting the data source providerId for the tile with id "kpi1" + providerId: DrilldownDataSourceRealApi.providerId, + properties: {}, + } as IProviderConfiguration, + }, + }, + header: { + properties: { + title: "Drilldown Widget", + subtitle: "Search is case sensitive!", + }, + }, + listWidget: { + providers: { + [WellKnownProviders.Adapter]: { + providerId: NOVA_DRILLDOWN_DATASOURCE_ADAPTER, + properties: { + // widget + navigationBarId: "navigationBar", + componentId: "listWidget", + dataPath: "data", + + // adapter props + drillstate: [], + groupBy: ["regionName", "subregionName"], + groups: ["regionName", "subregionName"], + + // components + componentsConfig: { + group: { + componentType: + ListGroupItemComponent.lateLoadKey, + properties: { + dataFieldIds: { + id: "id", + label: "label", + statuses: "statuses", + }, + }, + itemProperties: { + canNavigate: true, + }, + }, + leaf: { + componentType: + ListLeafItemComponent.lateLoadKey, + properties: { + dataFieldIds: { + icon: "icon", + status: "icon_status", + detailedUrl: "capital", + label: "name", + }, + }, + itemProperties: { + canNavigate: false, + }, + }, + } as IDrilldownComponentsConfiguration, + }, + }, + }, + properties: { + configuration: { + // FORMAT: + // componentType: ListLeafItemComponent.lateLoadKey, + // properties: { + // dataFieldIds: { + // icon: "", + // status: "code", + // detailedUrl: "capital", + // label: "name", + // }, + // }, + // + } as IListWidgetConfiguration, + }, + }, + }, + }, +}; + +const getLast = (arr: any[]) => arr[arr.length - 1]; +\\\\\\\`, + "widget-types/drilldown/drilldown-widget/mock-data-source.ts": \\\\\\\`// © 2022 SolarWinds Worldwide, LLC. All rights reserved. +// +// Permission is hereby granted, free of charge, to any person obtaining a copy +// of this software and associated documentation files (the "Software"), to +// deal in the Software without restriction, including without limitation the +// rights to use, copy, modify, merge, publish, distribute, sublicense, and/or +// sell copies of the Software, and to permit persons to whom the Software is +// furnished to do so, subject to the following conditions: +// +// The above copyright notice and this permission notice shall be included in +// all copies or substantial portions of the Software. +// +// THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR +// IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, +// FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE +// AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER +// LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, +// OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN +// THE SOFTWARE. + +import { Injectable, OnDestroy } from "@angular/core"; +import groupBy from "lodash/groupBy"; +import { BehaviorSubject, Observable, of, Subject } from "rxjs"; +import { + catchError, + delay, + finalize, + map, + // eslint-disable-next-line import/no-deprecated + switchMap, + tap, +} from "rxjs/operators"; + +import { + DataSourceService, + IDataField, + IDataSource, + IFilters, + INovaFilters, +} from "@nova-ui/bits"; + +import { GRAPH_DATA_MOCK } from "./data-mock"; + +/** + * A simple KPI data source to retrieve the average rating of Harry Potter and the Sorcerer's Stone (book) via googleapis + */ +@Injectable() +export class DrilldownDataSource + extends DataSourceService + implements IDataSource, OnDestroy +{ + // This is the ID we'll use to identify the provider + public static providerId = "DrilldownDataSource"; + + // Use this subject to communicate the data source's busy state + public busy = new BehaviorSubject(false); + public dataFields: Partial[] = [ + { id: "continent.name", label: "Continent name" }, + { id: "currency", label: "Currency" }, + ]; + + private drillState: string[] = []; + private groupBy: string[]; + private cache: any; + private applyFilters$ = new Subject(); + + constructor() { + super(); + + // TODO: remove Partial in vNext after marking dataType field as optional - NUI-5838 + ( + this.dataFieldsConfig.dataFields$ as BehaviorSubject< + Partial[] + > + ).next(this.dataFields); + + this.applyFilters$ + // eslint-disable-next-line import/no-deprecated + .pipe(switchMap((filters) => this.getData(filters))) + .subscribe(async (res) => { + this.outputsSubject.next(await this.getFilteredData(res)); + }); + } + + private groupedDataHistory: any[] = []; + + // In this example, getFilteredData is invoked every 10 minutes (Take a look at the refresher + // provider definition in the widget configuration below to see how the interval is set) + public async getFilteredData(data: any): Promise { + return of(data) + .pipe( + map((countries) => { + const widgetInput = this.getOutput(countries); + + if (this.isDrillDown()) { + const activeDrillLvl = this.drillState.length; + const group = this.groupBy[activeDrillLvl]; + const [lastGroupedValue, groupedData] = + this.getTransformedDataForGroup(widgetInput, group); + + this.groupedDataHistory.push(lastGroupedValue); + + return groupedData; + } + + return widgetInput; + }) + ) + .toPromise(); + } + + public ngOnDestroy(): void { + this.outputsSubject.complete(); + } + + // redefine parent method + public async applyFilters(): Promise { + this.applyFilters$.next(this.getFilters()); + } + + private getData(filters: INovaFilters): Observable { + this.drillState = filters.drillstate?.value; + this.groupBy = filters.group?.value; + + this.busy.next(true); + + return of(this.cache || GRAPH_DATA_MOCK).pipe( + delay(1000), + tap((data) => (this.cache = data)), + map((data) => data.data.countries), + catchError((e) => of([])), + finalize(() => this.busy.next(false)) + ); + } + + private getTransformedDataForGroup(data: any, groupName: string) { + const groupedDict = groupBy(data, groupName); + const dataArr = Object.keys(groupedDict).map((property) => ({ + id: property, + label: property, + // TODO: apply groups mapping here + statuses: [ + { key: "state_ok", value: groupedDict[property].length }, + { + key: "status_unreachable", + value: generateNumberUpTo(100000), + }, + { key: "status_warning", value: generateNumberUpTo(10000) }, + { key: "status_unknown", value: generateNumberUpTo(1000) }, + ], + })); + + return [groupedDict, dataArr]; + } + + private isHome(): boolean { + return !this.drillState || this.drillState.length === 0; + } + + private isBack(): boolean { + return ( + this.groupedDataHistory.length > this.drillState?.length && + !this.isHome() + ); + } + + private isDrillDown(): boolean { + return this.drillState?.length !== this.groupBy?.length; + } + + private getOutput(data: any) { + if (this.isHome()) { + this.groupedDataHistory.length = 0; + } + + if (this.isBack()) { + this.groupedDataHistory.length = this.groupedDataHistory.length - 1; + } + + const lastHistoryValue = getLast(this.groupedDataHistory); + + if (!lastHistoryValue) { + return data; + } + + return lastHistoryValue[getLast(this.drillState)] || lastHistoryValue; + } +} + +const getLast = (arr: any[]) => arr[arr.length - 1]; +const generateNumberUpTo = (upperLimit: number): number => + Math.floor(Math.random() * upperLimit + 1); +\\\\\\\`, + "widget-types/drilldown/drilldown-widget-docs.component.ts": \\\\\\\`// © 2022 SolarWinds Worldwide, LLC. All rights reserved. +// +// Permission is hereby granted, free of charge, to any person obtaining a copy +// of this software and associated documentation files (the "Software"), to +// deal in the Software without restriction, including without limitation the +// rights to use, copy, modify, merge, publish, distribute, sublicense, and/or +// sell copies of the Software, and to permit persons to whom the Software is +// furnished to do so, subject to the following conditions: +// +// The above copyright notice and this permission notice shall be included in +// all copies or substantial portions of the Software. +// +// THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR +// IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, +// FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE +// AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER +// LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, +// OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN +// THE SOFTWARE. + +import { Component, OnInit } from "@angular/core"; + +import { mapContentFile } from "../../../../demo-files-factory"; + +@Component({ + selector: "nui-drilldown-docs", + templateUrl: "./drilldown-widget-docs.component.html", + standalone: false, +}) +export class DrilldownDocsComponent implements OnInit { + public widgetFileText = ""; + public configuratorFileText = ""; + + public predefinedGroping = \\\\\\\\\\\\\\\` +listWidget: { + providers: { + [WellKnownProviders.Adapter]: { + providerId: NOVA_DRILLDOWN_DATASOURCE_ADAPTER, + properties: { + ... + // adapter props + drillstate: [], + groupBy: ["regionName", "subregionName"], + groups: ["regionName", "subregionName"], + ... + }, + }, + }, +}, +\\\\\\\\\\\\\\\`; + public featuredDeclaredText = \\\\\\\\\\\\\\\` + private supportedFeatures: IDataSourceFeatures = { + search: { enabled: true }, + };\\\\\\\\\\\\\\\`; + public featuresUsedText = \\\\\\\\\\\\\\\` + this.features = new DataSourceFeatures(this.supportedFeatures); + \\\\\\\\\\\\\\\`; + + public async ngOnInit(): Promise { + this.widgetFileText = await import( + "./../../../../../../src/lib/widget-types/drilldown/drilldown-widget" + ).then(mapContentFile); + this.configuratorFileText = await import( + "./../../../../../../src/lib/widget-types/drilldown/drilldown-configurator" + ).then(mapContentFile); + } +} +\\\\\\\`, + "widget-types/drilldown/drilldown-widget-docs.module.ts": \\\\\\\`// © 2022 SolarWinds Worldwide, LLC. All rights reserved. +// +// Permission is hereby granted, free of charge, to any person obtaining a copy +// of this software and associated documentation files (the "Software"), to +// deal in the Software without restriction, including without limitation the +// rights to use, copy, modify, merge, publish, distribute, sublicense, and/or +// sell copies of the Software, and to permit persons to whom the Software is +// furnished to do so, subject to the following conditions: +// +// The above copyright notice and this permission notice shall be included in +// all copies or substantial portions of the Software. +// +// THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR +// IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, +// FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE +// AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER +// LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, +// OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN +// THE SOFTWARE. + +import { NgModule } from "@angular/core"; +import { RouterModule, Routes } from "@angular/router"; + +// eslint-disable-next-line max-len +import { + NuiButtonModule, + NuiDocsModule, + NuiMessageModule, + NuiSwitchModule, + DEMO_PATH_TOKEN, +} from "@nova-ui/bits"; +import { NuiDashboardsModule } from "@nova-ui/dashboards"; + +import { DrilldownMultiRequestWidgetExampleComponent } from "./drilldown-multi-request-widget/drilldown-multi-request-widget-example.component"; +import { DrilldownWidgetExampleComponent } from "./drilldown-widget/drilldown-widget-example.component"; +import { DrilldownDocsComponent } from "./drilldown-widget-docs.component"; +import { getDemoFiles } from "../../../../demo-files-factory"; + +const routes: Routes = [ + { + path: "", + component: DrilldownDocsComponent, + data: { + srlc: { + hideIndicator: true, + }, + }, + }, + { + path: "example", + component: DrilldownWidgetExampleComponent, + data: { + srlc: { + hideIndicator: true, + }, + }, + }, + { + path: "multiple-requests", + component: DrilldownMultiRequestWidgetExampleComponent, + data: { + srlc: { + hideIndicator: true, + }, + }, + }, +]; + +@NgModule({ + imports: [ + RouterModule.forChild(routes), + NuiButtonModule, + NuiDocsModule, + NuiMessageModule, + NuiDashboardsModule, + NuiSwitchModule, + ], + declarations: [ + DrilldownDocsComponent, + DrilldownWidgetExampleComponent, + DrilldownMultiRequestWidgetExampleComponent, + ], + providers: [ + { + provide: DEMO_PATH_TOKEN, + useValue: getDemoFiles("drilldown"), + }, + ], +}) +export default class DrilldownDocsModule {} +\\\\\\\`, + "widget-types/embedded-content/embedded-content-docs.component.ts": \\\\\\\`// © 2022 SolarWinds Worldwide, LLC. All rights reserved. +// +// Permission is hereby granted, free of charge, to any person obtaining a copy +// of this software and associated documentation files (the "Software"), to +// deal in the Software without restriction, including without limitation the +// rights to use, copy, modify, merge, publish, distribute, sublicense, and/or +// sell copies of the Software, and to permit persons to whom the Software is +// furnished to do so, subject to the following conditions: +// +// The above copyright notice and this permission notice shall be included in +// all copies or substantial portions of the Software. +// +// THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR +// IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, +// FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE +// AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER +// LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, +// OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN +// THE SOFTWARE. + +import { Component, OnInit } from "@angular/core"; + +import { mapContentFile } from "../../../../demo-files-factory"; + +@Component({ + selector: "nui-embedded-content-docs", + templateUrl: "./embedded-content-docs.component.html", + standalone: false, +}) +export class EmbeddedContentDocsComponent implements OnInit { + public embeddedContentWidgetFileText = ""; + public embeddedContentConfiguratorFileText = ""; + + public async ngOnInit(): Promise { + this.embeddedContentWidgetFileText = await import( + "./../../../../../../src/lib/widget-types/embedded-content/embedded-content-widget" + ).then(mapContentFile); + this.embeddedContentWidgetFileText = await import( + "./../../../../../../src/lib/widget-types/embedded-content/embedded-content-configurator" + ).then(mapContentFile); + } +} +\\\\\\\`, + "widget-types/embedded-content/embedded-content-docs.module.ts": \\\\\\\`// © 2022 SolarWinds Worldwide, LLC. All rights reserved. +// +// Permission is hereby granted, free of charge, to any person obtaining a copy +// of this software and associated documentation files (the "Software"), to +// deal in the Software without restriction, including without limitation the +// rights to use, copy, modify, merge, publish, distribute, sublicense, and/or +// sell copies of the Software, and to permit persons to whom the Software is +// furnished to do so, subject to the following conditions: +// +// The above copyright notice and this permission notice shall be included in +// all copies or substantial portions of the Software. +// +// THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR +// IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, +// FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE +// AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER +// LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, +// OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN +// THE SOFTWARE. + +import { NgModule } from "@angular/core"; +import { RouterModule, Routes } from "@angular/router"; + +// eslint-disable-next-line max-len +import { + NuiButtonModule, + NuiDocsModule, + NuiMessageModule, + NuiSwitchModule, + DEMO_PATH_TOKEN, +} from "@nova-ui/bits"; +import { NuiDashboardsModule } from "@nova-ui/dashboards"; + +import { EmbeddedContentDocsComponent } from "./embedded-content-docs.component"; +import { EmbeddedContentWidgetExampleComponent } from "./embedded-content-widget-example/embedded-content-widget-example.component"; +import { getDemoFiles } from "../../../../demo-files-factory"; + +const routes: Routes = [ + { + path: "", + component: EmbeddedContentDocsComponent, + data: { + srlc: { + hideIndicator: true, + }, + }, + }, + { + path: "example", + component: EmbeddedContentWidgetExampleComponent, + data: { + srlc: { + hideIndicator: true, + }, + }, + }, +]; + +@NgModule({ + imports: [ + RouterModule.forChild(routes), + NuiButtonModule, + NuiDocsModule, + NuiMessageModule, + NuiDashboardsModule, + NuiSwitchModule, + ], + declarations: [ + EmbeddedContentDocsComponent, + EmbeddedContentWidgetExampleComponent, + ], + providers: [ + { + provide: DEMO_PATH_TOKEN, + useValue: getDemoFiles("embedded-content"), + }, + ], +}) +export default class EmbeddedContentDocsModule {} +\\\\\\\`, + "widget-types/embedded-content/embedded-content-widget-example/embedded-content-widget-example.component.ts": \\\\\\\`// © 2022 SolarWinds Worldwide, LLC. All rights reserved. +// +// Permission is hereby granted, free of charge, to any person obtaining a copy +// of this software and associated documentation files (the "Software"), to +// deal in the Software without restriction, including without limitation the +// rights to use, copy, modify, merge, publish, distribute, sublicense, and/or +// sell copies of the Software, and to permit persons to whom the Software is +// furnished to do so, subject to the following conditions: +// +// The above copyright notice and this permission notice shall be included in +// all copies or substantial portions of the Software. +// +// THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR +// IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, +// FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE +// AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER +// LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, +// OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN +// THE SOFTWARE. + +import { ChangeDetectorRef, Component, OnInit } from "@angular/core"; +import { GridsterConfig, GridsterItem } from "angular-gridster2"; + +import { + ComponentRegistryService, + EmbeddedContentComponent, + EmbeddedContentConfigurationComponent, + EmbeddedContentMode, + IDashboard, + IWidget, + IWidgets, + PizzagnaLayer, + WidgetTypesService, +} from "@nova-ui/dashboards"; + +@Component({ + selector: "embedded-content-widget-example", + templateUrl: "./embedded-content-widget-example.component.html", + styleUrls: ["./embedded-content-widget-example.component.less"], + standalone: false, +}) +export class EmbeddedContentWidgetExampleComponent implements OnInit { + // This variable will hold all the data needed to define the layout and behavior of the widgets. + // Pass this to the dashboard component's dashboard input in the template. + public dashboard: IDashboard | undefined; + + // Angular gridster requires a configuration object even if it's empty. + // Pass this to the dashboard component's gridsterConfig input in the template. + public gridsterConfig: GridsterConfig = {}; + + // Boolean passed as an input to the dashboard. When true, widgets can be moved, resized, removed, or edited + public editMode: boolean = false; + + constructor( + // WidgetTypesService provides the widget's necessary structure information + private widgetTypesService: WidgetTypesService, + + private componentRegistry: ComponentRegistryService, + private changeDetectorRef: ChangeDetectorRef + ) {} + + public ngOnInit(): void { + this.prepareNovaDashboards(); + this.initializeDashboard(); + } + + /** Used for restoring widgets state */ + public reInitializeDashboard(): void { + // destroys the components and their providers so the dashboard can re init data + this.dashboard = undefined; + this.changeDetectorRef.detectChanges(); + + this.initializeDashboard(); + } + + public initializeDashboard(): void { + // We're using a static configuration object for this example, but this is where + // the widget's configuration could potentially be populated from a database + const embeddedContentWidget = widgetConfig; + const widgets: IWidgets = { + // Complete the widget with information coming from its type definition + [embeddedContentWidget.id]: + this.widgetTypesService.mergeWithWidgetType( + embeddedContentWidget + ), + }; + + // Setting the widget dimensions and position (this is for gridster) + const positions: Record = { + [embeddedContentWidget.id]: { + cols: 10, + rows: 10, + y: 0, + x: 0, + }, + }; + + // Finally, assigning the variables we created above to the dashboard + this.dashboard = { positions, widgets }; + } + + private prepareNovaDashboards() { + this.componentRegistry.registerByLateLoadKey(EmbeddedContentComponent); + this.componentRegistry.registerByLateLoadKey( + EmbeddedContentConfigurationComponent + ); + } +} + +const widgetConfig: IWidget = { + id: "embeddedContentWidgetId", + type: "embedded-content", + pizzagna: { + [PizzagnaLayer.Configuration]: { + header: { + properties: { + title: "Embedded Content Widget", + subtitle: "", + }, + }, + mainContent: { + properties: { + sanitized: true, + mode: EmbeddedContentMode.URL, + customEmbeddedContent: "https://www.ventusky.com/", + }, + }, + }, + }, +}; +\\\\\\\`, + "widget-types/kpi/kpi-docs.component.ts": \\\\\\\`// © 2022 SolarWinds Worldwide, LLC. All rights reserved. +// +// Permission is hereby granted, free of charge, to any person obtaining a copy +// of this software and associated documentation files (the "Software"), to +// deal in the Software without restriction, including without limitation the +// rights to use, copy, modify, merge, publish, distribute, sublicense, and/or +// sell copies of the Software, and to permit persons to whom the Software is +// furnished to do so, subject to the following conditions: +// +// The above copyright notice and this permission notice shall be included in +// all copies or substantial portions of the Software. +// +// THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR +// IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, +// FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE +// AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER +// LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, +// OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN +// THE SOFTWARE. + +import { Component, OnInit } from "@angular/core"; + +import { mapContentFile } from "../../../../demo-files-factory"; + +@Component({ + selector: "nui-kpi-docs", + templateUrl: "./kpi-docs.component.html", + standalone: false, +}) +export class KpiDocsComponent implements OnInit { + public kpiWidgetFileText = ""; + public kpiConfiguratorFileText = ""; + + public async ngOnInit(): Promise { + this.kpiWidgetFileText = await import( + "./../../../../../../src/lib/widget-types/kpi/kpi-widget" + ).then(mapContentFile); + this.kpiConfiguratorFileText = await import( + "./../../../../../../src/lib/widget-types/kpi/kpi-configurator" + ).then(mapContentFile); + } +} +\\\\\\\`, + "widget-types/kpi/kpi-docs.module.ts": \\\\\\\`// © 2022 SolarWinds Worldwide, LLC. All rights reserved. +// +// Permission is hereby granted, free of charge, to any person obtaining a copy +// of this software and associated documentation files (the "Software"), to +// deal in the Software without restriction, including without limitation the +// rights to use, copy, modify, merge, publish, distribute, sublicense, and/or +// sell copies of the Software, and to permit persons to whom the Software is +// furnished to do so, subject to the following conditions: +// +// The above copyright notice and this permission notice shall be included in +// all copies or substantial portions of the Software. +// +// THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR +// IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, +// FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE +// AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER +// LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, +// OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN +// THE SOFTWARE. + +import { NgModule } from "@angular/core"; +import { RouterModule, Routes } from "@angular/router"; + +import { + NuiButtonModule, + NuiDocsModule, + NuiMessageModule, + NuiSwitchModule, + DEMO_PATH_TOKEN, +} from "@nova-ui/bits"; +import { + KpiColorComparatorsRegistryService, + NuiDashboardsModule, +} from "@nova-ui/dashboards"; + +import { KpiDocsComponent } from "./kpi-docs.component"; +import { KpiSyncBrokerExampleComponent } from "./kpi-sync-broker/kpi-sync-broker-example.component"; +import { KpiSyncBrokerDocsComponent } from "./kpi-sync-broker-docs.component"; +import { KpiSyncBrokerForAllTilesExampleComponent } from "./kpi-sync-broker-for-all-tiles/kpi-sync-broker-for-all-tiles-example.component"; +import { KpiWidgetExampleComponent } from "./kpi-widget/kpi-widget-example.component"; +import { KpiWidgetBackgroundColorExampleComponent } from "./kpi-widget-background-color/kpi-widget-background-color-example.component"; +import { KpiWidgetBackgroundColorDocsComponent } from "./kpi-widget-background-color-docs.component"; +import { KpiWidgetInteractiveExampleComponent } from "./kpi-widget-interactive/kpi-widget-interactive-example.component"; +import { getDemoFiles } from "../../../../demo-files-factory"; + +const routes: Routes = [ + { + path: "", + component: KpiDocsComponent, + data: { + srlc: { + hideIndicator: true, + }, + showThemeSwitcher: true, + }, + }, + { + path: "example", + component: KpiWidgetExampleComponent, + data: { + srlc: { + hideIndicator: true, + }, + }, + }, + { + path: "background-color", + component: KpiWidgetBackgroundColorDocsComponent, + data: { + srlc: { + hideIndicator: true, + }, + }, + }, + { + path: "sync-broker", + component: KpiSyncBrokerDocsComponent, + data: { + srlc: { + hideIndicator: true, + }, + }, + }, +]; + +@NgModule({ + imports: [ + RouterModule.forChild(routes), + NuiButtonModule, + NuiDocsModule, + NuiMessageModule, + NuiDashboardsModule, + NuiSwitchModule, + ], + declarations: [ + KpiDocsComponent, + KpiWidgetExampleComponent, + KpiWidgetInteractiveExampleComponent, + KpiWidgetBackgroundColorDocsComponent, + KpiWidgetBackgroundColorExampleComponent, + KpiSyncBrokerDocsComponent, + KpiSyncBrokerExampleComponent, + KpiSyncBrokerForAllTilesExampleComponent, + ], + providers: [ + KpiColorComparatorsRegistryService, + { + provide: DEMO_PATH_TOKEN, + useValue: getDemoFiles("kpi"), + }, + ], +}) +export default class KpiDocsModule { + constructor( + private comparatorsRegistry: KpiColorComparatorsRegistryService + ) { + this.backgroundColorDocsSetup(); + } + + private backgroundColorDocsSetup() { + this.comparatorsRegistry.registerComparators({ + "!=": { + comparatorFn: (actual: any, reference: any) => + // eslint-disable-next-line eqeqeq + actual != reference, + label: "Not equal", + }, + }); + } +} +\\\\\\\`, + "widget-types/kpi/kpi-sync-broker/kpi-sync-broker-example.component.ts": \\\\\\\`// © 2022 SolarWinds Worldwide, LLC. All rights reserved. +// +// Permission is hereby granted, free of charge, to any person obtaining a copy +// of this software and associated documentation files (the "Software"), to +// deal in the Software without restriction, including without limitation the +// rights to use, copy, modify, merge, publish, distribute, sublicense, and/or +// sell copies of the Software, and to permit persons to whom the Software is +// furnished to do so, subject to the following conditions: +// +// The above copyright notice and this permission notice shall be included in +// all copies or substantial portions of the Software. +// +// THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR +// IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, +// FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE +// AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER +// LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, +// OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN +// THE SOFTWARE. + +import { HttpClient, HttpErrorResponse } from "@angular/common/http"; +import { + ChangeDetectorRef, + Component, + Injectable, + OnDestroy, + OnInit, +} from "@angular/core"; +import { GridsterConfig, GridsterItem } from "angular-gridster2"; +import keyBy from "lodash/keyBy"; +import { BehaviorSubject, of } from "rxjs"; +import { delay, finalize, take } from "rxjs/operators"; + +import { DataSourceService, IFilteringOutputs } from "@nova-ui/bits"; +import { + DATA_SOURCE, + IDashboard, + IKpiData, + IProviderConfiguration, + IWidget, + KpiComponent, + NOVA_KPI_DATASOURCE_ADAPTER, + NOVA_KPI_SCALE_SYNC_BROKER, + PizzagnaLayer, + ProviderRegistryService, + WellKnownPathKey, + WellKnownProviders, + WidgetTypesService, +} from "@nova-ui/dashboards"; + +/** + * A simple KPI data source to retrieve the average rating of Harry Potter and the Sorcerer's Stone (book) via googleapis + */ +@Injectable() +export class AverageRatingKpiDataSource + extends DataSourceService + implements OnDestroy +{ + public static providerId = "AverageRatingKpiDataSource"; + public busy = new BehaviorSubject(false); + + constructor(private http: HttpClient) { + super(); + } + + public async getFilteredData(): Promise { + this.busy.next(true); + return new Promise((resolve) => { + // *** Make a resource request to an external API (if needed) + this.http + .get("https://www.googleapis.com/books/v1/volumes/5MQFrgEACAAJ") + .pipe(finalize(() => this.busy.next(false))) + .subscribe({ + next: (data: any) => { + resolve({ + result: { + value: data.volumeInfo.averageRating, + }, + }); + }, + error: (error: HttpErrorResponse) => { + resolve({ + result: null, + error: { + type: error.status, + }, + }); + }, + }); + }); + } + + public ngOnDestroy(): void { + this.outputsSubject.complete(); + } +} + +/** + * A simple KPI data source to retrieve the ratings count of Harry Potter and the Sorcerer's Stone (book) via googleapis + */ +@Injectable() +export class RatingsCountKpiDataSource + extends DataSourceService + implements OnDestroy +{ + public static providerId = "RatingsCountKpiDataSource"; + + // Use this subject to communicate the data source's busy state + public busy = new BehaviorSubject(false); + + constructor(private http: HttpClient) { + super(); + } + + public async getFilteredData(): Promise { + this.busy.next(true); + return new Promise((resolve) => { + this.http + .get("https://www.googleapis.com/books/v1/volumes/5MQFrgEACAAJ") + .pipe( + delay(2000), + finalize(() => this.busy.next(false)) + ) + .subscribe({ + next: (data: any) => { + resolve({ + result: { + value: data.volumeInfo.ratingsCount, + }, + }); + }, + error: (error: HttpErrorResponse) => { + resolve({ + result: null, + error: { + type: error.status, + }, + }); + }, + }); + }); + } + + public ngOnDestroy(): void { + this.outputsSubject.complete(); + } +} +/** + * A simple KPI data source to retrieve the ratings count of Harry Potter and the Sorcerer's Stone (book) via googleapis + */ +@Injectable() +export class MockKpiDataSource + extends DataSourceService + implements OnDestroy +{ + public static providerId = "MockKpiDataSource"; + + // Use this subject to communicate the data source's busy state + public busy = new BehaviorSubject(false); + + constructor() { + super(); + } + + public async getFilteredData(): Promise { + this.busy.next(true); + return new Promise((resolve) => { + of(3381342) + .pipe( + delay(5000), + take(1), + finalize(() => this.busy.next(false)) + ) + .subscribe({ + next: (data: any) => { + resolve({ + result: { + value: data, + }, + }); + }, + }); + }); + } + + public ngOnDestroy(): void { + this.outputsSubject.complete(); + } +} + +/** + * A component that instantiates the dashboard + */ +@Component({ + selector: "kpi-sync-broker-example", + templateUrl: "./kpi-sync-broker-example.component.html", + styleUrls: ["./kpi-sync-broker-example.component.less"], + standalone: false, +}) +export class KpiSyncBrokerExampleComponent implements OnInit { + public dashboard: IDashboard | undefined; + public gridsterConfig: GridsterConfig = {}; + public editMode: boolean = false; + + constructor( + private widgetTypesService: WidgetTypesService, + private providerRegistry: ProviderRegistryService, + private changeDetectorRef: ChangeDetectorRef + ) {} + + public ngOnInit(): void { + this.setupDashboard(); + + this.initializeDashboard(); + } + + private setupDashboard() { + const widgetTemplate = this.widgetTypesService.getWidgetType("kpi", 1); + + this.widgetTypesService.setNode( + widgetTemplate, + "configurator", + WellKnownPathKey.DataSourceProviders, + [ + AverageRatingKpiDataSource.providerId, + RatingsCountKpiDataSource.providerId, + MockKpiDataSource.providerId, + ] + ); + + this.providerRegistry.setProviders({ + [AverageRatingKpiDataSource.providerId]: { + provide: DATA_SOURCE, + useClass: AverageRatingKpiDataSource, + deps: [HttpClient], + }, + [RatingsCountKpiDataSource.providerId]: { + provide: DATA_SOURCE, + useClass: RatingsCountKpiDataSource, + deps: [HttpClient], + }, + [MockKpiDataSource.providerId]: { + provide: DATA_SOURCE, + useClass: MockKpiDataSource, + deps: [], + }, + }); + } + + /** Used for restoring widgets state */ + public reInitializeDashboard(): void { + // destroys the components and their providers so the dashboard can re init data + this.dashboard = undefined; + this.changeDetectorRef.detectChanges(); + + this.initializeDashboard(); + } + + private initializeDashboard(): void { + const widgetsWithStructure = widgetsConfig.map((w) => + this.widgetTypesService.mergeWithWidgetType(w) + ); + const widgetsIndex = keyBy(widgetsWithStructure, (w: IWidget) => w.id); + + const positions: Record = { + kpiWidgetId: { + cols: 3, + rows: 6, + y: 0, + x: 0, + }, + kpiWidgetId2: { + cols: 3, + rows: 6, + y: 0, + x: 0, + }, + }; + + this.dashboard = { + positions, + widgets: widgetsIndex, + }; + } +} + +const widgetsConfig: IWidget[] = [ + { + id: "kpiWidgetId", + type: "kpi", + pizzagna: { + [PizzagnaLayer.Configuration]: { + header: { + properties: { + title: "NO Sync Broker", + subtitle: "Values sizes are being not synced", + }, + }, + tiles: { + properties: { + nodes: ["kpi1", "kpi2", "kpi3"], + }, + }, + kpi1: { + id: "kpi1", + componentType: KpiComponent.lateLoadKey, + properties: { + widgetData: { + units: \\\\\\\\\\\\\\\`out of 5 Stars\\\\\\\\\\\\\\\`, + label: \\\\\\\\\\\\\\\`Average Rating\\\\\\\\\\\\\\\`, + backgroundColor: "lightpink", + }, + }, + providers: { + [WellKnownProviders.DataSource]: { + providerId: AverageRatingKpiDataSource.providerId, + } as IProviderConfiguration, + [WellKnownProviders.Adapter]: { + providerId: NOVA_KPI_DATASOURCE_ADAPTER, + properties: { + componentId: "kpi1", + propertyPath: "widgetData", + }, + } as IProviderConfiguration, + }, + }, + kpi2: { + id: "kpi2", + componentType: KpiComponent.lateLoadKey, + properties: { + widgetData: { + label: \\\\\\\\\\\\\\\`Another label which might be a pretty long one\\\\\\\\\\\\\\\`, + units: \\\\\\\\\\\\\\\`Which comes from somewhere\\\\\\\\\\\\\\\`, + backgroundColor: "skyblue", + }, + }, + providers: { + [WellKnownProviders.DataSource]: { + providerId: RatingsCountKpiDataSource.providerId, + } as IProviderConfiguration, + [WellKnownProviders.Adapter]: { + providerId: NOVA_KPI_DATASOURCE_ADAPTER, + properties: { + componentId: "kpi2", + propertyPath: "widgetData", + }, + } as IProviderConfiguration, + }, + }, + kpi3: { + id: "kpi3", + componentType: KpiComponent.lateLoadKey, + properties: { + widgetData: { + label: \\\\\\\\\\\\\\\`Random\\\\\\\\\\\\\\\`, + units: \\\\\\\\\\\\\\\`Data\\\\\\\\\\\\\\\`, + }, + }, + providers: { + [WellKnownProviders.DataSource]: { + providerId: MockKpiDataSource.providerId, + } as IProviderConfiguration, + [WellKnownProviders.Adapter]: { + providerId: NOVA_KPI_DATASOURCE_ADAPTER, + properties: { + componentId: "kpi3", + propertyPath: "widgetData", + }, + } as IProviderConfiguration, + }, + }, + }, + }, + }, + { + id: "kpiWidgetId2", + type: "kpi", + pizzagna: { + [PizzagnaLayer.Configuration]: { + header: { + properties: { + title: "WITH Sync Broker", + subtitle: + "Now the values of label, units, and value are being synced", + }, + }, + tiles: { + properties: { + nodes: ["kpi4", "kpi5", "kpi6"], + }, + providers: { + // This is where and how you set the sync broker provider + kpiScaleSyncBroker: { + providerId: NOVA_KPI_SCALE_SYNC_BROKER, + properties: { + scaleSyncConfig: [ + // You can decide which values to keep in sync. For instance, you can leave only 'label' id in the array below + { id: "value" }, + { id: "label" }, + { id: "units" }, + ], + }, + }, + }, + }, + kpi4: { + id: "kpi4", + componentType: KpiComponent.lateLoadKey, + properties: { + widgetData: { + units: \\\\\\\\\\\\\\\`out of 5 Stars\\\\\\\\\\\\\\\`, + label: \\\\\\\\\\\\\\\`Average Rating\\\\\\\\\\\\\\\`, + backgroundColor: "lightpink", + }, + }, + providers: { + [WellKnownProviders.DataSource]: { + providerId: AverageRatingKpiDataSource.providerId, + } as IProviderConfiguration, + [WellKnownProviders.Adapter]: { + providerId: NOVA_KPI_DATASOURCE_ADAPTER, + properties: { + componentId: "kpi4", + propertyPath: "widgetData", + }, + } as IProviderConfiguration, + }, + }, + kpi5: { + id: "kpi5", + componentType: KpiComponent.lateLoadKey, + properties: { + widgetData: { + label: \\\\\\\\\\\\\\\`Another label which might be a pretty long one\\\\\\\\\\\\\\\`, + units: \\\\\\\\\\\\\\\`Which comes from somewhere\\\\\\\\\\\\\\\`, + backgroundColor: "skyblue", + }, + }, + providers: { + [WellKnownProviders.DataSource]: { + providerId: RatingsCountKpiDataSource.providerId, + } as IProviderConfiguration, + [WellKnownProviders.Adapter]: { + providerId: NOVA_KPI_DATASOURCE_ADAPTER, + properties: { + componentId: "kpi5", + propertyPath: "widgetData", + }, + } as IProviderConfiguration, + }, + }, + kpi6: { + id: "kpi6", + componentType: KpiComponent.lateLoadKey, + properties: { + widgetData: { + label: \\\\\\\\\\\\\\\`Random\\\\\\\\\\\\\\\`, + units: \\\\\\\\\\\\\\\`Data\\\\\\\\\\\\\\\`, + }, + }, + providers: { + [WellKnownProviders.DataSource]: { + providerId: MockKpiDataSource.providerId, + } as IProviderConfiguration, + [WellKnownProviders.Adapter]: { + providerId: NOVA_KPI_DATASOURCE_ADAPTER, + properties: { + componentId: "kpi6", + propertyPath: "widgetData", + }, + } as IProviderConfiguration, + }, + }, + }, + }, + }, +]; +\\\\\\\`, + "widget-types/kpi/kpi-sync-broker-docs.component.ts": \\\\\\\`// © 2022 SolarWinds Worldwide, LLC. All rights reserved. +// +// Permission is hereby granted, free of charge, to any person obtaining a copy +// of this software and associated documentation files (the "Software"), to +// deal in the Software without restriction, including without limitation the +// rights to use, copy, modify, merge, publish, distribute, sublicense, and/or +// sell copies of the Software, and to permit persons to whom the Software is +// furnished to do so, subject to the following conditions: +// +// The above copyright notice and this permission notice shall be included in +// all copies or substantial portions of the Software. +// +// THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR +// IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, +// FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE +// AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER +// LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, +// OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN +// THE SOFTWARE. + +import { Component } from "@angular/core"; + +@Component({ + selector: "kpi-sync-broker-docs", + templateUrl: "./kpi-sync-broker-docs.component.html", + standalone: false, +}) +export class KpiSyncBrokerDocsComponent { + public kpiScaleSyncBroker = \\\\\\\\\\\\\\\` +"tiles": { + "providers": { + kpiScaleSyncBroker: { + providerId: NOVA_KPI_SCALE_SYNC_BROKER, + properties: { + scaleSyncConfig: [ + { id: "value" }, + { id: "label" }, + { id: "units" }, + ], + }, + }, + }, +}, +\\\\\\\\\\\\\\\`; + + public defineScaleBrokerOnDashboardSetup = \\\\\\\\\\\\\\\` +// To add the sync broker globally to all the kpi tiles you may start with setting up the broker config +// Here you define which values to keep in sync +const brokerConfig = { + providerId: NOVA_KPI_SCALE_SYNC_BROKER, + properties: { + scaleSyncConfig: [ + { id: "value" }, + { id: "label" }, + { id: "units" }, + ], + }, + }; + +// And here is how you set the sync broker for every KPI widget in the dashboard. +// Later, you will be able to override this setting for each separate KPI widget in the configuration (just like it is shown in the third +// width of the example with the 'kpiWidgetId3') +this.widgetTypesService.setNode( + widgetTemplate, + "widget", + "tiles.providers.kpiScaleSyncBroker", + brokerConfig +); +\\\\\\\\\\\\\\\`; +} +\\\\\\\`, + "widget-types/kpi/kpi-sync-broker-for-all-tiles/kpi-sync-broker-for-all-tiles-example.component.ts": \\\\\\\`// © 2022 SolarWinds Worldwide, LLC. All rights reserved. +// +// Permission is hereby granted, free of charge, to any person obtaining a copy +// of this software and associated documentation files (the "Software"), to +// deal in the Software without restriction, including without limitation the +// rights to use, copy, modify, merge, publish, distribute, sublicense, and/or +// sell copies of the Software, and to permit persons to whom the Software is +// furnished to do so, subject to the following conditions: +// +// The above copyright notice and this permission notice shall be included in +// all copies or substantial portions of the Software. +// +// THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR +// IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, +// FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE +// AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER +// LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, +// OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN +// THE SOFTWARE. + +import { HttpClient, HttpErrorResponse } from "@angular/common/http"; +import { + ChangeDetectorRef, + Component, + Injectable, + OnDestroy, + OnInit, +} from "@angular/core"; +import { GridsterConfig, GridsterItem } from "angular-gridster2"; +import keyBy from "lodash/keyBy"; +import { BehaviorSubject, of } from "rxjs"; +import { delay, finalize, take } from "rxjs/operators"; + +import { DataSourceService, IFilteringOutputs } from "@nova-ui/bits"; +import { + DATA_SOURCE, + IDashboard, + IKpiData, + IProviderConfiguration, + IWidget, + KpiComponent, + NOVA_KPI_DATASOURCE_ADAPTER, + NOVA_KPI_SCALE_SYNC_BROKER, + PizzagnaLayer, + ProviderRegistryService, + WellKnownPathKey, + WellKnownProviders, + WidgetTypesService, +} from "@nova-ui/dashboards"; + +/** + * A simple KPI data source to retrieve the average rating of Harry Potter and the Sorcerer's Stone (book) via googleapis + */ +@Injectable() +export class AverageRatingKpiDataSource + extends DataSourceService + implements OnDestroy +{ + public static providerId = "AverageRatingKpiDataSource"; + public busy = new BehaviorSubject(false); + + constructor(private http: HttpClient) { + super(); + } + + public async getFilteredData(): Promise { + this.busy.next(true); + return new Promise((resolve) => { + // *** Make a resource request to an external API (if needed) + this.http + .get("https://www.googleapis.com/books/v1/volumes/5MQFrgEACAAJ") + .pipe(finalize(() => this.busy.next(false))) + .subscribe({ + next: (data: any) => { + resolve({ + result: { + value: data.volumeInfo.averageRating, + }, + }); + }, + error: (error: HttpErrorResponse) => { + resolve({ + result: null, + error: { + type: error.status, + }, + }); + }, + }); + }); + } + + public ngOnDestroy(): void { + this.outputsSubject.complete(); + } +} + +/** + * A simple KPI data source to retrieve the ratings count of Harry Potter and the Sorcerer's Stone (book) via googleapis + */ +@Injectable() +export class RatingsCountKpiDataSource + extends DataSourceService + implements OnDestroy +{ + public static providerId = "RatingsCountKpiDataSource"; + + // Use this subject to communicate the data source's busy state + public busy = new BehaviorSubject(false); + + constructor(private http: HttpClient) { + super(); + } + + public async getFilteredData(): Promise { + this.busy.next(true); + return new Promise((resolve) => { + this.http + .get("https://www.googleapis.com/books/v1/volumes/5MQFrgEACAAJ") + .pipe( + delay(2000), + finalize(() => this.busy.next(false)) + ) + .subscribe({ + next: (data: any) => { + resolve({ + result: { + value: data.volumeInfo.ratingsCount, + }, + }); + }, + error: (error: HttpErrorResponse) => { + resolve({ + result: null, + error: { + type: error.status, + }, + }); + }, + }); + }); + } + + public ngOnDestroy(): void { + this.outputsSubject.complete(); + } +} +/** + * A simple KPI data source to retrieve the ratings count of Harry Potter and the Sorcerer's Stone (book) via googleapis + */ +@Injectable() +export class MockKpiDataSource + extends DataSourceService + implements OnDestroy +{ + public static providerId = "MockKpiDataSource"; + + // Use this subject to communicate the data source's busy state + public busy = new BehaviorSubject(false); + public value: number = 3381342; + + constructor() { + super(); + } + + public async getFilteredData(): Promise { + this.busy.next(true); + return new Promise((resolve) => { + of(this.value) + .pipe( + delay(5000), + take(1), + finalize(() => this.busy.next(false)) + ) + .subscribe({ + next: (data: any) => { + resolve({ + result: { + value: data, + }, + }); + }, + }); + }); + } + + public ngOnDestroy(): void { + this.outputsSubject.complete(); + } +} + +/** + * A component that instantiates the dashboard + */ +@Component({ + selector: "kpi-sync-broker-for-all-tiles-example", + templateUrl: "./kpi-sync-broker-for-all-tiles-example.component.html", + styleUrls: ["./kpi-sync-broker-for-all-tiles-example.component.less"], + standalone: false, +}) +export class KpiSyncBrokerForAllTilesExampleComponent implements OnInit { + public dashboard: IDashboard | undefined; + public gridsterConfig: GridsterConfig = {}; + public editMode: boolean = false; + + constructor( + private widgetTypesService: WidgetTypesService, + private providerRegistry: ProviderRegistryService, + private changeDetectorRef: ChangeDetectorRef + ) {} + + public ngOnInit(): void { + this.setupDashboard(); + + this.initializeDashboard(); + } + + /** Used for restoring widgets state */ + public reInitializeDashboard(): void { + // destroys the components and their providers so the dashboard can re init data + this.dashboard = undefined; + this.changeDetectorRef.detectChanges(); + + this.initializeDashboard(); + } + + private setupDashboard() { + // To add the sync broker globally to all the kpi tiles you may start with setting up the broker config + // Here you define which values to keep in sync + const brokerConfig = { + providerId: NOVA_KPI_SCALE_SYNC_BROKER, + properties: { + scaleSyncConfig: [ + { id: "value" }, + { id: "label" }, + { id: "units" }, + ], + }, + }; + const widgetTemplate = this.widgetTypesService.getWidgetType("kpi", 1); + + this.widgetTypesService.setNode( + widgetTemplate, + "configurator", + WellKnownPathKey.DataSourceProviders, + [ + AverageRatingKpiDataSource.providerId, + RatingsCountKpiDataSource.providerId, + MockKpiDataSource.providerId, + ] + ); + + // And here is how you set the sync broker for every KPI widget in the dashboard. + // Later, you will be able to override this setting for each separate KPI widget in the configuration (just like it is shown in the third + // width of the example with the 'kpiWidgetId3') + this.widgetTypesService.setNode( + widgetTemplate, + "widget", + "tiles.providers.kpiScaleSyncBroker", + brokerConfig + ); + + this.providerRegistry.setProviders({ + [AverageRatingKpiDataSource.providerId]: { + provide: DATA_SOURCE, + useClass: AverageRatingKpiDataSource, + deps: [HttpClient], + }, + [RatingsCountKpiDataSource.providerId]: { + provide: DATA_SOURCE, + useClass: RatingsCountKpiDataSource, + deps: [HttpClient], + }, + [MockKpiDataSource.providerId]: { + provide: DATA_SOURCE, + useClass: MockKpiDataSource, + deps: [], + }, + }); + } + + private initializeDashboard(): void { + const widgetsWithStructure = widgetsConfig.map((w) => + this.widgetTypesService.mergeWithWidgetType(w) + ); + const widgetsIndex = keyBy(widgetsWithStructure, (w: IWidget) => w.id); + + const positions: Record = { + kpiWidgetId: { + cols: 3, + rows: 6, + y: 0, + x: 0, + }, + kpiWidgetId2: { + cols: 3, + rows: 6, + y: 0, + x: 3, + }, + kpiWidgetId3: { + cols: 3, + rows: 6, + y: 0, + x: 6, + }, + }; + + this.dashboard = { + positions, + widgets: widgetsIndex, + }; + } +} + +const widgetsConfig: IWidget[] = [ + { + id: "kpiWidgetId", + type: "kpi", + pizzagna: { + [PizzagnaLayer.Configuration]: { + header: { + properties: { + title: "Sync Broker Applied for ALL Widgets", + subtitle: "Values are being synced", + }, + }, + tiles: { + properties: { + nodes: ["kpi1", "kpi2", "kpi3"], + }, + }, + kpi1: { + id: "kpi1", + componentType: KpiComponent.lateLoadKey, + properties: { + widgetData: { + units: \\\\\\\\\\\\\\\`out of 5 Stars\\\\\\\\\\\\\\\`, + label: \\\\\\\\\\\\\\\`Average Rating\\\\\\\\\\\\\\\`, + backgroundColor: "lightpink", + }, + }, + providers: { + [WellKnownProviders.DataSource]: { + providerId: AverageRatingKpiDataSource.providerId, + } as IProviderConfiguration, + [WellKnownProviders.Adapter]: { + providerId: NOVA_KPI_DATASOURCE_ADAPTER, + properties: { + componentId: "kpi1", + propertyPath: "widgetData", + }, + } as IProviderConfiguration, + }, + }, + kpi2: { + id: "kpi2", + componentType: KpiComponent.lateLoadKey, + properties: { + widgetData: { + label: \\\\\\\\\\\\\\\`Another label which might be a pretty long one\\\\\\\\\\\\\\\`, + units: \\\\\\\\\\\\\\\`Which comes from somewhere\\\\\\\\\\\\\\\`, + backgroundColor: "skyblue", + }, + }, + providers: { + [WellKnownProviders.DataSource]: { + providerId: RatingsCountKpiDataSource.providerId, + } as IProviderConfiguration, + [WellKnownProviders.Adapter]: { + providerId: NOVA_KPI_DATASOURCE_ADAPTER, + properties: { + componentId: "kpi2", + propertyPath: "widgetData", + }, + } as IProviderConfiguration, + }, + }, + kpi3: { + id: "kpi3", + componentType: KpiComponent.lateLoadKey, + properties: { + widgetData: { + label: \\\\\\\\\\\\\\\`Random\\\\\\\\\\\\\\\`, + units: \\\\\\\\\\\\\\\`Data\\\\\\\\\\\\\\\`, + }, + }, + providers: { + [WellKnownProviders.DataSource]: { + providerId: MockKpiDataSource.providerId, + } as IProviderConfiguration, + [WellKnownProviders.Adapter]: { + providerId: NOVA_KPI_DATASOURCE_ADAPTER, + properties: { + componentId: "kpi3", + propertyPath: "widgetData", + }, + } as IProviderConfiguration, + }, + }, + }, + }, + }, + { + id: "kpiWidgetId2", + type: "kpi", + pizzagna: { + [PizzagnaLayer.Configuration]: { + header: { + properties: { + title: "Sync Broker Applied for ALL Widgets", + subtitle: + "Now the values of label, units, and value are being synced", + }, + }, + tiles: { + properties: { + nodes: ["kpi1", "kpi2", "kpi3"], + }, + }, + kpi1: { + id: "kpi1", + componentType: KpiComponent.lateLoadKey, + properties: { + widgetData: { + units: \\\\\\\\\\\\\\\`out of 5 Stars\\\\\\\\\\\\\\\`, + label: \\\\\\\\\\\\\\\`Average Rating\\\\\\\\\\\\\\\`, + backgroundColor: "lightpink", + }, + }, + providers: { + [WellKnownProviders.DataSource]: { + providerId: AverageRatingKpiDataSource.providerId, + } as IProviderConfiguration, + [WellKnownProviders.Adapter]: { + providerId: NOVA_KPI_DATASOURCE_ADAPTER, + properties: { + componentId: "kpi1", + propertyPath: "widgetData", + }, + } as IProviderConfiguration, + }, + }, + kpi2: { + id: "kpi2", + componentType: KpiComponent.lateLoadKey, + properties: { + widgetData: { + label: \\\\\\\\\\\\\\\`Another label which might be a pretty long one\\\\\\\\\\\\\\\`, + units: \\\\\\\\\\\\\\\`Which comes from somewhere\\\\\\\\\\\\\\\`, + backgroundColor: "skyblue", + }, + }, + providers: { + [WellKnownProviders.DataSource]: { + providerId: RatingsCountKpiDataSource.providerId, + } as IProviderConfiguration, + [WellKnownProviders.Adapter]: { + providerId: NOVA_KPI_DATASOURCE_ADAPTER, + properties: { + componentId: "kpi2", + propertyPath: "widgetData", + }, + } as IProviderConfiguration, + }, + }, + kpi3: { + id: "kpi3", + componentType: KpiComponent.lateLoadKey, + properties: { + widgetData: { + label: \\\\\\\\\\\\\\\`Random\\\\\\\\\\\\\\\`, + units: \\\\\\\\\\\\\\\`Data\\\\\\\\\\\\\\\`, + }, + }, + providers: { + [WellKnownProviders.DataSource]: { + providerId: MockKpiDataSource.providerId, + } as IProviderConfiguration, + [WellKnownProviders.Adapter]: { + providerId: NOVA_KPI_DATASOURCE_ADAPTER, + properties: { + componentId: "kpi3", + propertyPath: "widgetData", + }, + } as IProviderConfiguration, + }, + }, + }, + }, + }, + { + id: "kpiWidgetId3", + type: "kpi", + pizzagna: { + [PizzagnaLayer.Configuration]: { + header: { + properties: { + title: "Here We Sync Only Labels and Units", + subtitle: + "Now only the label, and units are being synced", + }, + }, + tiles: { + properties: { + nodes: ["kpi1", "kpi2", "kpi3"], + }, + providers: { + // This is where and how you can override the globally set broker config + kpiScaleSyncBroker: { + providerId: NOVA_KPI_SCALE_SYNC_BROKER, + properties: { + scaleSyncConfig: [ + { id: "label" }, + { id: "units" }, + ], + }, + }, + }, + }, + kpi1: { + id: "kpi1", + componentType: KpiComponent.lateLoadKey, + properties: { + widgetData: { + units: \\\\\\\\\\\\\\\`out of 5 Stars\\\\\\\\\\\\\\\`, + label: \\\\\\\\\\\\\\\`Average Rating\\\\\\\\\\\\\\\`, + backgroundColor: "lightpink", + }, + }, + providers: { + [WellKnownProviders.DataSource]: { + providerId: AverageRatingKpiDataSource.providerId, + } as IProviderConfiguration, + [WellKnownProviders.Adapter]: { + providerId: NOVA_KPI_DATASOURCE_ADAPTER, + properties: { + componentId: "kpi1", + propertyPath: "widgetData", + }, + } as IProviderConfiguration, + }, + }, + kpi2: { + id: "kpi2", + componentType: KpiComponent.lateLoadKey, + properties: { + widgetData: { + label: \\\\\\\\\\\\\\\`Another label which might be a pretty long one\\\\\\\\\\\\\\\`, + units: \\\\\\\\\\\\\\\`Which comes from somewhere\\\\\\\\\\\\\\\`, + backgroundColor: "skyblue", + }, + }, + providers: { + [WellKnownProviders.DataSource]: { + providerId: RatingsCountKpiDataSource.providerId, + } as IProviderConfiguration, + [WellKnownProviders.Adapter]: { + providerId: NOVA_KPI_DATASOURCE_ADAPTER, + properties: { + componentId: "kpi2", + propertyPath: "widgetData", + }, + } as IProviderConfiguration, + }, + }, + kpi3: { + id: "kpi3", + componentType: KpiComponent.lateLoadKey, + properties: { + widgetData: { + label: \\\\\\\\\\\\\\\`Random\\\\\\\\\\\\\\\`, + units: \\\\\\\\\\\\\\\`Data\\\\\\\\\\\\\\\`, + }, + }, + providers: { + [WellKnownProviders.DataSource]: { + providerId: MockKpiDataSource.providerId, + } as IProviderConfiguration, + [WellKnownProviders.Adapter]: { + providerId: NOVA_KPI_DATASOURCE_ADAPTER, + properties: { + componentId: "kpi3", + propertyPath: "widgetData", + }, + } as IProviderConfiguration, + }, + }, + }, + }, + }, +]; +\\\\\\\`, + "widget-types/kpi/kpi-widget/kpi-widget-example.component.ts": \\\\\\\`// © 2022 SolarWinds Worldwide, LLC. All rights reserved. +// +// Permission is hereby granted, free of charge, to any person obtaining a copy +// of this software and associated documentation files (the "Software"), to +// deal in the Software without restriction, including without limitation the +// rights to use, copy, modify, merge, publish, distribute, sublicense, and/or +// sell copies of the Software, and to permit persons to whom the Software is +// furnished to do so, subject to the following conditions: +// +// The above copyright notice and this permission notice shall be included in +// all copies or substantial portions of the Software. +// +// THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR +// IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, +// FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE +// AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER +// LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, +// OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN +// THE SOFTWARE. + +import { HttpClient, HttpErrorResponse } from "@angular/common/http"; +import { Component, Injectable, OnDestroy, OnInit } from "@angular/core"; +import { GridsterConfig, GridsterItem } from "angular-gridster2"; +import { BehaviorSubject } from "rxjs"; +import { finalize } from "rxjs/operators"; + +import { DataSourceService, IFilteringOutputs } from "@nova-ui/bits"; +import { + DATA_SOURCE, + DEFAULT_PIZZAGNA_ROOT, + IDashboard, + IKpiData, + IProviderConfiguration, + IRefresherProperties, + IWidget, + IWidgets, + KpiComponent, + NOVA_KPI_DATASOURCE_ADAPTER, + PizzagnaLayer, + ProviderRegistryService, + WellKnownPathKey, + WellKnownProviders, + WidgetTypesService, +} from "@nova-ui/dashboards"; + +/** + * A simple KPI data source to retrieve the average rating of Harry Potter and the Sorcerer's Stone (book) via googleapis + */ +@Injectable() +export class AverageRatingKpiDataSource + extends DataSourceService + implements OnDestroy +{ + // This is the ID we'll use to identify the provider + public static providerId = "AverageRatingKpiDataSource"; + + // Use this subject to communicate the data source's busy state + public busy = new BehaviorSubject(false); + + constructor(private http: HttpClient) { + super(); + } + + // In this example, getFilteredData is invoked every 10 minutes (Take a look at the refresher + // provider definition in the widget configuration below to see how the interval is set) + public async getFilteredData(): Promise { + this.busy.next(true); + return new Promise((resolve) => { + // *** Make a resource request to an external API (if needed) + this.http + .get("https://www.googleapis.com/books/v1/volumes/5MQFrgEACAAJ") + .pipe(finalize(() => this.busy.next(false))) + .subscribe({ + next: (data: any) => { + resolve({ + result: { + value: data.volumeInfo.averageRating, + }, + }); + }, + error: (error: HttpErrorResponse) => { + resolve({ + result: null, + error: { + type: error.status, + }, + }); + }, + }); + }); + } + + public ngOnDestroy(): void { + this.outputsSubject.complete(); + } +} + +/** + * A component that instantiates the dashboard + */ +@Component({ + selector: "kpi-widget-example", + templateUrl: "./kpi-widget-example.component.html", + styleUrls: ["./kpi-widget-example.component.less"], + standalone: false, +}) +export class KpiWidgetExampleComponent implements OnInit { + // This variable will hold all the data needed to define the layout and behavior of the widgets. + // Pass this to the dashboard component's dashboard input in the template. + public dashboard: IDashboard; + + // Angular gridster requires a configuration object even if it's empty. + // Pass this to the dashboard component's gridsterConfig input in the template. + public gridsterConfig: GridsterConfig = {}; + + // Boolean passed as an input to the dashboard. When true, widgets can be moved, resized, removed, or edited + public editMode: boolean = false; + + constructor( + // WidgetTypesService provides the widget's necessary structure information + private widgetTypesService: WidgetTypesService, + + // In general, the ProviderRegistryService is used for making entities available for injection into dynamically loaded components. + private providerRegistry: ProviderRegistryService + ) {} + + public ngOnInit(): void { + // Grabbing the widget's default template which will be needed as a parameter for setNode + const widgetTemplate = this.widgetTypesService.getWidgetType("kpi", 1); + // Registering our data sources as dropdown options in the widget editor/configurator + // Note: This could also be done in the parent module's constructor so that + // multiple dashboards could have access to the same widget template modification. + this.widgetTypesService.setNode( + // This is the template we grabbed above with getWidgetType + widgetTemplate, + // We are setting the editor/configurator part of the widget template + "configurator", + // This indicates which node you are changing and we want to change + // the data source providers available for selection in the editor. + WellKnownPathKey.DataSourceProviders, + // We are setting the data sources available for selection in the editor + [AverageRatingKpiDataSource.providerId] + ); + + // Registering the data source for injection into the KPI tile. + // Note: Each tile of a KPI widget is assigned its own instance of the data source + this.providerRegistry.setProviders({ + [AverageRatingKpiDataSource.providerId]: { + provide: DATA_SOURCE, + useClass: AverageRatingKpiDataSource, + // Any dependencies that need to be injected into the provider must be listed here + deps: [HttpClient], + }, + }); + + this.initializeDashboard(); + } + + public initializeDashboard(): void { + // We're using a static configuration object for this example, but this is where + // the widget's configuration could potentially be populated from a database + const kpiWidget = widgetConfig; + const widgetIndex: IWidgets = { + // Complete the KPI widget with information coming from its type definition + [kpiWidget.id]: + this.widgetTypesService.mergeWithWidgetType(kpiWidget), + }; + + // Setting the widget dimensions and position (this is for gridster) + const positions: Record = { + [kpiWidget.id]: { + cols: 4, + rows: 6, + y: 0, + x: 0, + }, + }; + + // Finally, assigning the variables we created above to the dashboard + this.dashboard = { + positions, + widgets: widgetIndex, + }; + } +} + +const widgetConfig: IWidget = { + id: "kpiWidgetId", + type: "kpi", + pizzagna: { + [PizzagnaLayer.Configuration]: { + [DEFAULT_PIZZAGNA_ROOT]: { + providers: { + [WellKnownProviders.Refresher]: { + properties: { + // Configuring the refresher interval so that our data source is invoked every ten minutes + interval: 60 * 10, + enabled: true, + } as IRefresherProperties, + } as Partial, + }, + }, + header: { + properties: { + title: "Harry Potter and the Sorcerer's Stone", + subtitle: "By J. K. Rowling", + }, + }, + tiles: { + properties: { + nodes: ["kpi1"], + }, + }, + kpi1: { + id: "kpi1", + componentType: KpiComponent.lateLoadKey, + properties: { + widgetData: { + units: \\\\\\\\\\\\\\\`out of 5 Stars\\\\\\\\\\\\\\\`, + label: \\\\\\\\\\\\\\\`Average Rating\\\\\\\\\\\\\\\`, + }, + }, + providers: { + [WellKnownProviders.DataSource]: { + // Setting the data source providerId for the tile with id "kpi1" + providerId: AverageRatingKpiDataSource.providerId, + } as IProviderConfiguration, + [WellKnownProviders.Adapter]: { + providerId: NOVA_KPI_DATASOURCE_ADAPTER, + properties: { + componentId: "kpi1", + propertyPath: "widgetData", + }, + } as IProviderConfiguration, + }, + }, + }, + }, +}; +\\\\\\\`, + "widget-types/kpi/kpi-widget-background-color/kpi-widget-background-color-example.component.ts": \\\\\\\`// © 2022 SolarWinds Worldwide, LLC. All rights reserved. +// +// Permission is hereby granted, free of charge, to any person obtaining a copy +// of this software and associated documentation files (the "Software"), to +// deal in the Software without restriction, including without limitation the +// rights to use, copy, modify, merge, publish, distribute, sublicense, and/or +// sell copies of the Software, and to permit persons to whom the Software is +// furnished to do so, subject to the following conditions: +// +// The above copyright notice and this permission notice shall be included in +// all copies or substantial portions of the Software. +// +// THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR +// IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, +// FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE +// AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER +// LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, +// OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN +// THE SOFTWARE. + +import { HttpClient, HttpErrorResponse } from "@angular/common/http"; +import { + ChangeDetectorRef, + Component, + Injectable, + OnDestroy, + OnInit, +} from "@angular/core"; +import { GridsterConfig, GridsterItem } from "angular-gridster2"; +import { BehaviorSubject } from "rxjs"; +import { finalize } from "rxjs/operators"; + +import { DataSourceService, IFilteringOutputs } from "@nova-ui/bits"; +import { + DATA_SOURCE, + DEFAULT_KPI_BACKGROUND_COLORS, + IDashboard, + IKpiColorRules, + IKpiData, + IProviderConfiguration, + IWidget, + IWidgets, + KpiComponent, + NOVA_KPI_COLOR_PRIORITIZER, + NOVA_KPI_DATASOURCE_ADAPTER, + PizzagnaLayer, + ProviderRegistryService, + WellKnownPathKey, + WellKnownProviders, + WidgetTypesService, +} from "@nova-ui/dashboards"; + +/** + * A simple KPI data source to retrieve the average rating of Harry Potter and the Sorcerer's Stone (book) via googleapis + */ +@Injectable() +export class AverageRatingKpiDataSource + extends DataSourceService + implements OnDestroy +{ + public static providerId = "AverageRatingKpiDataSource"; + public busy = new BehaviorSubject(false); + + constructor(private http: HttpClient) { + super(); + } + + public async getFilteredData(): Promise { + this.busy.next(true); + return new Promise((resolve) => { + // *** Make a resource request to an external API (if needed) + this.http + .get("https://www.googleapis.com/books/v1/volumes/5MQFrgEACAAJ") + .pipe(finalize(() => this.busy.next(false))) + .subscribe({ + next: (data: any) => { + resolve({ + result: { + value: data.volumeInfo.averageRating, + // setting the color on the dataSource "Sea Green", + // uncomment to get the background color update from the "Data" layer + // backgroundColor: "var(--nui-color-chart-three)", + }, + }); + }, + error: (error: HttpErrorResponse) => { + resolve({ + result: null, + error: { + type: error.status, + }, + }); + }, + }); + }); + } + + public ngOnDestroy(): void { + this.outputsSubject.complete(); + } +} + +/** + * A component that instantiates the dashboard + */ +@Component({ + selector: "kpi-widget-background-color-example", + templateUrl: "./kpi-widget-background-color-example.component.html", + styleUrls: ["./kpi-widget-background-color-example.component.less"], + standalone: false, +}) +export class KpiWidgetBackgroundColorExampleComponent implements OnInit { + public dashboard: IDashboard | undefined; + public gridsterConfig: GridsterConfig = {}; + public editMode: boolean = false; + + constructor( + private widgetTypesService: WidgetTypesService, + private providerRegistry: ProviderRegistryService, + private changeDetectorRef: ChangeDetectorRef + ) {} + + public ngOnInit(): void { + this.setupDashboard(); + + // KPI tile default color setup + this.setupDefaultColorStructure(); + + // Sets the custom pallette to the 'Description' section + this.setupCustomPalletteDescription(); + + // Sets the custom pallette to the 'Background color rules' section + this.setupCustomPalletteRules(); + + this.initializeDashboard(); + } + + /** Used for restoring widgets state */ + public reInitializeDashboard(): void { + // destroys the components and their providers so the dashboard can re init data + this.dashboard = undefined; + this.changeDetectorRef.detectChanges(); + + this.initializeDashboard(); + } + + private setupCustomPalletteDescription() { + const kpiWidgetTemplate = this.widgetTypesService.getWidgetType( + "kpi", + 1 + ); + this.widgetTypesService.setNode( + kpiWidgetTemplate, + "configurator", + WellKnownPathKey.TileDescriptionBackgroundColors, + [ + { color: "var(--nui-color-chart-one)", label: "Blue" }, + { + color: "var(--nui-color-chart-one-light)", + label: "Blue Light", + }, + { + color: "var(--nui-color-chart-one-dark)", + label: "Blue Dark", + }, + ] + ); + } + + private setupCustomPalletteRules() { + const kpiWidgetTemplate = this.widgetTypesService.getWidgetType( + "kpi", + 1 + ); + this.widgetTypesService.setNode( + kpiWidgetTemplate, + "configurator", + WellKnownPathKey.TileBackgroundColorRulesBackgroundColors, + [ + { color: "red", label: "Native Red" }, + ...DEFAULT_KPI_BACKGROUND_COLORS, + ] + ); + } + + private setupDefaultColorStructure() { + const widgetTemplate = this.widgetTypesService.getWidgetType("kpi", 1); + this.widgetTypesService.setNode( + widgetTemplate, + "widget", + "tiles.properties.template.properties.widgetData.backgroundColor", + "red" + ); + } + + private setupDashboard() { + const widgetTemplate = this.widgetTypesService.getWidgetType("kpi", 1); + this.widgetTypesService.setNode( + widgetTemplate, + "configurator", + WellKnownPathKey.DataSourceProviders, + [AverageRatingKpiDataSource.providerId] + ); + + this.providerRegistry.setProviders({ + [AverageRatingKpiDataSource.providerId]: { + provide: DATA_SOURCE, + useClass: AverageRatingKpiDataSource, + deps: [HttpClient], + }, + }); + } + + private initializeDashboard(): void { + const kpiWidget = widgetConfig; + const widgetIndex: IWidgets = { + [kpiWidget.id]: + this.widgetTypesService.mergeWithWidgetType(kpiWidget), + }; + + const positions: Record = { + [kpiWidget.id]: { + cols: 4, + rows: 6, + y: 0, + x: 0, + }, + }; + + this.dashboard = { + positions, + widgets: widgetIndex, + }; + } +} + +const widgetConfig: IWidget = { + id: "kpiWidgetId", + type: "kpi", + pizzagna: { + [PizzagnaLayer.Configuration]: { + header: { + properties: { + title: "Harry Potter and the Sorcerer's Stone", + subtitle: "By J. K. Rowling", + }, + }, + tiles: { + properties: { + nodes: ["kpi1"], + }, + }, + kpi1: { + id: "kpi1", + componentType: KpiComponent.lateLoadKey, + properties: { + widgetData: { + units: \\\\\\\\\\\\\\\`out of 5 Stars\\\\\\\\\\\\\\\`, + label: \\\\\\\\\\\\\\\`Average Rating\\\\\\\\\\\\\\\`, + // Configuration color "Blue" + backgroundColor: "var(--nui-color-chart-one)", + }, + }, + providers: { + [WellKnownProviders.DataSource]: { + providerId: AverageRatingKpiDataSource.providerId, + } as IProviderConfiguration, + [WellKnownProviders.Adapter]: { + providerId: NOVA_KPI_DATASOURCE_ADAPTER, + properties: { + componentId: "kpi1", + propertyPath: "widgetData", + }, + } as IProviderConfiguration, + [WellKnownProviders.KpiColorPrioritizer]: { + providerId: NOVA_KPI_COLOR_PRIORITIZER, + properties: { + // Color Prioritizer Rules + // settings rules - if the value is more than "2" display "Violet" color + rules: [ + { + comparisonType: ">", + value: 2, + color: "var(--nui-color-chart-four)", + }, + ] as IKpiColorRules[], + }, + } as IProviderConfiguration, + }, + }, + }, + }, +}; +\\\\\\\`, + "widget-types/kpi/kpi-widget-background-color-docs.component.ts": \\\\\\\`// © 2022 SolarWinds Worldwide, LLC. All rights reserved. +// +// Permission is hereby granted, free of charge, to any person obtaining a copy +// of this software and associated documentation files (the "Software"), to +// deal in the Software without restriction, including without limitation the +// rights to use, copy, modify, merge, publish, distribute, sublicense, and/or +// sell copies of the Software, and to permit persons to whom the Software is +// furnished to do so, subject to the following conditions: +// +// The above copyright notice and this permission notice shall be included in +// all copies or substantial portions of the Software. +// +// THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR +// IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, +// FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE +// AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER +// LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, +// OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN +// THE SOFTWARE. + +import { Component } from "@angular/core"; + +@Component({ + selector: "nui-kpi-background-color-docs", + templateUrl: "./kpi-widget-background-color-docs.component.html", + standalone: false, +}) +export class KpiWidgetBackgroundColorDocsComponent { + public comparatorsRegistryCode = \\\\\\\\\\\\\\\` + this.comparatorsRegistry.registerComparators({ + "!=": { + comparatorFn: (actual: any, reference: any) => actual != reference, + label: "Not equal", + }, + }); + \\\\\\\\\\\\\\\`; +} +\\\\\\\`, + "widget-types/kpi/kpi-widget-interactive/kpi-widget-interactive-example.component.ts": \\\\\\\`// © 2022 SolarWinds Worldwide, LLC. All rights reserved. +// +// Permission is hereby granted, free of charge, to any person obtaining a copy +// of this software and associated documentation files (the "Software"), to +// deal in the Software without restriction, including without limitation the +// rights to use, copy, modify, merge, publish, distribute, sublicense, and/or +// sell copies of the Software, and to permit persons to whom the Software is +// furnished to do so, subject to the following conditions: +// +// The above copyright notice and this permission notice shall be included in +// all copies or substantial portions of the Software. +// +// THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR +// IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, +// FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE +// AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER +// LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, +// OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN +// THE SOFTWARE. + +import { HttpClient, HttpErrorResponse } from "@angular/common/http"; +import { Component, Injectable, OnDestroy, OnInit } from "@angular/core"; +import { GridsterConfig, GridsterItem } from "angular-gridster2"; +import { BehaviorSubject } from "rxjs"; +import { finalize } from "rxjs/operators"; + +import { DataSourceService, IFilteringOutputs } from "@nova-ui/bits"; +import { + DATA_SOURCE, + IDashboard, + IKpiData, + IProviderConfiguration, + IWidget, + IWidgets, + KpiComponent, + NOVA_KPI_DATASOURCE_ADAPTER, + NOVA_URL_INTERACTION_HANDLER, + PizzagnaLayer, + ProviderRegistryService, + WellKnownPathKey, + WellKnownProviders, + WidgetTypesService, +} from "@nova-ui/dashboards"; + +/** + * A simple KPI data source to retrieve the average rating of Harry Potter and the Sorcerer's Stone (book) via googleapis + */ +@Injectable() +export class BookRatingDataSource + extends DataSourceService + implements OnDestroy +{ + // This is the ID we'll use to identify the provider + public static providerId = "BookRatingDataSource"; + + // Use this subject to communicate the data source's busy state + public busy = new BehaviorSubject(false); + + constructor(private http: HttpClient) { + super(); + } + + // In this example, getFilteredData is invoked every 10 minutes (Take a look at the refresher + // provider definition in the widget configuration below to see how the interval is set) + public async getFilteredData(): Promise { + this.busy.next(true); + return new Promise((resolve) => { + // *** Make a resource request to an external API (if needed) + this.http + .get("https://www.googleapis.com/books/v1/volumes/zpvysRGsBlwC") + .pipe(finalize(() => this.busy.next(false))) + .subscribe({ + next: (data: any) => { + resolve({ + result: { + value: data.volumeInfo.averageRating, + link: data.volumeInfo.infoLink, + }, + }); + }, + error: (error: HttpErrorResponse) => { + resolve({ + result: null, + error: { + type: error.status, + }, + }); + }, + }); + }); + } + + public ngOnDestroy(): void { + this.outputsSubject.complete(); + } +} + +/** + * A component that instantiates the dashboard + */ +@Component({ + selector: "kpi-widget-interactive-example", + templateUrl: "./kpi-widget-interactive-example.component.html", + styleUrls: ["./kpi-widget-interactive-example.component.less"], + standalone: false, +}) +export class KpiWidgetInteractiveExampleComponent implements OnInit { + // This variable will hold all the data needed to define the layout and behavior of the widgets. + // Pass this to the dashboard component's dashboard input in the template. + public dashboard: IDashboard; + + // Angular gridster requires a configuration object even if it's empty. + // Pass this to the dashboard component's gridsterConfig input in the template. + public gridsterConfig: GridsterConfig = {}; + + // Boolean passed as an input to the dashboard. When true, widgets can be moved, resized, removed, or edited + public editMode: boolean = false; + + constructor( + // WidgetTypesService provides the widget's necessary structure information + private widgetTypesService: WidgetTypesService, + + // In general, the ProviderRegistryService is used for making entities available for injection into dynamically loaded components. + private providerRegistry: ProviderRegistryService + ) {} + + public ngOnInit(): void { + // Grabbing the widget's default template which will be needed as a parameter for setNode + const widgetTemplate = this.widgetTypesService.getWidgetType("kpi", 1); + // Registering our data sources as dropdown options in the widget editor/configurator + // Note: This could also be done in the parent module's constructor so that + // multiple dashboards could have access to the same widget template modification. + this.widgetTypesService.setNode( + // This is the template we grabbed above with getWidgetType + widgetTemplate, + // We are setting the editor/configurator part of the widget template + "configurator", + // This indicates which node you are changing and we want to change + // the data source providers available for selection in the editor. + WellKnownPathKey.DataSourceProviders, + // We are setting the data sources available for selection in the editor + [BookRatingDataSource.providerId] + ); + + // Registering the data source for injection into the KPI tile. + // Note: Each tile of a KPI widget is assigned its own instance of the data source + this.providerRegistry.setProviders({ + [BookRatingDataSource.providerId]: { + provide: DATA_SOURCE, + useClass: BookRatingDataSource, + // Any dependencies that need to be injected into the provider must be listed here + deps: [HttpClient], + }, + }); + + this.initializeDashboard(); + } + + public initializeDashboard(): void { + // We're using a static configuration object for this example, but this is where + // the widget's configuration could potentially be populated from a database + const kpiWidget = widgetConfig; + const widgetIndex: IWidgets = { + // Complete the KPI widget with information coming from its type definition + [kpiWidget.id]: + this.widgetTypesService.mergeWithWidgetType(kpiWidget), + }; + + // Setting the widget dimensions and position (this is for gridster) + const positions: Record = { + [kpiWidget.id]: { + cols: 4, + rows: 6, + y: 0, + x: 0, + }, + }; + + // Finally, assigning the variables we created above to the dashboard + this.dashboard = { + positions, + widgets: widgetIndex, + }; + } +} + +const widgetConfig: IWidget = { + id: "kpiWidgetId", + type: "kpi", + pizzagna: { + [PizzagnaLayer.Configuration]: { + header: { + properties: { + title: "Harry Potter and the Order of the Phoenix", + subtitle: "By: J. K. Rowling", + }, + }, + tiles: { + providers: { + interaction: { + // Configuring the UrlInteractionHandler for interactions on the tiles + providerId: NOVA_URL_INTERACTION_HANDLER, + properties: { + // the 'url' property tells the handler what link to use when interaction occurs on the series + url: "\\\\\\\\\\\\\\\${data.link}", + }, + }, + }, + properties: { + nodes: ["kpi1"], + }, + }, + kpi1: { + id: "kpi1", + componentType: KpiComponent.lateLoadKey, + properties: { + widgetData: { + units: \\\\\\\\\\\\\\\`out of 5 stars\\\\\\\\\\\\\\\`, + label: \\\\\\\\\\\\\\\`Average Rating\\\\\\\\\\\\\\\`, + value: 0, + // the link property that is passed to the UrlInteractionHandler when the title is clicked + // this will be updated in BookRatingDataSource's 'getFilteredData' call. + link: "http://www.google.com", + }, + }, + providers: { + [WellKnownProviders.DataSource]: { + // Setting the data source providerId for the tile with id "kpi1" + providerId: BookRatingDataSource.providerId, + } as IProviderConfiguration, + [WellKnownProviders.Adapter]: { + providerId: NOVA_KPI_DATASOURCE_ADAPTER, + properties: { + componentId: "kpi1", + propertyPath: "widgetData", + }, + } as IProviderConfiguration, + }, + }, + }, + }, +}; +\\\\\\\`, + "widget-types/proportional/models.ts": \\\\\\\`export interface IMockBeerReview { + id: string; + name: string; + data: number[]; + icon: string; + link?: string; + value: string; + color?: string; +} +\\\\\\\`, + "widget-types/proportional/proportional-docs.component.ts": \\\\\\\`// © 2022 SolarWinds Worldwide, LLC. All rights reserved. +// +// Permission is hereby granted, free of charge, to any person obtaining a copy +// of this software and associated documentation files (the "Software"), to +// deal in the Software without restriction, including without limitation the +// rights to use, copy, modify, merge, publish, distribute, sublicense, and/or +// sell copies of the Software, and to permit persons to whom the Software is +// furnished to do so, subject to the following conditions: +// +// The above copyright notice and this permission notice shall be included in +// all copies or substantial portions of the Software. +// +// THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR +// IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, +// FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE +// AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER +// LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, +// OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN +// THE SOFTWARE. + +import { Component, OnInit } from "@angular/core"; + +import { mapContentFile } from "../../../../demo-files-factory"; + +@Component({ + selector: "nui-proportional-docs", + templateUrl: "./proportional-docs.component.html", + standalone: false, +}) +export class ProportionalDocsComponent implements OnInit { + public proportionalWidgetFileText = ""; + public proportionalConfiguratorFileText = ""; + + public async ngOnInit(): Promise { + this.proportionalWidgetFileText = await import( + "./../../../../../../src/lib/widget-types/proportional/proportional-widget" + ).then(mapContentFile); + this.proportionalConfiguratorFileText = await import( + "./../../../../../../src/lib/widget-types/proportional/proportional-configurator" + ).then(mapContentFile); + } +} +\\\\\\\`, + "widget-types/proportional/proportional-docs.module.ts": \\\\\\\`// © 2022 SolarWinds Worldwide, LLC. All rights reserved. +// +// Permission is hereby granted, free of charge, to any person obtaining a copy +// of this software and associated documentation files (the "Software"), to +// deal in the Software without restriction, including without limitation the +// rights to use, copy, modify, merge, publish, distribute, sublicense, and/or +// sell copies of the Software, and to permit persons to whom the Software is +// furnished to do so, subject to the following conditions: +// +// The above copyright notice and this permission notice shall be included in +// all copies or substantial portions of the Software. +// +// THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR +// IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, +// FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE +// AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER +// LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, +// OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN +// THE SOFTWARE. + +import { NgModule } from "@angular/core"; +import { RouterModule, Routes } from "@angular/router"; + +import { + NuiButtonModule, + NuiDocsModule, + NuiMessageModule, + NuiSwitchModule, + DEMO_PATH_TOKEN, +} from "@nova-ui/bits"; +import { NuiDashboardsModule } from "@nova-ui/dashboards"; + +import { ProportionalDocsComponent } from "./proportional-docs.component"; +import { ProportionalDonutContentDocsComponent } from "./proportional-donut-content-docs.component"; +import { ProportionalWidgetDonutContentFormattersExampleComponent } from "./proportional-donut-content-formatters/proportional-donut-content-formatters-example.component"; +import { ProportionalWidgetExampleComponent } from "./proportional-widget/proportional-widget-example.component"; +import { ProportionalWidgetInteractiveExampleComponent } from "./proportional-widget-interactive/proportional-widget-interactive-example.component"; +import { getDemoFiles } from "../../../../demo-files-factory"; + +const routes: Routes = [ + { + path: "", + component: ProportionalDocsComponent, + data: { + srlc: { + hideIndicator: true, + }, + showThemeSwitcher: true, + }, + }, + { + path: "example", + component: ProportionalWidgetExampleComponent, + data: { + srlc: { + hideIndicator: true, + }, + }, + }, + { + path: "donut-content-formatters", + component: ProportionalDonutContentDocsComponent, + data: { + srlc: { + hideIndicator: true, + }, + }, + }, + { + path: "donut-content-formatters-example", + component: ProportionalWidgetDonutContentFormattersExampleComponent, + data: { + srlc: { + hideIndicator: true, + }, + }, + }, + { + path: "proportional-widget-interactive-example", + component: ProportionalWidgetInteractiveExampleComponent, + data: { + srlc: { + hideIndicator: true, + }, + }, + }, +]; + +@NgModule({ + imports: [ + RouterModule.forChild(routes), + NuiButtonModule, + NuiDocsModule, + NuiDashboardsModule, + NuiMessageModule, + NuiSwitchModule, + ], + declarations: [ + ProportionalDocsComponent, + ProportionalWidgetExampleComponent, + ProportionalWidgetInteractiveExampleComponent, + ProportionalWidgetDonutContentFormattersExampleComponent, + ProportionalDonutContentDocsComponent, + ], + providers: [ + { + provide: DEMO_PATH_TOKEN, + useValue: getDemoFiles("proportional"), + }, + ], +}) +export default class ProportionalDocsModule {} +\\\\\\\`, + "widget-types/proportional/proportional-donut-content-docs.component.ts": \\\\\\\`// © 2022 SolarWinds Worldwide, LLC. All rights reserved. +// +// Permission is hereby granted, free of charge, to any person obtaining a copy +// of this software and associated documentation files (the "Software"), to +// deal in the Software without restriction, including without limitation the +// rights to use, copy, modify, merge, publish, distribute, sublicense, and/or +// sell copies of the Software, and to permit persons to whom the Software is +// furnished to do so, subject to the following conditions: +// +// The above copyright notice and this permission notice shall be included in +// all copies or substantial portions of the Software. +// +// THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR +// IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, +// FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE +// AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER +// LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, +// OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN +// THE SOFTWARE. + +import { Component } from "@angular/core"; + +@Component({ + selector: "nui-proportional-donut-content-docs", + templateUrl: "./proportional-donut-content-docs.component.html", + standalone: false, +}) +export class ProportionalDonutContentDocsComponent { + public dataSourceDataFieldsConfig = \\\\\\\\\\\\\\\` +public dataFieldsConfig: IProportionalDataFieldsConfig = { + dataFields$: new BehaviorSubject(this.dataFields), + chartSeriesDataFields$: new BehaviorSubject(this.chartSeriesDataFields), +}; + \\\\\\\\\\\\\\\`; + + public widgetConfigSlice = \\\\\\\\\\\\\\\` +"properties": { + "configuration": { + "chartOptions": { + donutContentConfig: { + formatter: { + componentType: SiUnitsFormatterComponent.lateLoadKey, + }, + aggregator: { + aggregatorType: sumAggregator.aggregatorType, + }, + }, + } + } +} + + \\\\\\\\\\\\\\\`; +} +\\\\\\\`, + "widget-types/proportional/proportional-donut-content-formatters/proportional-donut-content-formatters-example.component.ts": \\\\\\\`// © 2022 SolarWinds Worldwide, LLC. All rights reserved. +// +// Permission is hereby granted, free of charge, to any person obtaining a copy +// of this software and associated documentation files (the "Software"), to +// deal in the Software without restriction, including without limitation the +// rights to use, copy, modify, merge, publish, distribute, sublicense, and/or +// sell copies of the Software, and to permit persons to whom the Software is +// furnished to do so, subject to the following conditions: +// +// The above copyright notice and this permission notice shall be included in +// all copies or substantial portions of the Software. +// +// THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR +// IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, +// FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE +// AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER +// LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, +// OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN +// THE SOFTWARE. + +import { + ChangeDetectorRef, + Component, + Injectable, + OnDestroy, + OnInit, +} from "@angular/core"; +import { GridsterConfig, GridsterItem } from "angular-gridster2"; +import { BehaviorSubject } from "rxjs"; + +import { + DataSourceService, + IDataField, + IDataSource, + IFilteringOutputs, +} from "@nova-ui/bits"; +import { IAccessors, IChartAssistSeries } from "@nova-ui/charts"; +import { + DATA_SOURCE, + DEFAULT_LEGEND_FORMATTERS, + DEFAULT_PIZZAGNA_ROOT, + DEFAULT_PROPORTIONAL_CONTENT_AGGREGATORS, + DEFAULT_PROPORTIONAL_CONTENT_FORMATTERS, + DONUT_CONTENT_CONFIGURATION_SLICE, + IDashboard, + IDonutContentConfig, + IProportionalDataFieldsConfig, + IProportionalWidgetChartOptions, + IProportionalWidgetConfig, + IProviderConfiguration, + IWidget, + IWidgets, + LegendPlacement, + PizzagnaLayer, + ProportionalContentAggregatorsRegistryService, + ProportionalDonutContentFormattersRegistryService, + ProportionalLegendFormattersRegistryService, + ProportionalWidgetChartTypes, + ProviderRegistryService, + SiUnitsFormatterComponent, + sumAggregator, + WellKnownPathKey, + WellKnownProviders, + WidgetTypesService, +} from "@nova-ui/dashboards"; + +import { IMockBeerReview } from "../models"; + +/** + * A simple proportional data source to retrieve beer review counts by city + */ +@Injectable() +export class BeerReviewCountsByCityMockDataSource + extends DataSourceService> + implements IDataSource>, OnDestroy +{ + public static providerId = "BeerReviewCountsByCityMockDataSource"; + public busy = new BehaviorSubject(false); + + protected dataFields: IDataField[] = [ + { + id: "Brno", + label: "Brno", + // @ts-ignore + dataType: null, + }, + { + id: "kyiv", + label: "Kyiv", + // @ts-ignore + dataType: null, + }, + { + id: "austin", + label: "Austin", + // @ts-ignore + dataType: null, + }, + { + id: "lisbon", + label: "Lisbon", + // @ts-ignore + dataType: null, + }, + { + id: "sydney", + label: "Sydney", + // @ts-ignore + dataType: null, + }, + { + id: "nur-sultan", + label: "Nur-Sultan", + // @ts-ignore + dataType: null, + }, + ]; + protected chartSeriesDataFields: IDataField[] = [ + // default field in the chart series that is used for the aggregation + { + id: "data[0]", + label: "data", + // @ts-ignore + dataType: null, + }, + // any custom field in the chart series that is used for the aggregation + { + id: "customDonutContent", + label: "Custom Donut Content", + // @ts-ignore + dataType: null, + }, + ]; + + /** + * DataSource needs to implement the "IDataFieldsConfig" for this scenario. + * + * It's necessary to provide the "chartSeriesDataFields", + * that's why proportional widget dataSource has it's own interface for that - IProportionalDataFieldsConfig. + * + * dataFields$ - stands for possible series fields + * chartSeriesDataFields$ - stands for the fields IN the series + * + * see declaration of "dataFields" and "chartSeriesDataFields" for the example. + */ + public dataFieldsConfig: IProportionalDataFieldsConfig = { + dataFields$: new BehaviorSubject(this.dataFields), + chartSeriesDataFields$: new BehaviorSubject( + this.chartSeriesDataFields + ), + }; + + public async getFilteredData(): Promise { + this.busy.next(true); + return new Promise((resolve) => { + setTimeout(() => { + this.outputsSubject.next({ + result: getMockBeerReviewCountsByCity(), + }); + this.busy.next(false); + }, 300); + }); + } + + public ngOnDestroy(): void { + this.outputsSubject.complete(); + } +} + +/** + * A component that instantiates the dashboard + */ +@Component({ + selector: "proportional-widget-donut-content-formatters-example", + templateUrl: "./proportional-donut-content-formatters-example.component.html", + styleUrls: [ + "./proportional-donut-content-formatters-example.component.less", + ], + standalone: false, +}) +export class ProportionalWidgetDonutContentFormattersExampleComponent + implements OnInit +{ + public dashboard: IDashboard | undefined; + + // Angular gridster requires a configuration object even if it's empty. + // Pass this to the dashboard component's gridsterConfig input in the template. + public gridsterConfig: GridsterConfig = {}; + + // Boolean passed as an input to the dashboard. When true, widgets can be moved, resized, removed, or edited + public editMode: boolean = false; + + constructor( + // WidgetTypesService provides the widget's necessary structure information + private widgetTypesService: WidgetTypesService, + // In general, the ProviderRegistryService is used for making entities available for injection into dynamically loaded components. + private providerRegistry: ProviderRegistryService, + // registry for adding the formatter for donut content + contentFormattersRegistry: ProportionalDonutContentFormattersRegistryService, + // registry for adding the formatter for proportional legend + legendFormattersRegistry: ProportionalLegendFormattersRegistryService, + // registry for adding the aggregators for donut content + aggregatorRegistry: ProportionalContentAggregatorsRegistryService, + private changeDetectorRef: ChangeDetectorRef + ) { + // on the dashboard startup, it's necessary to add possible content formatters, legend formatters and content aggregators to the registry. + // using registry is a way for setting the available formatters. + legendFormattersRegistry.addItems(DEFAULT_LEGEND_FORMATTERS); + contentFormattersRegistry.addItems( + DEFAULT_PROPORTIONAL_CONTENT_FORMATTERS + ); + aggregatorRegistry.addItems(DEFAULT_PROPORTIONAL_CONTENT_AGGREGATORS); + } + + public ngOnInit(): void { + // Grabbing the widget's default template which will be needed as a parameter for setNode + const widgetTemplate = this.widgetTypesService.getWidgetType( + "proportional", + 1 + ); + + // Registering our data sources as dropdown options in the widget editor/configurator + // Note: This could also be done in the parent module's constructor so that + // multiple dashboards could have access to the same widget template modification. + this.widgetTypesService.setNode( + // This is the template we grabbed above with getWidgetType + widgetTemplate, + // We are setting the editor/configurator part of the widget template + "configurator", + // This indicates which node you are changing and we want to change + // the data source providers available for selection in the editor. + WellKnownPathKey.DataSourceProviders, + // We are setting the data sources available for selection in the editor + [BeerReviewCountsByCityMockDataSource.providerId] + ); + + // Setup of the configurator is done here + this.setupConfigurator(); + + // Registering the data source for injection into the Proportional widget. + this.providerRegistry.setProviders({ + [BeerReviewCountsByCityMockDataSource.providerId]: { + provide: DATA_SOURCE, + useClass: BeerReviewCountsByCityMockDataSource, + deps: [], + }, + }); + + this.initializeDashboard(); + } + + /** Used for restoring widgets state */ + public reInitializeDashboard(): void { + // destroys the components and their providers so the dashboard can re init data + this.dashboard = undefined; + this.changeDetectorRef.detectChanges(); + + this.initializeDashboard(); + } + + private initializeDashboard(): void { + // We're using a static configuration object for this example, but this is where + // the widget's configuration could potentially be populated from a database + const widgetIndex: IWidgets = { + // Complete the proportional widget with information coming from its type definition + [widgetConfig.id]: + this.widgetTypesService.mergeWithWidgetType(widgetConfig), + }; + + // Setting the widget dimensions and position (this is for gridster) + const positions: Record = { + [widgetConfig.id]: { + cols: 6, + rows: 6, + y: 0, + x: 0, + }, + }; + + // Finally, assigning the variables we created above to the dashboard + this.dashboard = { + positions, + widgets: widgetIndex, + }; + } + + /** + * Sets up the configurator sections for proportional donut + */ + private setupConfigurator() { + const widgetTemplate = this.widgetTypesService.getWidgetType( + "proportional", + 1 + ); + + // remove old "presentation", "chartOptionsEditor" and "donutContentConfiguration" sections from the configurator + delete widgetTemplate.configurator?.structure?.presentation; + delete widgetTemplate.configurator?.structure?.chartOptionsEditor; + delete widgetTemplate.configurator?.structure + ?.donutContentConfiguration; + + // add new "presentation" section + this.widgetTypesService.setNode( + widgetTemplate, + "configurator", + "presentation", + DONUT_CONTENT_CONFIGURATION_SLICE.presentation + ); + // add new "chartOptionsEditor" section + this.widgetTypesService.setNode( + widgetTemplate, + "configurator", + "chartOptionsEditor", + DONUT_CONTENT_CONFIGURATION_SLICE.chartOptionsEditor + ); + // add new "donutContentConfiguration" section + this.widgetTypesService.setNode( + widgetTemplate, + "configurator", + "donutContentConfiguration", + DONUT_CONTENT_CONFIGURATION_SLICE.donutContentConfiguration + ); + } +} + +const widgetConfig: IWidget = { + id: "proportionalWidgetId", + type: "proportional", + pizzagna: { + [PizzagnaLayer.Configuration]: { + [DEFAULT_PIZZAGNA_ROOT]: { + providers: {}, + }, + header: { + properties: { + title: "Beer Review Tally by City", + subtitle: "These People Love Beer", + }, + }, + chart: { + providers: { + [WellKnownProviders.DataSource]: { + // Setting the data source providerId for the chart + providerId: + BeerReviewCountsByCityMockDataSource.providerId, + } as IProviderConfiguration, + }, + properties: { + configuration: { + chartOptions: { + type: ProportionalWidgetChartTypes.DonutChart, + legendPlacement: LegendPlacement.Right, + // old configuration looks like this + // contentFormatter: { + // componentType: DonutContentSumFormatterComponent.lateLoadKey, + // }, + + // NEW configuration looks like this + donutContentConfig: { + formatter: { + componentType: + SiUnitsFormatterComponent.lateLoadKey, + }, + aggregator: { + aggregatorType: + sumAggregator.aggregatorType, + properties: { + // example of a default metric to be used for the percentage calculation + // activeMetricId: "austin", + }, + }, + } as IDonutContentConfig, + } as IProportionalWidgetChartOptions, + } as IProportionalWidgetConfig, + }, + }, + }, + }, +}; + +export function getMockBeerReviewCountsByCity(): IMockBeerReview[] { + return [ + { + id: "Brno", + name: "Brno", + data: [Math.round(Math.random() * 1000000)], + icon: "status_down", + link: "https://en.wikipedia.org/wiki/Brno", + value: "Brno", + customDonutContent: "Custom Brno", + }, + { + id: "kyiv", + name: "Kyiv", + data: [Math.round(Math.random() * 1000000)], + icon: "status_critical", + link: "https://en.wikipedia.org/wiki/Kyiv", + value: "Kyiv", + customDonutContent: "Custom Kyiv", + }, + { + id: "austin", + name: "Austin", + data: [Math.round(Math.random() * 1000000)], + icon: "status_warning", + link: "https://en.wikipedia.org/wiki/Austin", + value: "Austin", + customDonutContent: "Custom Austin", + }, + { + id: "lisbon", + name: "Lisbon", + data: [Math.round(Math.random() * 1000000)], + icon: "status_unknown", + link: "https://en.wikipedia.org/wiki/Lisbon", + value: "Lisbon", + customDonutContent: "Custom Lisbon", + }, + { + id: "sydney", + name: "Sydney", + data: [Math.round(Math.random() * 1000000)], + icon: "status_up", + link: "https://en.wikipedia.org/wiki/Sydney", + value: "Sydney", + customDonutContent: "Custom Sydney", + }, + { + id: "nur-sultan", + name: "Nur-Sultan", + data: [Math.round(Math.random() * 1000000)], + icon: "status_unmanaged", + link: "https://en.wikipedia.org/wiki/Nur-Sultan", + value: "Nur-Sultan", + customDonutContent: "Custom Nur-Sultan", + }, + ].sort((a, b) => a.data[0] - b.data[0]); +} +\\\\\\\`, + "widget-types/proportional/proportional-widget/proportional-widget-example.component.ts": \\\\\\\`// © 2022 SolarWinds Worldwide, LLC. All rights reserved. +// +// Permission is hereby granted, free of charge, to any person obtaining a copy +// of this software and associated documentation files (the "Software"), to +// deal in the Software without restriction, including without limitation the +// rights to use, copy, modify, merge, publish, distribute, sublicense, and/or +// sell copies of the Software, and to permit persons to whom the Software is +// furnished to do so, subject to the following conditions: +// +// The above copyright notice and this permission notice shall be included in +// all copies or substantial portions of the Software. +// +// THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR +// IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, +// FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE +// AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER +// LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, +// OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN +// THE SOFTWARE. + +import { + ChangeDetectorRef, + Component, + Injectable, + OnDestroy, + OnInit, +} from "@angular/core"; +import { GridsterConfig, GridsterItem } from "angular-gridster2"; +import { BehaviorSubject } from "rxjs"; + +import { + DataSourceService, + IDataSource, + IFilteringOutputs, +} from "@nova-ui/bits"; +import { + DATA_SOURCE, + DEFAULT_PIZZAGNA_ROOT, + IDashboard, + IProportionalWidgetChartOptions, + IProportionalWidgetConfig, + IProportionalWidgetData, + IProviderConfiguration, + IRefresherProperties, + IWidget, + IWidgets, + LegendPlacement, + PizzagnaLayer, + ProportionalWidgetChartTypes, + ProviderRegistryService, + WellKnownPathKey, + WellKnownProviders, + WidgetTypesService, +} from "@nova-ui/dashboards"; + +import { IMockBeerReview } from "../models"; + +/** + * A simple proportional data source to retrieve beer review counts by city + */ +@Injectable() +export class BeerReviewCountsByCityMockDataSource + extends DataSourceService + implements IDataSource, OnDestroy +{ + // This is the ID we'll use to identify the provider + public static providerId = "BeerReviewCountsByCityMockDataSource"; + public busy = new BehaviorSubject(false); + + public async getFilteredData(): Promise { + this.busy.next(true); + return new Promise((resolve) => { + setTimeout(() => { + this.outputsSubject.next({ + result: getMockBeerReviewCountsByCity(), + }); + this.busy.next(false); + }, 300); + }); + } + + public ngOnDestroy(): void { + this.outputsSubject.complete(); + } +} + +/** + * A component that instantiates the dashboard + */ +@Component({ + selector: "proportional-widget-example", + templateUrl: "./proportional-widget-example.component.html", + styleUrls: ["./proportional-widget-example.component.less"], + standalone: false, +}) +export class ProportionalWidgetExampleComponent implements OnInit { + // This variable will hold all the data needed to define the layout and behavior of the widgets. + // Pass this to the dashboard component's dashboard input in the template. + public dashboard: IDashboard | undefined; + + // Angular gridster requires a configuration object even if it's empty. + // Pass this to the dashboard component's gridsterConfig input in the template. + public gridsterConfig: GridsterConfig = {}; + + // Boolean passed as an input to the dashboard. When true, widgets can be moved, resized, removed, or edited + public editMode: boolean = false; + + constructor( + // WidgetTypesService provides the widget's necessary structure information + private widgetTypesService: WidgetTypesService, + // In general, the ProviderRegistryService is used for making entities available for injection into dynamically loaded components. + private providerRegistry: ProviderRegistryService, + private changeDetectorRef: ChangeDetectorRef + ) {} + + public ngOnInit(): void { + // Grabbing the widget's default template which will be needed as a parameter for setNode + const widgetTemplate = this.widgetTypesService.getWidgetType( + "proportional", + 1 + ); + + // Registering our data sources as dropdown options in the widget editor/configurator + // Note: This could also be done in the parent module's constructor so that + // multiple dashboards could have access to the same widget template modification. + this.widgetTypesService.setNode( + // This is the template we grabbed above with getWidgetType + widgetTemplate, + // We are setting the editor/configurator part of the widget template + "configurator", + // This indicates which node you are changing and we want to change + // the data source providers available for selection in the editor. + WellKnownPathKey.DataSourceProviders, + // We are setting the data sources available for selection in the editor + [BeerReviewCountsByCityMockDataSource.providerId] + ); + + // Registering the data source for injection into the Proportional widget. + this.providerRegistry.setProviders({ + [BeerReviewCountsByCityMockDataSource.providerId]: { + provide: DATA_SOURCE, + useClass: BeerReviewCountsByCityMockDataSource, + deps: [], + }, + }); + + this.initializeDashboard(); + } + + /** Used for restoring widgets state */ + public reInitializeDashboard(): void { + // destroys the components and their providers so the dashboard can re init data + this.dashboard = undefined; + this.changeDetectorRef.detectChanges(); + + this.initializeDashboard(); + } + + public initializeDashboard(): void { + // We're using a static configuration object for this example, but this is where + // the widget's configuration could potentially be populated from a database + const widgetIndex: IWidgets = { + // Complete the proportional widget with information coming from its type definition + [widgetConfig.id]: + this.widgetTypesService.mergeWithWidgetType(widgetConfig), + }; + + // Setting the widget dimensions and position (this is for gridster) + const positions: Record = { + [widgetConfig.id]: { + cols: 5, + rows: 6, + y: 0, + x: 0, + }, + }; + + // Finally, assigning the variables we created above to the dashboard + this.dashboard = { + positions, + widgets: widgetIndex, + }; + } +} + +const widgetConfig: IWidget = { + id: "proportionalWidgetId", + type: "proportional", + pizzagna: { + [PizzagnaLayer.Configuration]: { + [DEFAULT_PIZZAGNA_ROOT]: { + providers: { + [WellKnownProviders.Refresher]: { + properties: { + // Configuring the refresher interval so that our data source is invoked every ten minutes + interval: 60 * 10, + enabled: true, + } as IRefresherProperties, + } as Partial, + }, + }, + header: { + properties: { + title: "Beer Review Tally by City", + subtitle: "These People Love Beer", + }, + }, + chart: { + providers: { + [WellKnownProviders.DataSource]: { + // Setting the data source providerId for the chart + providerId: + BeerReviewCountsByCityMockDataSource.providerId, + } as IProviderConfiguration, + }, + properties: { + configuration: { + chartOptions: { + type: ProportionalWidgetChartTypes.DonutChart, + legendPlacement: LegendPlacement.Right, + } as IProportionalWidgetChartOptions, + // You can optionally define custom colors for the chart by setting the 'chartColors' configuration property + // "chartColors": [ + // "var(--nui-color-chart-five)", + // "var(--nui-color-chart-six)", + // "var(--nui-color-chart-seven)", + // "var(--nui-color-chart-eight)", + // "var(--nui-color-chart-nine)", + // "var(--nui-color-chart-ten)", + // ], + // or use-mapped structure + chartColors: { + Brno: "var(--nui-color-chart-five)", + kyiv: "var(--nui-color-chart-six)", + austin: "var(--nui-color-chart-seven)", + lisbon: "var(--nui-color-chart-eight)", + sydney: "var(--nui-color-chart-nine)", + "nur-sultan": "var(--nui-color-chart-ten)", + }, + prioritizeWidgetColors: false, + } as IProportionalWidgetConfig, + }, + }, + }, + }, +}; + +export function getMockBeerReviewCountsByCity(): IMockBeerReview[] { + return [ + { + id: "Brno", + name: "Brno", + data: [Math.round(Math.random() * 100000)], + icon: "status_down", + link: "https://en.wikipedia.org/wiki/Brno", + value: "Brno", + color: "var(--nui-color-chart-one)", + }, + { + id: "kyiv", + name: "Kyiv", + data: [Math.round(Math.random() * 100000)], + icon: "status_critical", + link: "https://en.wikipedia.org/wiki/Kyiv", + value: "Kyiv", + color: "var(--nui-color-chart-two)", + }, + { + id: "austin", + name: "Austin", + data: [Math.round(Math.random() * 100000)], + icon: "status_warning", + link: "https://en.wikipedia.org/wiki/Austin", + value: "Austin", + color: "var(--nui-color-chart-three)", + }, + { + id: "lisbon", + name: "Lisbon", + data: [Math.round(Math.random() * 100000)], + icon: "status_unknown", + link: "https://en.wikipedia.org/wiki/Lisbon", + value: "Lisbon", + color: "var(--nui-color-chart-four)", + }, + { + id: "sydney", + name: "Sydney", + data: [Math.round(Math.random() * 100000)], + icon: "status_up", + link: "https://en.wikipedia.org/wiki/Sydney", + value: "Sydney", + color: "var(--nui-color-chart-five)", + }, + { + id: "nur-sultan", + name: "Nur-Sultan", + data: [Math.round(Math.random() * 100000)], + icon: "status_unmanaged", + link: "https://en.wikipedia.org/wiki/Nur-Sultan", + value: "Nur-Sultan", + color: "var(--nui-color-chart-six)", + }, + ].sort((a, b) => a.data[0] - b.data[0]); +} +\\\\\\\`, + "widget-types/proportional/proportional-widget-interactive/proportional-widget-interactive-example.component.ts": \\\\\\\`// © 2022 SolarWinds Worldwide, LLC. All rights reserved. +// +// Permission is hereby granted, free of charge, to any person obtaining a copy +// of this software and associated documentation files (the "Software"), to +// deal in the Software without restriction, including without limitation the +// rights to use, copy, modify, merge, publish, distribute, sublicense, and/or +// sell copies of the Software, and to permit persons to whom the Software is +// furnished to do so, subject to the following conditions: +// +// The above copyright notice and this permission notice shall be included in +// all copies or substantial portions of the Software. +// +// THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR +// IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, +// FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE +// AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER +// LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, +// OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN +// THE SOFTWARE. + +import { + ChangeDetectorRef, + Component, + Injectable, + OnDestroy, + OnInit, +} from "@angular/core"; +import { GridsterConfig, GridsterItem } from "angular-gridster2"; +import keyBy from "lodash/keyBy"; +import { BehaviorSubject } from "rxjs"; + +import { + DataSourceService, + IDataSource, + IFilteringOutputs, +} from "@nova-ui/bits"; +import { + DATA_SOURCE, + DEFAULT_PIZZAGNA_ROOT, + IDashboard, + IProportionalWidgetChartOptions, + IProportionalWidgetConfig, + IProportionalWidgetData, + IProviderConfiguration, + IWidget, + LegendPlacement, + NOVA_URL_INTERACTION_HANDLER, + PizzagnaLayer, + ProportionalWidgetChartTypes, + ProviderRegistryService, + WellKnownPathKey, + WellKnownProviders, + WidgetTypesService, +} from "@nova-ui/dashboards"; + +import { IMockBeerReview } from "../models"; + +/** + * A simple proportional data source to retrieve beer review counts by city + */ +@Injectable() +export class ReviewCountsByCityMockDataSource + extends DataSourceService + implements IDataSource, OnDestroy +{ + // This is the ID we'll use to identify the provider + public static providerId = "ReviewCountsByCityMockDataSource"; + public busy = new BehaviorSubject(false); + + public async getFilteredData(): Promise { + this.busy.next(true); + return new Promise((resolve) => { + setTimeout(() => { + this.outputsSubject.next({ + result: getMockBeerReviewCountsByCity(), + }); + this.busy.next(false); + }, 300); + }); + } + + public ngOnDestroy(): void { + this.outputsSubject.complete(); + } +} + +/** + * A component that instantiates the dashboard + */ +@Component({ + selector: "proportional-widget-interactive-example", + templateUrl: "./proportional-widget-interactive-example.component.html", + styleUrls: ["./proportional-widget-interactive-example.component.less"], + standalone: false, +}) +export class ProportionalWidgetInteractiveExampleComponent implements OnInit { + // This variable will hold all the data needed to define the layout and behavior of the widgets. + // Pass this to the dashboard component's dashboard input in the template. + public dashboard: IDashboard | undefined; + + // Angular gridster requires a configuration object even if it's empty. + // Pass this to the dashboard component's gridsterConfig input in the template. + public gridsterConfig: GridsterConfig = {}; + + // Boolean passed as an input to the dashboard. When true, widgets can be moved, resized, removed, or edited + public editMode: boolean = false; + + constructor( + // WidgetTypesService provides the widget's necessary structure information + private widgetTypesService: WidgetTypesService, + // In general, the ProviderRegistryService is used for making entities available for injection into dynamically loaded components. + private providerRegistry: ProviderRegistryService, + private changeDetectorRef: ChangeDetectorRef + ) {} + + public ngOnInit(): void { + // Grabbing the widget's default template which will be needed as a parameter for setNode + const widgetTemplate = this.widgetTypesService.getWidgetType( + "proportional", + 1 + ); + + // Registering our data sources as dropdown options in the widget editor/configurator + // Note: This could also be done in the parent module's constructor so that + // multiple dashboards could have access to the same widget template modification. + this.widgetTypesService.setNode( + // This is the template we grabbed above with getWidgetType + widgetTemplate, + // We are setting the editor/configurator part of the widget template + "configurator", + // This indicates which node you are changing and we want to change + // the data source providers available for selection in the editor. + WellKnownPathKey.DataSourceProviders, + // We are setting the data sources available for selection in the editor + [ReviewCountsByCityMockDataSource.providerId] + ); + + // Registering the data source for injection into the Proportional widget. + this.providerRegistry.setProviders({ + [ReviewCountsByCityMockDataSource.providerId]: { + provide: DATA_SOURCE, + useClass: ReviewCountsByCityMockDataSource, + deps: [], + }, + }); + + this.initializeDashboard(); + } + + /** Used for restoring widgets state */ + public reInitializeDashboard(): void { + // destroys the components and their providers so the dashboard can re init data + this.dashboard = undefined; + this.changeDetectorRef.detectChanges(); + + this.initializeDashboard(); + } + + public initializeDashboard(): void { + // We're using a static configuration object for this example, but this is where + // the widget's configuration could potentially be populated from a database + const widgetsWithStructure = widgetConfigs.map((w) => + this.widgetTypesService.mergeWithWidgetType(w) + ); + const widgetsIndex = keyBy(widgetsWithStructure, (w: IWidget) => w.id); + + // Setting the widget dimensions and position (this is for gridster) + const positions: Record = { + [widgetConfigs[0].id]: { + cols: 6, + rows: 6, + y: 0, + x: 0, + }, + [widgetConfigs[1].id]: { + cols: 6, + rows: 6, + y: 0, + x: 6, + }, + }; + + // Finally, assigning the variables we created above to the dashboard + this.dashboard = { + positions, + widgets: widgetsIndex, + }; + } +} + +const widgetConfigs: IWidget[] = [ + { + id: "widget1", + type: "proportional", + pizzagna: { + [PizzagnaLayer.Configuration]: { + [DEFAULT_PIZZAGNA_ROOT]: { + providers: { + // Configuring the UrlInteractionHandler to handle interactions + [WellKnownProviders.InteractionHandler]: { + providerId: NOVA_URL_INTERACTION_HANDLER, + properties: { + // the 'url' property tells the handler what link to use when interaction occurs on the series + // if the series does not have a link we are passing one to the handler + url: "\\\\\\\\\\\\\\\${data.link || 'https://en.wikipedia.org/wiki/'+data.id}", + // by default the link is opened in the current window, set 'newWindow' to true to open in a new tab instead + // newWindow: true, + }, + }, + }, + }, + header: { + properties: { + title: "Proportional Widget", + subtitle: "With interaction handler", + }, + }, + chart: { + providers: { + [WellKnownProviders.DataSource]: { + // Setting the data source providerId for the chart + providerId: + ReviewCountsByCityMockDataSource.providerId, + } as IProviderConfiguration, + }, + properties: { + configuration: { + // Setting the interactive to true + interactive: true, + chartOptions: { + type: ProportionalWidgetChartTypes.VerticalBarChart, + legendPlacement: LegendPlacement.Bottom, + } as IProportionalWidgetChartOptions, + prioritizeWidgetColors: false, + } as IProportionalWidgetConfig, + }, + }, + }, + }, + }, + { + id: "widget2", + type: "proportional", + pizzagna: { + [PizzagnaLayer.Configuration]: { + header: { + properties: { + title: "Proportional Widget", + subtitle: "Without interaction handler", + }, + }, + chart: { + providers: { + [WellKnownProviders.DataSource]: { + // Setting the data source providerId for the chart + providerId: + ReviewCountsByCityMockDataSource.providerId, + } as IProviderConfiguration, + }, + properties: { + configuration: { + // interactive set to false so series without links are not styled like a link + interactive: false, + chartOptions: { + type: ProportionalWidgetChartTypes.HorizontalBarChart, + legendPlacement: LegendPlacement.Bottom, + } as IProportionalWidgetChartOptions, + prioritizeWidgetColors: false, + } as IProportionalWidgetConfig, + }, + }, + }, + }, + }, +]; + +export function getMockBeerReviewCountsByCity(): IMockBeerReview[] { + return [ + { + id: "Brno", + name: "Brno", + data: [Math.round(Math.random() * 100000)], + icon: "status_down", + link: "https://en.wikipedia.org/wiki/Brno", + value: "Brno", + color: "var(--nui-color-chart-one)", + }, + { + id: "kyiv", + name: "Kyiv", + data: [Math.round(Math.random() * 100000)], + icon: "status_critical", + link: "https://en.wikipedia.org/wiki/Kyiv", + value: "Kyiv", + color: "var(--nui-color-chart-two)", + }, + { + id: "austin", + name: "Austin", + data: [Math.round(Math.random() * 100000)], + icon: "status_warning", + value: "Austin", + color: "var(--nui-color-chart-three)", + }, + { + id: "lisbon", + name: "Lisbon", + data: [Math.round(Math.random() * 100000)], + icon: "status_unknown", + link: "https://en.wikipedia.org/wiki/Lisbon", + value: "Lisbon", + color: "var(--nui-color-chart-four)", + }, + { + id: "sydney", + name: "Sydney", + data: [Math.round(Math.random() * 100000)], + icon: "status_up", + value: "Sydney", + color: "var(--nui-color-chart-five)", + }, + { + id: "nur-sultan", + name: "Nur-Sultan", + data: [Math.round(Math.random() * 100000)], + icon: "status_unmanaged", + value: "Nur-Sultan", + color: "var(--nui-color-chart-six)", + }, + ].sort((a, b) => a.data[0] - b.data[0]); +} +\\\\\\\`, + "widget-types/risk-score/risk-score-docs.component.ts": \\\\\\\`// © 2023 SolarWinds Worldwide, LLC. All rights reserved. +// +// Permission is hereby granted, free of charge, to any person obtaining a copy +// of this software and associated documentation files (the "Software"), to +// deal in the Software without restriction, including without limitation the +// rights to use, copy, modify, merge, publish, distribute, sublicense, and/or +// sell copies of the Software, and to permit persons to whom the Software is +// furnished to do so, subject to the following conditions: +// +// The above copyright notice and this permission notice shall be included in +// all copies or substantial portions of the Software. +// +// THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR +// IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, +// FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE +// AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER +// LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, +// OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN +// THE SOFTWARE. + +import { Component, OnInit } from "@angular/core"; + +import { mapContentFile } from "../../../../demo-files-factory"; + +@Component({ + selector: "nui-risk-score-docs", + templateUrl: "./risk-score-docs.component.html", + standalone: false, +}) +export class RiskScoreDocsComponent implements OnInit { + public riskScoreWidgetFileText = ""; + public riskScoreConfiguratorFileText = ""; + + public async ngOnInit(): Promise { + this.riskScoreWidgetFileText = await import( + "./../../../../../../src/lib/widget-types/risk-score/risk-score-widget" + ).then(mapContentFile); + this.riskScoreConfiguratorFileText = await import( + "./../../../../../../src/lib/widget-types/risk-score/risk-score-configurator" + ).then(mapContentFile); + } +} +\\\\\\\`, + "widget-types/risk-score/risk-score-docs.module.ts": \\\\\\\`// © 2023 SolarWinds Worldwide, LLC. All rights reserved. +// +// Permission is hereby granted, free of charge, to any person obtaining a copy +// of this software and associated documentation files (the "Software"), to +// deal in the Software without restriction, including without limitation the +// rights to use, copy, modify, merge, publish, distribute, sublicense, and/or +// sell copies of the Software, and to permit persons to whom the Software is +// furnished to do so, subject to the following conditions: +// +// The above copyright notice and this permission notice shall be included in +// all copies or substantial portions of the Software. +// +// THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR +// IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, +// FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE +// AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER +// LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, +// OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN +// THE SOFTWARE. + +import { NgModule } from "@angular/core"; +import { RouterModule, Routes } from "@angular/router"; + +import { + DEMO_PATH_TOKEN, + NuiButtonModule, + NuiDocsModule, + NuiMessageModule, + NuiSwitchModule, +} from "@nova-ui/bits"; +import { NuiDashboardsModule } from "@nova-ui/dashboards"; + +import { RiskScoreDocsComponent } from "./risk-score-docs.component"; +import { RiskScoreWidgetExampleComponent } from "./risk-score-widget-example/risk-score-widget-example.component"; +import { getDemoFiles } from "../../../../demo-files-factory"; + +const routes: Routes = [ + { + path: "", + component: RiskScoreDocsComponent, + data: { + srlc: { + hideIndicator: true, + }, + showThemeSwitcher: true, + }, + }, + { + path: "example", + component: RiskScoreWidgetExampleComponent, + data: { + srlc: { + hideIndicator: true, + }, + }, + }, +]; + +@NgModule({ + imports: [ + RouterModule.forChild(routes), + NuiButtonModule, + NuiDocsModule, + NuiMessageModule, + NuiDashboardsModule, + NuiSwitchModule, + ], + declarations: [RiskScoreDocsComponent, RiskScoreWidgetExampleComponent], + providers: [ + { + provide: DEMO_PATH_TOKEN, + useValue: getDemoFiles("risk-score"), + }, + ], +}) +export default class RiskScoreDocsModule {} +\\\\\\\`, + "widget-types/risk-score/risk-score-widget-example/risk-score-widget-example.component.ts": \\\\\\\`// © 2023 SolarWinds Worldwide, LLC. All rights reserved. +// +// Permission is hereby granted, free of charge, to any person obtaining a copy +// of this software and associated documentation files (the "Software"), to +// deal in the Software without restriction, including without limitation the +// rights to use, copy, modify, merge, publish, distribute, sublicense, and/or +// sell copies of the Software, and to permit persons to whom the Software is +// furnished to do so, subject to the following conditions: +// +// The above copyright notice and this permission notice shall be included in +// all copies or substantial portions of the Software. +// +// THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR +// IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, +// FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE +// AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER +// LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, +// OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN +// THE SOFTWARE. + +import { HttpClient, HttpErrorResponse } from "@angular/common/http"; +import { Component, Injectable, OnDestroy, OnInit } from "@angular/core"; +import { GridsterConfig, GridsterItem } from "angular-gridster2"; +import { BehaviorSubject } from "rxjs"; +import { finalize } from "rxjs/operators"; + +import { DataSourceService, IFilteringOutputs } from "@nova-ui/bits"; +import { + DATA_SOURCE, + DEFAULT_PIZZAGNA_ROOT, + IDashboard, + IRiskScoreData, + IProviderConfiguration, + IRefresherProperties, + IWidget, + IWidgets, + RiskScoreTileComponent, + NOVA_KPI_DATASOURCE_ADAPTER, + PizzagnaLayer, + ProviderRegistryService, + WellKnownPathKey, + WellKnownProviders, + WidgetTypesService, +} from "@nova-ui/dashboards"; + +/** + * A simple KPI data source to retrieve the average rating of Harry Potter and the Sorcerer's Stone (book) via googleapis + */ +@Injectable() +export class AverageRatingRiskScoreDataSource + extends DataSourceService + implements OnDestroy +{ + // This is the ID we'll use to identify the provider + public static providerId = "AverageRatingRiskScoreDataSource"; + + // Use this subject to communicate the data source's busy state + public busy = new BehaviorSubject(false); + + constructor(private http: HttpClient) { + super(); + } + + // In this example, getFilteredData is invoked every 10 minutes (Take a look at the refresher + // provider definition in the widget configuration below to see how the interval is set) + public async getFilteredData(): Promise { + this.busy.next(true); + return new Promise((resolve) => { + // *** Make a resource request to an external API (if needed) + this.http + .get("https://www.googleapis.com/books/v1/volumes/5MQFrgEACAAJ") + .pipe(finalize(() => this.busy.next(false))) + .subscribe({ + next: (data: any) => { + resolve({ + result: { + value: data.volumeInfo.averageRating, + }, + }); + }, + error: (error: HttpErrorResponse) => { + resolve({ + result: null, + error: { + type: error.status, + }, + }); + }, + }); + }); + } + + public ngOnDestroy(): void { + this.outputsSubject.complete(); + } +} + +/** + * A component that instantiates the dashboard + */ +@Component({ + selector: "risk-score-widget-example", + templateUrl: "./risk-score-widget-example.component.html", + styleUrls: ["./risk-score-widget-example.component.less"], + standalone: false, +}) +export class RiskScoreWidgetExampleComponent implements OnInit { + // This variable will hold all the data needed to define the layout and behavior of the widgets. + // Pass this to the dashboard component's dashboard input in the template. + public dashboard: IDashboard; + + // Angular gridster requires a configuration object even if it's empty. + // Pass this to the dashboard component's gridsterConfig input in the template. + public gridsterConfig: GridsterConfig = {}; + + // Boolean passed as an input to the dashboard. When true, widgets can be moved, resized, removed, or edited + public editMode: boolean = false; + + constructor( + // WidgetTypesService provides the widget's necessary structure information + private widgetTypesService: WidgetTypesService, + + // In general, the ProviderRegistryService is used for making entities available for injection into dynamically loaded components. + private providerRegistry: ProviderRegistryService + ) {} + + public ngOnInit(): void { + // Grabbing the widget's default template which will be needed as a parameter for setNode + const widgetTemplate = this.widgetTypesService.getWidgetType( + "risk-score", + 1 + ); + // Registering our data sources as dropdown options in the widget editor/configurator + // Note: This could also be done in the parent module's constructor so that + // multiple dashboards could have access to the same widget template modification. + this.widgetTypesService.setNode( + // This is the template we grabbed above with getWidgetType + widgetTemplate, + // We are setting the editor/configurator part of the widget template + "configurator", + // This indicates which node you are changing and we want to change + // the data source providers available for selection in the editor. + WellKnownPathKey.DataSourceProviders, + // We are setting the data sources available for selection in the editor + [AverageRatingRiskScoreDataSource.providerId] + ); + + // Registering the data source for injection into the KPI tile. + // Note: Each tile of a KPI widget is assigned its own instance of the data source + this.providerRegistry.setProviders({ + [AverageRatingRiskScoreDataSource.providerId]: { + provide: DATA_SOURCE, + useClass: AverageRatingRiskScoreDataSource, + // Any dependencies that need to be injected into the provider must be listed here + deps: [HttpClient], + }, + }); + + this.initializeDashboard(); + } + + public initializeDashboard(): void { + // We're using a static configuration object for this example, but this is where + // the widget's configuration could potentially be populated from a database + const riskScoreWidget = widgetConfig; + const widgetIndex: IWidgets = { + // Complete the KPI widget with information coming from its type definition + [riskScoreWidget.id]: + this.widgetTypesService.mergeWithWidgetType(riskScoreWidget), + }; + + // Setting the widget dimensions and position (this is for gridster) + const positions: Record = { + [riskScoreWidget.id]: { + cols: 4, + rows: 6, + y: 0, + x: 0, + }, + }; + + // Finally, assigning the variables we created above to the dashboard + this.dashboard = { + positions, + widgets: widgetIndex, + }; + } +} + +const widgetConfig: IWidget = { + id: "riskScoreWidgetId", + type: "risk-score", + pizzagna: { + [PizzagnaLayer.Configuration]: { + [DEFAULT_PIZZAGNA_ROOT]: { + providers: { + [WellKnownProviders.Refresher]: { + properties: { + // Configuring the refresher interval so that our data source is invoked every ten minutes + interval: 60 * 10, + enabled: true, + } as IRefresherProperties, + } as Partial, + }, + }, + header: { + properties: { + title: "Harry Potter and the Sorcerer's Stone", + subtitle: "By J. K. Rowling", + }, + }, + tiles: { + properties: { + nodes: ["riskScore1"], + }, + }, + riskScore1: { + id: "riskScore1", + componentType: RiskScoreTileComponent.lateLoadKey, + properties: { + widgetData: { + minValue: 0, + maxValue: 5, + useStaticLabel: false, + staticLabel: undefined, + label: \\\\\\\\\\\\\\\`Average Rating\\\\\\\\\\\\\\\`, + description: \\\\\\\\\\\\\\\`Harry Potter and the Sorcerer's Stone By J. K. Rowling Average Rating Risk Score\\\\\\\\\\\\\\\`, + }, + }, + providers: { + [WellKnownProviders.DataSource]: { + // Setting the data source providerId for the tile with id "kpi1" + providerId: AverageRatingRiskScoreDataSource.providerId, + } as IProviderConfiguration, + [WellKnownProviders.Adapter]: { + providerId: NOVA_KPI_DATASOURCE_ADAPTER, + properties: { + componentId: "riskScore1", + propertyPath: "widgetData", + }, + } as IProviderConfiguration, + }, + }, + }, + }, +}; +\\\\\\\`, + "widget-types/table/table-docs.component.ts": \\\\\\\`// © 2022 SolarWinds Worldwide, LLC. All rights reserved. +// +// Permission is hereby granted, free of charge, to any person obtaining a copy +// of this software and associated documentation files (the "Software"), to +// deal in the Software without restriction, including without limitation the +// rights to use, copy, modify, merge, publish, distribute, sublicense, and/or +// sell copies of the Software, and to permit persons to whom the Software is +// furnished to do so, subject to the following conditions: +// +// The above copyright notice and this permission notice shall be included in +// all copies or substantial portions of the Software. +// +// THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR +// IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, +// FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE +// AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER +// LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, +// OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN +// THE SOFTWARE. + +import { Component, OnInit } from "@angular/core"; + +import { mapContentFile } from "../../../../demo-files-factory"; + +@Component({ + selector: "nui-table-docs", + templateUrl: "./table-docs.component.html", + standalone: false, +}) +export class TableDocsComponent implements OnInit { + public widgetFileText = ""; + public configuratorFileText = ""; + + public async ngOnInit(): Promise { + this.widgetFileText = await import( + "./../../../../../../src/lib/widget-types/table/table-widget" + ).then(mapContentFile); + this.configuratorFileText = await import( + "./../../../../../../src/lib/widget-types/table/table-configurator" + ).then(mapContentFile); + } +} +\\\\\\\`, + "widget-types/table/table-docs.module.ts": \\\\\\\`// © 2022 SolarWinds Worldwide, LLC. All rights reserved. +// +// Permission is hereby granted, free of charge, to any person obtaining a copy +// of this software and associated documentation files (the "Software"), to +// deal in the Software without restriction, including without limitation the +// rights to use, copy, modify, merge, publish, distribute, sublicense, and/or +// sell copies of the Software, and to permit persons to whom the Software is +// furnished to do so, subject to the following conditions: +// +// The above copyright notice and this permission notice shall be included in +// all copies or substantial portions of the Software. +// +// THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR +// IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, +// FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE +// AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER +// LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, +// OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN +// THE SOFTWARE. + +import { NgModule } from "@angular/core"; +import { RouterModule, Routes } from "@angular/router"; + +import { DEMO_PATH_TOKEN } from "@nova-ui/bits"; +import { + NuiButtonModule, + NuiDocsModule, + NuiMessageModule, + NuiSwitchModule, +} from "@nova-ui/bits"; +import { + NuiDashboardsModule, + TableFormatterRegistryService, +} from "@nova-ui/dashboards"; + +import { TableDocsComponent } from "./table-docs.component"; +import { TablePaginatorDocsComponent } from "./table-paginator-docs.component"; +import { TableSelectableDocsComponent } from "./table-selectable-docs.component"; +import { TableWidgetExampleComponent } from "./table-widget/table-widget-example.component"; +import { TableWidgetInteractiveExampleComponent } from "./table-widget-interactive/table-widget-interactive-example.component"; +import { TableWidgetPaginatorExampleComponent } from "./table-widget-paginator/table-widget-paginator-example.component"; +import { TableWidgetSearchExampleComponent } from "./table-widget-search/table-widget-search-example.component"; +import { TableSearchDocsComponent } from "./table-widget-search-docs.component"; +import { TableWidgetSelectableMultiExampleComponent } from "./table-widget-selectable/table-widget-selectable-multi/table-widget-selectable-multi.example.component"; +import { TableWidgetSelectableRadioExampleComponent } from "./table-widget-selectable/table-widget-selectable-radio/table-widget-selectable-radio.example.component"; +import { TableWidgetSelectableSingleExampleComponent } from "./table-widget-selectable/table-widget-selectable-single/table-widget-selectable-single.example.component"; +import { TableWidgetSelectableExampleComponent } from "./table-widget-selectable/table-widget-selectable.example.component"; +import { DEFAULT_TABLE_FORMATTERS } from "../../../../../../src/lib/widget-types/table/default-table-formatters"; +import { getDemoFiles } from "../../../../demo-files-factory"; + +const routes: Routes = [ + { + path: "", + component: TableDocsComponent, + data: { + srlc: { + hideIndicator: true, + }, + showThemeSwitcher: true, + }, + }, + { + path: "example", + component: TableWidgetExampleComponent, + data: { + srlc: { + hideIndicator: true, + }, + }, + }, + { + path: "table-search", + component: TableSearchDocsComponent, + data: { + srlc: { + hideIndicator: true, + }, + showThemeSwitcher: true, + }, + }, + { + path: "table-paginator", + component: TablePaginatorDocsComponent, + data: { + srlc: { + hideIndicator: true, + }, + showThemeSwitcher: true, + }, + }, + { + path: "table-select", + component: TableSelectableDocsComponent, + data: { + srlc: { + hideIndicator: true, + }, + showThemeSwitcher: true, + }, + }, +]; + +@NgModule({ + imports: [ + RouterModule.forChild(routes), + NuiButtonModule, + NuiDocsModule, + NuiMessageModule, + NuiSwitchModule, + NuiDashboardsModule, + ], + declarations: [ + TableDocsComponent, + TableSearchDocsComponent, + TablePaginatorDocsComponent, + TableWidgetPaginatorExampleComponent, + TableSelectableDocsComponent, + TableWidgetInteractiveExampleComponent, + TableWidgetExampleComponent, + TableWidgetSearchExampleComponent, + TableWidgetSelectableExampleComponent, + TableWidgetSelectableMultiExampleComponent, + TableWidgetSelectableSingleExampleComponent, + TableWidgetSelectableRadioExampleComponent, + ], + providers: [ + { + provide: DEMO_PATH_TOKEN, + useValue: getDemoFiles("table"), + }, + ], +}) +export default class TableDocsModule { + constructor(tableFormattersRegistryService: TableFormatterRegistryService) { + tableFormattersRegistryService.addItems(DEFAULT_TABLE_FORMATTERS); + } +} +\\\\\\\`, + "widget-types/table/table-paginator-docs.component.ts": \\\\\\\`// © 2022 SolarWinds Worldwide, LLC. All rights reserved. +// +// Permission is hereby granted, free of charge, to any person obtaining a copy +// of this software and associated documentation files (the "Software"), to +// deal in the Software without restriction, including without limitation the +// rights to use, copy, modify, merge, publish, distribute, sublicense, and/or +// sell copies of the Software, and to permit persons to whom the Software is +// furnished to do so, subject to the following conditions: +// +// The above copyright notice and this permission notice shall be included in +// all copies or substantial portions of the Software. +// +// THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR +// IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, +// FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE +// AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER +// LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, +// OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN +// THE SOFTWARE. + +import { Component } from "@angular/core"; + +@Component({ + selector: "nui-table-paginator-docs", + templateUrl: "./table-paginator-docs.component.html", + standalone: false, +}) +export class TablePaginatorDocsComponent { + public tableConfigurationText = \\\\\\\\\\\\\\\` + "table": { + ... + properties: { + configuration: { + // define paginator configuration here + scrollType: ScrollType.paginator, + paginatorConfiguration: { + pageSize: 10, // Value have to be one of pageSizeSet values + pageSizeSet: [10, 20, 30], + }, + // If not specified, default is set to + // pageSize: 10, + // pageSizeSet: [10, 20, 50], + hasVirtualScroll: false, // Has to be speciefied because of backward compatibility + } as ITableWidgetConfig, + }, + }, + \\\\\\\\\\\\\\\`; +} +\\\\\\\`, + "widget-types/table/table-selectable-docs.component.ts": \\\\\\\`// © 2022 SolarWinds Worldwide, LLC. All rights reserved. +// +// Permission is hereby granted, free of charge, to any person obtaining a copy +// of this software and associated documentation files (the "Software"), to +// deal in the Software without restriction, including without limitation the +// rights to use, copy, modify, merge, publish, distribute, sublicense, and/or +// sell copies of the Software, and to permit persons to whom the Software is +// furnished to do so, subject to the following conditions: +// +// The above copyright notice and this permission notice shall be included in +// all copies or substantial portions of the Software. +// +// THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR +// IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, +// FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE +// AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER +// LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, +// OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN +// THE SOFTWARE. + +import { Component } from "@angular/core"; + +@Component({ + selector: "nui-table-selectable-docs", + templateUrl: "./table-selectable-docs.component.html", + standalone: false, +}) +export class TableSelectableDocsComponent { + public tableConfigurationText = \\\\\\\\\\\\\\\` + "table": { + ... + properties: { + // enabling selection here + selectionConfiguration: { + // whether the selection is enabled or disabled + enabled: true, + // can be Multi | Radio | Single + selectionMode: TableSelectionMode.Multi, + // property that uniquely identifies row in a table + trackByProperty: "id", + // whether clicking on row should select it + clickableRow: true, + }, + }, + }, + \\\\\\\\\\\\\\\`; + + public eventSubscriptionText = \\\\\\\\\\\\\\\` +... +constructor(Inject(PIZZAGNA_EVENT_BUS) eventBus: EventBus) { + eventBus + .getStream(SELECTION) + // don't forget to unsubscribe! + .subscribe((selection: ISelection) => ...) +} +... + \\\\\\\\\\\\\\\`; +} +\\\\\\\`, + "widget-types/table/table-widget/table-widget-example.component.ts": \\\\\\\`// © 2022 SolarWinds Worldwide, LLC. All rights reserved. +// +// Permission is hereby granted, free of charge, to any person obtaining a copy +// of this software and associated documentation files (the "Software"), to +// deal in the Software without restriction, including without limitation the +// rights to use, copy, modify, merge, publish, distribute, sublicense, and/or +// sell copies of the Software, and to permit persons to whom the Software is +// furnished to do so, subject to the following conditions: +// +// The above copyright notice and this permission notice shall be included in +// all copies or substantial portions of the Software. +// +// THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR +// IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, +// FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE +// AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER +// LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, +// OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN +// THE SOFTWARE. + +import { ChangeDetectorRef, Component, OnInit } from "@angular/core"; +import { GridsterConfig, GridsterItem } from "angular-gridster2"; +import orderBy from "lodash/orderBy"; +import { BehaviorSubject, firstValueFrom, from } from "rxjs"; +import { map, tap } from "rxjs/operators"; + +import { + DataSourceService, + IDataField, + INovaFilteringOutputs, + INovaFilters, + nameof, +} from "@nova-ui/bits"; +import { + DATA_SOURCE, + IDashboard, + ITableWidgetColumnConfig, + IWidget, + IWidgets, + ProviderRegistryService, + WellKnownPathKey, + WellKnownProviders, + WidgetTypesService, +} from "@nova-ui/dashboards"; + +export const BREW_API_URL = "https://api.punkapi.com/v2/beers"; + +export interface IBrewInfo { + id: string; + name: string; + tagline: string; + first_brewed: string; + description: string; + brewers_tips: string; +} + +export interface IBrewDatasourceResponse { + brewInfo: IBrewInfo[]; + total: number; +} + +export class BeerDataSource extends DataSourceService { + public static providerId = "BeerDataSource"; + + private cache: IBrewInfo[] = []; + + public busy = new BehaviorSubject(false); + + public dataFields: Array = [ + { + id: nameof("id"), + label: "No", + dataType: "number", + sortable: true, + }, + // To indicate that a column should not be sortable, set the optional IDataField 'sortable' property to false + { + id: nameof("name"), + label: "Name", + dataType: "string", + sortable: true, + }, + { + id: nameof("tagline"), + label: "Tagline", + dataType: "string", + sortable: true, + }, + { + id: nameof("first_brewed"), + label: "First Brewed", + dataType: "string", + sortable: true, + }, + { + id: nameof("description"), + label: "Description", + dataType: "string", + sortable: false, + }, + { + id: nameof("brewers_tips"), + label: "Brewer's Tips", + dataType: "string", + sortable: false, + }, + ]; + + public async getFilteredData( + filters: INovaFilters + ): Promise { + const start = filters.virtualScroll?.value?.start ?? 0; + const end = filters.virtualScroll?.value?.end ?? 0; + + // Resetting cache on first page request + if (start === 0) { + this.cache = []; + } + + // extract sorter settings to send to the backend + // filters.sorterValue.sortBy; filters.sorterValue.direction + return firstValueFrom( + from(this.fetch(start, end)).pipe( + tap((response: IBrewDatasourceResponse | undefined) => { + if (!response) { + return; + } + this.cache = this.sortData( + this.cache.concat(response.brewInfo), + filters + ); + this.dataSubject.next(this.cache); + }), + map(() => ({ + repeat: { itemsSource: this.cache }, + dataFields: this.dataFields, + })) + ) + ); + } + + public async fetch( + start: number, + end: number + ): Promise { + const delta: number = end - start; + const currentPage: number = end / delta || 0; + const response: object | Array = await ( + await fetch( + \\\\\\\\\\\\\\\`\\\\\\\\\\\\\\\${BREW_API_URL}/?page=\\\\\\\\\\\\\\\${currentPage}&per_page=\\\\\\\\\\\\\\\${delta}\\\\\\\\\\\\\\\` + ) + ).json(); + + // Note: In case request fails we should not proceed with mapping + if (!Array.isArray(response)) { + return undefined; + } + + return { + brewInfo: response.map((result: IBrewInfo) => ({ + id: result.id, + name: result.name, + tagline: result.tagline, + first_brewed: result.first_brewed, + description: result.description, + brewers_tips: result.brewers_tips, + })), + total: response.length, + }; + } + + private sortData(data: IBrewInfo[], filters: INovaFilters): IBrewInfo[] { + return orderBy( + data, + filters.sorter?.value?.sortBy, + filters.sorter?.value?.direction as "desc" | "asc" + ); + } +} + +/** + * A component that instantiates the dashboard + */ +@Component({ + selector: "table-widget-example", + templateUrl: "./table-widget-example.component.html", + styleUrls: ["./table-widget-example.component.less"], + standalone: false, +}) +export class TableWidgetExampleComponent implements OnInit { + // This variable will hold all the data needed to define the layout and behavior of the widgets. + // Pass this to the dashboard component's dashboard input in the template. + public dashboard: IDashboard | undefined; + + // Angular gridster requires a configuration object even if it's empty. + // Pass this to the dashboard component's gridsterConfig input in the template. + public gridsterConfig: GridsterConfig = {}; + + // Boolean passed as an input to the dashboard. When true, widgets can be moved, resized, removed, or edited + public editMode: boolean = false; + + constructor( + // WidgetTypesService provides the widget's necessary structure information + private widgetTypesService: WidgetTypesService, + // In general, the ProviderRegistryService is used for making entities available for injection into dynamically loaded components. + private providerRegistry: ProviderRegistryService, + private changeDetectorRef: ChangeDetectorRef + ) {} + + public ngOnInit(): void { + // Grabbing the widget's default template which will be needed as a parameter for setNode + const widgetTemplate = this.widgetTypesService.getWidgetType( + "table", + 1 + ); + + // Registering our data sources as dropdown options in the widget editor/configurator + // Note: This could also be done in the parent module's constructor so that + // multiple dashboards could have access to the same widget template modification. + this.widgetTypesService.setNode( + // This is the template we grabbed above with getWidgetType + widgetTemplate, + // We are setting the editor/configurator part of the widget template + "configurator", + // This indicates which node you are changing and we want to change + // the data source providers available for selection in the editor. + WellKnownPathKey.DataSourceProviders, + // We are setting the data sources available for selection in the editor + [BeerDataSource.providerId] + ); + + // Registering the data source for injection into the widget. + this.providerRegistry.setProviders({ + [BeerDataSource.providerId]: { + provide: DATA_SOURCE, + useClass: BeerDataSource, + // Any dependencies that need to be injected into the provider must be listed here + deps: [], + }, + }); + + this.initializeDashboard(); + } + + /** Used for restoring widgets state */ + public reInitializeDashboard(): void { + // destroys the components and their providers so the dashboard can re init data + this.dashboard = undefined; + this.changeDetectorRef.detectChanges(); + + this.initializeDashboard(); + } + + public initializeDashboard(): void { + // We're using a static configuration object for this example, but this is where + // the widget's configuration could potentially be populated from a database + const tableWidget = widgetConfig; + const widgetIndex: IWidgets = { + // Enhance the widget with information coming from it's type definition + [tableWidget.id]: + this.widgetTypesService.mergeWithWidgetType(tableWidget), + }; + + // Setting the widget dimensions and position (this is for gridster) + const positions: Record = { + [tableWidget.id]: { + cols: 12, + rows: 6, + y: 0, + x: 0, + }, + }; + + // Finally, assigning the variables we created above to the dashboard + this.dashboard = { + positions, + widgets: widgetIndex, + }; + } +} + +const TABLE_COLUMNS: ITableWidgetColumnConfig[] = [ + { + id: "column1", + label: $localize\\\\\\\\\\\\\\\`Beer Name\\\\\\\\\\\\\\\`, + isActive: true, + width: 185, + formatter: { + componentType: "RawFormatterComponent", + properties: { + dataFieldIds: { + value: "name", + }, + }, + }, + }, + { + id: "column2", + label: $localize\\\\\\\\\\\\\\\`Tagline\\\\\\\\\\\\\\\`, + isActive: true, + width: 250, + formatter: { + componentType: "RawFormatterComponent", + properties: { + dataFieldIds: { + value: "tagline", + }, + }, + }, + }, + { + id: "column3", + label: $localize\\\\\\\\\\\\\\\`First Brewed\\\\\\\\\\\\\\\`, + isActive: true, + width: 100, + formatter: { + componentType: "RawFormatterComponent", + properties: { + dataFieldIds: { + value: "first_brewed", + }, + }, + }, + }, + { + id: "column4", + label: $localize\\\\\\\\\\\\\\\`Description\\\\\\\\\\\\\\\`, + isActive: true, + formatter: { + componentType: "RawFormatterComponent", + properties: { + dataFieldIds: { + value: "description", + }, + }, + }, + }, +]; + +export const widgetConfig: IWidget = { + id: "tableWidgetId", + type: "table", + pizzagna: { + configuration: { + header: { + properties: { + title: "Stupendous Suds", + subtitle: "Try These Brilliant Brews", + }, + }, + table: { + providers: { + [WellKnownProviders.DataSource]: { + providerId: BeerDataSource.providerId, + }, + }, + properties: { + configuration: { + columns: TABLE_COLUMNS, + sortable: true, + sorterConfiguration: { + descendantSorting: false, + sortBy: "", + }, + hasVirtualScroll: true, + }, + }, + }, + }, + }, +}; +\\\\\\\`, + "widget-types/table/table-widget-interactive/table-widget-interactive-example.component.ts": \\\\\\\`// © 2022 SolarWinds Worldwide, LLC. All rights reserved. +// +// Permission is hereby granted, free of charge, to any person obtaining a copy +// of this software and associated documentation files (the "Software"), to +// deal in the Software without restriction, including without limitation the +// rights to use, copy, modify, merge, publish, distribute, sublicense, and/or +// sell copies of the Software, and to permit persons to whom the Software is +// furnished to do so, subject to the following conditions: +// +// The above copyright notice and this permission notice shall be included in +// all copies or substantial portions of the Software. +// +// THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR +// IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, +// FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE +// AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER +// LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, +// OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN +// THE SOFTWARE. + +import { ChangeDetectorRef, Component, OnInit } from "@angular/core"; +import { GridsterConfig, GridsterItem } from "angular-gridster2"; +import orderBy from "lodash/orderBy"; +import { BehaviorSubject, firstValueFrom, from } from "rxjs"; +import { map, tap } from "rxjs/operators"; + +import { + DataSourceService, + IDataField, + INovaFilteringOutputs, + INovaFilters, + nameof, +} from "@nova-ui/bits"; +import { + DATA_SOURCE, + DEFAULT_PIZZAGNA_ROOT, + IDashboard, + ITableWidgetColumnConfig, + IWidget, + IWidgets, + NOVA_URL_INTERACTION_HANDLER, + ProviderRegistryService, + WellKnownPathKey, + WellKnownProviders, + WidgetTypesService, +} from "@nova-ui/dashboards"; + +export const BREW_API_URL = "https://api.punkapi.com/v2/beers"; + +export interface IBrewInfo { + id: string; + name: string; + tagline: string; + first_brewed: string; + description: string; + brewers_tips: string; +} + +export interface IBrewDatasourceResponse { + brewInfo: IBrewInfo[]; + total: number; +} + +export class MockBeerDataSource extends DataSourceService { + public static providerId = "MockBeerDataSource"; + + private cache: IBrewInfo[] = []; + + public busy = new BehaviorSubject(false); + + public dataFields: Array = [ + { + id: nameof("id"), + label: "No", + dataType: "number", + sortable: true, + }, + // To indicate that a column should not be sortable, set the optional IDataField 'sortable' property to false + { + id: nameof("name"), + label: "Name", + dataType: "string", + sortable: true, + }, + { + id: nameof("tagline"), + label: "Tagline", + dataType: "string", + sortable: true, + }, + { + id: nameof("first_brewed"), + label: "First Brewed", + dataType: "string", + sortable: true, + }, + { + id: nameof("description"), + label: "Description", + dataType: "string", + sortable: false, + }, + { + id: nameof("brewers_tips"), + label: "Brewer's Tips", + dataType: "string", + sortable: false, + }, + ]; + + public async getFilteredData( + filters: INovaFilters + ): Promise { + const start = filters.virtualScroll?.value?.start ?? 0; + const end = filters.virtualScroll?.value?.end ?? 0; + + // Resetting cache on first page request + if (start === 0) { + this.cache = []; + } + + // extract sorter settings to send to the backend + // filters.sorterValue.sortBy; filters.sorterValue.direction + return firstValueFrom( + from(this.fetch(start, end)).pipe( + tap((response) => { + if (!response) { + return; + } + this.cache = this.sortData( + this.cache.concat(response.brewInfo), + filters + ); + this.dataSubject.next(this.cache); + }), + map(() => ({ + repeat: { itemsSource: this.cache }, + dataFields: this.dataFields, + })) + ) + ); + } + + public async fetch( + start: number, + end: number + ): Promise { + const delta: number = end - start; + const currentPage: number = end / delta || 0; + const response: object | Array = await ( + await fetch( + \\\\\\\\\\\\\\\`\\\\\\\\\\\\\\\${BREW_API_URL}/?page=\\\\\\\\\\\\\\\${currentPage}&per_page=\\\\\\\\\\\\\\\${delta}\\\\\\\\\\\\\\\` + ) + ).json(); + console.log( + "📘 table-widget-interactive-example.component: 85# -> response:", + response + ); + + // Note: In case request fails we should not proceed with mapping + if (!Array.isArray(response)) { + return undefined; + } + + return { + brewInfo: response.map((result: IBrewInfo) => ({ + id: result.id, + name: result.name, + tagline: result.tagline, + first_brewed: result.first_brewed, + description: result.description, + brewers_tips: result.brewers_tips, + })), + total: response.length, + }; + } + + private sortData(data: IBrewInfo[], filters: INovaFilters): IBrewInfo[] { + return orderBy( + data, + filters.sorter?.value?.sortBy, + filters.sorter?.value?.direction as "desc" | "asc" + ); + } +} + +/** + * A component that instantiates the dashboard + */ +@Component({ + selector: "table-widget-interactive-example", + templateUrl: "./table-widget-interactive-example.component.html", + styleUrls: ["./table-widget-interactive-example.component.less"], + standalone: false, +}) +export class TableWidgetInteractiveExampleComponent implements OnInit { + // This variable will hold all the data needed to define the layout and behavior of the widgets. + // Pass this to the dashboard component's dashboard input in the template. + public dashboard: IDashboard | undefined; + + // Angular gridster requires a configuration object even if it's empty. + // Pass this to the dashboard component's gridsterConfig input in the template. + public gridsterConfig: GridsterConfig = {}; + + // Boolean passed as an input to the dashboard. When true, widgets can be moved, resized, removed, or edited + public editMode: boolean = false; + + constructor( + // WidgetTypesService provides the widget's necessary structure information + private widgetTypesService: WidgetTypesService, + // In general, the ProviderRegistryService is used for making entities available for injection into dynamically loaded components. + private providerRegistry: ProviderRegistryService, + private changeDetectorRef: ChangeDetectorRef + ) {} + + public ngOnInit(): void { + // Grabbing the widget's default template which will be needed as a parameter for setNode + const widgetTemplate = this.widgetTypesService.getWidgetType( + "table", + 1 + ); + + // Registering our data sources as dropdown options in the widget editor/configurator + // Note: This could also be done in the parent module's constructor so that + // multiple dashboards could have access to the same widget template modification. + this.widgetTypesService.setNode( + // This is the template we grabbed above with getWidgetType + widgetTemplate, + // We are setting the editor/configurator part of the widget template + "configurator", + // This indicates which node you are changing and we want to change + // the data source providers available for selection in the editor. + WellKnownPathKey.DataSourceProviders, + // We are setting the data sources available for selection in the editor + [MockBeerDataSource.providerId] + ); + + // Registering the data source for injection into the widget. + this.providerRegistry.setProviders({ + [MockBeerDataSource.providerId]: { + provide: DATA_SOURCE, + useClass: MockBeerDataSource, + // Any dependencies that need to be injected into the provider must be listed here + deps: [], + }, + }); + + this.initializeDashboard(); + } + + /** Used for restoring widgets state */ + public reInitializeDashboard(): void { + // destroys the components and their providers so the dashboard can re init data + this.dashboard = undefined; + this.changeDetectorRef.detectChanges(); + + this.initializeDashboard(); + } + + public initializeDashboard(): void { + // We're using a static configuration object for this example, but this is where + // the widget's configuration could potentially be populated from a database + const tableWidget = widgetConfig; + const widgetIndex: IWidgets = { + // Enhance the widget with information coming from it's type definition + [tableWidget.id]: + this.widgetTypesService.mergeWithWidgetType(tableWidget), + }; + + // Setting the widget dimensions and position (this is for gridster) + const positions: Record = { + [tableWidget.id]: { + cols: 12, + rows: 6, + y: 0, + x: 0, + }, + }; + + // Finally, assigning the variables we created above to the dashboard + this.dashboard = { + positions, + widgets: widgetIndex, + }; + } +} + +const TABLE_COLUMNS: ITableWidgetColumnConfig[] = [ + { + id: "column1", + label: $localize\\\\\\\\\\\\\\\`Beer Name\\\\\\\\\\\\\\\`, + isActive: true, + width: 185, + formatter: { + componentType: "RawFormatterComponent", + properties: { + dataFieldIds: { + value: "name", + }, + }, + }, + }, + { + id: "column2", + label: $localize\\\\\\\\\\\\\\\`Tagline\\\\\\\\\\\\\\\`, + isActive: true, + width: 250, + formatter: { + componentType: "RawFormatterComponent", + properties: { + dataFieldIds: { + value: "tagline", + }, + }, + }, + }, + { + id: "column3", + label: $localize\\\\\\\\\\\\\\\`First Brewed\\\\\\\\\\\\\\\`, + isActive: true, + width: 100, + formatter: { + componentType: "RawFormatterComponent", + properties: { + dataFieldIds: { + value: "first_brewed", + }, + }, + }, + }, + { + id: "column4", + label: $localize\\\\\\\\\\\\\\\`Description\\\\\\\\\\\\\\\`, + isActive: true, + formatter: { + componentType: "RawFormatterComponent", + properties: { + dataFieldIds: { + value: "description", + }, + }, + }, + }, +]; + +export const widgetConfig: IWidget = { + id: "tableWidgetId", + type: "table", + pizzagna: { + configuration: { + [DEFAULT_PIZZAGNA_ROOT]: { + providers: { + [WellKnownProviders.InteractionHandler]: { + // Configuring the UrlInteractionHandler to handle interactions + providerId: NOVA_URL_INTERACTION_HANDLER, + properties: { + // the 'url' property tells the handler what link to use when interaction occurs on the series + url: "\\\\\\\\\\\\\\\${'https://untappd.com/search?q='+data.name}", + // by default the link is opened in the current window, set 'newWindow' to true to open in a new tab instead + newWindow: true, + }, + }, + }, + }, + header: { + properties: { + title: "Stupendous Suds", + subtitle: "Try These Brilliant Brews", + }, + }, + table: { + providers: { + [WellKnownProviders.DataSource]: { + providerId: MockBeerDataSource.providerId, + }, + }, + properties: { + configuration: { + // set interactions to true on the table + interactive: true, + columns: TABLE_COLUMNS, + sortable: true, + sorterConfiguration: { + descendantSorting: false, + sortBy: "", + }, + hasVirtualScroll: true, + }, + }, + }, + }, + }, +}; +\\\\\\\`, + "widget-types/table/table-widget-paginator/table-widget-paginator-example.component.ts": \\\\\\\`// © 2022 SolarWinds Worldwide, LLC. All rights reserved. +// +// Permission is hereby granted, free of charge, to any person obtaining a copy +// of this software and associated documentation files (the "Software"), to +// deal in the Software without restriction, including without limitation the +// rights to use, copy, modify, merge, publish, distribute, sublicense, and/or +// sell copies of the Software, and to permit persons to whom the Software is +// furnished to do so, subject to the following conditions: +// +// The above copyright notice and this permission notice shall be included in +// all copies or substantial portions of the Software. +// +// THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR +// IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, +// FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE +// AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER +// LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, +// OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN +// THE SOFTWARE. + +import { HttpClient } from "@angular/common/http"; +import { ChangeDetectorRef, Component, OnInit } from "@angular/core"; +import { GridsterConfig, GridsterItem } from "angular-gridster2"; + +import { LoggerService } from "@nova-ui/bits"; +import { + DATA_SOURCE, + DEFAULT_PIZZAGNA_ROOT, + IDashboard, + IProviderConfiguration, + ITableWidgetConfig, + IWidget, + IWidgets, + NOVA_URL_INTERACTION_HANDLER, + PizzagnaLayer, + ProviderRegistryService, + RawFormatterComponent, + ScrollType, + WellKnownPathKey, + WellKnownProviders, + WidgetTypesService, +} from "@nova-ui/dashboards"; + +import { AcmeTableMockDataSource } from "../../../../prototypes/data/table/acme-table-mock-data-source.service"; + +/** + * A component that instantiates the dashboard + */ +@Component({ + selector: "table-widget-paginator-example", + templateUrl: "./table-widget-paginator-example.component.html", + styleUrls: ["./table-widget-paginator-example.component.less"], + standalone: false, +}) +export class TableWidgetPaginatorExampleComponent implements OnInit { + public dashboard: IDashboard | undefined; + public gridsterConfig: GridsterConfig = {}; + public editMode: boolean = false; + + constructor( + private widgetTypesService: WidgetTypesService, + private providerRegistry: ProviderRegistryService, + private changeDetectorRef: ChangeDetectorRef + ) {} + + public ngOnInit(): void { + const widgetTemplate = this.widgetTypesService.getWidgetType( + "table", + 1 + ); + this.widgetTypesService.setNode( + widgetTemplate, + "configurator", + WellKnownPathKey.DataSourceProviders, + [AcmeTableMockDataSource.providerId] + ); + + this.providerRegistry.setProviders({ + [AcmeTableMockDataSource.providerId]: { + provide: DATA_SOURCE, + useClass: AcmeTableMockDataSource, + deps: [LoggerService, HttpClient], + }, + }); + + this.initializeDashboard(); + } + + /** Used for restoring widgets state */ + public reInitializeDashboard(): void { + // destroys the components and their providers so the dashboard can re init data + this.dashboard = undefined; + this.changeDetectorRef.detectChanges(); + + this.initializeDashboard(); + } + + public initializeDashboard(): void { + const tableWithPaginator = tableWidgetWithPaginator; + const tableWithVirtualScroll = tableWidgetWithVirtualScroll; + + const widgetIndex: IWidgets = { + [tableWithPaginator.id]: + this.widgetTypesService.mergeWithWidgetType(tableWithPaginator), + [tableWithVirtualScroll.id]: + this.widgetTypesService.mergeWithWidgetType( + tableWithVirtualScroll + ), + }; + + const positions: Record = { + [tableWithPaginator.id]: { + cols: 6, + rows: 6, + y: 0, + x: 0, + }, + [tableWithVirtualScroll.id]: { + cols: 6, + rows: 6, + y: 0, + x: 0, + }, + }; + + this.dashboard = { + positions, + widgets: widgetIndex, + }; + } +} + +export const tableWidgetWithPaginator: IWidget = { + id: "widget1", + type: "table", + pizzagna: { + [PizzagnaLayer.Configuration]: { + [DEFAULT_PIZZAGNA_ROOT]: { + providers: { + [WellKnownProviders.InteractionHandler]: { + providerId: NOVA_URL_INTERACTION_HANDLER, + }, + }, + }, + header: { + properties: { + title: "Table Widget with paginator!", + subtitle: "Basic table widget", + collapsible: true, + }, + }, + table: { + providers: { + [WellKnownProviders.DataSource]: { + providerId: AcmeTableMockDataSource.providerId, + } as IProviderConfiguration, + }, + properties: { + configuration: { + interactive: true, + columns: [ + { + id: "column1", + label: "No.", + isActive: true, + formatter: { + componentType: + RawFormatterComponent.lateLoadKey, + properties: { + dataFieldIds: { + value: "position", + }, + }, + }, + }, + { + id: "column2", + label: "Name", + isActive: true, + formatter: { + componentType: + RawFormatterComponent.lateLoadKey, + properties: { + dataFieldIds: { + value: "name", + }, + }, + }, + }, + { + id: "column3", + label: "Status", + isActive: true, + formatter: { + componentType: + RawFormatterComponent.lateLoadKey, + properties: { + dataFieldIds: { + value: "status", + }, + }, + }, + }, + ], + sorterConfiguration: { + descendantSorting: false, + sortBy: "column1", + }, + scrollType: ScrollType.paginator, + paginatorConfiguration: { + pageSize: 5, + pageSizeSet: [5, 10, 20, 30], + }, + hasVirtualScroll: false, + searchConfiguration: { + enabled: true, + }, + } as ITableWidgetConfig, + }, + }, + }, + }, +}; + +export const tableWidgetWithVirtualScroll: IWidget = { + id: "widget2", + type: "table", + pizzagna: { + [PizzagnaLayer.Configuration]: { + [DEFAULT_PIZZAGNA_ROOT]: { + providers: { + [WellKnownProviders.InteractionHandler]: { + providerId: NOVA_URL_INTERACTION_HANDLER, + }, + }, + }, + header: { + properties: { + title: "Table Widget with virtual scroll!", + subtitle: "Basic table widget", + collapsible: true, + }, + }, + table: { + providers: { + [WellKnownProviders.DataSource]: { + providerId: AcmeTableMockDataSource.providerId, + } as IProviderConfiguration, + }, + properties: { + configuration: { + interactive: true, + columns: [ + { + id: "column1", + label: "No.", + isActive: true, + formatter: { + componentType: + RawFormatterComponent.lateLoadKey, + properties: { + dataFieldIds: { + value: "position", + }, + }, + }, + }, + { + id: "column2", + label: "Name", + isActive: true, + formatter: { + componentType: + RawFormatterComponent.lateLoadKey, + properties: { + dataFieldIds: { + value: "name", + }, + }, + }, + }, + { + id: "column3", + label: "Status", + isActive: true, + formatter: { + componentType: + RawFormatterComponent.lateLoadKey, + properties: { + dataFieldIds: { + value: "status", + }, + }, + }, + }, + ], + sorterConfiguration: { + descendantSorting: false, + sortBy: "column1", + }, + hasVirtualScroll: true, + searchConfiguration: { + enabled: true, + }, + } as ITableWidgetConfig, + }, + }, + }, + }, +}; +\\\\\\\`, + "widget-types/table/table-widget-search/table-widget-search-example.component.ts": \\\\\\\`// © 2022 SolarWinds Worldwide, LLC. All rights reserved. +// +// Permission is hereby granted, free of charge, to any person obtaining a copy +// of this software and associated documentation files (the "Software"), to +// deal in the Software without restriction, including without limitation the +// rights to use, copy, modify, merge, publish, distribute, sublicense, and/or +// sell copies of the Software, and to permit persons to whom the Software is +// furnished to do so, subject to the following conditions: +// +// The above copyright notice and this permission notice shall be included in +// all copies or substantial portions of the Software. +// +// THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR +// IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, +// FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE +// AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER +// LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, +// OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN +// THE SOFTWARE. + +import { HttpClient } from "@angular/common/http"; +import { + ChangeDetectorRef, + Component, + Injectable, + OnInit, +} from "@angular/core"; +import { GridsterConfig, GridsterItem } from "angular-gridster2"; +import isEqual from "lodash/isEqual"; +import isNil from "lodash/isNil"; +import { BehaviorSubject, firstValueFrom, Observable, of, Subject } from "rxjs"; +import { + catchError, + delay, + finalize, + map, + // eslint-disable-next-line import/no-deprecated + switchMap, + tap, +} from "rxjs/operators"; + +import { + DataSourceFeatures, + DataSourceService, + IDataField, + IDataSource, + IDataSourceFeatures, + IDataSourceFeaturesConfiguration, + IDataSourceOutput, + IFilter, + IFilters, + INovaFilteringOutputs, + INovaFilters, + LoggerService, +} from "@nova-ui/bits"; +import { + DATA_SOURCE, + IDashboard, + ITableWidgetConfig, + IWidget, + IWidgets, + ProviderRegistryService, + WellKnownPathKey, + WellKnownProviders, + WidgetTypesService, +} from "@nova-ui/dashboards"; + +import { GBOOKS_API_URL } from "../../../../prototypes/data/table/constants"; + +interface IGBooksApiResponse { + kind: string; + totalItems: number; + items: IGBooksItemModel[]; + [key: string]: any; +} + +interface IGBooksItemModel { + id: string; + volumeInfo: { + title: string; + subtitle: string; + authors: string[]; + [key: string]: any; + }; + accessInfo: { [key: string]: any }; + saleInfo: { [key: string]: any }; +} + +interface IGBooksData { + books: IGBooksVolume[]; + totalItems: number; +} + +interface IGBooksVolume { + title: string; + authors: string; +} + +type searchableColumnType = "title" | "authors"; + +@Injectable() +export class AcmeTableGBooksDataSource + extends DataSourceService + implements IDataSource +{ + public static providerId = "AcmeTableGBooksDataSource"; + public static mockError = false; + + public searchableColumn: searchableColumnType = "title"; + + public page: number = 1; + public busy = new BehaviorSubject(false); + public features: IDataSourceFeaturesConfiguration; + + private cache = Array.from({ length: 0 }); + private previousFilters: INovaFilters; + // DataSource Features declared + private supportedFeatures: IDataSourceFeatures = { + search: { enabled: true }, + pagination: { enabled: true }, + }; + private columnToQueryParamMap: { [k in searchableColumnType]: string } = { + title: "intitle", + authors: "inauthor", + }; + + private applyFilters$ = new Subject(); + + public dataFields: Array = [ + { + id: "title", + label: $localize\\\\\\\\\\\\\\\`Title\\\\\\\\\\\\\\\`, + dataType: "string", + sortable: false, + }, + { + id: "authors", + label: $localize\\\\\\\\\\\\\\\`Authors\\\\\\\\\\\\\\\`, + dataType: "string", + sortable: false, + }, + ]; + + constructor(private logger: LoggerService, private http: HttpClient) { + super(); + // Using Nova DataSourceFeatures implementation for the features + this.features = new DataSourceFeatures(this.supportedFeatures); + + this.applyFilters$ + // eslint-disable-next-line import/no-deprecated + .pipe(switchMap((filters) => this.getData(filters))) + .subscribe(async (res) => { + this.outputsSubject.next(await this.getFilteredData(res)); + }); + } + + public async getFilteredData( + booksData: IGBooksData + ): Promise> { + return firstValueFrom( + of(booksData).pipe( + tap((response) => { + this.cache = this.cache.concat(response.books); + }), + map((response) => ({ + result: { + repeat: { itemsSource: this.cache }, + paginator: { total: response.totalItems }, + dataFields: this.dataFields, + }, + })) + ) + ); + } + + private getData(filters: INovaFilters): Observable { + if ( + this.isNewSearchTerm(filters.search) && + filters.virtualScroll?.value.start === 0 + ) { + this.cache = []; + } + + return this.http + .get(this.getComposedUrl(filters)) + .pipe( + tap(() => this.busy.next(true)), + delay(300), // mock + map((response) => ({ + books: + response.items?.map((volume) => ({ + title: volume.volumeInfo.title, + authors: + volume.volumeInfo.authors?.join(", ") || "", + })) || [], + totalItems: response.totalItems, + })), + catchError((e) => { + this.logger.error(e); + return of({ + books: [], + totalItems: 0, + }); + }), + finalize(() => { + this.busy.next(false); + this.previousFilters = filters; + }) + ); + } + + private getComposedUrl(filters: INovaFilters) { + const initialUrl = \\\\\\\\\\\\\\\`\\\\\\\\\\\\\\\${GBOOKS_API_URL}?q=\\\\\\\\\\\\\\\`; + const maxResults = \\\\\\\\\\\\\\\`maxResults=\\\\\\\\\\\\\\\${ + (filters.virtualScroll?.value.end || 0) - + (filters.virtualScroll?.value.start || 0) + }\\\\\\\\\\\\\\\`; + + const virtualScrollPart = filters.virtualScroll + ? \\\\\\\\\\\\\\\`startIndex=\\\\\\\\\\\\\\\${filters.virtualScroll.value.start}\\\\\\\\\\\\\\\` + : ""; + + const searchQueryParam = + this.columnToQueryParamMap[this.searchableColumn]; + const searchPart = filters.search + ? \\\\\\\\\\\\\\\`\\\\\\\\\\\\\\\${searchQueryParam}:\\\\\\\\\\\\\\\${filters.search.value}\\\\\\\\\\\\\\\` + : "_"; // google books api requires some criteria to do the search + + return \\\\\\\\\\\\\\\`\\\\\\\\\\\\\\\${initialUrl}\\\\\\\\\\\\\\\${searchPart}&\\\\\\\\\\\\\\\${maxResults}&\\\\\\\\\\\\\\\${virtualScrollPart}&filter=full\\\\\\\\\\\\\\\`; + } + + private isNewSearchTerm(search: IFilter | undefined) { + return ( + !isNil(search?.value) && + !isEqual(search?.value, this.previousFilters?.search?.value) + ); + } + + // redefine parent method + public async applyFilters(): Promise { + this.applyFilters$.next(this.getFilters()); + } +} + +/** + * A component that instantiates the dashboard + */ +@Component({ + selector: "table-widget-search-example", + templateUrl: "./table-widget-search-example.component.html", + styleUrls: ["./table-widget-search-example.component.less"], + standalone: false, +}) +export class TableWidgetSearchExampleComponent implements OnInit { + public dashboard: IDashboard | undefined; + public gridsterConfig: GridsterConfig = {}; + public editMode: boolean = false; + + constructor( + private widgetTypesService: WidgetTypesService, + private providerRegistry: ProviderRegistryService, + private changeDetectorRef: ChangeDetectorRef + ) {} + + public ngOnInit(): void { + const widgetTemplate = this.widgetTypesService.getWidgetType( + "table", + 1 + ); + this.widgetTypesService.setNode( + widgetTemplate, + "configurator", + WellKnownPathKey.DataSourceProviders, + [AcmeTableGBooksDataSource.providerId] + ); + + this.providerRegistry.setProviders({ + [AcmeTableGBooksDataSource.providerId]: { + provide: DATA_SOURCE, + useClass: AcmeTableGBooksDataSource, + deps: [LoggerService, HttpClient], + }, + }); + + this.initializeDashboard(); + } + + /** Used for restoring widgets state */ + public reInitializeDashboard(): void { + // destroys the components and their providers so the dashboard can re init data + this.dashboard = undefined; + this.changeDetectorRef.detectChanges(); + + this.initializeDashboard(); + } + + public initializeDashboard(): void { + const tableWidget = widgetConfig; + const widgetIndex: IWidgets = { + [tableWidget.id]: + this.widgetTypesService.mergeWithWidgetType(tableWidget), + }; + + const positions: Record = { + [tableWidget.id]: { + cols: 12, + rows: 6, + y: 0, + x: 0, + }, + }; + + this.dashboard = { + positions, + widgets: widgetIndex, + }; + } +} + +export const widgetConfig: IWidget = { + id: "tableWidgetId", + type: "table", + pizzagna: { + configuration: { + header: { + properties: { + title: "Google Books", + }, + }, + table: { + providers: { + [WellKnownProviders.DataSource]: { + providerId: AcmeTableGBooksDataSource.providerId, + }, + }, + properties: { + configuration: { + columns: [ + { + id: "column1", + label: $localize\\\\\\\\\\\\\\\`Title\\\\\\\\\\\\\\\`, + isActive: true, + formatter: { + componentType: "RawFormatterComponent", + properties: { + dataFieldIds: { + value: "title", + }, + }, + }, + }, + { + id: "column2", + label: $localize\\\\\\\\\\\\\\\`Author\\\\\\\\\\\\\\\`, + isActive: true, + formatter: { + componentType: "RawFormatterComponent", + properties: { + dataFieldIds: { + value: "authors", + }, + }, + }, + }, + ], + sortable: false, + // define search configuration here + searchConfiguration: { + enabled: true, + // following properties below can be configured as well + // searchTerm: "search criteria here", + // searchDebounce: 300, + }, + hasVirtualScroll: true, + } as ITableWidgetConfig, + }, + }, + }, + }, +}; +\\\\\\\`, + "widget-types/table/table-widget-search-docs.component.ts": \\\\\\\`// © 2022 SolarWinds Worldwide, LLC. All rights reserved. +// +// Permission is hereby granted, free of charge, to any person obtaining a copy +// of this software and associated documentation files (the "Software"), to +// deal in the Software without restriction, including without limitation the +// rights to use, copy, modify, merge, publish, distribute, sublicense, and/or +// sell copies of the Software, and to permit persons to whom the Software is +// furnished to do so, subject to the following conditions: +// +// The above copyright notice and this permission notice shall be included in +// all copies or substantial portions of the Software. +// +// THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR +// IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, +// FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE +// AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER +// LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, +// OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN +// THE SOFTWARE. + +import { Component } from "@angular/core"; + +@Component({ + selector: "nui-table-search-docs", + templateUrl: "./table-widget-search-docs.component.html", + standalone: false, +}) +export class TableSearchDocsComponent { + public featuredDeclaredText = \\\\\\\\\\\\\\\` + private supportedFeatures: IDataSourceFeatures = { + search: { enabled: true }, + pagination: { enabled: true }, + };\\\\\\\\\\\\\\\`; + public featuresUsedText = \\\\\\\\\\\\\\\` + this.features = new DataSourceFeatures(this.supportedFeatures); + \\\\\\\\\\\\\\\`; + public tableConfigurationText = \\\\\\\\\\\\\\\` + "table": { + ... + properties: { + configuration: { + // define search configuration here + searchConfiguration: { + enabled: true, + // following optional properties below can be configured as well + // searchTerm: "search criteria here", + // searchDebounce: 300, + }, + } as ITableWidgetConfig, + }, + }, + \\\\\\\\\\\\\\\`; +} +\\\\\\\`, + "widget-types/table/table-widget-selectable/table-widget-selectable-multi/table-widget-selectable-multi.example.component.ts": \\\\\\\`// © 2022 SolarWinds Worldwide, LLC. All rights reserved. +// +// Permission is hereby granted, free of charge, to any person obtaining a copy +// of this software and associated documentation files (the "Software"), to +// deal in the Software without restriction, including without limitation the +// rights to use, copy, modify, merge, publish, distribute, sublicense, and/or +// sell copies of the Software, and to permit persons to whom the Software is +// furnished to do so, subject to the following conditions: +// +// The above copyright notice and this permission notice shall be included in +// all copies or substantial portions of the Software. +// +// THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR +// IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, +// FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE +// AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER +// LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, +// OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN +// THE SOFTWARE. + +import { Component } from "@angular/core"; + +import { TableSelectionMode } from "@nova-ui/bits"; +import { TableWidgetSelectionConfig } from "@nova-ui/dashboards"; + +/** + * A component that instantiates the dashboard + */ +@Component({ + selector: "table-widget-selectable-multi-example", + templateUrl: "./table-widget-selectable-multi.example.component.html", + styleUrls: ["./table-widget-selectable-multi.example.component.less"], + standalone: false, +}) +export class TableWidgetSelectableMultiExampleComponent { + public selectionConfiguration: TableWidgetSelectionConfig = { + enabled: true, + selectionMode: TableSelectionMode.Multi, + trackByProperty: "id", + clickableRow: true, + }; +} +\\\\\\\`, + "widget-types/table/table-widget-selectable/table-widget-selectable-radio/table-widget-selectable-radio.example.component.ts": \\\\\\\`// © 2022 SolarWinds Worldwide, LLC. All rights reserved. +// +// Permission is hereby granted, free of charge, to any person obtaining a copy +// of this software and associated documentation files (the "Software"), to +// deal in the Software without restriction, including without limitation the +// rights to use, copy, modify, merge, publish, distribute, sublicense, and/or +// sell copies of the Software, and to permit persons to whom the Software is +// furnished to do so, subject to the following conditions: +// +// The above copyright notice and this permission notice shall be included in +// all copies or substantial portions of the Software. +// +// THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR +// IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, +// FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE +// AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER +// LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, +// OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN +// THE SOFTWARE. + +import { Component } from "@angular/core"; + +import { TableSelectionMode } from "@nova-ui/bits"; +import { TableWidgetSelectionConfig } from "@nova-ui/dashboards"; + +/** + * A component that instantiates the dashboard + */ +@Component({ + selector: "table-widget-selectable-radio-example", + templateUrl: "./table-widget-selectable-radio.example.component.html", + styleUrls: ["./table-widget-selectable-radio.example.component.less"], + standalone: false, +}) +export class TableWidgetSelectableRadioExampleComponent { + public selectionConfiguration: TableWidgetSelectionConfig = { + enabled: true, + selectionMode: TableSelectionMode.Radio, + trackByProperty: "id", + clickableRow: true, + }; +} +\\\\\\\`, + "widget-types/table/table-widget-selectable/table-widget-selectable-single/table-widget-selectable-single.example.component.ts": \\\\\\\`// © 2022 SolarWinds Worldwide, LLC. All rights reserved. +// +// Permission is hereby granted, free of charge, to any person obtaining a copy +// of this software and associated documentation files (the "Software"), to +// deal in the Software without restriction, including without limitation the +// rights to use, copy, modify, merge, publish, distribute, sublicense, and/or +// sell copies of the Software, and to permit persons to whom the Software is +// furnished to do so, subject to the following conditions: +// +// The above copyright notice and this permission notice shall be included in +// all copies or substantial portions of the Software. +// +// THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR +// IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, +// FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE +// AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER +// LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, +// OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN +// THE SOFTWARE. + +import { Component } from "@angular/core"; + +import { TableSelectionMode } from "@nova-ui/bits"; +import { TableWidgetSelectionConfig } from "@nova-ui/dashboards"; + +/** + * A component that instantiates the dashboard + */ +@Component({ + selector: "table-widget-selectable-single-example", + templateUrl: "./table-widget-selectable-single.example.component.html", + styleUrls: ["./table-widget-selectable-single.example.component.less"], + standalone: false, +}) +export class TableWidgetSelectableSingleExampleComponent { + public selectionConfiguration: TableWidgetSelectionConfig = { + enabled: true, + selectionMode: TableSelectionMode.Single, + trackByProperty: "id", + }; +} +\\\\\\\`, + "widget-types/table/table-widget-selectable/table-widget-selectable.example.component.ts": \\\\\\\`// © 2022 SolarWinds Worldwide, LLC. All rights reserved. +// +// Permission is hereby granted, free of charge, to any person obtaining a copy +// of this software and associated documentation files (the "Software"), to +// deal in the Software without restriction, including without limitation the +// rights to use, copy, modify, merge, publish, distribute, sublicense, and/or +// sell copies of the Software, and to permit persons to whom the Software is +// furnished to do so, subject to the following conditions: +// +// The above copyright notice and this permission notice shall be included in +// all copies or substantial portions of the Software. +// +// THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR +// IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, +// FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE +// AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER +// LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, +// OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN +// THE SOFTWARE. + +import { HttpClient } from "@angular/common/http"; +import { ChangeDetectorRef, Component, Input, OnInit } from "@angular/core"; +import { GridsterConfig, GridsterItem } from "angular-gridster2"; + +import { LoggerService, TableSelectionMode } from "@nova-ui/bits"; +import { + DATA_SOURCE, + DEFAULT_PIZZAGNA_ROOT, + IDashboard, + IProviderConfiguration, + ITableWidgetConfig, + IWidget, + IWidgets, + NOVA_URL_INTERACTION_HANDLER, + PizzagnaLayer, + ProviderRegistryService, + RawFormatterComponent, + TableWidgetSelectionConfig, + WellKnownPathKey, + WellKnownProviders, + WidgetTypesService, +} from "@nova-ui/dashboards"; + +import { AcmeTableMockDataSource } from "../../../../prototypes/data/table/acme-table-mock-data-source.service"; + +/** + * A component that instantiates the dashboard + */ +@Component({ + selector: "table-widget-selectable-example", + templateUrl: "./table-widget-selectable.example.component.html", + styleUrls: ["./table-widget-selectable.example.component.less"], + standalone: false, +}) +export class TableWidgetSelectableExampleComponent implements OnInit { + public dashboard: IDashboard | undefined; + public gridsterConfig: GridsterConfig = {}; + public editMode: boolean = false; + + @Input() public selectionConfiguration: TableWidgetSelectionConfig = { + enabled: false, + selectionMode: TableSelectionMode.None, + }; + + constructor( + private widgetTypesService: WidgetTypesService, + private providerRegistry: ProviderRegistryService, + private changeDetectorRef: ChangeDetectorRef + ) {} + + public ngOnInit(): void { + const widgetTemplate = this.widgetTypesService.getWidgetType( + "table", + 1 + ); + this.widgetTypesService.setNode( + widgetTemplate, + "configurator", + WellKnownPathKey.DataSourceProviders, + [AcmeTableMockDataSource.providerId] + ); + + this.providerRegistry.setProviders({ + [AcmeTableMockDataSource.providerId]: { + provide: DATA_SOURCE, + useClass: AcmeTableMockDataSource, + deps: [LoggerService, HttpClient], + }, + }); + + this.initializeDashboard(); + } + + /** Used for restoring widgets state */ + public reInitializeDashboard(): void { + // destroys the components and their providers so the dashboard can re init data + this.dashboard = undefined; + this.changeDetectorRef.detectChanges(); + + this.initializeDashboard(); + } + + public initializeDashboard(): void { + const tableWidget = this.widgetConfig; + const widgetIndex: IWidgets = { + [tableWidget.id]: + this.widgetTypesService.mergeWithWidgetType(tableWidget), + }; + + const positions: Record = { + [tableWidget.id]: { + cols: 12, + rows: 6, + y: 0, + x: 0, + }, + }; + + this.dashboard = { + positions, + widgets: widgetIndex, + }; + } + + private get widgetConfig(): IWidget { + return { + id: "widget1", + type: "table", + pizzagna: { + [PizzagnaLayer.Configuration]: { + [DEFAULT_PIZZAGNA_ROOT]: { + providers: { + [WellKnownProviders.InteractionHandler]: { + providerId: NOVA_URL_INTERACTION_HANDLER, + }, + }, + }, + header: { + properties: { + title: "Table Widget with Selection!", + subtitle: "Basic table widget", + collapsible: true, + }, + }, + table: { + providers: { + [WellKnownProviders.DataSource]: { + providerId: AcmeTableMockDataSource.providerId, + } as IProviderConfiguration, + }, + properties: { + configuration: { + // enabling selection here + selectionConfiguration: + this.selectionConfiguration, + columns: [ + { + id: "column1", + label: "No.", + isActive: true, + formatter: { + componentType: + RawFormatterComponent.lateLoadKey, + properties: { + dataFieldIds: { + value: "position", + }, + }, + }, + }, + { + id: "column2", + label: "Name", + isActive: true, + formatter: { + componentType: + RawFormatterComponent.lateLoadKey, + properties: { + dataFieldIds: { + value: "name", + }, + }, + }, + }, + { + id: "column3", + label: "Status", + isActive: true, + formatter: { + componentType: + RawFormatterComponent.lateLoadKey, + properties: { + dataFieldIds: { + value: "status", + }, + }, + }, + }, + ], + } as ITableWidgetConfig, + }, + }, + }, + }, + }; + } +} +\\\\\\\`, + "widget-types/timeseries/timeseries-docs.component.ts": \\\\\\\`// © 2022 SolarWinds Worldwide, LLC. All rights reserved. +// +// Permission is hereby granted, free of charge, to any person obtaining a copy +// of this software and associated documentation files (the "Software"), to +// deal in the Software without restriction, including without limitation the +// rights to use, copy, modify, merge, publish, distribute, sublicense, and/or +// sell copies of the Software, and to permit persons to whom the Software is +// furnished to do so, subject to the following conditions: +// +// The above copyright notice and this permission notice shall be included in +// all copies or substantial portions of the Software. +// +// THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR +// IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, +// FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE +// AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER +// LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, +// OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN +// THE SOFTWARE. + +import { Component, OnInit } from "@angular/core"; + +import { mapContentFile } from "../../../../demo-files-factory"; + +@Component({ + selector: "nui-timeseries-docs", + templateUrl: "./timeseries-docs.component.html", + standalone: false, +}) +export class TimeseriesDocsComponent implements OnInit { + public timeseriesWidgetFileText = ""; + public timeseriesConfiguratorFileText = ""; + + async ngOnInit(): Promise { + this.timeseriesWidgetFileText = await import( + "./../../../../../../src/lib/widget-types/timeseries/timeseries-widget" + ).then(mapContentFile); + this.timeseriesConfiguratorFileText = await import( + "./../../../../../../src/lib/widget-types/timeseries/timeseries-configurator" + ).then(mapContentFile); + } +} +\\\\\\\`, + "widget-types/timeseries/timeseries-docs.module.ts": \\\\\\\`// © 2022 SolarWinds Worldwide, LLC. All rights reserved. +// +// Permission is hereby granted, free of charge, to any person obtaining a copy +// of this software and associated documentation files (the "Software"), to +// deal in the Software without restriction, including without limitation the +// rights to use, copy, modify, merge, publish, distribute, sublicense, and/or +// sell copies of the Software, and to permit persons to whom the Software is +// furnished to do so, subject to the following conditions: +// +// The above copyright notice and this permission notice shall be included in +// all copies or substantial portions of the Software. +// +// THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR +// IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, +// FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE +// AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER +// LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, +// OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN +// THE SOFTWARE. + +import { NgModule } from "@angular/core"; +import { RouterModule, Routes } from "@angular/router"; + +import { DEMO_PATH_TOKEN } from "@nova-ui/bits"; +import { + NuiButtonModule, + NuiDocsModule, + NuiMessageModule, + NuiSwitchModule, +} from "@nova-ui/bits"; +import { NuiDashboardsModule } from "@nova-ui/dashboards"; + +import { TimeseriesDocsComponent } from "./timeseries-docs.component"; +import { TimeseriesWidgetExampleComponent } from "./timeseries-widget-example/timeseries-widget-example.component"; +import { TimeseriesWidgetInteractiveExampleComponent } from "./timeseries-widget-interactive-example/timeseries-widget-interactive-example.component"; +import { TimeseriesWidgetStatusBarExampleComponent } from "./timeseries-widget-status-bar-example/timeseries-widget-status-bar-example.component"; +import { getDemoFiles } from "../../../../demo-files-factory"; + +const routes: Routes = [ + { + path: "", + component: TimeseriesDocsComponent, + data: { + srlc: { + hideIndicator: true, + }, + showThemeSwitcher: true, + }, + }, + { + path: "example", + component: TimeseriesWidgetExampleComponent, + data: { + srlc: { + hideIndicator: true, + }, + }, + }, +]; + +@NgModule({ + imports: [ + RouterModule.forChild(routes), + NuiButtonModule, + NuiDocsModule, + NuiMessageModule, + NuiSwitchModule, + NuiDashboardsModule, + ], + declarations: [ + TimeseriesDocsComponent, + TimeseriesWidgetExampleComponent, + TimeseriesWidgetInteractiveExampleComponent, + TimeseriesWidgetStatusBarExampleComponent, + ], + providers: [ + { + provide: DEMO_PATH_TOKEN, + useValue: getDemoFiles("timeseries"), + }, + ], +}) +export default class TimeseriesDocsModule {} +\\\\\\\`, + "widget-types/timeseries/timeseries-widget-example/timeseries-widget-example.component.ts": \\\\\\\`// © 2022 SolarWinds Worldwide, LLC. All rights reserved. +// +// Permission is hereby granted, free of charge, to any person obtaining a copy +// of this software and associated documentation files (the "Software"), to +// deal in the Software without restriction, including without limitation the +// rights to use, copy, modify, merge, publish, distribute, sublicense, and/or +// sell copies of the Software, and to permit persons to whom the Software is +// furnished to do so, subject to the following conditions: +// +// The above copyright notice and this permission notice shall be included in +// all copies or substantial portions of the Software. +// +// THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR +// IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, +// FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE +// AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER +// LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, +// OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN +// THE SOFTWARE. + +import { + ChangeDetectorRef, + Component, + Injectable, + OnInit, +} from "@angular/core"; +import { GridsterConfig, GridsterItem } from "angular-gridster2"; +import cloneDeep from "lodash/cloneDeep"; +import keyBy from "lodash/keyBy"; +import moment, { Moment } from "moment/moment"; +import { BehaviorSubject } from "rxjs"; + +import { + DataSourceService, + IDataSource, + INovaFilters, + ITimeframe, +} from "@nova-ui/bits"; +import { + DATA_SOURCE, + DEFAULT_PIZZAGNA_ROOT, + IDashboard, + IDataSourceOutput, + IProviderConfiguration, + ISerializableTimeframe, + ITimeseriesItemConfiguration, + ITimeseriesOutput, + ITimeseriesScaleConfig, + ITimeseriesWidgetConfig, + ITimeseriesWidgetData, + ITimeseriesWidgetSeriesData, + IWidget, + LegendPlacement, + PizzagnaLayer, + ProviderRegistryService, + TimeseriesChartPreset, + TimeseriesScaleType, + WellKnownPathKey, + WellKnownProviders, + WidgetTypesService, +} from "@nova-ui/dashboards"; + +/** + * A simple Timeseries data source implementation + */ +@Injectable() +export class BeerVsReadingMockDataSource + extends DataSourceService + implements IDataSource +{ + public static providerId = "BeerVsReadingMockDataSource"; + + public busy = new BehaviorSubject(false); + + public async getFilteredData( + filters: INovaFilters + ): Promise> { + // In this example we're using some static mock data located at the bottom of this file. In a real-world + // scenario, the data for the chart would likely be retrieved via an asynchronous backend call. + let filteredData = getData(); + + this.busy.next(true); + + // Filtering using the filter registered by the TimeFrameBar + const timeframeFilter = filters.timeframe?.value as ITimeframe; + if (timeframeFilter) { + filteredData = filteredData.map((item: ITimeseriesWidgetData) => ({ + id: item.id, + name: item.name, + description: item.description, + data: item.data.filter( + (seriesData: ITimeseriesWidgetSeriesData) => + filterDates( + seriesData.x, + timeframeFilter.startDatetime, + timeframeFilter.endDatetime + ) + ), + })); + } + + this.busy.next(false); + + return { result: { series: filteredData } }; + } +} + +function filterDates(dateToCheck: Date, startDate: Moment, endDate: Moment) { + const mom = moment(dateToCheck); + return ( + mom.isBetween(startDate, endDate) || + mom.isSame(startDate) || + mom.isSame(endDate) + ); +} + +/** + * A component that instantiates the dashboard + */ +@Component({ + selector: "timeseries-widget-example", + templateUrl: "./timeseries-widget-example.component.html", + styleUrls: ["./timeseries-widget-example.component.less"], + standalone: false, +}) +export class TimeseriesWidgetExampleComponent implements OnInit { + // This variable will hold all the data needed to define the layout and behavior of the widgets. + // Pass this to the dashboard component's dashboard input in the template. + public dashboard: IDashboard | undefined; + + // Angular gridster requires a configuration object even if it's empty. + // Pass this to the dashboard component's gridsterConfig input in the template. + public gridsterConfig: GridsterConfig = {}; + + // Boolean passed as an input to the dashboard. When true, widgets can be moved, resized, removed, or edited + public editMode: boolean = false; + + constructor( + // WidgetTypesService provides the widget's necessary structure information + private widgetTypesService: WidgetTypesService, + + // In general, the ProviderRegistryService is used for making entities available for injection into dynamically loaded components. + private providerRegistry: ProviderRegistryService, + + // Angular's ChangeDetectorRef + private changeDetectorRef: ChangeDetectorRef + ) {} + + public ngOnInit(): void { + // Grabbing the widget's default template which will be needed as a parameter for setNode + const widgetTemplate = this.widgetTypesService.getWidgetType( + "timeseries", + 1 + ); + // Registering our data sources as dropdown options in the widget editor/configurator + // Note: This could also be done in the parent module's constructor so that + // multiple dashboards could have access to the same widget template modification. + this.widgetTypesService.setNode( + // This is the template we grabbed above with getWidgetType + widgetTemplate, + // We are setting the editor/configurator part of the widget template + "configurator", + // This indicates which node you are changing and we want to change + // the data source providers available for selection in the editor. + WellKnownPathKey.DataSourceProviders, + // We are setting the data sources available for selection in the editor + [BeerVsReadingMockDataSource.providerId] + ); + + // Registering the data source for injection into the widget. + this.providerRegistry.setProviders({ + [BeerVsReadingMockDataSource.providerId]: { + provide: DATA_SOURCE, + useClass: BeerVsReadingMockDataSource, + deps: [], + }, + }); + + this.initializeDashboard(); + } + + public initializeDashboard(): void { + // We're using a static configuration object for this example, but this is where + // the widget's configuration could potentially be populated from a database + const widgetsWithStructure = widgetConfigs.map((w) => + this.widgetTypesService.mergeWithWidgetType(w) + ); + const widgetsIndex = keyBy(widgetsWithStructure, (w: IWidget) => w.id); + + // Finally, assigning the variables we created above to the dashboard + this.dashboard = { + positions: cloneDeep(positions), + widgets: widgetsIndex, + }; + } + + /** Used for restoring widgets state */ + public reInitializeDashboard(): void { + // destroys the components and their providers so the dashboard can re init data + this.dashboard = undefined; + this.changeDetectorRef.detectChanges(); + + this.initializeDashboard(); + } +} + +const widgetConfigs: IWidget[] = [ + { + id: "lineWidgetId", + type: "timeseries", + pizzagna: { + [PizzagnaLayer.Configuration]: { + [DEFAULT_PIZZAGNA_ROOT]: { + providers: { + [WellKnownProviders.DataSource]: { + // Setting the initially selected data source providerId + providerId: BeerVsReadingMockDataSource.providerId, + } as IProviderConfiguration, + }, + }, + header: { + properties: { + title: "Line Chart", + subtitle: "Survey of 1000 Solarians", + }, + }, + chart: { + providers: { + [WellKnownProviders.Adapter]: { + properties: { + // Setting the series and corresponding labels to initially display on the chart + series: [ + { + id: "series-1", + label: "Beer Tasting", + selectedSeriesId: "series-1", + }, + { + id: "series-2", + label: "Reading", + selectedSeriesId: "series-2", + }, + ] as ITimeseriesItemConfiguration[], + }, + } as Partial, + }, + properties: { + // Setting the general chart configuration + configuration: { + legendPlacement: LegendPlacement.Right, + enableZoom: true, + leftAxisLabel: "Solarians (%)", + // You can optionally define custom colors for the chart by setting the 'chartColors' configuration property + // "chartColors": [ + // "var(--nui-color-chart-eight)", + // "var(--nui-color-chart-nine)", + // "var(--nui-color-chart-ten)", + // ], + } as ITimeseriesWidgetConfig, + }, + }, + timeframeSelection: { + properties: { + // Setting the initial timeframe selected in the timeframe bar + timeframe: { + selectedPresetId: "last7Days", + } as ISerializableTimeframe, + minDate: moment().subtract(60, "days").format(), + maxDate: moment().format(), + }, + }, + }, + }, + }, + { + id: "stackedAreaWidgetId", + type: "timeseries", + pizzagna: { + [PizzagnaLayer.Configuration]: { + [DEFAULT_PIZZAGNA_ROOT]: { + providers: { + [WellKnownProviders.DataSource]: { + // Setting the initially selected data source providerId + providerId: BeerVsReadingMockDataSource.providerId, + } as IProviderConfiguration, + }, + }, + header: { + properties: { + title: "Stacked Area Chart", + subtitle: "Survey of 1000 Solarians", + }, + }, + chart: { + providers: { + [WellKnownProviders.Adapter]: { + properties: { + // Setting the series and corresponding labels to initially display on the chart + series: [ + { + id: "series-1", + label: "Beer Tasting", + selectedSeriesId: "series-1", + }, + { + id: "series-2", + label: "Reading", + selectedSeriesId: "series-2", + }, + ] as ITimeseriesItemConfiguration[], + }, + } as Partial, + }, + properties: { + // Setting the general chart configuration + configuration: { + legendPlacement: LegendPlacement.Right, + enableZoom: true, + // Setting the preset to stacked area + preset: TimeseriesChartPreset.StackedArea, + leftAxisLabel: "Solarians (%)", + // You can optionally define custom colors for the chart by setting the 'chartColors' configuration property + // "chartColors": [ + // "var(--nui-color-chart-eight)", + // "var(--nui-color-chart-nine)", + // "var(--nui-color-chart-ten)", + // ], + } as ITimeseriesWidgetConfig, + }, + }, + timeframeSelection: { + properties: { + // Setting the initial timeframe selected in the timeframe bar + timeframe: { + selectedPresetId: "last7Days", + } as ISerializableTimeframe, + minDate: moment().subtract(60, "days").format(), + maxDate: moment().format(), + }, + }, + }, + }, + }, + { + id: "stackedPercentageAreaWidgetId", + type: "timeseries", + pizzagna: { + [PizzagnaLayer.Configuration]: { + [DEFAULT_PIZZAGNA_ROOT]: { + providers: { + [WellKnownProviders.DataSource]: { + // Setting the initially selected data source providerId + providerId: BeerVsReadingMockDataSource.providerId, + } as IProviderConfiguration, + }, + }, + header: { + properties: { + title: "Stacked Percentage Area Chart", + subtitle: "Survey of 1000 Solarians", + }, + }, + chart: { + providers: { + [WellKnownProviders.Adapter]: { + properties: { + // Setting the series and corresponding labels to initially display on the chart + series: [ + { + id: "series-1", + label: "Beer Tasting", + selectedSeriesId: "series-1", + }, + { + id: "series-2", + label: "Reading", + selectedSeriesId: "series-2", + }, + ] as ITimeseriesItemConfiguration[], + }, + } as Partial, + }, + properties: { + // Setting the general chart configuration + configuration: { + legendPlacement: LegendPlacement.Right, + enableZoom: true, + // Setting the preset to stacked percentage area + preset: TimeseriesChartPreset.StackedPercentageArea, + leftAxisLabel: "Solarians (%)", + // You can optionally define custom colors for the chart by setting the 'chartColors' configuration property + // "chartColors": [ + // "var(--nui-color-chart-eight)", + // "var(--nui-color-chart-nine)", + // "var(--nui-color-chart-ten)", + // ], + } as ITimeseriesWidgetConfig, + }, + }, + timeframeSelection: { + properties: { + // Setting the initial timeframe selected in the timeframe bar + timeframe: { + selectedPresetId: "last7Days", + } as ISerializableTimeframe, + minDate: moment().subtract(60, "days").format(), + maxDate: moment().format(), + }, + }, + }, + }, + }, + { + id: "stackedBarWidgetId", + type: "timeseries", + pizzagna: { + [PizzagnaLayer.Configuration]: { + [DEFAULT_PIZZAGNA_ROOT]: { + providers: { + [WellKnownProviders.DataSource]: { + // Setting the initially selected data source providerId + providerId: BeerVsReadingMockDataSource.providerId, + } as IProviderConfiguration, + }, + }, + header: { + properties: { + title: "Stacked Bar Chart", + subtitle: "Survey of 1000 Solarians", + }, + }, + chart: { + providers: { + [WellKnownProviders.Adapter]: { + properties: { + // Setting the series and corresponding labels to initially display on the chart + series: [ + { + id: "series-1", + label: "Beer Tasting", + selectedSeriesId: "series-1", + }, + { + id: "series-2", + label: "Reading", + selectedSeriesId: "series-2", + }, + ] as ITimeseriesItemConfiguration[], + }, + } as Partial, + }, + properties: { + configuration: { + legendPlacement: LegendPlacement.Right, + enableZoom: true, + leftAxisLabel: "Solarians (%)", + // Setting the preset to stacked bar + preset: TimeseriesChartPreset.StackedBar, + scales: { + x: { + type: TimeseriesScaleType.TimeInterval, + properties: { + interval: 24 * 60 * 60, + }, + } as ITimeseriesScaleConfig, + }, + } as ITimeseriesWidgetConfig, + }, + }, + timeframeSelection: { + properties: { + // Setting the initial timeframe selected in the timeframe bar + timeframe: { + selectedPresetId: "last7Days", + } as ISerializableTimeframe, + minDate: moment().subtract(60, "days").format(), + maxDate: moment().format(), + }, + }, + }, + }, + }, +]; + +// using startOf("day") so that each band for the bar chart corresponds to a calendar day +const now = moment().startOf("day"); + +export const getData = (): ITimeseriesWidgetData[] => [ + { + id: "series-1", + name: "Beer Tasting", + description: "Havin' some suds", + data: [ + { x: now.clone().subtract(20, "day").toDate(), y: 30 }, + { x: now.clone().subtract(19, "day").toDate(), y: 35 }, + { x: now.clone().subtract(18, "day").toDate(), y: 33 }, + { x: now.clone().subtract(17, "day").toDate(), y: 40 }, + { x: now.clone().subtract(16, "day").toDate(), y: 35 }, + { x: now.clone().subtract(15, "day").toDate(), y: 30 }, + { x: now.clone().subtract(14, "day").toDate(), y: 35 }, + { x: now.clone().subtract(13, "day").toDate(), y: 15 }, + { x: now.clone().subtract(12, "day").toDate(), y: 30 }, + { x: now.clone().subtract(11, "day").toDate(), y: 45 }, + { x: now.clone().subtract(10, "day").toDate(), y: 60 }, + { x: now.clone().subtract(9, "day").toDate(), y: 54 }, + { x: now.clone().subtract(8, "day").toDate(), y: 42 }, + { x: now.clone().subtract(7, "day").toDate(), y: 44 }, + { x: now.clone().subtract(6, "day").toDate(), y: 54 }, + { x: now.clone().subtract(5, "day").toDate(), y: 43 }, + { x: now.clone().subtract(4, "day").toDate(), y: 76 }, + { x: now.clone().subtract(3, "day").toDate(), y: 54 }, + { x: now.clone().subtract(2, "day").toDate(), y: 42 }, + { x: now.clone().subtract(1, "day").toDate(), y: 34 }, + ], + }, + { + id: "series-2", + name: "Reading", + description: "Hittin' the books", + data: [ + { x: now.clone().subtract(20, "day").toDate(), y: 60 }, + { x: now.clone().subtract(19, "day").toDate(), y: 64 }, + { x: now.clone().subtract(18, "day").toDate(), y: 70 }, + { x: now.clone().subtract(17, "day").toDate(), y: 55 }, + { x: now.clone().subtract(16, "day").toDate(), y: 55 }, + { x: now.clone().subtract(15, "day").toDate(), y: 45 }, + { x: now.clone().subtract(14, "day").toDate(), y: 60 }, + { x: now.clone().subtract(13, "day").toDate(), y: 65 }, + { x: now.clone().subtract(12, "day").toDate(), y: 63 }, + { x: now.clone().subtract(11, "day").toDate(), y: 60 }, + { x: now.clone().subtract(10, "day").toDate(), y: 61 }, + { x: now.clone().subtract(9, "day").toDate(), y: 65 }, + { x: now.clone().subtract(8, "day").toDate(), y: 63 }, + { x: now.clone().subtract(7, "day").toDate(), y: 58 }, + { x: now.clone().subtract(6, "day").toDate(), y: 64 }, + { x: now.clone().subtract(5, "day").toDate(), y: 63 }, + { x: now.clone().subtract(4, "day").toDate(), y: 60 }, + { x: now.clone().subtract(3, "day").toDate(), y: 62 }, + { x: now.clone().subtract(2, "day").toDate(), y: 61 }, + { x: now.clone().subtract(1, "day").toDate(), y: 62 }, + ], + }, +]; + +// Setting the widget dimensions and position (this is for gridster) +const positions: Record = { + [widgetConfigs[0].id]: { + cols: 6, + rows: 6, + y: 0, + x: 0, + }, + [widgetConfigs[1].id]: { + cols: 6, + rows: 6, + y: 0, + x: 6, + }, + [widgetConfigs[3].id]: { + cols: 6, + rows: 6, + y: 6, + x: 0, + }, + [widgetConfigs[2].id]: { + cols: 6, + rows: 6, + y: 6, + x: 6, + }, +}; +\\\\\\\`, + "widget-types/timeseries/timeseries-widget-interactive-example/timeseries-widget-interactive-example.component.ts": \\\\\\\`// © 2022 SolarWinds Worldwide, LLC. All rights reserved. +// +// Permission is hereby granted, free of charge, to any person obtaining a copy +// of this software and associated documentation files (the "Software"), to +// deal in the Software without restriction, including without limitation the +// rights to use, copy, modify, merge, publish, distribute, sublicense, and/or +// sell copies of the Software, and to permit persons to whom the Software is +// furnished to do so, subject to the following conditions: +// +// The above copyright notice and this permission notice shall be included in +// all copies or substantial portions of the Software. +// +// THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR +// IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, +// FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE +// AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER +// LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, +// OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN +// THE SOFTWARE. + +import { + ChangeDetectorRef, + Component, + Injectable, + OnInit, +} from "@angular/core"; +import { GridsterConfig, GridsterItem } from "angular-gridster2"; +import cloneDeep from "lodash/cloneDeep"; +import keyBy from "lodash/keyBy"; +import moment, { Moment } from "moment/moment"; +import { BehaviorSubject } from "rxjs"; + +import { + DataSourceService, + IDataSource, + INovaFilters, + ITimeframe, +} from "@nova-ui/bits"; +import { + DATA_SOURCE, + DEFAULT_PIZZAGNA_ROOT, + IDashboard, + IDataSourceOutput, + IProviderConfiguration, + ISerializableTimeframe, + ITimeseriesItemConfiguration, + ITimeseriesOutput, + ITimeseriesScaleConfig, + ITimeseriesWidgetConfig, + ITimeseriesWidgetData, + ITimeseriesWidgetSeriesData, + IWidget, + NOVA_URL_INTERACTION_HANDLER, + LegendPlacement, + PizzagnaLayer, + ProviderRegistryService, + TimeseriesChartPreset, + TimeseriesScaleType, + WellKnownPathKey, + WellKnownProviders, + WidgetTypesService, +} from "@nova-ui/dashboards"; + +/** + * A simple Timeseries data source implementation + */ +@Injectable() +export class TimeseriesMockDataSource + extends DataSourceService + implements IDataSource +{ + public static providerId = "TimeseriesMockDataSource"; + + public busy = new BehaviorSubject(false); + + public async getFilteredData( + filters: INovaFilters + ): Promise> { + // In this example we're using some static mock data located at the bottom of this file. In a real-world + // scenario, the data for the chart would likely be retrieved via an asynchronous backend call. + let filteredData = getData(); + + this.busy.next(true); + + // Filtering using the filter registered by the TimeFrameBar + const timeframeFilter = filters.timeframe?.value as ITimeframe; + if (timeframeFilter) { + filteredData = filteredData.map((item: ITimeseriesWidgetData) => ({ + id: item.id, + name: item.name, + description: item.description, + // the filtered data should return the provided links if they are set. + link: item?.link, + secondaryLink: item?.secondaryLink, + data: item.data.filter( + (seriesData: ITimeseriesWidgetSeriesData) => + filterDates( + seriesData.x, + timeframeFilter.startDatetime, + timeframeFilter.endDatetime + ) + ), + })); + } + + this.busy.next(false); + + return { result: { series: filteredData } }; + } +} + +function filterDates(dateToCheck: Date, startDate: Moment, endDate: Moment) { + const mom = moment(dateToCheck); + return ( + mom.isBetween(startDate, endDate) || + mom.isSame(startDate) || + mom.isSame(endDate) + ); +} + +/** + * A component that instantiates the dashboard + */ +@Component({ + selector: "timeseries-widget-interactive-example", + templateUrl: "./timeseries-widget-interactive-example.component.html", + styleUrls: ["./timeseries-widget-interactive-example.component.less"], + standalone: false, +}) +export class TimeseriesWidgetInteractiveExampleComponent implements OnInit { + // This variable will hold all the data needed to define the layout and behavior of the widgets. + // Pass this to the dashboard component's dashboard input in the template. + public dashboard: IDashboard | undefined; + + // Angular gridster requires a configuration object even if it's empty. + // Pass this to the dashboard component's gridsterConfig input in the template. + public gridsterConfig: GridsterConfig = {}; + + // Boolean passed as an input to the dashboard. When true, widgets can be moved, resized, removed, or edited + public editMode: boolean = false; + + constructor( + // WidgetTypesService provides the widget's necessary structure information + private widgetTypesService: WidgetTypesService, + + // In general, the ProviderRegistryService is used for making entities available for injection into dynamically loaded components. + private providerRegistry: ProviderRegistryService, + + // Angular's ChangeDetectorRef + private changeDetectorRef: ChangeDetectorRef + ) {} + + public ngOnInit(): void { + // Grabbing the widget's default template which will be needed as a parameter for setNode + const widgetTemplate = this.widgetTypesService.getWidgetType( + "timeseries", + 1 + ); + // Registering our data sources as dropdown options in the widget editor/configurator + // Note: This could also be done in the parent module's constructor so that + // multiple dashboards could have access to the same widget template modification. + this.widgetTypesService.setNode( + // This is the template we grabbed above with getWidgetType + widgetTemplate, + // We are setting the editor/configurator part of the widget template + "configurator", + // This indicates which node you are changing and we want to change + // the data source providers available for selection in the editor. + WellKnownPathKey.DataSourceProviders, + // We are setting the data sources available for selection in the editor + [TimeseriesMockDataSource.providerId] + ); + + // Registering the data source for injection into the widget. + this.providerRegistry.setProviders({ + [TimeseriesMockDataSource.providerId]: { + provide: DATA_SOURCE, + useClass: TimeseriesMockDataSource, + deps: [], + }, + }); + + this.initializeDashboard(); + } + + public initializeDashboard(): void { + // We're using a static configuration object for this example, but this is where + // the widget's configuration could potentially be populated from a database + const widgetsWithStructure = widgetConfigs.map((w) => + this.widgetTypesService.mergeWithWidgetType(w) + ); + const widgetsIndex = keyBy(widgetsWithStructure, (w: IWidget) => w.id); + + // Finally, assigning the variables we created above to the dashboard + this.dashboard = { + positions: cloneDeep(positions), + widgets: widgetsIndex, + }; + } + + /** Used for restoring widgets state */ + public reInitializeDashboard(): void { + // destroys the components and their providers so the dashboard can re init data + this.dashboard = undefined; + this.changeDetectorRef.detectChanges(); + + this.initializeDashboard(); + } +} + +const widgetConfigs: IWidget[] = [ + { + id: "lineWidgetId", + type: "timeseries", + pizzagna: { + [PizzagnaLayer.Configuration]: { + [DEFAULT_PIZZAGNA_ROOT]: { + providers: { + [WellKnownProviders.DataSource]: { + // Setting the initially selected data source providerId + providerId: TimeseriesMockDataSource.providerId, + } as IProviderConfiguration, + [WellKnownProviders.InteractionHandler]: { + // Setting the UrlInteractionHandler as an interactionHandler + providerId: NOVA_URL_INTERACTION_HANDLER, + properties: { + // the 'url' property tells the handler what link to use when interaction occurs on the series + url: "\\\\\\\\\\\\\\\${data.link || 'https://en.wikipedia.org/wiki/'+data.legendDescriptionPrimary}", + // by default the link is opened in the current window, set 'newWindow' to true to open in a new tab instead + // newWindow: true, + }, + }, + }, + }, + header: { + properties: { + title: "Line Chart", + subtitle: "Basic Timeseries with Interaction", + }, + }, + chart: { + providers: { + [WellKnownProviders.Adapter]: { + properties: { + // Setting the series and corresponding labels to initially display on the chart + series: [ + { + id: "series-1", + label: "Nur-Sultan", + selectedSeriesId: "series-1", + }, + { + id: "series-2", + label: "Brno", + selectedSeriesId: "series-2", + }, + { + id: "series-3", + label: "Lisbon", + selectedSeriesId: "series-3", + }, + { + id: "series-4", + label: "Austin", + selectedSeriesId: "series-4", + }, + ] as ITimeseriesItemConfiguration[], + }, + } as Partial, + }, + properties: { + // Setting the general chart configuration + configuration: { + // setting interaction to 'series' will make all series in the chart interactable + interaction: "series", + legendPlacement: LegendPlacement.Right, + enableZoom: true, + } as ITimeseriesWidgetConfig, + }, + }, + timeframeSelection: { + properties: { + timeframe: { + selectedPresetId: "last7Days", + } as ISerializableTimeframe, + minDate: moment().subtract(60, "days").format(), + maxDate: moment().format(), + }, + }, + }, + }, + }, + { + id: "stackedBarWidgetId", + type: "timeseries", + pizzagna: { + [PizzagnaLayer.Configuration]: { + [DEFAULT_PIZZAGNA_ROOT]: { + providers: { + [WellKnownProviders.DataSource]: { + // Setting the initially selected data source providerId + providerId: TimeseriesMockDataSource.providerId, + } as IProviderConfiguration, + }, + }, + header: { + properties: { + title: "Stacked Bar Chart", + subtitle: + "Basic Timeseries without Interaction Handler", + }, + }, + chart: { + providers: { + [WellKnownProviders.Adapter]: { + properties: { + // Setting the series and corresponding labels to initially display on the chart + series: [ + { + id: "series-1", + label: "Nur-Sultan", + selectedSeriesId: "series-1", + }, + { + id: "series-2", + label: "Brno", + selectedSeriesId: "series-2", + }, + { + id: "series-3", + label: "Lisbon", + selectedSeriesId: "series-3", + }, + { + id: "series-4", + label: "Austin", + selectedSeriesId: "series-4", + }, + ] as ITimeseriesItemConfiguration[], + }, + } as Partial, + }, + properties: { + // Setting the general chart configuration + configuration: { + legendPlacement: LegendPlacement.Right, + enableZoom: true, + // Setting the preset to stacked bar + preset: TimeseriesChartPreset.StackedBar, + scales: { + x: { + type: TimeseriesScaleType.TimeInterval, + properties: { + interval: 24 * 60 * 60, + }, + } as ITimeseriesScaleConfig, + }, + } as ITimeseriesWidgetConfig, + }, + }, + timeframeSelection: { + properties: { + // Setting the initial timeframe selected in the timeframe bar + timeframe: { + selectedPresetId: "last7Days", + } as ISerializableTimeframe, + minDate: moment().subtract(60, "days").format(), + maxDate: moment().format(), + }, + }, + }, + }, + }, +]; + +// using startOf("day") so that each band for the bar chart corresponds to a calendar day +const startOfToday = moment().startOf("day").toDate(); + +export const getData = (): ITimeseriesWidgetData[] => [ + { + id: "series-1", + name: "Nur-Sultan", + description: "'link' only", + link: "https://en.wikipedia.org/wiki/Nur-Sultan", + data: [ + { x: moment(startOfToday).subtract(59, "day").toDate(), y: 35 }, + { x: moment(startOfToday).subtract(58, "day").toDate(), y: 33 }, + { x: moment(startOfToday).subtract(57, "day").toDate(), y: 40 }, + { x: moment(startOfToday).subtract(56, "day").toDate(), y: 35 }, + { x: moment(startOfToday).subtract(55, "day").toDate(), y: 30 }, + { x: moment(startOfToday).subtract(54, "day").toDate(), y: 35 }, + { x: moment(startOfToday).subtract(53, "day").toDate(), y: 15 }, + { x: moment(startOfToday).subtract(52, "day").toDate(), y: 30 }, + { x: moment(startOfToday).subtract(51, "day").toDate(), y: 35 }, + { x: moment(startOfToday).subtract(50, "day").toDate(), y: 34 }, + { x: moment(startOfToday).subtract(49, "day").toDate(), y: 35 }, + { x: moment(startOfToday).subtract(48, "day").toDate(), y: 33 }, + { x: moment(startOfToday).subtract(47, "day").toDate(), y: 40 }, + { x: moment(startOfToday).subtract(46, "day").toDate(), y: 35 }, + { x: moment(startOfToday).subtract(45, "day").toDate(), y: 30 }, + { x: moment(startOfToday).subtract(44, "day").toDate(), y: 35 }, + { x: moment(startOfToday).subtract(43, "day").toDate(), y: 15 }, + { x: moment(startOfToday).subtract(42, "day").toDate(), y: 30 }, + { x: moment(startOfToday).subtract(41, "day").toDate(), y: 35 }, + { x: moment(startOfToday).subtract(40, "day").toDate(), y: 34 }, + { x: moment(startOfToday).subtract(39, "day").toDate(), y: 35 }, + { x: moment(startOfToday).subtract(38, "day").toDate(), y: 33 }, + { x: moment(startOfToday).subtract(37, "day").toDate(), y: 40 }, + { x: moment(startOfToday).subtract(36, "day").toDate(), y: 35 }, + { x: moment(startOfToday).subtract(35, "day").toDate(), y: 30 }, + { x: moment(startOfToday).subtract(34, "day").toDate(), y: 35 }, + { x: moment(startOfToday).subtract(33, "day").toDate(), y: 15 }, + { x: moment(startOfToday).subtract(32, "day").toDate(), y: 30 }, + { x: moment(startOfToday).subtract(31, "day").toDate(), y: 35 }, + { x: moment(startOfToday).subtract(30, "day").toDate(), y: 34 }, + { x: moment(startOfToday).subtract(29, "day").toDate(), y: 35 }, + { x: moment(startOfToday).subtract(28, "day").toDate(), y: 33 }, + { x: moment(startOfToday).subtract(27, "day").toDate(), y: 40 }, + { x: moment(startOfToday).subtract(26, "day").toDate(), y: 35 }, + { x: moment(startOfToday).subtract(25, "day").toDate(), y: 30 }, + { x: moment(startOfToday).subtract(24, "day").toDate(), y: 35 }, + { x: moment(startOfToday).subtract(23, "day").toDate(), y: 15 }, + { x: moment(startOfToday).subtract(22, "day").toDate(), y: 30 }, + { x: moment(startOfToday).subtract(21, "day").toDate(), y: 35 }, + { x: moment(startOfToday).subtract(20, "day").toDate(), y: 34 }, + { x: moment(startOfToday).subtract(19, "day").toDate(), y: 35 }, + { x: moment(startOfToday).subtract(18, "day").toDate(), y: 33 }, + { x: moment(startOfToday).subtract(17, "day").toDate(), y: 40 }, + { x: moment(startOfToday).subtract(16, "day").toDate(), y: 35 }, + { x: moment(startOfToday).subtract(15, "day").toDate(), y: 30 }, + { x: moment(startOfToday).subtract(14, "day").toDate(), y: 35 }, + { x: moment(startOfToday).subtract(13, "day").toDate(), y: 15 }, + { x: moment(startOfToday).subtract(12, "day").toDate(), y: 30 }, + { x: moment(startOfToday).subtract(11, "day").toDate(), y: 35 }, + { x: moment(startOfToday).subtract(10, "day").toDate(), y: 34 }, + { x: moment(startOfToday).subtract(9, "day").toDate(), y: 33 }, + { x: moment(startOfToday).subtract(8, "day").toDate(), y: 35 }, + { x: moment(startOfToday).subtract(7, "day").toDate(), y: 36 }, + { x: moment(startOfToday).subtract(6, "day").toDate(), y: 34 }, + { x: moment(startOfToday).subtract(5, "day").toDate(), y: 33 }, + { x: moment(startOfToday).subtract(4, "day").toDate(), y: 30 }, + { x: moment(startOfToday).subtract(3, "day").toDate(), y: 32 }, + { x: moment(startOfToday).subtract(2, "day").toDate(), y: 31 }, + { x: moment(startOfToday).subtract(1, "day").toDate(), y: 34 }, + { x: moment(startOfToday).toDate(), y: 25 }, + ], + }, + { + id: "series-2", + name: "Brno", + description: "'link' and 'secondaryLink'", + link: "https://en.wikipedia.org/wiki/Brno", + secondaryLink: "https://en.wikipedia.org/wiki/Europe", + data: [ + { x: moment(startOfToday).subtract(59, "day").toDate(), y: 35 }, + { x: moment(startOfToday).subtract(58, "day").toDate(), y: 33 }, + { x: moment(startOfToday).subtract(57, "day").toDate(), y: 40 }, + { x: moment(startOfToday).subtract(56, "day").toDate(), y: 35 }, + { x: moment(startOfToday).subtract(55, "day").toDate(), y: 30 }, + { x: moment(startOfToday).subtract(54, "day").toDate(), y: 35 }, + { x: moment(startOfToday).subtract(53, "day").toDate(), y: 15 }, + { x: moment(startOfToday).subtract(52, "day").toDate(), y: 30 }, + { x: moment(startOfToday).subtract(51, "day").toDate(), y: 35 }, + { x: moment(startOfToday).subtract(50, "day").toDate(), y: 34 }, + { x: moment(startOfToday).subtract(49, "day").toDate(), y: 35 }, + { x: moment(startOfToday).subtract(48, "day").toDate(), y: 33 }, + { x: moment(startOfToday).subtract(47, "day").toDate(), y: 40 }, + { x: moment(startOfToday).subtract(46, "day").toDate(), y: 35 }, + { x: moment(startOfToday).subtract(45, "day").toDate(), y: 30 }, + { x: moment(startOfToday).subtract(44, "day").toDate(), y: 35 }, + { x: moment(startOfToday).subtract(43, "day").toDate(), y: 15 }, + { x: moment(startOfToday).subtract(42, "day").toDate(), y: 30 }, + { x: moment(startOfToday).subtract(41, "day").toDate(), y: 35 }, + { x: moment(startOfToday).subtract(40, "day").toDate(), y: 34 }, + { x: moment(startOfToday).subtract(39, "day").toDate(), y: 35 }, + { x: moment(startOfToday).subtract(38, "day").toDate(), y: 33 }, + { x: moment(startOfToday).subtract(37, "day").toDate(), y: 40 }, + { x: moment(startOfToday).subtract(36, "day").toDate(), y: 35 }, + { x: moment(startOfToday).subtract(35, "day").toDate(), y: 30 }, + { x: moment(startOfToday).subtract(34, "day").toDate(), y: 35 }, + { x: moment(startOfToday).subtract(33, "day").toDate(), y: 15 }, + { x: moment(startOfToday).subtract(32, "day").toDate(), y: 30 }, + { x: moment(startOfToday).subtract(31, "day").toDate(), y: 35 }, + { x: moment(startOfToday).subtract(30, "day").toDate(), y: 34 }, + { x: moment(startOfToday).subtract(29, "day").toDate(), y: 35 }, + { x: moment(startOfToday).subtract(28, "day").toDate(), y: 33 }, + { x: moment(startOfToday).subtract(27, "day").toDate(), y: 40 }, + { x: moment(startOfToday).subtract(26, "day").toDate(), y: 35 }, + { x: moment(startOfToday).subtract(25, "day").toDate(), y: 30 }, + { x: moment(startOfToday).subtract(24, "day").toDate(), y: 35 }, + { x: moment(startOfToday).subtract(23, "day").toDate(), y: 15 }, + { x: moment(startOfToday).subtract(22, "day").toDate(), y: 30 }, + { x: moment(startOfToday).subtract(21, "day").toDate(), y: 35 }, + { x: moment(startOfToday).subtract(20, "day").toDate(), y: 34 }, + { x: moment(startOfToday).subtract(19, "day").toDate(), y: 64 }, + { x: moment(startOfToday).subtract(18, "day").toDate(), y: 70 }, + { x: moment(startOfToday).subtract(17, "day").toDate(), y: 55 }, + { x: moment(startOfToday).subtract(16, "day").toDate(), y: 55 }, + { x: moment(startOfToday).subtract(15, "day").toDate(), y: 45 }, + { x: moment(startOfToday).subtract(14, "day").toDate(), y: 10 }, + { x: moment(startOfToday).subtract(13, "day").toDate(), y: 65 }, + { x: moment(startOfToday).subtract(12, "day").toDate(), y: 35 }, + { x: moment(startOfToday).subtract(11, "day").toDate(), y: 60 }, + { x: moment(startOfToday).subtract(10, "day").toDate(), y: 61 }, + { x: moment(startOfToday).subtract(9, "day").toDate(), y: 65 }, + { x: moment(startOfToday).subtract(8, "day").toDate(), y: 63 }, + { x: moment(startOfToday).subtract(7, "day").toDate(), y: 58 }, + { x: moment(startOfToday).subtract(6, "day").toDate(), y: 64 }, + { x: moment(startOfToday).subtract(5, "day").toDate(), y: 63 }, + { x: moment(startOfToday).subtract(4, "day").toDate(), y: 60 }, + { x: moment(startOfToday).subtract(3, "day").toDate(), y: 62 }, + { x: moment(startOfToday).subtract(2, "day").toDate(), y: 61 }, + { x: moment(startOfToday).subtract(1, "day").toDate(), y: 62 }, + { x: moment(startOfToday).toDate(), y: 55 }, + ], + }, + { + id: "series-3", + name: "Lisbon", + description: "'secondaryLink' only", + secondaryLink: "https://en.wikipedia.org/wiki/Lisbon", + data: [ + { x: moment(startOfToday).subtract(59, "day").toDate(), y: 35 }, + { x: moment(startOfToday).subtract(58, "day").toDate(), y: 33 }, + { x: moment(startOfToday).subtract(57, "day").toDate(), y: 40 }, + { x: moment(startOfToday).subtract(56, "day").toDate(), y: 35 }, + { x: moment(startOfToday).subtract(55, "day").toDate(), y: 30 }, + { x: moment(startOfToday).subtract(54, "day").toDate(), y: 35 }, + { x: moment(startOfToday).subtract(53, "day").toDate(), y: 15 }, + { x: moment(startOfToday).subtract(52, "day").toDate(), y: 30 }, + { x: moment(startOfToday).subtract(51, "day").toDate(), y: 35 }, + { x: moment(startOfToday).subtract(50, "day").toDate(), y: 34 }, + { x: moment(startOfToday).subtract(49, "day").toDate(), y: 35 }, + { x: moment(startOfToday).subtract(48, "day").toDate(), y: 33 }, + { x: moment(startOfToday).subtract(47, "day").toDate(), y: 40 }, + { x: moment(startOfToday).subtract(46, "day").toDate(), y: 35 }, + { x: moment(startOfToday).subtract(45, "day").toDate(), y: 30 }, + { x: moment(startOfToday).subtract(44, "day").toDate(), y: 35 }, + { x: moment(startOfToday).subtract(43, "day").toDate(), y: 15 }, + { x: moment(startOfToday).subtract(42, "day").toDate(), y: 30 }, + { x: moment(startOfToday).subtract(41, "day").toDate(), y: 35 }, + { x: moment(startOfToday).subtract(40, "day").toDate(), y: 34 }, + { x: moment(startOfToday).subtract(39, "day").toDate(), y: 35 }, + { x: moment(startOfToday).subtract(38, "day").toDate(), y: 33 }, + { x: moment(startOfToday).subtract(37, "day").toDate(), y: 40 }, + { x: moment(startOfToday).subtract(36, "day").toDate(), y: 35 }, + { x: moment(startOfToday).subtract(35, "day").toDate(), y: 30 }, + { x: moment(startOfToday).subtract(34, "day").toDate(), y: 35 }, + { x: moment(startOfToday).subtract(33, "day").toDate(), y: 15 }, + { x: moment(startOfToday).subtract(32, "day").toDate(), y: 30 }, + { x: moment(startOfToday).subtract(31, "day").toDate(), y: 35 }, + { x: moment(startOfToday).subtract(30, "day").toDate(), y: 34 }, + { x: moment(startOfToday).subtract(29, "day").toDate(), y: 35 }, + { x: moment(startOfToday).subtract(28, "day").toDate(), y: 33 }, + { x: moment(startOfToday).subtract(27, "day").toDate(), y: 40 }, + { x: moment(startOfToday).subtract(26, "day").toDate(), y: 35 }, + { x: moment(startOfToday).subtract(25, "day").toDate(), y: 30 }, + { x: moment(startOfToday).subtract(24, "day").toDate(), y: 35 }, + { x: moment(startOfToday).subtract(23, "day").toDate(), y: 15 }, + { x: moment(startOfToday).subtract(22, "day").toDate(), y: 30 }, + { x: moment(startOfToday).subtract(21, "day").toDate(), y: 35 }, + { x: moment(startOfToday).subtract(20, "day").toDate(), y: 34 }, + { x: moment(startOfToday).subtract(19, "day").toDate(), y: 80 }, + { x: moment(startOfToday).subtract(18, "day").toDate(), y: 70 }, + { x: moment(startOfToday).subtract(17, "day").toDate(), y: 95 }, + { x: moment(startOfToday).subtract(16, "day").toDate(), y: 90 }, + { x: moment(startOfToday).subtract(15, "day").toDate(), y: 85 }, + { x: moment(startOfToday).subtract(14, "day").toDate(), y: 70 }, + { x: moment(startOfToday).subtract(13, "day").toDate(), y: 75 }, + { x: moment(startOfToday).subtract(12, "day").toDate(), y: 69 }, + { x: moment(startOfToday).subtract(11, "day").toDate(), y: 75 }, + { x: moment(startOfToday).subtract(10, "day").toDate(), y: 81 }, + { x: moment(startOfToday).subtract(9, "day").toDate(), y: 93 }, + { x: moment(startOfToday).subtract(8, "day").toDate(), y: 83 }, + { x: moment(startOfToday).subtract(7, "day").toDate(), y: 70 }, + { x: moment(startOfToday).subtract(6, "day").toDate(), y: 74 }, + { x: moment(startOfToday).subtract(5, "day").toDate(), y: 73 }, + { x: moment(startOfToday).subtract(4, "day").toDate(), y: 68 }, + { x: moment(startOfToday).subtract(3, "day").toDate(), y: 72 }, + { x: moment(startOfToday).subtract(2, "day").toDate(), y: 61 }, + { x: moment(startOfToday).subtract(1, "day").toDate(), y: 69 }, + { x: moment(startOfToday).toDate(), y: 60 }, + ], + }, + { + id: "series-4", + name: "Austin", + description: "No links", + data: [ + { x: moment(startOfToday).subtract(59, "day").toDate(), y: 25 }, + { x: moment(startOfToday).subtract(58, "day").toDate(), y: 43 }, + { x: moment(startOfToday).subtract(57, "day").toDate(), y: 40 }, + { x: moment(startOfToday).subtract(56, "day").toDate(), y: 65 }, + { x: moment(startOfToday).subtract(55, "day").toDate(), y: 30 }, + { x: moment(startOfToday).subtract(54, "day").toDate(), y: 25 }, + { x: moment(startOfToday).subtract(53, "day").toDate(), y: 45 }, + { x: moment(startOfToday).subtract(52, "day").toDate(), y: 30 }, + { x: moment(startOfToday).subtract(51, "day").toDate(), y: 85 }, + { x: moment(startOfToday).subtract(50, "day").toDate(), y: 74 }, + { x: moment(startOfToday).subtract(49, "day").toDate(), y: 55 }, + { x: moment(startOfToday).subtract(48, "day").toDate(), y: 23 }, + { x: moment(startOfToday).subtract(47, "day").toDate(), y: 40 }, + { x: moment(startOfToday).subtract(46, "day").toDate(), y: 15 }, + { x: moment(startOfToday).subtract(45, "day").toDate(), y: 20 }, + { x: moment(startOfToday).subtract(44, "day").toDate(), y: 65 }, + { x: moment(startOfToday).subtract(43, "day").toDate(), y: 25 }, + { x: moment(startOfToday).subtract(42, "day").toDate(), y: 40 }, + { x: moment(startOfToday).subtract(41, "day").toDate(), y: 25 }, + { x: moment(startOfToday).subtract(40, "day").toDate(), y: 54 }, + { x: moment(startOfToday).subtract(39, "day").toDate(), y: 65 }, + { x: moment(startOfToday).subtract(38, "day").toDate(), y: 33 }, + { x: moment(startOfToday).subtract(37, "day").toDate(), y: 50 }, + { x: moment(startOfToday).subtract(36, "day").toDate(), y: 45 }, + { x: moment(startOfToday).subtract(35, "day").toDate(), y: 20 }, + { x: moment(startOfToday).subtract(34, "day").toDate(), y: 25 }, + { x: moment(startOfToday).subtract(33, "day").toDate(), y: 35 }, + { x: moment(startOfToday).subtract(32, "day").toDate(), y: 20 }, + { x: moment(startOfToday).subtract(31, "day").toDate(), y: 35 }, + { x: moment(startOfToday).subtract(30, "day").toDate(), y: 14 }, + { x: moment(startOfToday).subtract(29, "day").toDate(), y: 55 }, + { x: moment(startOfToday).subtract(28, "day").toDate(), y: 23 }, + { x: moment(startOfToday).subtract(27, "day").toDate(), y: 10 }, + { x: moment(startOfToday).subtract(26, "day").toDate(), y: 5 }, + { x: moment(startOfToday).subtract(25, "day").toDate(), y: 20 }, + { x: moment(startOfToday).subtract(24, "day").toDate(), y: 35 }, + { x: moment(startOfToday).subtract(23, "day").toDate(), y: 15 }, + { x: moment(startOfToday).subtract(22, "day").toDate(), y: 30 }, + { x: moment(startOfToday).subtract(21, "day").toDate(), y: 35 }, + { x: moment(startOfToday).subtract(20, "day").toDate(), y: 34 }, + { x: moment(startOfToday).subtract(19, "day").toDate(), y: 50 }, + { x: moment(startOfToday).subtract(18, "day").toDate(), y: 60 }, + { x: moment(startOfToday).subtract(17, "day").toDate(), y: 95 }, + { x: moment(startOfToday).subtract(16, "day").toDate(), y: 80 }, + { x: moment(startOfToday).subtract(15, "day").toDate(), y: 65 }, + { x: moment(startOfToday).subtract(14, "day").toDate(), y: 80 }, + { x: moment(startOfToday).subtract(13, "day").toDate(), y: 85 }, + { x: moment(startOfToday).subtract(12, "day").toDate(), y: 69 }, + { x: moment(startOfToday).subtract(11, "day").toDate(), y: 65 }, + { x: moment(startOfToday).subtract(10, "day").toDate(), y: 71 }, + { x: moment(startOfToday).subtract(9, "day").toDate(), y: 73 }, + { x: moment(startOfToday).subtract(8, "day").toDate(), y: 43 }, + { x: moment(startOfToday).subtract(7, "day").toDate(), y: 70 }, + { x: moment(startOfToday).subtract(6, "day").toDate(), y: 84 }, + { x: moment(startOfToday).subtract(5, "day").toDate(), y: 73 }, + { x: moment(startOfToday).subtract(4, "day").toDate(), y: 38 }, + { x: moment(startOfToday).subtract(3, "day").toDate(), y: 72 }, + { x: moment(startOfToday).subtract(2, "day").toDate(), y: 81 }, + { x: moment(startOfToday).subtract(1, "day").toDate(), y: 59 }, + { x: moment(startOfToday).toDate(), y: 60 }, + ], + }, +]; +// Setting the widget dimensions and position (this is for gridster) +const positions: Record = { + [widgetConfigs[0].id]: { + cols: 6, + rows: 6, + y: 0, + x: 0, + }, + [widgetConfigs[1].id]: { + cols: 6, + rows: 6, + y: 0, + x: 6, + }, +}; +\\\\\\\`, + "widget-types/timeseries/timeseries-widget-status-bar-example/timeseries-widget-status-bar-example.component.ts": \\\\\\\`// © 2022 SolarWinds Worldwide, LLC. All rights reserved. +// +// Permission is hereby granted, free of charge, to any person obtaining a copy +// of this software and associated documentation files (the "Software"), to +// deal in the Software without restriction, including without limitation the +// rights to use, copy, modify, merge, publish, distribute, sublicense, and/or +// sell copies of the Software, and to permit persons to whom the Software is +// furnished to do so, subject to the following conditions: +// +// The above copyright notice and this permission notice shall be included in +// all copies or substantial portions of the Software. +// +// THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR +// IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, +// FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE +// AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER +// LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, +// OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN +// THE SOFTWARE. + +import { + ChangeDetectorRef, + Component, + Injectable, + OnInit, +} from "@angular/core"; +import { GridsterConfig, GridsterItem } from "angular-gridster2"; +import keyBy from "lodash/keyBy"; +import moment, { Moment } from "moment/moment"; +import { BehaviorSubject } from "rxjs"; + +import { + DataSourceService, + IDataSource, + IDataSourceOutput, + INovaFilters, + ITimeframe, +} from "@nova-ui/bits"; +import { CHART_PALETTE_CS_S_EXTENDED } from "@nova-ui/charts"; +import { + applyStatusEndpoints, + DATA_SOURCE, + DEFAULT_PIZZAGNA_ROOT, + IDashboard, + IProviderConfiguration, + ISerializableTimeframe, + ITimeseriesItemConfiguration, + ITimeseriesOutput, + ITimeseriesScaleConfig, + ITimeseriesWidgetConfig, + ITimeseriesWidgetData, + ITimeseriesWidgetSeriesData, + ITimeseriesWidgetStatusData, + IWidget, + LegendPlacement, + PizzagnaLayer, + ProviderRegistryService, + TimeseriesChartPreset, + TimeseriesScaleType, + WellKnownPathKey, + WellKnownProviders, + WidgetTypesService, +} from "@nova-ui/dashboards"; + +/** + * A simple Timeseries data source implementation with continuous (non-interval-based) output + */ +@Injectable() +export class TimeseriesStatusContinuousDataSource + extends DataSourceService + implements IDataSource> +{ + public static providerId = "TimeseriesStatusContinuousDataSource"; + + public busy = new BehaviorSubject(false); + + public async getFilteredData( + filters: INovaFilters + ): Promise< + IDataSourceOutput> + > { + // In this example we're using some static mock data located at the bottom of this file. In a real-world + // scenario, the data for the chart would likely be retrieved via an asynchronous backend call. + const data = getContinuousData(); + let filteredData = data; + + this.busy.next(true); + + // Filtering using the filter registered by the TimeFrameBar + const timeframeFilter = filters.timeframe?.value as ITimeframe; + if (timeframeFilter) { + filteredData = filteredData.map((item: ITimeseriesWidgetData) => ({ + id: item.id, + name: item.name, + description: item.description, + data: item.data.filter( + (seriesData: ITimeseriesWidgetSeriesData) => + filterDates( + seriesData.x, + timeframeFilter.startDatetime, + timeframeFilter.endDatetime + ) + ), + })); + + // apply endpoints on the filtered status data so that when the status chart is zoomed (filtered), + // each status visualizations is ensured to have valid start and end values + filteredData = applyStatusEndpoints( + timeframeFilter, + filteredData, + data + ); + } + + this.busy.next(false); + return { result: { series: filteredData } }; + } +} + +/** + * A simple Timeseries data source implementation with interval-based output + */ +@Injectable() +export class TimeseriesStatusIntervalDataSource + extends DataSourceService + implements IDataSource> +{ + public static providerId = "TimeseriesStatusIntervalDataSource"; + + public busy = new BehaviorSubject(false); + + public async getFilteredData( + filters: INovaFilters + ): Promise< + IDataSourceOutput> + > { + // In this example we're using some static mock data located at the bottom of this file. In a real-world + // scenario, the data for the chart would likely be retrieved via an asynchronous backend call. + const data = getIntervalData(); + let filteredData = data; + + this.busy.next(true); + + // Filtering using the filter registered by the TimeFrameBar + const timeframeFilter = filters.timeframe?.value as ITimeframe; + if (timeframeFilter) { + filteredData = filteredData.map((item: ITimeseriesWidgetData) => ({ + id: item.id, + name: item.name, + description: item.description, + data: item.data.filter( + (seriesData: ITimeseriesWidgetSeriesData) => + filterDates( + seriesData.x, + timeframeFilter.startDatetime, + timeframeFilter.endDatetime + ) + ), + })); + + // Note: There's no need to apply filter endpoints to the status data in this case since we know it's visualized in regular intervals + } + + this.busy.next(false); + return { result: { series: filteredData } }; + } +} + +function filterDates(dateToCheck: Date, startDate: Moment, endDate: Moment) { + const mom = moment(dateToCheck); + return ( + mom.isBetween(startDate, endDate) || + mom.isSame(startDate) || + mom.isSame(endDate) + ); +} + +/** + * A component that instantiates the dashboard + */ +@Component({ + selector: "timeseries-widget-status-bar-example", + templateUrl: "./timeseries-widget-status-bar-example.component.html", + styleUrls: ["./timeseries-widget-status-bar-example.component.less"], + standalone: false, +}) +export class TimeseriesWidgetStatusBarExampleComponent implements OnInit { + // This variable will hold all the data needed to define the layout and behavior of the widgets. + // Pass this to the dashboard component's dashboard input in the template. + public dashboard: IDashboard | undefined; + + // Angular gridster requires a configuration object even if it's empty. + // Pass this to the dashboard component's gridsterConfig input in the template. + public gridsterConfig: GridsterConfig = {}; + + // Boolean passed as an input to the dashboard. When true, widgets can be moved, resized, removed, or edited + public editMode: boolean = false; + + constructor( + // WidgetTypesService provides the widget's necessary structure information + private widgetTypesService: WidgetTypesService, + + // In general, the ProviderRegistryService is used for making entities available for injection into dynamically loaded components. + private providerRegistry: ProviderRegistryService, + private changeDetectorRef: ChangeDetectorRef + ) {} + + public ngOnInit(): void { + // Grabbing the widget's default template which will be needed as a parameter for setNode + const widgetTemplate = this.widgetTypesService.getWidgetType( + "timeseries", + 1 + ); + // Registering our data sources as dropdown options in the widget editor/configurator + // Note: This could also be done in the parent module's constructor so that + // multiple dashboards could have access to the same widget template modification. + this.widgetTypesService.setNode( + // This is the template we grabbed above with getWidgetType + widgetTemplate, + // We are setting the editor/configurator part of the widget template + "configurator", + // This indicates which node you are changing and we want to change + // the data source providers available for selection in the editor. + WellKnownPathKey.DataSourceProviders, + // We are setting the data sources available for selection in the editor + [ + TimeseriesStatusContinuousDataSource.providerId, + TimeseriesStatusIntervalDataSource.providerId, + ] + ); + + // Registering the data source for injection into the widget. + this.providerRegistry.setProviders({ + [TimeseriesStatusContinuousDataSource.providerId]: { + provide: DATA_SOURCE, + useClass: TimeseriesStatusContinuousDataSource, + deps: [], + }, + [TimeseriesStatusIntervalDataSource.providerId]: { + provide: DATA_SOURCE, + useClass: TimeseriesStatusIntervalDataSource, + deps: [], + }, + }); + + this.initializeDashboard(); + } + + /** Used for restoring widgets state */ + public reInitializeDashboard(): void { + // destroys the components and their providers so the dashboard can re init data + this.dashboard = undefined; + this.changeDetectorRef.detectChanges(); + + this.initializeDashboard(); + } + + public initializeDashboard(): void { + // We're using a static configuration object for this example, but this is where + // the widget's configuration could potentially be populated from a database + const widgetsWithStructure = widgetConfigs.map((w) => + this.widgetTypesService.mergeWithWidgetType(w) + ); + const widgetsIndex = keyBy(widgetsWithStructure, (w: IWidget) => w.id); + + // Finally, assigning the variables we created above to the dashboard + this.dashboard = { + positions, + widgets: widgetsIndex, + }; + } +} + +const widgetConfigs: IWidget[] = [ + { + id: "statusChartWidgetId", + type: "timeseries", + pizzagna: { + [PizzagnaLayer.Configuration]: { + [DEFAULT_PIZZAGNA_ROOT]: { + providers: { + [WellKnownProviders.DataSource]: { + // Setting the initially selected data source providerId + providerId: + TimeseriesStatusContinuousDataSource.providerId, + } as IProviderConfiguration, + }, + }, + header: { + properties: { + title: "Status Bar Chart with Continuous (Non-Interval) Scale", + subtitle: "Basic Timeseries Widget", + }, + }, + chart: { + providers: { + [WellKnownProviders.Adapter]: { + properties: { + // Setting the series and corresponding labels to initially display on the chart + series: [ + { + id: "series-1", + label: "Node Status", + selectedSeriesId: "series-1", + }, + { + id: "series-2", + label: "Node Status", + selectedSeriesId: "series-2", + }, + ] as ITimeseriesItemConfiguration[], + }, + } as Partial, + }, + properties: { + configuration: { + legendPlacement: LegendPlacement.Right, + enableZoom: true, + // Setting the preset to status bar + preset: TimeseriesChartPreset.StatusBar, + } as ITimeseriesWidgetConfig, + }, + }, + timeframeSelection: { + properties: { + // Setting the initial timeframe selected in the timeframe bar + timeframe: { + selectedPresetId: "last7Days", + } as ISerializableTimeframe, + maxDate: moment().format(), + }, + }, + }, + }, + }, + { + id: "statusIntervalChartWidgetId", + type: "timeseries", + pizzagna: { + [PizzagnaLayer.Configuration]: { + [DEFAULT_PIZZAGNA_ROOT]: { + providers: { + [WellKnownProviders.DataSource]: { + // Setting the initially selected data source providerId + providerId: + TimeseriesStatusIntervalDataSource.providerId, + } as IProviderConfiguration, + }, + }, + header: { + properties: { + title: "Status Bar Chart with Interval Scale", + subtitle: "Basic Timeseries Widget", + }, + }, + chart: { + providers: { + [WellKnownProviders.Adapter]: { + properties: { + // Setting the series and corresponding labels to initially display on the chart + series: [ + { + id: "series-1", + label: "Node Status", + selectedSeriesId: "series-1", + }, + { + id: "series-2", + label: "Node Status", + selectedSeriesId: "series-2", + }, + ] as ITimeseriesItemConfiguration[], + }, + } as Partial, + }, + properties: { + configuration: { + legendPlacement: LegendPlacement.Right, + enableZoom: true, + // Setting the preset to status bar + preset: TimeseriesChartPreset.StatusBar, + scales: { + x: { + type: TimeseriesScaleType.TimeInterval, + properties: { + // one-day interval in seconds + interval: 24 * 60 * 60, + }, + } as ITimeseriesScaleConfig, + }, + } as ITimeseriesWidgetConfig, + }, + }, + timeframeSelection: { + properties: { + // Setting the initial timeframe selected in the timeframe bar + timeframe: { + selectedPresetId: "last7Days", + } as ISerializableTimeframe, + maxDate: moment().format(), + }, + }, + }, + }, + }, +]; + +export const startOfToday = (): Moment => moment().startOf("day"); + +export const getContinuousData = + (): ITimeseriesWidgetData[] => { + const series: ITimeseriesWidgetData[] = [ + { + id: "series-1", + name: "Node Status", + description: "lastchance.demo.lab", + data: [ + // the 'x' value is set to the time and 'y' to the status at that given time + { + x: startOfToday().subtract(20, "day").toDate(), + y: Status.Warning, + }, + { + x: startOfToday().subtract(19, "day").toDate(), + y: Status.Down, + }, + { + x: startOfToday().subtract(17, "day").toDate(), + y: Status.Critical, + }, + { + x: startOfToday().subtract(16, "day").toDate(), + y: Status.Warning, + }, + { + x: startOfToday().subtract(15, "day").toDate(), + y: Status.Down, + }, + { + x: startOfToday().subtract(14, "day").toDate(), + y: Status.Critical, + }, + { + x: startOfToday().subtract(12, "day").toDate(), + y: Status.Unknown, + }, + { + x: startOfToday().subtract(10, "day").toDate(), + y: Status.Up, + }, + { + x: startOfToday().subtract(9, "day").toDate(), + y: Status.Critical, + }, + { + x: startOfToday().subtract(6, "day").toDate(), + y: Status.Up, + }, + { + x: startOfToday().subtract(3, "day").toDate(), + y: Status.Warning, + }, + { + x: startOfToday().subtract(2, "day").toDate(), + y: Status.Critical, + }, + { + x: startOfToday().subtract(1, "day").toDate(), + y: Status.Up, + }, + // This data point will be ignored and is only here to provide an endpoint for the previous status. + { x: moment().toDate(), y: Status.Up }, + ], + }, + { + id: "series-2", + name: "Node Status", + description: "newhope.demo.lab", + data: [ + { + x: startOfToday().subtract(19, "day").toDate(), + y: Status.Critical, + }, + { + x: startOfToday().subtract(18, "day").toDate(), + y: Status.Unknown, + }, + { + x: startOfToday().subtract(17, "day").toDate(), + y: Status.Warning, + }, + { + x: startOfToday().subtract(15, "day").toDate(), + y: Status.Down, + }, + { + x: startOfToday().subtract(8, "day").toDate(), + y: Status.Critical, + }, + { + x: startOfToday().subtract(7, "day").toDate(), + y: Status.Down, + }, + { + x: startOfToday().subtract(6, "day").toDate(), + y: Status.Up, + }, + { + x: startOfToday().subtract(5, "day").toDate(), + y: Status.Critical, + }, + { + x: startOfToday().subtract(4, "day").toDate(), + y: Status.Up, + }, + { + x: startOfToday().subtract(3, "day").toDate(), + y: Status.Warning, + }, + { + x: startOfToday().subtract(2, "day").toDate(), + y: Status.Up, + }, + { + x: startOfToday().subtract(1, "day").toDate(), + y: Status.Down, + }, + // This data point will be ignored and is only here to provide an endpoint for the previous status. + { x: moment().toDate(), y: Status.Down }, + ], + }, + ]; + + for (const s of series) { + // here are we setting the color and icon associated to the status for each data point + s.data = s.data.map((d: any, i: number) => ({ + ...d, + color: statusColors[d.y as Status], + // The thickness of the line is dependant on the status. If the status equals 'Up' then 'thick' is set to false. + thick: d.y !== Status.Up, + icon: "status_" + d.y, + })); + } + + return series; + }; + +// Note that the output of this function is spaced evenly at one-day intervals +export const getIntervalData = + (): ITimeseriesWidgetData[] => { + const series: ITimeseriesWidgetData[] = [ + { + id: "series-1", + name: "Node Status", + description: "lastchance.demo.lab", + data: [ + // the 'x' value is set to the time and 'y' to the status at that given time + { + x: startOfToday().subtract(20, "day").toDate(), + y: Status.Up, + }, + { + x: startOfToday().subtract(19, "day").toDate(), + y: Status.Critical, + }, + { + x: startOfToday().subtract(18, "day").toDate(), + y: Status.Warning, + }, + { + x: startOfToday().subtract(17, "day").toDate(), + y: Status.Down, + }, + { + x: startOfToday().subtract(16, "day").toDate(), + y: Status.Critical, + }, + { + x: startOfToday().subtract(15, "day").toDate(), + y: Status.Down, + }, + { + x: startOfToday().subtract(14, "day").toDate(), + y: Status.Up, + }, + { + x: startOfToday().subtract(13, "day").toDate(), + y: Status.Warning, + }, + { + x: startOfToday().subtract(12, "day").toDate(), + y: Status.Up, + }, + { + x: startOfToday().subtract(11, "day").toDate(), + y: Status.Critical, + }, + { + x: startOfToday().subtract(10, "day").toDate(), + y: Status.Up, + }, + { + x: startOfToday().subtract(9, "day").toDate(), + y: Status.Down, + }, + { + x: startOfToday().subtract(8, "day").toDate(), + y: Status.Critical, + }, + { + x: startOfToday().subtract(7, "day").toDate(), + y: Status.Warning, + }, + { + x: startOfToday().subtract(6, "day").toDate(), + y: Status.Down, + }, + { + x: startOfToday().subtract(5, "day").toDate(), + y: Status.Critical, + }, + { + x: startOfToday().subtract(4, "day").toDate(), + y: Status.Down, + }, + { + x: startOfToday().subtract(3, "day").toDate(), + y: Status.Up, + }, + { + x: startOfToday().subtract(2, "day").toDate(), + y: Status.Warning, + }, + { + x: startOfToday().subtract(1, "day").toDate(), + y: Status.Critical, + }, + { x: startOfToday().toDate(), y: Status.Up }, + ], + }, + { + id: "series-2", + name: "Node Status", + description: "newhope.demo.lab", + data: [ + { + x: startOfToday().subtract(20, "day").toDate(), + y: Status.Up, + }, + { + x: startOfToday().subtract(19, "day").toDate(), + y: Status.Up, + }, + { + x: startOfToday().subtract(18, "day").toDate(), + y: Status.Down, + }, + { + x: startOfToday().subtract(17, "day").toDate(), + y: Status.Critical, + }, + { + x: startOfToday().subtract(16, "day").toDate(), + y: Status.Down, + }, + { + x: startOfToday().subtract(15, "day").toDate(), + y: Status.Up, + }, + { + x: startOfToday().subtract(14, "day").toDate(), + y: Status.Critical, + }, + { + x: startOfToday().subtract(13, "day").toDate(), + y: Status.Up, + }, + { + x: startOfToday().subtract(12, "day").toDate(), + y: Status.Critical, + }, + { + x: startOfToday().subtract(11, "day").toDate(), + y: Status.Warning, + }, + { + x: startOfToday().subtract(10, "day").toDate(), + y: Status.Up, + }, + { + x: startOfToday().subtract(9, "day").toDate(), + y: Status.Down, + }, + { + x: startOfToday().subtract(8, "day").toDate(), + y: Status.Up, + }, + { + x: startOfToday().subtract(7, "day").toDate(), + y: Status.Down, + }, + { + x: startOfToday().subtract(6, "day").toDate(), + y: Status.Critical, + }, + { + x: startOfToday().subtract(5, "day").toDate(), + y: Status.Down, + }, + { + x: startOfToday().subtract(4, "day").toDate(), + y: Status.Up, + }, + { + x: startOfToday().subtract(3, "day").toDate(), + y: Status.Critical, + }, + { + x: startOfToday().subtract(2, "day").toDate(), + y: Status.Up, + }, + { + x: startOfToday().subtract(1, "day").toDate(), + y: Status.Warning, + }, + { x: startOfToday().toDate(), y: Status.Critical }, + ], + }, + ]; + + for (const s of series) { + // here are we setting the color and icon associated to the status for each data point + s.data = s.data.map((d: any, i: number) => ({ + ...d, + color: statusColors[d.y as Status], + // The thickness of the line is dependant on the status. If the status equals 'Up' then 'thick' is set to false. + thick: d.y !== Status.Up, + icon: "status_" + d.y, + })); + } + + return series; + }; + +// An enumeration of statuses +enum Status { + Unknown = "unknown", + Up = "up", + Warning = "warning", + Down = "down", + Critical = "critical", +} + +// This is the map used for setting the color of each status bar +const statusColors: Record = { + [Status.Unknown]: CHART_PALETTE_CS_S_EXTENDED[6], + [Status.Up]: CHART_PALETTE_CS_S_EXTENDED[8], + [Status.Warning]: CHART_PALETTE_CS_S_EXTENDED[4], + [Status.Down]: CHART_PALETTE_CS_S_EXTENDED[0], + [Status.Critical]: CHART_PALETTE_CS_S_EXTENDED[2], +}; + +// Setting the widget dimensions and position (this is for gridster) +const positions: Record = { + [widgetConfigs[0].id]: { + cols: 12, + rows: 4, + y: 0, + x: 0, + }, + [widgetConfigs[1].id]: { + cols: 12, + rows: 4, + y: 4, + x: 0, + }, +}; +\\\\\\\`, + "widget-types/view-components/kpi-tile-view-basic/kpi-tile-view-basic-example.component.ts": \\\\\\\`// © 2022 SolarWinds Worldwide, LLC. All rights reserved. +// +// Permission is hereby granted, free of charge, to any person obtaining a copy +// of this software and associated documentation files (the "Software"), to +// deal in the Software without restriction, including without limitation the +// rights to use, copy, modify, merge, publish, distribute, sublicense, and/or +// sell copies of the Software, and to permit persons to whom the Software is +// furnished to do so, subject to the following conditions: +// +// The above copyright notice and this permission notice shall be included in +// all copies or substantial portions of the Software. +// +// THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR +// IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, +// FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE +// AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER +// LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, +// OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN +// THE SOFTWARE. + +import { Component } from "@angular/core"; +import { FormControl } from "@angular/forms"; + +type KpiTileState = "normal" | "loading" | "empty"; + +/** + * KPI Tile View - Playground example. + * Switch between all visual states (normal / loading / empty) and toggle + * interactivity to explore every variant the standalone tile supports. + */ +@Component({ + selector: "kpi-tile-view-basic-example", + templateUrl: "./kpi-tile-view-basic-example.component.html", + standalone: false, +}) +export class KpiTileViewBasicExampleComponent { + public readonly stateControl = new FormControl("normal", { + nonNullable: true, + }); + public interactive = false; + public lastClicked = ""; + + public readonly stateOptions: KpiTileState[] = ["normal", "loading", "empty"]; + + public onTileClick(label: string): void { + this.lastClicked = label; + } +} +\\\\\\\`, + "widget-types/view-components/kpi-tile-view-interactive/kpi-tile-view-interactive-example.component.ts": \\\\\\\`// © 2022 SolarWinds Worldwide, LLC. All rights reserved. +// +// Permission is hereby granted, free of charge, to any person obtaining a copy +// of this software and associated documentation files (the "Software"), to +// deal in the Software without restriction, including without limitation the +// rights to use, copy, modify, merge, publish, distribute, sublicense, and/or +// sell copies of the Software, and to permit persons to whom the Software is +// furnished to do so, subject to the following conditions: +// +// The above copyright notice and this permission notice shall be included in +// all copies or substantial portions of the Software. +// +// THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR +// IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, +// FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE +// AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER +// LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, +// OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN +// THE SOFTWARE. + +import { Component, TemplateRef, ViewChild } from "@angular/core"; + +/** + * Interactive KPI Tile View example with custom value formatting + * and click event handling. + */ +@Component({ + selector: "kpi-tile-view-interactive-example", + templateUrl: "./kpi-tile-view-interactive-example.component.html", + standalone: false, +}) +export class KpiTileViewInteractiveExampleComponent { + public currentValue = 1_247; + public lastClickedTile = ""; + + @ViewChild("customValueTpl", { static: true }) + public customValueTpl: TemplateRef; + + public onTileClick(): void { + this.lastClickedTile = "Active Sessions"; + } + + public onUptimeClick(): void { + this.lastClickedTile = "Uptime"; + } +} +\\\\\\\`, + "widget-types/view-components/proportional-chart-view-playground/proportional-chart-view-playground-example.component.ts": \\\\\\\`// © 2022 SolarWinds Worldwide, LLC. All rights reserved. +// +// Permission is hereby granted, free of charge, to any person obtaining a copy +// of this software and associated documentation files (the "Software"), to +// deal in the Software without restriction, including without limitation the +// rights to use, copy, modify, merge, publish, distribute, sublicense, and/or +// sell copies of the Software, and to permit persons to whom the Software is +// furnished to do so, subject to the following conditions: +// +// The above copyright notice and this permission notice shall be included in +// all copies or substantial portions of the Software. +// +// THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR +// IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, +// FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE +// AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER +// LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, +// OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN +// THE SOFTWARE. + +import { Component } from "@angular/core"; +import { FormControl } from "@angular/forms"; + +import { IProportionalDataItem } from "@nova-ui/dashboards"; + +type ProportionalChartType = "donut" | "pie" | "verticalBar" | "horizontalBar"; +type LegendPlacement = "right" | "bottom" | "none"; + +/** + * Proportional Chart View - Playground example. + * Lets you switch between all supported chart types and legend placements + * to see every visual variant the standalone view component provides. + */ +@Component({ + selector: "proportional-chart-view-playground-example", + templateUrl: "./proportional-chart-view-playground-example.component.html", + standalone: false, +}) +export class ProportionalChartViewPlaygroundExampleComponent { + public readonly chartTypeControl = new FormControl( + "donut", + { nonNullable: true } + ); + public readonly legendPlacementControl = new FormControl( + "right", + { nonNullable: true } + ); + + public readonly chartTypeOptions: ProportionalChartType[] = [ + "donut", + "pie", + "verticalBar", + "horizontalBar", + ]; + public readonly legendPlacementOptions: LegendPlacement[] = [ + "right", + "bottom", + "none", + ]; + + public colors: Record = { + down: "#dc3545", + up: "#2cc079", + warning: "#f3a002", + unknown: "#707070", + }; + + public chartData: Array = [ + { id: "up", name: "Up", value: 78 }, + { id: "down", name: "Down", value: 8 }, + { id: "warning", name: "Warning", value: 12 }, + { id: "unknown", name: "Unknown", value: 2 }, + ]; + + public totalOf(data: IProportionalDataItem[] | undefined): number { + return (data ?? []).reduce((sum, d) => sum + (d?.value ?? 0), 0); + } +} +\\\\\\\`, + "widget-types/view-components/view-components-docs.component.ts": \\\\\\\`// © 2022 SolarWinds Worldwide, LLC. All rights reserved. +// +// Permission is hereby granted, free of charge, to any person obtaining a copy +// of this software and associated documentation files (the "Software"), to +// deal in the Software without restriction, including without limitation the +// rights to use, copy, modify, merge, publish, distribute, sublicense, and/or +// sell copies of the Software, and to permit persons to whom the Software is +// furnished to do so, subject to the following conditions: +// +// The above copyright notice and this permission notice shall be included in +// all copies or substantial portions of the Software. +// +// THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR +// IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, +// FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE +// AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER +// LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, +// OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN +// THE SOFTWARE. + +import { Component } from "@angular/core"; + +@Component({ + selector: "nui-view-components-docs", + templateUrl: "./view-components-docs.component.html", + standalone: false, +}) +export class ViewComponentsDocsComponent { + public readonly installationSnippet = \\\\\\\\\\\\\\\`import { NuiDashboardViewsModule } from "@nova-ui/dashboards"; + +@NgModule({ + imports: [NuiDashboardViewsModule], +}) +export class MyFeatureModule {}\\\\\\\\\\\\\\\`; + + public readonly proportionalDataItemSnippet = \\\\\\\\\\\\\\\`interface IProportionalDataItem { + id: string; // Unique segment identifier + name: string; // Display name in legend + value: number; // Numeric value determining segment size + color?: string; // Optional CSS color (hex or token) + icon?: string; // Optional icon name for legend + link?: string; // Optional drill-down URL +}\\\\\\\\\\\\\\\`; +} +\\\\\\\`, + "widget-types/view-components/view-components-docs.module.ts": \\\\\\\`// © 2022 SolarWinds Worldwide, LLC. All rights reserved. +// +// Permission is hereby granted, free of charge, to any person obtaining a copy +// of this software and associated documentation files (the "Software"), to +// deal in the Software without restriction, including without limitation the +// rights to use, copy, modify, merge, publish, distribute, sublicense, and/or +// sell copies of the Software, and to permit persons to whom the Software is +// furnished to do so, subject to the following conditions: +// +// The above copyright notice and this permission notice shall be included in +// all copies or substantial portions of the Software. +// +// THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR +// IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, +// FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE +// AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER +// LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, +// OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN +// THE SOFTWARE. + +import { CommonModule } from "@angular/common"; +import { NgModule } from "@angular/core"; +import { ReactiveFormsModule } from "@angular/forms"; +import { RouterModule, Routes } from "@angular/router"; + +import { + NuiDocsModule, + NuiIconModule, + NuiMessageModule, + NuiFormFieldModule, + NuiSelectV2Module, + NuiSwitchModule, + DEMO_PATH_TOKEN, +} from "@nova-ui/bits"; +import { NuiDashboardViewsModule } from "@nova-ui/dashboards"; + +import { getDemoFiles } from "../../../../demo-files-factory"; +import { KpiTileViewBasicExampleComponent } from "./kpi-tile-view-basic/kpi-tile-view-basic-example.component"; +import { KpiTileViewInteractiveExampleComponent } from "./kpi-tile-view-interactive/kpi-tile-view-interactive-example.component"; +import { ProportionalChartViewPlaygroundExampleComponent } from "./proportional-chart-view-playground/proportional-chart-view-playground-example.component"; +import { ViewComponentsDocsComponent } from "./view-components-docs.component"; + +const routes: Routes = [ + { + path: "", + component: ViewComponentsDocsComponent, + data: { + srlc: { + hideIndicator: true, + }, + showThemeSwitcher: true, + }, + }, + { + path: "kpi-tile-view-basic", + component: KpiTileViewBasicExampleComponent, + data: { + srlc: { + hideIndicator: true, + }, + }, + }, + { + path: "proportional-chart-view-playground", + component: ProportionalChartViewPlaygroundExampleComponent, + data: { + srlc: { + hideIndicator: true, + }, + }, + }, +]; + +@NgModule({ + imports: [ + CommonModule, + ReactiveFormsModule, + RouterModule.forChild(routes), + NuiDocsModule, + NuiMessageModule, + NuiIconModule, + NuiFormFieldModule, + NuiSelectV2Module, + NuiSwitchModule, + NuiDashboardViewsModule, + ], + declarations: [ + ViewComponentsDocsComponent, + KpiTileViewBasicExampleComponent, + KpiTileViewInteractiveExampleComponent, + ProportionalChartViewPlaygroundExampleComponent, + ], + providers: [ + { + provide: DEMO_PATH_TOKEN, + useValue: getDemoFiles("view-components"), + }, + ], +}) +export default class ViewComponentsDocsModule {} +\\\\\\\`, + "widget-types/widget-types.module.ts": \\\\\\\`// © 2022 SolarWinds Worldwide, LLC. All rights reserved. +// +// Permission is hereby granted, free of charge, to any person obtaining a copy +// of this software and associated documentation files (the "Software"), to +// deal in the Software without restriction, including without limitation the +// rights to use, copy, modify, merge, publish, distribute, sublicense, and/or +// sell copies of the Software, and to permit persons to whom the Software is +// furnished to do so, subject to the following conditions: +// +// The above copyright notice and this permission notice shall be included in +// all copies or substantial portions of the Software. +// +// THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR +// IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, +// FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE +// AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER +// LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, +// OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN +// THE SOFTWARE. + +import { NgModule, Type } from "@angular/core"; +import { RouterModule, Routes } from "@angular/router"; + +import { NuiDocsModule } from "@nova-ui/bits"; +import { + ConfiguratorHeadingService, + NuiDashboardsModule, +} from "@nova-ui/dashboards"; + +export enum WidgetTypesRoute { + kpi = "kpi", + riskScore = "risk-score", + timeseries = "timeseries", + table = "table", + proportional = "proportional", + embedded = "embedded", + drilldown = "drilldown", + viewComponents = "view-components", +} + +const routes: Routes = [ + { + path: WidgetTypesRoute.kpi, + loadChildren: async () => + import("./kpi/kpi-docs.module") as object as Promise>, + data: { + srlc: { + hideIndicator: true, + }, + }, + }, + { + path: WidgetTypesRoute.riskScore, + loadChildren: async () => + import("./risk-score/risk-score-docs.module") as object as Promise< + Type + >, + data: { + srlc: { + hideIndicator: true, + }, + }, + }, + { + path: WidgetTypesRoute.timeseries, + loadChildren: async () => + import("./timeseries/timeseries-docs.module") as object as Promise< + Type + >, + data: { + srlc: { + hideIndicator: true, + }, + }, + }, + { + path: WidgetTypesRoute.table, + loadChildren: async () => + import("./table/table-docs.module") as object as Promise>, + data: { + srlc: { + hideIndicator: true, + }, + }, + }, + { + path: WidgetTypesRoute.proportional, + loadChildren: async () => + import( + "./proportional/proportional-docs.module" + ) as object as Promise>, + data: { + srlc: { + hideIndicator: true, + }, + }, + }, + { + path: WidgetTypesRoute.embedded, + loadChildren: async () => + import( + "./embedded-content/embedded-content-docs.module" + ) as object as Promise>, + data: { + srlc: { + hideIndicator: true, + }, + }, + }, + { + path: WidgetTypesRoute.drilldown, + loadChildren: async () => + import( + "./drilldown/drilldown-widget-docs.module" + ) as object as Promise>, + data: { + srlc: { + hideIndicator: true, + }, + }, + }, + { + path: WidgetTypesRoute.viewComponents, + loadChildren: async () => + import( + "./view-components/view-components-docs.module" + ) as object as Promise>, + data: { + srlc: { + hideIndicator: true, + }, + }, + }, +]; + +@NgModule({ + imports: [ + RouterModule.forChild(routes), + NuiDocsModule, + NuiDashboardsModule, + ], + providers: [ConfiguratorHeadingService], +}) +export default class WidgetTypesModule {} +\\\\\\\`, +}; +\\\`, + "overview/hero/dashboard/hero-dashboard.component.ts": \\\`// © 2022 SolarWinds Worldwide, LLC. All rights reserved. +// +// Permission is hereby granted, free of charge, to any person obtaining a copy +// of this software and associated documentation files (the "Software"), to +// deal in the Software without restriction, including without limitation the +// rights to use, copy, modify, merge, publish, distribute, sublicense, and/or +// sell copies of the Software, and to permit persons to whom the Software is +// furnished to do so, subject to the following conditions: +// +// The above copyright notice and this permission notice shall be included in +// all copies or substantial portions of the Software. +// +// THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR +// IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, +// FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE +// AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER +// LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, +// OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN +// THE SOFTWARE. + +import { HttpClient } from "@angular/common/http"; +import { + ChangeDetectionStrategy, + Component, + OnInit, + ViewEncapsulation, +} from "@angular/core"; +import keyBy from "lodash/keyBy"; + +import { LoggerService, ThemeSwitchService } from "@nova-ui/bits"; +import { + DATA_SOURCE, + IDashboard, + IWidget, + ProviderRegistryService, + WidgetTypesService, +} from "@nova-ui/dashboards"; + +import { positions, widgets } from "./widget-configs"; +import { + HarryPotterAverageRatingDataSource, + HarryPotterRatingsCountDataSource, +} from "../data/kpi-datasources"; +import { + BeerReviewCountsByCityMockDataSource, + BeerReviewCountsByCityMockDataSource2, +} from "../data/proportional-datasources"; +import { BeerDataSource } from "../data/table/beer-data-source"; +import { RandomUserDataSource } from "../data/table/random-user-data-source"; +import { + BeerVsReadingMockDataSource, + LoungingVsFrisbeeGolfMockDataSource, +} from "../data/timeseries-data-sources"; + +/** + * A component that instantiates the dashboard + */ +@Component({ + selector: "hero-dashboard", + templateUrl: "./hero-dashboard.component.html", + styleUrls: ["./hero-dashboard.component.less"], + encapsulation: ViewEncapsulation.Emulated, + changeDetection: ChangeDetectionStrategy.Default, + standalone: false, +}) +export class HeroDashboardComponent implements OnInit { + public dashboard: IDashboard = { + positions: {}, + widgets: {}, + }; + + public gridsterConfig = {}; + public editMode = false; + + constructor( + private providerRegistry: ProviderRegistryService, + public themeSwitcherService: ThemeSwitchService, + private widgetTypesService: WidgetTypesService + ) { + this.providerRegistry.setProviders({ + [HarryPotterAverageRatingDataSource.providerId]: { + provide: DATA_SOURCE, + useClass: HarryPotterAverageRatingDataSource, + deps: [HttpClient], + }, + [HarryPotterRatingsCountDataSource.providerId]: { + provide: DATA_SOURCE, + useClass: HarryPotterRatingsCountDataSource, + deps: [HttpClient], + }, + [BeerReviewCountsByCityMockDataSource.providerId]: { + provide: DATA_SOURCE, + useClass: BeerReviewCountsByCityMockDataSource, + deps: [], + }, + [BeerReviewCountsByCityMockDataSource2.providerId]: { + provide: DATA_SOURCE, + useClass: BeerReviewCountsByCityMockDataSource2, + deps: [], + }, + [RandomUserDataSource.providerId]: { + provide: DATA_SOURCE, + useClass: RandomUserDataSource, + deps: [LoggerService], + }, + [BeerDataSource.providerId]: { + provide: DATA_SOURCE, + useClass: BeerDataSource, + deps: [LoggerService], + }, + [BeerVsReadingMockDataSource.providerId]: { + provide: DATA_SOURCE, + useClass: BeerVsReadingMockDataSource, + deps: [], + }, + [LoungingVsFrisbeeGolfMockDataSource.providerId]: { + provide: DATA_SOURCE, + useClass: LoungingVsFrisbeeGolfMockDataSource, + deps: [], + }, + }); + } + + public ngOnInit(): void { + const widgetsWithStructure = widgets.map((w) => ({ + ...w, + pizzagna: { + ...this.widgetTypesService.getWidgetType(w.type, w.version) + .widget, + ...w.pizzagna, + }, + })); + const widgetsIndex = keyBy(widgetsWithStructure, (w: IWidget) => w.id); + + this.dashboard = { + positions: positions, + widgets: widgetsIndex, + }; + } +} +\\\`, + "overview/hero/dashboard/widget-configs.ts": \\\`// © 2022 SolarWinds Worldwide, LLC. All rights reserved. +// +// Permission is hereby granted, free of charge, to any person obtaining a copy +// of this software and associated documentation files (the "Software"), to +// deal in the Software without restriction, including without limitation the +// rights to use, copy, modify, merge, publish, distribute, sublicense, and/or +// sell copies of the Software, and to permit persons to whom the Software is +// furnished to do so, subject to the following conditions: +// +// The above copyright notice and this permission notice shall be included in +// all copies or substantial portions of the Software. +// +// THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR +// IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, +// FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE +// AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER +// LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, +// OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN +// THE SOFTWARE. + +import { GridsterItem } from "angular-gridster2"; + +import { IWidget } from "@nova-ui/dashboards"; + +import { kpiConfig } from "../widget-configs/kpi"; +import { proportionalConfig } from "../widget-configs/proportional"; +import { tableConfig } from "../widget-configs/table"; +import { timeseriesConfig } from "../widget-configs/timeseries"; + +export const positions: Record = { + [tableConfig.id]: { + cols: 7, + rows: 7, + y: 0, + x: 0, + }, + [proportionalConfig.id]: { + cols: 5, + rows: 7, + y: 0, + x: 7, + }, + [kpiConfig.id]: { + cols: 6, + rows: 7, + y: 7, + x: 0, + }, + [timeseriesConfig.id]: { + cols: 6, + rows: 7, + y: 7, + x: 6, + }, +}; + +export const widgets: IWidget[] = [ + { + ...tableConfig, + }, + { + ...proportionalConfig, + }, + { + ...kpiConfig, + }, + { + ...timeseriesConfig, + }, +]; +\\\`, + "overview/hero/data/kpi-datasources.ts": \\\`// © 2022 SolarWinds Worldwide, LLC. All rights reserved. +// +// Permission is hereby granted, free of charge, to any person obtaining a copy +// of this software and associated documentation files (the "Software"), to +// deal in the Software without restriction, including without limitation the +// rights to use, copy, modify, merge, publish, distribute, sublicense, and/or +// sell copies of the Software, and to permit persons to whom the Software is +// furnished to do so, subject to the following conditions: +// +// The above copyright notice and this permission notice shall be included in +// all copies or substantial portions of the Software. +// +// THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR +// IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, +// FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE +// AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER +// LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, +// OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN +// THE SOFTWARE. + +import { HttpClient, HttpErrorResponse } from "@angular/common/http"; +import { Injectable, OnDestroy } from "@angular/core"; +import { BehaviorSubject } from "rxjs"; +import { finalize } from "rxjs/operators"; + +import { DataSourceService, IFilteringOutputs } from "@nova-ui/bits"; +import { IKpiData } from "@nova-ui/dashboards"; + +import { GOOGLE_BOOKS_URL } from "./table/constants"; + +@Injectable() +export class HarryPotterAverageRatingDataSource + extends DataSourceService + implements OnDestroy +{ + public static providerId = "HarryPotterAverageRatingDataSource"; + + public busy = new BehaviorSubject(false); + + constructor(private http: HttpClient) { + super(); + } + + public async getFilteredData(): Promise { + this.busy.next(true); + return new Promise((resolve) => { + // *** Make a resource request to an external API (if needed) + this.http + .get(\\\\\\\`\\\\\\\${GOOGLE_BOOKS_URL}/5MQFrgEACAAJ\\\\\\\`) + .pipe(finalize(() => this.busy.next(false))) + .subscribe({ + next: (data: any) => { + resolve({ + result: { + value: data.volumeInfo.averageRating, + }, + }); + }, + error: (error: HttpErrorResponse) => { + resolve({ + result: null, + error: { + type: error.status, + }, + }); + }, + }); + }); + } + + public ngOnDestroy(): void { + this.outputsSubject.complete(); + } +} + +@Injectable() +export class HarryPotterRatingsCountDataSource + extends DataSourceService + implements OnDestroy +{ + public static providerId = "HarryPotterRatingsCountDataSource"; + + public busy = new BehaviorSubject(false); + + constructor(private http: HttpClient) { + super(); + } + + public async getFilteredData(): Promise { + this.busy.next(true); + return new Promise((resolve) => { + this.http + .get(\\\\\\\`\\\\\\\${GOOGLE_BOOKS_URL}/5MQFrgEACAAJ\\\\\\\`) + .pipe(finalize(() => this.busy.next(false))) + .subscribe({ + next: (data: any) => { + resolve({ + result: { + value: data.volumeInfo.ratingsCount, + }, + }); + }, + error: (error: HttpErrorResponse) => { + resolve({ + result: null, + error: { + type: error.status, + }, + }); + }, + }); + }); + } + + public ngOnDestroy(): void { + this.outputsSubject.complete(); + } +} +\\\`, + "overview/hero/data/proportional-datasources.ts": \\\`// © 2022 SolarWinds Worldwide, LLC. All rights reserved. +// +// Permission is hereby granted, free of charge, to any person obtaining a copy +// of this software and associated documentation files (the "Software"), to +// deal in the Software without restriction, including without limitation the +// rights to use, copy, modify, merge, publish, distribute, sublicense, and/or +// sell copies of the Software, and to permit persons to whom the Software is +// furnished to do so, subject to the following conditions: +// +// The above copyright notice and this permission notice shall be included in +// all copies or substantial portions of the Software. +// +// THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR +// IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, +// FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE +// AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER +// LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, +// OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN +// THE SOFTWARE. + +import { Injectable, OnDestroy } from "@angular/core"; +import { BehaviorSubject } from "rxjs"; + +import { + DataSourceService, + IDataSource, + IFilteringOutputs, +} from "@nova-ui/bits"; + +import { + getMockBeerReviewCountsByCity, + getMockBeerReviewCountsByCity2, + IProportionalWidgetData, +} from "./widget-data"; + +@Injectable() +export class BeerReviewCountsByCityMockDataSource + extends DataSourceService + implements IDataSource, OnDestroy +{ + // This is the ID we'll use to identify the provider + public static providerId = "BeerReviewCountsByCityMockDataSource"; + public busy = new BehaviorSubject(false); + + public async getFilteredData(): Promise { + this.busy.next(true); + return new Promise((resolve) => { + setTimeout(() => { + this.outputsSubject.next({ + result: getMockBeerReviewCountsByCity(), + }); + this.busy.next(false); + }, 300); + }); + } + + public ngOnDestroy(): void { + this.outputsSubject.complete(); + } +} + +@Injectable() +export class BeerReviewCountsByCityMockDataSource2 + extends DataSourceService + implements IDataSource, OnDestroy +{ + // This is the ID we'll use to identify the provider + public static providerId = "BeerReviewCountsByCityMockDataSource2"; + public busy = new BehaviorSubject(false); + + public async getFilteredData(): Promise { + this.busy.next(true); + return new Promise((resolve) => { + setTimeout(() => { + this.outputsSubject.next({ + result: getMockBeerReviewCountsByCity2(), + }); + this.busy.next(false); + }, 1500); + }); + } + + public ngOnDestroy(): void { + this.outputsSubject.complete(); + } +} +\\\`, + "overview/hero/data/table/beer-data-source.ts": \\\`// © 2022 SolarWinds Worldwide, LLC. All rights reserved. +// +// Permission is hereby granted, free of charge, to any person obtaining a copy +// of this software and associated documentation files (the "Software"), to +// deal in the Software without restriction, including without limitation the +// rights to use, copy, modify, merge, publish, distribute, sublicense, and/or +// sell copies of the Software, and to permit persons to whom the Software is +// furnished to do so, subject to the following conditions: +// +// The above copyright notice and this permission notice shall be included in +// all copies or substantial portions of the Software. +// +// THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR +// IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, +// FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE +// AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER +// LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, +// OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN +// THE SOFTWARE. + +import { ListRange } from "@angular/cdk/collections"; +import { Injectable } from "@angular/core"; +import isEqual from "lodash/isEqual"; +import orderBy from "lodash/orderBy"; +import { BehaviorSubject } from "rxjs"; + +import { + DataSourceService, + IDataField, + INovaFilteringOutputs, + INovaFilters, + ISorterFilter, + LoggerService, +} from "@nova-ui/bits"; + +import { IBrewDatasourceResponse, IBrewInfo } from "../types"; +import { BREW_API_URL } from "./constants"; + +@Injectable() +export class BeerDataSource extends DataSourceService { + public static providerId = "BeerDataSource"; + + private cache = Array.from({ length: 0 }); + private lastSortValue?: ISorterFilter; + private lastVirtualScroll?: ListRange; + private totalItems: number = 325; + + public page: number = 1; + public busy = new BehaviorSubject(false); + + public dataFields: Array = [ + { id: "id", label: "No", dataType: "number" }, + { id: "name", label: "Name", dataType: "string" }, + { id: "tagline", label: "Tagline", dataType: "string" }, + { id: "first_brewed", label: "First Brewed", dataType: "string" }, + { id: "description", label: "Description", dataType: "string" }, + { id: "brewers_tips", label: "Brewer's Tips", dataType: "string" }, + ]; + + constructor(private logger: LoggerService) { + super(); + } + + public async getFilteredData( + filters: INovaFilters + ): Promise { + const start = filters.virtualScroll?.value?.start ?? 0; + const end = filters.virtualScroll?.value?.end ?? 0; + const delta = end - start; + + // This condition handles sorting. We want to sort columns without fetching another chunk of data. + // Since the data is being fetched when scrolled, we compare virtual scroll indexes here in the condition as well. + if (filters.sorter?.value) { + if ( + !isEqual(this.lastSortValue, filters.sorter.value) && + isEqual(this.lastVirtualScroll, filters.virtualScroll?.value) + ) { + const totalPages = Math.ceil( + delta ? this.totalItems / delta : 1 + ); + const itemsPerPage: number = Math.max( + delta < 80 ? delta : 80, + 1 + ); + let response: Array | null = null; + let map: IBrewDatasourceResponse; + + if (filters.sorter?.value?.direction === "desc") { + this.cache = []; + for (let i = 0; i < this.page; ++i) { + response = await ( + await fetch( + \\\\\\\`\\\\\\\${BREW_API_URL}/?page=\\\\\\\${ + totalPages - i || 1 + }&per_page=\\\\\\\${itemsPerPage}\\\\\\\` + ) + ).json(); + + // since the last page contains only 5 items we need to fetch another page to give virtual scroll enough space to work + if (response && response.length < itemsPerPage) { + this.page++; + } + map = { + brewInfo: response?.map((result: IBrewInfo) => ({ + id: result.id, + name: result.name, + tagline: result.tagline, + first_brewed: result.first_brewed, + description: result.description, + brewers_tips: result.brewers_tips, + })), + total: response?.length, + } as IBrewDatasourceResponse; + this.cache = + totalPages - i !== 0 + ? this.cache.concat(map.brewInfo) + : this.cache; + } + } + + if (filters.sorter?.value?.direction === "asc") { + this.cache = []; + for (let i = 0; i < this.page; i++) { + response = await ( + await fetch( + \\\\\\\`\\\\\\\${BREW_API_URL}/?page=\\\\\\\${ + i + 1 + }&per_page=\\\\\\\${itemsPerPage}\\\\\\\` + ) + ).json(); + map = { + brewInfo: response?.map((result: IBrewInfo) => ({ + id: result.id, + name: result.name, + tagline: result.tagline, + first_brewed: result.first_brewed, + description: result.description, + brewers_tips: result.brewers_tips, + })), + total: response?.length, + } as IBrewDatasourceResponse; + this.cache = this.cache.concat(map.brewInfo); + } + } + + this.lastSortValue = filters.sorter?.value; + this.lastVirtualScroll = filters.virtualScroll?.value; + + return { + repeat: { itemsSource: this.sortData(this.cache, filters) }, + paginator: { total: this.totalItems }, + dataFields: this.dataFields, + }; + } + } + + this.busy.next(true); + return new Promise((resolve) => { + setTimeout(() => { + this.getData(start, end, filters).then( + (response: INovaFilteringOutputs) => { + if (!response) { + return; + } + + this.cache = this.cache.concat(response.brewInfo); + + this.dataSubject.next(this.cache); + resolve({ + repeat: { + itemsSource: this.sortData(this.cache, filters), + }, + paginator: { total: this.totalItems }, + dataFields: this.dataFields, + }); + + this.lastSortValue = filters.sorter?.value; + this.lastVirtualScroll = filters.virtualScroll?.value; + this.busy.next(false); + } + ); + }, 500); + }); + } + + public async getData( + start: number = 0, + end: number = 20, + filters: INovaFilters + ): Promise { + const delta = end - start; + const totalPages = Math.ceil(delta ? this.totalItems / delta : 1); + let response: Array | null = null; + // The api.punk.com is able to return only 80 items per page + const itemsPerPage: number = Math.max(delta < 80 ? delta : 80, 1); + + if (filters.sorter?.value?.direction === "asc") { + response = await ( + await fetch( + \\\\\\\`\\\\\\\${BREW_API_URL}/?page=\\\\\\\${this.page}&per_page=\\\\\\\${itemsPerPage}\\\\\\\` + ) + ).json(); + } + + if (filters.sorter?.value?.direction === "desc") { + response = await ( + await fetch( + \\\\\\\`\\\\\\\${BREW_API_URL}/?page=\\\\\\\${ + totalPages - this.page + }&per_page=\\\\\\\${itemsPerPage}\\\\\\\` + ) + ).json(); + } + + if (!filters.sorter) { + response = await ( + await fetch( + \\\\\\\`\\\\\\\${BREW_API_URL}/?page=\\\\\\\${this.page}&per_page=\\\\\\\${itemsPerPage}\\\\\\\` + ) + ).json(); + } + return { + brewInfo: response?.map((result: IBrewInfo, i: number) => ({ + id: result.id, + name: result.name, + tagline: result.tagline, + first_brewed: result.first_brewed, + description: result.description, + brewers_tips: result.brewers_tips, + })), + total: response?.length, + } as IBrewDatasourceResponse; + } + + private sortData(data: IBrewInfo[], filters: INovaFilters) { + return orderBy( + data, + filters.sorter?.value?.sortBy, + filters.sorter?.value?.direction as "desc" | "asc" + ); + } +} +\\\`, + "overview/hero/data/table/constants.ts": \\\`// © 2022 SolarWinds Worldwide, LLC. All rights reserved. +// +// Permission is hereby granted, free of charge, to any person obtaining a copy +// of this software and associated documentation files (the "Software"), to +// deal in the Software without restriction, including without limitation the +// rights to use, copy, modify, merge, publish, distribute, sublicense, and/or +// sell copies of the Software, and to permit persons to whom the Software is +// furnished to do so, subject to the following conditions: +// +// The above copyright notice and this permission notice shall be included in +// all copies or substantial portions of the Software. +// +// THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR +// IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, +// FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE +// AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER +// LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, +// OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN +// THE SOFTWARE. + +export const corsProxy = "https://cors-anywhere.herokuapp.com"; +export const RANDOMUSER_API_URL = "https://randomuser.me"; +export const BREW_API_URL = "https://api.punkapi.com/v2/beers"; +export const GOOGLE_BOOKS_URL = "https://www.googleapis.com/books/v1/volumes"; +export const apiRoute = "api/1.3"; +export const responseError = \\\\\\\`Error responding from server. Please visit \\\\\\\${RANDOMUSER_API_URL} and \\\\\\\${corsProxy} to see if they're available\\\\\\\`; +\\\`, + "overview/hero/data/table/random-user-data-source.ts": \\\`// © 2022 SolarWinds Worldwide, LLC. All rights reserved. +// +// Permission is hereby granted, free of charge, to any person obtaining a copy +// of this software and associated documentation files (the "Software"), to +// deal in the Software without restriction, including without limitation the +// rights to use, copy, modify, merge, publish, distribute, sublicense, and/or +// sell copies of the Software, and to permit persons to whom the Software is +// furnished to do so, subject to the following conditions: +// +// The above copyright notice and this permission notice shall be included in +// all copies or substantial portions of the Software. +// +// THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR +// IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, +// FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE +// AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER +// LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, +// OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN +// THE SOFTWARE. + +import { ListRange } from "@angular/cdk/collections"; +import { Injectable } from "@angular/core"; +import isEqual from "lodash/isEqual"; +import orderBy from "lodash/orderBy"; +import { BehaviorSubject } from "rxjs"; + +import { + DataSourceService, + IDataField, + INovaFilteringOutputs, + INovaFilters, + ISorterFilter, + LoggerService, +} from "@nova-ui/bits"; + +import { + IRandomUserResponse, + IRandomUserResults, + IRandomUserTableModel, + UsersQueryResponse, +} from "../types"; +import { + apiRoute, + corsProxy, + RANDOMUSER_API_URL, + responseError, +} from "./constants"; + +@Injectable() +export class RandomUserDataSource extends DataSourceService { + public static providerId = "RandomUserDataSource"; + + private readonly seed = "sw"; + + private cache = Array.from({ length: 0 }); + private lastSortValue?: ISorterFilter; + private lastVirtualScroll?: ListRange; + + public page: number = 1; + public busy = new BehaviorSubject(false); + + public dataFields: Array = [ + { id: "no", label: $localize\\\\\\\`No\\\\\\\`, dataType: "number" }, + { id: "nameTitle", label: $localize\\\\\\\`Title\\\\\\\`, dataType: "string" }, + { id: "nameFirst", label: $localize\\\\\\\`First\\\\\\\`, dataType: "string" }, + { id: "nameLast", label: $localize\\\\\\\`Last\\\\\\\`, dataType: "string" }, + { id: "gender", label: $localize\\\\\\\`Gender\\\\\\\`, dataType: "string" }, + { id: "country", label: $localize\\\\\\\`Country\\\\\\\`, dataType: "string" }, + { id: "city", label: $localize\\\\\\\`City\\\\\\\`, dataType: "string" }, + { id: "postcode", label: $localize\\\\\\\`Postcode\\\\\\\`, dataType: "number" }, + { id: "email", label: $localize\\\\\\\`E-Mail\\\\\\\`, dataType: "string" }, + { id: "cell", label: $localize\\\\\\\`Cell\\\\\\\`, dataType: "string" }, + ]; + + constructor(private logger: LoggerService) { + super(); + } + + public async getFilteredData( + filters: INovaFilters + ): Promise { + // This condition handles sorting. We want to sort columns without fetching another chunk of data. + // Since the data is being fetched when scrolled, we compare virtual scroll indexes here in the condition as well. + if (filters.sorter?.value) { + if ( + !isEqual(this.lastSortValue, filters.sorter.value) && + isEqual(this.lastVirtualScroll, filters.virtualScroll?.value) + ) { + this.lastSortValue = filters.sorter?.value; + this.lastVirtualScroll = filters.virtualScroll?.value; + + return { + repeat: { itemsSource: this.sortData(this.cache, filters) }, + paginator: { total: 200 }, + dataFields: this.dataFields, + }; + } + } + this.busy.next(true); + + const virtualScrollFilter = + filters.virtualScroll && filters.virtualScroll.value; + const start = virtualScrollFilter + ? filters.virtualScroll?.value.start + : 0; + const end = virtualScrollFilter ? filters.virtualScroll?.value.end : 0; + + // We're returning Promise with setTimeout here to make the response from the server longer, as the API being used sends responses + // almost immediately. We need it longer to be able the show the spinner component on data load + return new Promise((resolve) => { + setTimeout(() => { + this.getData(start, end).then( + (response: INovaFilteringOutputs | undefined) => { + if (!response) { + return; + } + + this.cache = this.cache.concat(response.users); + + this.dataSubject.next(this.cache); + resolve({ + repeat: { + itemsSource: this.sortData(this.cache, filters), + }, + // This API can return thousands of results, however doesn't return the max number of results, + // so we set the max number of result manually here. + paginator: { total: 200 }, + dataFields: this.dataFields, + }); + + this.lastSortValue = filters.sorter?.value; + this.lastVirtualScroll = filters.virtualScroll?.value; + this.busy.next(false); + } + ); + }, 300); + }); + } + + public async getData( + start: number = 0, + end: number = 20 + ): Promise { + let response: IRandomUserResponse | null = null; + try { + response = await ( + await fetch( + \\\\\\\`\\\\\\\${corsProxy}/\\\\\\\${RANDOMUSER_API_URL}/\\\\\\\${apiRoute}/?page=\\\\\\\${ + this.page + }&results=\\\\\\\${end - start}&seed=\\\\\\\${this.seed}\\\\\\\` + ) + ).json(); + return { + users: response?.results.map( + (result: IRandomUserResults, i: number) => ({ + no: this.cache.length + i + 1, + nameTitle: result.name.title, + nameFirst: result.name.first, + nameLast: result.name.last, + gender: result.gender, + country: result.location.country, + city: result.location.city, + postcode: result.location.postcode, + email: result.email, + cell: result.cell, + }) + ), + total: response?.results.length, + start: start, + } as UsersQueryResponse; + } catch (e) { + this.logger.error(responseError); + } + } + + private sortData(data: IRandomUserTableModel[], filters: INovaFilters) { + return orderBy( + data, + filters.sorter?.value?.sortBy, + filters.sorter?.value?.direction as "desc" | "asc" + ); + } +} +\\\`, + "overview/hero/data/table/types.ts": \\\`// © 2022 SolarWinds Worldwide, LLC. All rights reserved. +// +// Permission is hereby granted, free of charge, to any person obtaining a copy +// of this software and associated documentation files (the "Software"), to +// deal in the Software without restriction, including without limitation the +// rights to use, copy, modify, merge, publish, distribute, sublicense, and/or +// sell copies of the Software, and to permit persons to whom the Software is +// furnished to do so, subject to the following conditions: +// +// The above copyright notice and this permission notice shall be included in +// all copies or substantial portions of the Software. +// +// THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR +// IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, +// FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE +// AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER +// LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, +// OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN +// THE SOFTWARE. + +import { IDataField, INovaFilteringOutputs } from "@nova-ui/bits"; +export interface BasicTableModel { + position: number; + name: string; + features: any; + status: string; + checks: any; + "cpu-load": number; + firstUrl: string; + firstUrlLabel: string; + secondUrl: string; + secondUrlLabel: string; +} + +export interface ITableDataSourceOutput extends INovaFilteringOutputs { + dataFields: IDataField[]; +} +\\\`, + "overview/hero/data/timeseries-data-sources.ts": \\\`// © 2022 SolarWinds Worldwide, LLC. All rights reserved. +// +// Permission is hereby granted, free of charge, to any person obtaining a copy +// of this software and associated documentation files (the "Software"), to +// deal in the Software without restriction, including without limitation the +// rights to use, copy, modify, merge, publish, distribute, sublicense, and/or +// sell copies of the Software, and to permit persons to whom the Software is +// furnished to do so, subject to the following conditions: +// +// The above copyright notice and this permission notice shall be included in +// all copies or substantial portions of the Software. +// +// THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR +// IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, +// FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE +// AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER +// LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, +// OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN +// THE SOFTWARE. + +import { Injectable } from "@angular/core"; +import { Moment } from "moment/moment"; +import { BehaviorSubject } from "rxjs"; + +import { DataSourceService, IDataSource, INovaFilters } from "@nova-ui/bits"; +import { + ITimeseriesOutput, + ITimeseriesWidgetData, + ITimeseriesWidgetSeriesData, +} from "@nova-ui/dashboards"; + +import { + BEER_VS_READING_DATA, + LOUNGING_VS_ULTIMATE_FRISBEE_DATA, +} from "./widget-data"; + +@Injectable() +export class BeerVsReadingMockDataSource + extends DataSourceService + implements IDataSource +{ + public static providerId = "BeerVsReadingMockDataSource"; + + public busy = new BehaviorSubject(false); + + constructor() { + super(); + } + + public async getFilteredData( + filters: INovaFilters + ): Promise { + this.busy.next(true); + const result = await delay( + { series: getData(filters, BEER_VS_READING_DATA) }, + 1000 + ); + this.busy.next(false); + return result; + } +} + +@Injectable() +export class LoungingVsFrisbeeGolfMockDataSource + extends DataSourceService + implements IDataSource +{ + public static providerId = "LoungingVsFrisbeeGolfMockDataSource"; + + public busy = new BehaviorSubject(false); + + constructor() { + super(); + } + + public async getFilteredData( + filters: INovaFilters + ): Promise { + this.busy.next(true); + const result = await delay( + { series: getData(filters, LOUNGING_VS_ULTIMATE_FRISBEE_DATA) }, + 1000 + ); + this.busy.next(false); + return result; + } +} + +function getData( + filters: INovaFilters, + data: ITimeseriesWidgetData[] +): ITimeseriesWidgetData[] { + const timeframeFilter = filters.timeframe; + let filteredData = data; + // TIME FRAME PICKER FILTERING + if (timeframeFilter) { + filteredData = filteredData.map((item: ITimeseriesWidgetData) => ({ + id: item.id, + name: item.name, + description: item.description, + data: item.data.filter((seriesData: ITimeseriesWidgetSeriesData) => + filterDates( + seriesData.x, + timeframeFilter.value.startDatetime, + timeframeFilter.value.endDatetime + ) + ), + })); + } + + return filteredData; +} + +function filterDates(dateToCheck: Moment, startDate: Moment, endDate: Moment) { + return ( + dateToCheck.isBetween(startDate, endDate) || + dateToCheck.isSame(startDate) || + dateToCheck.isSame(endDate) + ); +} + +async function delay( + value: ITimeseriesOutput, + ms: number +): Promise { + return new Promise((resolve) => setTimeout(() => resolve(value), ms)); +} +\\\`, + "overview/hero/data/types.ts": \\\`// © 2022 SolarWinds Worldwide, LLC. All rights reserved. +// +// Permission is hereby granted, free of charge, to any person obtaining a copy +// of this software and associated documentation files (the "Software"), to +// deal in the Software without restriction, including without limitation the +// rights to use, copy, modify, merge, publish, distribute, sublicense, and/or +// sell copies of the Software, and to permit persons to whom the Software is +// furnished to do so, subject to the following conditions: +// +// The above copyright notice and this permission notice shall be included in +// all copies or substantial portions of the Software. +// +// THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR +// IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, +// FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE +// AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER +// LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, +// OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN +// THE SOFTWARE. + +export interface UsersQueryResponse { + users: IRandomUserTableModel[]; + total: number; + start: number; +} + +export interface IRandomUserResponse { + info: Array; + results: Array; +} + +export interface IRandomUserInfo { + page: number; + results: number; + seed: string; + version: string; +} + +export interface IRandomUserResults { + cell: string; + dob: { + age: number; + date: string; + }; + email: string; + gender: string; + id: any; + location: IRandomUserLocation; + login: { + md5: string; + password: string; + salt: string; + sha1: string; + sha256: string; + username: string; + uuid: string; + }; + name: { + title: string; + first: string; + last: string; + }; + nat: string; + phone: string; + picture: { + large: string; + medium: string; + thumbnail: string; + }; + registered: { + date: string; + age: number; + }; +} + +export interface IRandomUserTableModel { + no: number; + nameTitle: string; + nameFirst: string; + nameLast: string; + gender: string; + country: string; + city: string; + postcode: number; + email: string; + cell: string; +} + +export interface IRandomUserLocation { + city: string; + coordinates: { latitude: string; longitude: string }; + country: string; + postcode: number; + state: string; + street: { number: number; name: string }; + timezone: any; +} + +export interface IBrewInfo { + id: number; + name: string; + tagline: string; + first_brewed: string; + description: string; + brewers_tips: string; +} + +export interface IBrewDatasourceResponse { + brewInfo: IBrewInfo[]; + total: number; +} +\\\`, + "overview/hero/data/widget-data.ts": \\\`// © 2022 SolarWinds Worldwide, LLC. All rights reserved. +// +// Permission is hereby granted, free of charge, to any person obtaining a copy +// of this software and associated documentation files (the "Software"), to +// deal in the Software without restriction, including without limitation the +// rights to use, copy, modify, merge, publish, distribute, sublicense, and/or +// sell copies of the Software, and to permit persons to whom the Software is +// furnished to do so, subject to the following conditions: +// +// The above copyright notice and this permission notice shall be included in +// all copies or substantial portions of the Software. +// +// THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR +// IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, +// FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE +// AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER +// LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, +// OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN +// THE SOFTWARE. + +import moment from "moment/moment"; + +import { ITimeseriesWidgetData } from "@nova-ui/dashboards"; + +import { BasicTableModel } from "./table/types"; + +export interface IProportionalWidgetData { + id: string; + name: string; + data: number[]; + link: string; + value: string; +} + +export function getMockBeerReviewCountsByCity(): IProportionalWidgetData[] { + return [ + { + id: "Brno", + name: "Brno", + data: [Math.round(Math.random() * 100000)], + link: "https://en.wikipedia.org/wiki/Brno", + value: "Brno", + }, + { + id: "kyiv", + name: "Kyiv", + data: [Math.round(Math.random() * 100000)], + link: "https://en.wikipedia.org/wiki/Kyiv", + value: "Kyiv", + }, + { + id: "austin", + name: "Austin", + data: [Math.round(Math.random() * 100000)], + link: "https://en.wikipedia.org/wiki/Austin", + value: "Austin", + }, + { + id: "lisbon", + name: "Lisbon", + data: [Math.round(Math.random() * 100000)], + link: "https://en.wikipedia.org/wiki/Lisbon", + value: "Lisbon", + }, + { + id: "sydney", + name: "Sydney", + data: [Math.round(Math.random() * 100000)], + link: "https://en.wikipedia.org/wiki/Sydney", + value: "Sydney", + }, + { + id: "nur-sultan", + name: "Nur-Sultan", + data: [Math.round(Math.random() * 100000)], + link: "https://en.wikipedia.org/wiki/Nur-Sultan", + value: "Nur-Sultan", + }, + ].sort((a, b) => a.data[0] - b.data[0]); +} + +export function getMockBeerReviewCountsByCity2(): IProportionalWidgetData[] { + return [ + { + id: "london", + name: "London", + data: [Math.round(Math.random() * 100000)], + link: "https://en.wikipedia.org/wiki/London", + value: "London", + }, + { + id: "paris", + name: "Paris", + data: [Math.round(Math.random() * 100000)], + link: "https://en.wikipedia.org/wiki/Paris", + value: "Paris", + }, + { + id: "rio", + name: "Rio", + data: [Math.round(Math.random() * 100000)], + link: "https://en.wikipedia.org/wiki/Rio_de_Janeiro", + value: "Rio", + }, + ].sort((a, b) => a.data[0] - b.data[0]); +} + +export const BEER_VS_READING_DATA: ITimeseriesWidgetData[] = [ + { + id: "series-1", + name: "Beer Tasting", + description: "Havin' some suds", + data: [ + { x: moment().subtract(10, "day"), y: 30 }, + { x: moment().subtract(9, "day"), y: 35 }, + { x: moment().subtract(8, "day"), y: 33 }, + { x: moment().subtract(7, "day"), y: 40 }, + { x: moment().subtract(6, "day"), y: 35 }, + { x: moment().subtract(5, "day"), y: 30 }, + { x: moment().subtract(4, "day"), y: 35 }, + { x: moment().subtract(3, "day"), y: 15 }, + { x: moment().subtract(2, "day"), y: 30 }, + { x: moment().subtract(1, "day"), y: 35 }, + { x: moment().subtract(24, "hour"), y: 34 }, + { x: moment().subtract(15, "hour"), y: 33 }, + { x: moment().subtract(10, "hour"), y: 35 }, + { x: moment().subtract(5, "hour"), y: 36 }, + { x: moment().subtract(1, "hour"), y: 34 }, + { x: moment().subtract(50, "minute"), y: 33 }, + { x: moment().subtract(40, "minute"), y: 30 }, + { x: moment().subtract(30, "minute"), y: 32 }, + { x: moment().subtract(20, "minute"), y: 31 }, + { x: moment().subtract(10, "minute"), y: 34 }, + ], + }, + { + id: "series-2", + name: "Reading", + description: "Hittin' the books", + data: [ + { x: moment().subtract(10, "day"), y: 60 }, + { x: moment().subtract(9, "day"), y: 64 }, + { x: moment().subtract(8, "day"), y: 70 }, + { x: moment().subtract(7, "day"), y: 55 }, + { x: moment().subtract(6, "day"), y: 55 }, + { x: moment().subtract(5, "day"), y: 45 }, + { x: moment().subtract(4, "day"), y: 10 }, + { x: moment().subtract(3, "day"), y: 65 }, + { x: moment().subtract(2, "day"), y: 35 }, + { x: moment().subtract(1, "day"), y: 60 }, + { x: moment().subtract(24, "hour"), y: 61 }, + { x: moment().subtract(15, "hour"), y: 65 }, + { x: moment().subtract(10, "hour"), y: 63 }, + { x: moment().subtract(5, "hour"), y: 58 }, + { x: moment().subtract(1, "hour"), y: 64 }, + { x: moment().subtract(50, "minute"), y: 63 }, + { x: moment().subtract(40, "minute"), y: 60 }, + { x: moment().subtract(30, "minute"), y: 62 }, + { x: moment().subtract(20, "minute"), y: 61 }, + { x: moment().subtract(10, "minute"), y: 62 }, + ], + }, +]; + +export const LOUNGING_VS_ULTIMATE_FRISBEE_DATA: ITimeseriesWidgetData[] = [ + { + id: "series-a", + name: "Lounging", + description: "Shootin' the Breeze", + data: [ + { x: moment().subtract(10, "day"), y: 10 }, + { x: moment().subtract(9, "day"), y: 15 }, + { x: moment().subtract(8, "day"), y: 13 }, + { x: moment().subtract(7, "day"), y: 20 }, + { x: moment().subtract(6, "day"), y: 15 }, + { x: moment().subtract(5, "day"), y: 10 }, + { x: moment().subtract(4, "day"), y: 15 }, + { x: moment().subtract(3, "day"), y: 5 }, + { x: moment().subtract(2, "day"), y: 10 }, + { x: moment().subtract(1, "day"), y: 15 }, + { x: moment().subtract(24, "hour"), y: 14 }, + { x: moment().subtract(15, "hour"), y: 13 }, + { x: moment().subtract(10, "hour"), y: 15 }, + { x: moment().subtract(5, "hour"), y: 16 }, + { x: moment().subtract(1, "hour"), y: 14 }, + { x: moment().subtract(50, "minute"), y: 13 }, + { x: moment().subtract(40, "minute"), y: 10 }, + { x: moment().subtract(30, "minute"), y: 12 }, + { x: moment().subtract(20, "minute"), y: 11 }, + { x: moment().subtract(10, "minute"), y: 14 }, + ], + }, + { + id: "series-b", + name: "Frisbee Golfing", + description: "Golfin' with a disc", + data: [ + { x: moment().subtract(10, "day"), y: 80 }, + { x: moment().subtract(9, "day"), y: 84 }, + { x: moment().subtract(8, "day"), y: 80 }, + { x: moment().subtract(7, "day"), y: 75 }, + { x: moment().subtract(6, "day"), y: 95 }, + { x: moment().subtract(5, "day"), y: 85 }, + { x: moment().subtract(4, "day"), y: 80 }, + { x: moment().subtract(3, "day"), y: 85 }, + { x: moment().subtract(2, "day"), y: 85 }, + { x: moment().subtract(1, "day"), y: 80 }, + { x: moment().subtract(24, "hour"), y: 81 }, + { x: moment().subtract(15, "hour"), y: 85 }, + { x: moment().subtract(10, "hour"), y: 83 }, + { x: moment().subtract(5, "hour"), y: 88 }, + { x: moment().subtract(1, "hour"), y: 84 }, + { x: moment().subtract(50, "minute"), y: 83 }, + { x: moment().subtract(40, "minute"), y: 80 }, + { x: moment().subtract(30, "minute"), y: 82 }, + { x: moment().subtract(20, "minute"), y: 81 }, + { x: moment().subtract(10, "minute"), y: 82 }, + ], + }, +]; + +export const TABLE_DATA: BasicTableModel[] = [ + { + position: 1, + name: "FOCUS-SVR-02258", + features: ["remote-access-vpn-tunnel", "patch-manager01"], + status: "Active", + checks: { + icon: "status_up", + num: 25, + }, + "cpu-load": 86, + firstUrl: "https://en.wikipedia.org/wiki/Brno", + firstUrlLabel: "Brno", + secondUrl: "https://en.wikipedia.org/wiki/VMware_Workstation", + secondUrlLabel: "Workstation", + }, + { + position: 2, + name: "FOCUS-SVR-03312", + features: ["tools", "database", "orion-ape-backup"], + status: "Active", + checks: { + icon: "status_critical", + num: 25, + }, + "cpu-load": 47, + firstUrl: "https://en.wikipedia.org/wiki/Brno", + firstUrlLabel: "Brno", + secondUrl: "https://en.wikipedia.org/wiki/VMware_Workstation", + secondUrlLabel: "Workstation", + }, + { + position: 3, + name: "FOCUS-SVR-02258", + features: [ + "remote-access-vpn-tunnel", + "database", + "orion-ape-backup", + "patch-manager01", + ], + status: "Active", + checks: { + icon: "status_down", + num: 25, + }, + "cpu-load": 53, + firstUrl: "https://en.wikipedia.org/wiki/Kyiv", + firstUrlLabel: "Kyiv", + secondUrl: "https://en.wikipedia.org/wiki/VMware_Workstation", + secondUrlLabel: "Workstation", + }, + { + position: 4, + name: "Man-LT-JYJ4AD5", + features: [ + "remote-access-vpn-tunnel", + "tools", + "database", + "orion-ape-backup", + ], + status: "Active", + checks: { + icon: "status_up", + num: 25, + }, + "cpu-load": 32, + firstUrl: "https://en.wikipedia.org/wiki/Kyiv", + firstUrlLabel: "Kyiv", + secondUrl: "https://en.wikipedia.org/wiki/VirtualBox", + secondUrlLabel: "VirtualBox", + }, + { + position: 5, + name: "Man-LT-JYJ425", + features: [ + "remote-access-vpn-tunnel", + "tools", + "database", + "orion-ape-backup", + ], + status: "Active", + checks: { + icon: "status_up", + num: 25, + }, + "cpu-load": 22, + firstUrl: "https://en.wikipedia.org/wiki/Kyiv", + firstUrlLabel: "Kyiv", + secondUrl: "https://en.wikipedia.org/wiki/VirtualBox", + secondUrlLabel: "VirtualBox", + }, + { + position: 6, + name: "Man-LT-JYJ4333", + features: [ + "remote-access-vpn-tunnel", + "tools", + "database", + "orion-ape-backup", + ], + status: "Active", + checks: { + icon: "status_up", + num: 25, + }, + "cpu-load": 12, + firstUrl: "https://en.wikipedia.org/wiki/Kyiv", + firstUrlLabel: "Kyiv", + secondUrl: "https://en.wikipedia.org/wiki/VirtualBox", + secondUrlLabel: "VirtualBox", + }, + { + position: 7, + name: "FOCUS-SVR-02258", + features: ["remote-access-vpn-tunnel", "patch-manager01"], + status: "Active", + checks: { + icon: "status_up", + num: 25, + }, + "cpu-load": 86, + firstUrl: "https://en.wikipedia.org/wiki/Austin", + firstUrlLabel: "Austin", + secondUrl: "https://en.wikipedia.org/wiki/VMware_Workstation", + secondUrlLabel: "Workstation", + }, + { + position: 8, + name: "Man-LT-JYJ4AD5", + features: [ + "remote-access-vpn-tunnel", + "tools", + "database", + "orion-ape-backup", + "patch-manager01", + ], + status: "Active", + checks: { + icon: "status_inactive", + num: 25, + }, + "cpu-load": 35, + firstUrl: "https://en.wikipedia.org/wiki/Austin", + firstUrlLabel: "Austin", + secondUrl: "https://en.wikipedia.org/wiki/VMware_Workstation", + secondUrlLabel: "Workstation", + }, + { + position: 9, + name: "Man-LT-JYJ4AD5", + features: [ + "remote-access-vpn-tunnel", + "tools", + "database", + "patch-manager01", + ], + status: "Active", + checks: { + icon: "status_up", + num: 25, + }, + "cpu-load": 32, + firstUrl: "https://en.wikipedia.org/wiki/Brno", + firstUrlLabel: "Brno", + secondUrl: "https://en.wikipedia.org/wiki/VMware_Workstation", + secondUrlLabel: "Workstation", + }, + { + position: 10, + name: "Man-LT-JYJ4AD5", + features: [ + "remote-access-vpn-tunnel", + "database", + "orion-ape-backup", + "patch-manager01", + ], + status: "Active", + checks: { + icon: "status_up", + num: 25, + }, + "cpu-load": 64, + firstUrl: "https://en.wikipedia.org/wiki/Kyiv", + firstUrlLabel: "Kyiv", + secondUrl: "https://en.wikipedia.org/wiki/VMware_Workstation", + secondUrlLabel: "Workstation", + }, + { + position: 11, + name: "Man-LT-111", + features: [], + status: "Active", + checks: { + icon: "status_external", + num: 25, + }, + "cpu-load": 55, + firstUrl: "https://en.wikipedia.org/wiki/Brno", + firstUrlLabel: "Brno", + secondUrl: "https://en.wikipedia.org/wiki/VMware_Workstation", + secondUrlLabel: "Workstation", + }, + { + position: 12, + name: "Man-LT-2222", + features: [ + "remote-access-vpn-tunnel", + "tools", + "database", + "orion-ape-backup", + "patch-manager01", + ], + status: "Active", + checks: { + icon: "status_inactive", + num: 25, + }, + "cpu-load": 34, + firstUrl: "https://en.wikipedia.org/wiki/Brno", + firstUrlLabel: "Brno", + secondUrl: "https://en.wikipedia.org/wiki/VMware_Workstation", + secondUrlLabel: "Workstation", + }, + { + position: 13, + name: "Man-LT-333333", + features: [ + "remote-access-vpn-tunnel", + "tools", + "database", + "patch-manager01", + ], + status: "Active", + checks: { + icon: "status_up", + num: 25, + }, + "cpu-load": 56, + firstUrl: "https://en.wikipedia.org/wiki/Kyiv", + firstUrlLabel: "Kyiv", + secondUrl: "https://en.wikipedia.org/wiki/VMware_Workstation", + secondUrlLabel: "Workstation", + }, + { + position: 14, + name: "Man-LT-444444", + features: [ + "remote-access-vpn-tunnel", + "database", + "orion-ape-backup", + "patch-manager01", + ], + status: "Active", + checks: { + icon: "status_up", + num: 25, + }, + "cpu-load": 26, + firstUrl: "https://en.wikipedia.org/wiki/Kyiv", + firstUrlLabel: "Kyiv", + secondUrl: "https://en.wikipedia.org/wiki/VMware_Workstation", + secondUrlLabel: "Workstation", + }, + { + position: 15, + name: "Man-LT-555555", + features: [ + "remote-access-vpn-tunnel", + "database", + "orion-ape-backup", + "patch-manager01", + ], + status: "Active", + checks: { + icon: "status_up", + num: 25, + }, + "cpu-load": 76, + firstUrl: "https://en.wikipedia.org/wiki/Austin", + firstUrlLabel: "Austin", + secondUrl: "https://en.wikipedia.org/wiki/VMware_Workstation", + secondUrlLabel: "Workstation", + }, + { + position: 16, + name: "FOCUS-SVR-02258", + features: ["remote-access-vpn-tunnel", "patch-manager01"], + status: "Active", + checks: { + icon: "status_up", + num: 25, + }, + "cpu-load": 86, + firstUrl: "https://en.wikipedia.org/wiki/Brno", + firstUrlLabel: "Brno", + secondUrl: "https://en.wikipedia.org/wiki/VMware_Workstation", + secondUrlLabel: "Workstation", + }, + { + position: 17, + name: "FOCUS-SVR-03312", + features: ["tools", "database", "orion-ape-backup"], + status: "Active", + checks: { + icon: "status_critical", + num: 25, + }, + "cpu-load": 47, + firstUrl: "https://en.wikipedia.org/wiki/Brno", + firstUrlLabel: "Brno", + secondUrl: "https://en.wikipedia.org/wiki/VMware_Workstation", + secondUrlLabel: "Workstation", + }, + { + position: 18, + name: "FOCUS-SVR-02258", + features: [ + "remote-access-vpn-tunnel", + "database", + "orion-ape-backup", + "patch-manager01", + ], + status: "Active", + checks: { + icon: "status_down", + num: 25, + }, + "cpu-load": 53, + firstUrl: "https://en.wikipedia.org/wiki/Kyiv", + firstUrlLabel: "Kyiv", + secondUrl: "https://en.wikipedia.org/wiki/VMware_Workstation", + secondUrlLabel: "Workstation", + }, + { + position: 19, + name: "Man-LT-JYJ4AD5", + features: [ + "remote-access-vpn-tunnel", + "tools", + "database", + "orion-ape-backup", + ], + status: "Active", + checks: { + icon: "status_up", + num: 25, + }, + "cpu-load": 32, + firstUrl: "https://en.wikipedia.org/wiki/Kyiv", + firstUrlLabel: "Kyiv", + secondUrl: "https://en.wikipedia.org/wiki/VirtualBox", + secondUrlLabel: "VirtualBox", + }, + { + position: 20, + name: "Man-LT-JYJ425", + features: [ + "remote-access-vpn-tunnel", + "tools", + "database", + "orion-ape-backup", + ], + status: "Active", + checks: { + icon: "status_up", + num: 25, + }, + "cpu-load": 22, + firstUrl: "https://en.wikipedia.org/wiki/Kyiv", + firstUrlLabel: "Kyiv", + secondUrl: "https://en.wikipedia.org/wiki/VirtualBox", + secondUrlLabel: "VirtualBox", + }, + { + position: 21, + name: "Man-LT-JYJ4333", + features: [ + "remote-access-vpn-tunnel", + "tools", + "database", + "orion-ape-backup", + ], + status: "Active", + checks: { + icon: "status_up", + num: 25, + }, + "cpu-load": 12, + firstUrl: "https://en.wikipedia.org/wiki/Kyiv", + firstUrlLabel: "Kyiv", + secondUrl: "https://en.wikipedia.org/wiki/VirtualBox", + secondUrlLabel: "VirtualBox", + }, + { + position: 22, + name: "FOCUS-SVR-02258", + features: ["remote-access-vpn-tunnel", "patch-manager01"], + status: "Active", + checks: { + icon: "status_up", + num: 25, + }, + "cpu-load": 86, + firstUrl: "https://en.wikipedia.org/wiki/Austin", + firstUrlLabel: "Austin", + secondUrl: "https://en.wikipedia.org/wiki/VMware_Workstation", + secondUrlLabel: "Workstation", + }, + { + position: 23, + name: "Man-LT-JYJ4AD5", + features: [ + "remote-access-vpn-tunnel", + "tools", + "database", + "orion-ape-backup", + "patch-manager01", + ], + status: "Active", + checks: { + icon: "status_inactive", + num: 25, + }, + "cpu-load": 35, + firstUrl: "https://en.wikipedia.org/wiki/Austin", + firstUrlLabel: "Austin", + secondUrl: "https://en.wikipedia.org/wiki/VMware_Workstation", + secondUrlLabel: "Workstation", + }, + { + position: 24, + name: "Man-LT-JYJ4AD5", + features: [ + "remote-access-vpn-tunnel", + "tools", + "database", + "patch-manager01", + ], + status: "Active", + checks: { + icon: "status_up", + num: 25, + }, + "cpu-load": 32, + firstUrl: "https://en.wikipedia.org/wiki/Brno", + firstUrlLabel: "Brno", + secondUrl: "https://en.wikipedia.org/wiki/VMware_Workstation", + secondUrlLabel: "Workstation", + }, + { + position: 25, + name: "Man-LT-JYJ4AD5", + features: [ + "remote-access-vpn-tunnel", + "database", + "orion-ape-backup", + "patch-manager01", + ], + status: "Active", + checks: { + icon: "status_up", + num: 25, + }, + "cpu-load": 64, + firstUrl: "https://en.wikipedia.org/wiki/Kyiv", + firstUrlLabel: "Kyiv", + secondUrl: "https://en.wikipedia.org/wiki/VMware_Workstation", + secondUrlLabel: "Workstation", + }, + { + position: 26, + name: "Man-LT-111", + features: [], + status: "Active", + checks: { + icon: "status_external", + num: 25, + }, + "cpu-load": 55, + firstUrl: "https://en.wikipedia.org/wiki/Brno", + firstUrlLabel: "Brno", + secondUrl: "https://en.wikipedia.org/wiki/VMware_Workstation", + secondUrlLabel: "Workstation", + }, + { + position: 27, + name: "Man-LT-2222", + features: [ + "remote-access-vpn-tunnel", + "tools", + "database", + "orion-ape-backup", + "patch-manager01", + ], + status: "Active", + checks: { + icon: "status_inactive", + num: 25, + }, + "cpu-load": 34, + firstUrl: "https://en.wikipedia.org/wiki/Brno", + firstUrlLabel: "Brno", + secondUrl: "https://en.wikipedia.org/wiki/VMware_Workstation", + secondUrlLabel: "Workstation", + }, + { + position: 28, + name: "Man-LT-333333", + features: [ + "remote-access-vpn-tunnel", + "tools", + "database", + "patch-manager01", + ], + status: "Active", + checks: { + icon: "status_up", + num: 25, + }, + "cpu-load": 56, + firstUrl: "https://en.wikipedia.org/wiki/Kyiv", + firstUrlLabel: "Kyiv", + secondUrl: "https://en.wikipedia.org/wiki/VMware_Workstation", + secondUrlLabel: "Workstation", + }, + { + position: 29, + name: "Man-LT-444444", + features: [ + "remote-access-vpn-tunnel", + "database", + "orion-ape-backup", + "patch-manager01", + ], + status: "Active", + checks: { + icon: "status_up", + num: 25, + }, + "cpu-load": 26, + firstUrl: "https://en.wikipedia.org/wiki/Kyiv", + firstUrlLabel: "Kyiv", + secondUrl: "https://en.wikipedia.org/wiki/VMware_Workstation", + secondUrlLabel: "Workstation", + }, + { + position: 30, + name: "Man-LT-555555", + features: [ + "remote-access-vpn-tunnel", + "database", + "orion-ape-backup", + "patch-manager01", + ], + status: "Active", + checks: { + icon: "status_up", + num: 25, + }, + "cpu-load": 76, + firstUrl: "https://en.wikipedia.org/wiki/Austin", + firstUrlLabel: "Austin", + secondUrl: "https://en.wikipedia.org/wiki/VMware_Workstation", + secondUrlLabel: "Workstation", + }, + { + position: 31, + name: "FOCUS-SVR-02258", + features: ["remote-access-vpn-tunnel", "patch-manager01"], + status: "Active", + checks: { + icon: "status_up", + num: 25, + }, + "cpu-load": 86, + firstUrl: "https://en.wikipedia.org/wiki/Brno", + firstUrlLabel: "Brno", + secondUrl: "https://en.wikipedia.org/wiki/VMware_Workstation", + secondUrlLabel: "Workstation", + }, + { + position: 32, + name: "FOCUS-SVR-03312", + features: ["tools", "database", "orion-ape-backup"], + status: "Active", + checks: { + icon: "status_critical", + num: 25, + }, + "cpu-load": 47, + firstUrl: "https://en.wikipedia.org/wiki/Brno", + firstUrlLabel: "Brno", + secondUrl: "https://en.wikipedia.org/wiki/VMware_Workstation", + secondUrlLabel: "Workstation", + }, + { + position: 33, + name: "FOCUS-SVR-02258", + features: [ + "remote-access-vpn-tunnel", + "database", + "orion-ape-backup", + "patch-manager01", + ], + status: "Active", + checks: { + icon: "status_down", + num: 25, + }, + "cpu-load": 53, + firstUrl: "https://en.wikipedia.org/wiki/Kyiv", + firstUrlLabel: "Kyiv", + secondUrl: "https://en.wikipedia.org/wiki/VMware_Workstation", + secondUrlLabel: "Workstation", + }, + { + position: 34, + name: "Man-LT-JYJ4AD5", + features: [ + "remote-access-vpn-tunnel", + "tools", + "database", + "orion-ape-backup", + ], + status: "Active", + checks: { + icon: "status_up", + num: 25, + }, + "cpu-load": 32, + firstUrl: "https://en.wikipedia.org/wiki/Kyiv", + firstUrlLabel: "Kyiv", + secondUrl: "https://en.wikipedia.org/wiki/VirtualBox", + secondUrlLabel: "VirtualBox", + }, + { + position: 35, + name: "Man-LT-JYJ425", + features: [ + "remote-access-vpn-tunnel", + "tools", + "database", + "orion-ape-backup", + ], + status: "Active", + checks: { + icon: "status_up", + num: 25, + }, + "cpu-load": 22, + firstUrl: "https://en.wikipedia.org/wiki/Kyiv", + firstUrlLabel: "Kyiv", + secondUrl: "https://en.wikipedia.org/wiki/VirtualBox", + secondUrlLabel: "VirtualBox", + }, + { + position: 36, + name: "Man-LT-JYJ4333", + features: [ + "remote-access-vpn-tunnel", + "tools", + "database", + "orion-ape-backup", + ], + status: "Active", + checks: { + icon: "status_up", + num: 25, + }, + "cpu-load": 12, + firstUrl: "https://en.wikipedia.org/wiki/Kyiv", + firstUrlLabel: "Kyiv", + secondUrl: "https://en.wikipedia.org/wiki/VirtualBox", + secondUrlLabel: "VirtualBox", + }, + { + position: 37, + name: "FOCUS-SVR-02258", + features: ["remote-access-vpn-tunnel", "patch-manager01"], + status: "Active", + checks: { + icon: "status_up", + num: 25, + }, + "cpu-load": 86, + firstUrl: "https://en.wikipedia.org/wiki/Austin", + firstUrlLabel: "Austin", + secondUrl: "https://en.wikipedia.org/wiki/VMware_Workstation", + secondUrlLabel: "Workstation", + }, + { + position: 38, + name: "Man-LT-JYJ4AD5", + features: [ + "remote-access-vpn-tunnel", + "tools", + "database", + "orion-ape-backup", + "patch-manager01", + ], + status: "Active", + checks: { + icon: "status_inactive", + num: 25, + }, + "cpu-load": 35, + firstUrl: "https://en.wikipedia.org/wiki/Austin", + firstUrlLabel: "Austin", + secondUrl: "https://en.wikipedia.org/wiki/VMware_Workstation", + secondUrlLabel: "Workstation", + }, + { + position: 39, + name: "Man-LT-JYJ4AD5", + features: [ + "remote-access-vpn-tunnel", + "tools", + "database", + "patch-manager01", + ], + status: "Active", + checks: { + icon: "status_up", + num: 25, + }, + "cpu-load": 32, + firstUrl: "https://en.wikipedia.org/wiki/Brno", + firstUrlLabel: "Brno", + secondUrl: "https://en.wikipedia.org/wiki/VMware_Workstation", + secondUrlLabel: "Workstation", + }, + { + position: 40, + name: "Man-LT-JYJ4AD5", + features: [ + "remote-access-vpn-tunnel", + "database", + "orion-ape-backup", + "patch-manager01", + ], + status: "Active", + checks: { + icon: "status_up", + num: 25, + }, + "cpu-load": 64, + firstUrl: "https://en.wikipedia.org/wiki/Kyiv", + firstUrlLabel: "Kyiv", + secondUrl: "https://en.wikipedia.org/wiki/VMware_Workstation", + secondUrlLabel: "Workstation", + }, + { + position: 41, + name: "Man-LT-111", + features: [], + status: "Active", + checks: { + icon: "status_external", + num: 25, + }, + "cpu-load": 55, + firstUrl: "https://en.wikipedia.org/wiki/Brno", + firstUrlLabel: "Brno", + secondUrl: "https://en.wikipedia.org/wiki/VMware_Workstation", + secondUrlLabel: "Workstation", + }, + { + position: 42, + name: "Man-LT-2222", + features: [ + "remote-access-vpn-tunnel", + "tools", + "database", + "orion-ape-backup", + "patch-manager01", + ], + status: "Active", + checks: { + icon: "status_inactive", + num: 25, + }, + "cpu-load": 34, + firstUrl: "https://en.wikipedia.org/wiki/Brno", + firstUrlLabel: "Brno", + secondUrl: "https://en.wikipedia.org/wiki/VMware_Workstation", + secondUrlLabel: "Workstation", + }, + { + position: 43, + name: "Man-LT-333333", + features: [ + "remote-access-vpn-tunnel", + "tools", + "database", + "patch-manager01", + ], + status: "Active", + checks: { + icon: "status_up", + num: 25, + }, + "cpu-load": 56, + firstUrl: "https://en.wikipedia.org/wiki/Kyiv", + firstUrlLabel: "Kyiv", + secondUrl: "https://en.wikipedia.org/wiki/VMware_Workstation", + secondUrlLabel: "Workstation", + }, + { + position: 44, + name: "Man-LT-444444", + features: [ + "remote-access-vpn-tunnel", + "database", + "orion-ape-backup", + "patch-manager01", + ], + status: "Active", + checks: { + icon: "status_up", + num: 25, + }, + "cpu-load": 26, + firstUrl: "https://en.wikipedia.org/wiki/Kyiv", + firstUrlLabel: "Kyiv", + secondUrl: "https://en.wikipedia.org/wiki/VMware_Workstation", + secondUrlLabel: "Workstation", + }, + { + position: 45, + name: "Man-LT-555555", + features: [ + "remote-access-vpn-tunnel", + "database", + "orion-ape-backup", + "patch-manager01", + ], + status: "Active", + checks: { + icon: "status_up", + num: 25, + }, + "cpu-load": 76, + firstUrl: "https://en.wikipedia.org/wiki/Austin", + firstUrlLabel: "Austin", + secondUrl: "https://en.wikipedia.org/wiki/VMware_Workstation", + secondUrlLabel: "Workstation", + }, +]; +\\\`, + "overview/hero/widget-configs/kpi.ts": \\\`// © 2022 SolarWinds Worldwide, LLC. All rights reserved. +// +// Permission is hereby granted, free of charge, to any person obtaining a copy +// of this software and associated documentation files (the "Software"), to +// deal in the Software without restriction, including without limitation the +// rights to use, copy, modify, merge, publish, distribute, sublicense, and/or +// sell copies of the Software, and to permit persons to whom the Software is +// furnished to do so, subject to the following conditions: +// +// The above copyright notice and this permission notice shall be included in +// all copies or substantial portions of the Software. +// +// THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR +// IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, +// FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE +// AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER +// LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, +// OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN +// THE SOFTWARE. + +import { + DEFAULT_PIZZAGNA_ROOT, + IProviderConfiguration, + IRefresherProperties, + IWidget, + KpiComponent, + NOVA_KPI_DATASOURCE_ADAPTER, + PizzagnaLayer, + WellKnownProviders, +} from "@nova-ui/dashboards"; + +import { + HarryPotterAverageRatingDataSource, + HarryPotterRatingsCountDataSource, +} from "../data/kpi-datasources"; + +export const kpiConfig: IWidget = { + id: "kpiWidgetId", + type: "kpi", + pizzagna: { + [PizzagnaLayer.Configuration]: { + [DEFAULT_PIZZAGNA_ROOT]: { + providers: { + [WellKnownProviders.Refresher]: { + properties: { + // Configuring the refresher interval so that our data source is invoked every ten minutes + interval: 60 * 10, + enabled: true, + } as IRefresherProperties, + } as Partial, + }, + }, + header: { + properties: { + title: "Harry Potter and the Sorcerer's Stone", + subtitle: "By J. K. Rowling", + }, + }, + tiles: { + properties: { + nodes: ["kpi1", "kpi2"], + }, + }, + kpi1: { + id: "kpi1", + componentType: KpiComponent.lateLoadKey, + properties: { + widgetData: { + label: "Average Rating", + backgroundColor: "var(--nui-color-chart-three)", + units: "out of 5 Stars", + }, + }, + providers: { + [WellKnownProviders.DataSource]: { + // Setting the data source providerId for the tile with id "kpi1" + providerId: + HarryPotterAverageRatingDataSource.providerId, + } as IProviderConfiguration, + [WellKnownProviders.Adapter]: { + providerId: NOVA_KPI_DATASOURCE_ADAPTER, + properties: { + componentId: "kpi1", + propertyPath: "widgetData", + }, + } as IProviderConfiguration, + }, + }, + kpi2: { + id: "kpi2", + componentType: KpiComponent.lateLoadKey, + properties: { + widgetData: { + label: "Reader Feedback", + units: "Ratings", + }, + }, + providers: { + [WellKnownProviders.DataSource]: { + // Setting the data source providerId for the tile with id "kpi" + providerId: + HarryPotterRatingsCountDataSource.providerId, + } as IProviderConfiguration, + [WellKnownProviders.Adapter]: { + providerId: NOVA_KPI_DATASOURCE_ADAPTER, + properties: { + componentId: "kpi2", + propertyPath: "widgetData", + }, + } as IProviderConfiguration, + }, + }, + }, + }, +}; +\\\`, + "overview/hero/widget-configs/proportional.ts": \\\`// © 2022 SolarWinds Worldwide, LLC. All rights reserved. +// +// Permission is hereby granted, free of charge, to any person obtaining a copy +// of this software and associated documentation files (the "Software"), to +// deal in the Software without restriction, including without limitation the +// rights to use, copy, modify, merge, publish, distribute, sublicense, and/or +// sell copies of the Software, and to permit persons to whom the Software is +// furnished to do so, subject to the following conditions: +// +// The above copyright notice and this permission notice shall be included in +// all copies or substantial portions of the Software. +// +// THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR +// IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, +// FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE +// AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER +// LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, +// OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN +// THE SOFTWARE. + +import { + DEFAULT_PIZZAGNA_ROOT, + IProportionalWidgetChartOptions, + IProviderConfiguration, + IWidget, + LegendPlacement, + PizzagnaLayer, + ProportionalWidgetChartTypes, + WellKnownProviders, +} from "@nova-ui/dashboards"; + +import { BeerReviewCountsByCityMockDataSource } from "../data/proportional-datasources"; + +export const proportionalConfig: IWidget = { + id: "proportionalWidgetId", + type: "proportional", + pizzagna: { + [PizzagnaLayer.Configuration]: { + [DEFAULT_PIZZAGNA_ROOT]: { + providers: { + [WellKnownProviders.Refresher]: { + properties: { + interval: 0, + }, + }, + }, + }, + header: { + properties: { + title: "Beer Review Tally by City", + subtitle: "These People Love Beer", + }, + }, + chart: { + providers: { + [WellKnownProviders.DataSource]: { + providerId: + BeerReviewCountsByCityMockDataSource.providerId, + } as IProviderConfiguration, + }, + properties: { + configuration: { + chartOptions: { + type: ProportionalWidgetChartTypes.DonutChart, + legendPlacement: LegendPlacement.Right, + } as IProportionalWidgetChartOptions, + }, + }, + }, + }, + }, +}; +\\\`, + "overview/hero/widget-configs/risk-score.ts": \\\`// © 2023 SolarWinds Worldwide, LLC. All rights reserved. +// +// Permission is hereby granted, free of charge, to any person obtaining a copy +// of this software and associated documentation files (the "Software"), to +// deal in the Software without restriction, including without limitation the +// rights to use, copy, modify, merge, publish, distribute, sublicense, and/or +// sell copies of the Software, and to permit persons to whom the Software is +// furnished to do so, subject to the following conditions: +// +// The above copyright notice and this permission notice shall be included in +// all copies or substantial portions of the Software. +// +// THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR +// IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, +// FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE +// AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER +// LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, +// OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN +// THE SOFTWARE. + +import { + DEFAULT_PIZZAGNA_ROOT, + IProviderConfiguration, + IRefresherProperties, + IWidget, + RiskScoreTileComponent, + NOVA_KPI_DATASOURCE_ADAPTER, + PizzagnaLayer, + WellKnownProviders, +} from "@nova-ui/dashboards"; + +import { HarryPotterAverageRatingDataSource } from "../data/kpi-datasources"; + +export const riskScoreConfig: IWidget = { + id: "riskScoreWidgetId", + type: "risk-score", + pizzagna: { + [PizzagnaLayer.Configuration]: { + [DEFAULT_PIZZAGNA_ROOT]: { + providers: { + [WellKnownProviders.Refresher]: { + properties: { + // Configuring the refresher interval so that our data source is invoked every ten minutes + interval: 60 * 10, + enabled: true, + } as IRefresherProperties, + } as Partial, + }, + }, + header: { + properties: { + title: "Harry Potter and the Sorcerer's Stone", + subtitle: "By J. K. Rowling", + }, + }, + tiles: { + properties: { + nodes: ["riskScore1"], + }, + }, + riskScore1: { + id: "riskScore1", + componentType: RiskScoreTileComponent.lateLoadKey, + properties: { + widgetData: { + minValue: 0, + maxValue: 5, + useStaticLabel: false, + staticLabel: undefined, + label: \\\\\\\`Average Rating\\\\\\\`, + description: \\\\\\\`Harry Potter and the Sorcerer's Stone By J. K. Rowling Average Rating Risk Score\\\\\\\`, + }, + }, + providers: { + [WellKnownProviders.DataSource]: { + // Setting the data source providerId for the tile with id "riskScore1" + providerId: + HarryPotterAverageRatingDataSource.providerId, + } as IProviderConfiguration, + [WellKnownProviders.Adapter]: { + providerId: NOVA_KPI_DATASOURCE_ADAPTER, + properties: { + componentId: "riskScore1", + propertyPath: "widgetData", + }, + } as IProviderConfiguration, + }, + }, + }, + }, +}; +\\\`, + "overview/hero/widget-configs/table.ts": \\\`// © 2022 SolarWinds Worldwide, LLC. All rights reserved. +// +// Permission is hereby granted, free of charge, to any person obtaining a copy +// of this software and associated documentation files (the "Software"), to +// deal in the Software without restriction, including without limitation the +// rights to use, copy, modify, merge, publish, distribute, sublicense, and/or +// sell copies of the Software, and to permit persons to whom the Software is +// furnished to do so, subject to the following conditions: +// +// The above copyright notice and this permission notice shall be included in +// all copies or substantial portions of the Software. +// +// THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR +// IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, +// FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE +// AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER +// LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, +// OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN +// THE SOFTWARE. + +import { + ITableWidgetColumnConfig, + ITableWidgetSorterConfig, + IWidget, + PizzagnaLayer, + RawFormatterComponent, + WellKnownProviders, +} from "@nova-ui/dashboards"; + +import { BeerDataSource } from "../data/table/beer-data-source"; + +export const tableConfig: IWidget = { + id: "tableWidgetId", + type: "table", + pizzagna: { + [PizzagnaLayer.Configuration]: { + header: { + properties: { + title: "Stupendous Suds", + subtitle: "Try These Brilliant Brews", + }, + }, + table: { + providers: { + [WellKnownProviders.DataSource]: { + providerId: BeerDataSource.providerId, + }, + }, + properties: { + configuration: { + columns: [ + { + id: "column1", + label: "Beer Name", + isActive: true, + width: 185, + formatter: { + componentType: + RawFormatterComponent.lateLoadKey, + properties: { + dataFieldIds: { + value: "name", + }, + }, + }, + }, + { + id: "column2", + label: "Tagline", + isActive: true, + width: 250, + formatter: { + componentType: + RawFormatterComponent.lateLoadKey, + properties: { + dataFieldIds: { + value: "tagline", + }, + }, + }, + }, + { + id: "column3", + label: "First Brewed", + isActive: true, + formatter: { + componentType: + RawFormatterComponent.lateLoadKey, + properties: { + dataFieldIds: { + value: "first_brewed", + }, + }, + }, + }, + { + id: "column4", + label: "Description", + isActive: true, + width: 275, + formatter: { + componentType: + RawFormatterComponent.lateLoadKey, + properties: { + dataFieldIds: { + value: "description", + }, + }, + }, + }, + ] as ITableWidgetColumnConfig[], + sorterConfiguration: { + descendantSorting: false, + sortBy: "", + } as ITableWidgetSorterConfig, + hasVirtualScroll: true, + }, + }, + }, + }, + }, +}; +\\\`, + "overview/hero/widget-configs/timeseries.ts": \\\`// © 2022 SolarWinds Worldwide, LLC. All rights reserved. +// +// Permission is hereby granted, free of charge, to any person obtaining a copy +// of this software and associated documentation files (the "Software"), to +// deal in the Software without restriction, including without limitation the +// rights to use, copy, modify, merge, publish, distribute, sublicense, and/or +// sell copies of the Software, and to permit persons to whom the Software is +// furnished to do so, subject to the following conditions: +// +// The above copyright notice and this permission notice shall be included in +// all copies or substantial portions of the Software. +// +// THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR +// IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, +// FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE +// AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER +// LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, +// OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN +// THE SOFTWARE. + +import moment from "moment/moment"; + +import { + DEFAULT_PIZZAGNA_ROOT, + IProviderConfiguration, + ISerializableTimeframe, + ITimeseriesItemConfiguration, + IWidget, + LegendPlacement, + WellKnownProviders, +} from "@nova-ui/dashboards"; + +import { BeerVsReadingMockDataSource } from "../data/timeseries-data-sources"; + +export const timeseriesConfig: IWidget = { + id: "timeseriesWidgetId", + type: "timeseries", + pizzagna: { + configuration: { + [DEFAULT_PIZZAGNA_ROOT]: { + providers: { + [WellKnownProviders.DataSource]: { + providerId: BeerVsReadingMockDataSource.providerId, + } as IProviderConfiguration, + }, + }, + header: { + properties: { + title: "Primary Leisure Activity Over Time", + subtitle: "Survey of 1000 Solarians", + }, + }, + chart: { + providers: { + [WellKnownProviders.Adapter]: { + properties: { + series: [ + { + id: "series-1", + label: "Beer Tasting", + selectedSeriesId: "series-1", + }, + { + id: "series-2", + label: "Reading", + selectedSeriesId: "series-2", + }, + ] as ITimeseriesItemConfiguration[], + }, + } as Partial, + }, + properties: { + configuration: { + legendPlacement: LegendPlacement.Right, + enableZoom: true, + leftAxisLabel: "Solarians (%)", + }, + }, + }, + timeframeSelection: { + properties: { + timeframe: { + selectedPresetId: "last7Days", + } as ISerializableTimeframe, + minDate: moment().subtract(10, "days").format(), + maxDate: moment().format(), + }, + }, + }, + }, +}; +\\\`, + "overview/overview-docs.component.ts": \\\`// © 2022 SolarWinds Worldwide, LLC. All rights reserved. +// +// Permission is hereby granted, free of charge, to any person obtaining a copy +// of this software and associated documentation files (the "Software"), to +// deal in the Software without restriction, including without limitation the +// rights to use, copy, modify, merge, publish, distribute, sublicense, and/or +// sell copies of the Software, and to permit persons to whom the Software is +// furnished to do so, subject to the following conditions: +// +// The above copyright notice and this permission notice shall be included in +// all copies or substantial portions of the Software. +// +// THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR +// IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, +// FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE +// AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER +// LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, +// OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN +// THE SOFTWARE. + +import { Component } from "@angular/core"; + +@Component({ + selector: "nui-dashboard-overview-docs", + templateUrl: "./overview-docs.component.html", + standalone: false, +}) +export class OverviewDocsComponent {} +\\\`, + "overview/overview.module.ts": \\\`// © 2022 SolarWinds Worldwide, LLC. All rights reserved. +// +// Permission is hereby granted, free of charge, to any person obtaining a copy +// of this software and associated documentation files (the "Software"), to +// deal in the Software without restriction, including without limitation the +// rights to use, copy, modify, merge, publish, distribute, sublicense, and/or +// sell copies of the Software, and to permit persons to whom the Software is +// furnished to do so, subject to the following conditions: +// +// The above copyright notice and this permission notice shall be included in +// all copies or substantial portions of the Software. +// +// THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR +// IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, +// FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE +// AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER +// LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, +// OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN +// THE SOFTWARE. + +import { CommonModule } from "@angular/common"; +import { NgModule } from "@angular/core"; +import { RouterModule } from "@angular/router"; + +import { + NuiBusyModule, + NuiButtonModule, + NuiDocsModule, + NuiIconModule, + NuiMessageModule, + NuiSwitchModule, +} from "@nova-ui/bits"; +import { + ConfiguratorHeadingService, + IFormatterDefinition, + LinkFormatterComponent, + NuiDashboardsModule, + WellKnownPathKey, + WidgetTypesService, +} from "@nova-ui/dashboards"; + +import { HeroDashboardComponent } from "./hero/dashboard/hero-dashboard.component"; +import { + HarryPotterAverageRatingDataSource, + HarryPotterRatingsCountDataSource, +} from "./hero/data/kpi-datasources"; +import { + BeerReviewCountsByCityMockDataSource, + BeerReviewCountsByCityMockDataSource2, +} from "./hero/data/proportional-datasources"; +import { BeerDataSource } from "./hero/data/table/beer-data-source"; +import { RandomUserDataSource } from "./hero/data/table/random-user-data-source"; +import { + BeerVsReadingMockDataSource, + LoungingVsFrisbeeGolfMockDataSource, +} from "./hero/data/timeseries-data-sources"; +import { OverviewDocsComponent } from "./overview-docs.component"; + +const routes = [ + { + path: "", + component: OverviewDocsComponent, + data: { + srlc: { + hideIndicator: true, + }, + showThemeSwitcher: true, + }, + }, + { + path: "hero", + component: HeroDashboardComponent, + data: { + srlc: { + hideIndicator: true, + }, + }, + }, +]; + +@NgModule({ + imports: [ + CommonModule, + NuiDashboardsModule, + NuiBusyModule, + NuiButtonModule, + NuiDocsModule, + NuiMessageModule, + NuiSwitchModule, + NuiIconModule, + RouterModule.forChild(routes), + ], + declarations: [OverviewDocsComponent, HeroDashboardComponent], + providers: [ConfiguratorHeadingService], +}) +export default class OverviewModule { + constructor(private widgetTypesService: WidgetTypesService) { + this.setupDataSourceProviders(); + this.setupProportionalLegendFormatters(); + } + + private setupDataSourceProviders() { + this.setDataSourceProviders("table", [ + RandomUserDataSource.providerId, + BeerDataSource.providerId, + ]); + this.setDataSourceProviders("kpi", [ + HarryPotterAverageRatingDataSource.providerId, + HarryPotterRatingsCountDataSource.providerId, + ]); + this.setDataSourceProviders("risk-score", [ + HarryPotterAverageRatingDataSource.providerId, + HarryPotterRatingsCountDataSource.providerId, + ]); + this.setDataSourceProviders("proportional", [ + BeerReviewCountsByCityMockDataSource.providerId, + BeerReviewCountsByCityMockDataSource2.providerId, + ]); + this.setDataSourceProviders("timeseries", [ + BeerVsReadingMockDataSource.providerId, + LoungingVsFrisbeeGolfMockDataSource.providerId, + ]); + } + + private setDataSourceProviders(type: string, providers: string[]) { + const widgetTemplate = this.widgetTypesService.getWidgetType(type, 1); + this.widgetTypesService.setNode( + widgetTemplate, + "configurator", + WellKnownPathKey.DataSourceProviders, + providers + ); + } + + private setupProportionalLegendFormatters() { + const formatters: IFormatterDefinition[] = [ + { + componentType: LinkFormatterComponent.lateLoadKey, + label: $localize\\\\\\\`Link\\\\\\\`, + dataTypes: { + value: "label", + link: "link", + }, + }, + ]; + + const widgetTemplate = this.widgetTypesService.getWidgetType( + "proportional", + 1 + ); + this.widgetTypesService.setNode( + widgetTemplate, + "configurator", + WellKnownPathKey.Formatters, + formatters + ); + } +} +\\\`, + "tutorials/customization/configurator-section/custom-configurator-section/custom-configurator-section.example.component.ts": \\\`// © 2022 SolarWinds Worldwide, LLC. All rights reserved. +// +// Permission is hereby granted, free of charge, to any person obtaining a copy +// of this software and associated documentation files (the "Software"), to +// deal in the Software without restriction, including without limitation the +// rights to use, copy, modify, merge, publish, distribute, sublicense, and/or +// sell copies of the Software, and to permit persons to whom the Software is +// furnished to do so, subject to the following conditions: +// +// The above copyright notice and this permission notice shall be included in +// all copies or substantial portions of the Software. +// +// THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR +// IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, +// FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE +// AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER +// LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, +// OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN +// THE SOFTWARE. + +import { HttpClient, HttpErrorResponse } from "@angular/common/http"; +import { + ChangeDetectionStrategy, + ChangeDetectorRef, + Component, + EventEmitter, + Injectable, + Input, + OnChanges, + OnDestroy, + OnInit, + Output, + SimpleChanges, +} from "@angular/core"; +import { FormBuilder, FormGroup, Validators } from "@angular/forms"; +import { GridsterConfig, GridsterItem } from "angular-gridster2"; +// eslint-disable-next-line import/no-deprecated +import { BehaviorSubject, combineLatest, Observable } from "rxjs"; +// eslint-disable-next-line import/no-deprecated +import { finalize, map, startWith } from "rxjs/operators"; + +import { DataSourceService, IFilteringOutputs } from "@nova-ui/bits"; +import { + ComponentRegistryService, + DATA_SOURCE, + DEFAULT_PIZZAGNA_ROOT, + IDashboard, + IHasChangeDetector, + IHasForm, + IKpiData, + IProviderConfiguration, + IRefresherProperties, + IWidget, + IWidgets, + KpiComponent, + NOVA_KPI_DATASOURCE_ADAPTER, + PizzagnaLayer, + ProviderRegistryService, + WellKnownPathKey, + WellKnownProviders, + WidgetTypesService, +} from "@nova-ui/dashboards"; + +/** + * A custom version of the KpiDescriptionConfigurationComponent provided by the dashboards framework. + * --- + * For this example, the existing background color selection functionality has been replaced by custom + * template content. + */ +@Component({ + selector: "custom-kpi-description-configuration", + template: \\\\\\\` + + +
+
+ + + +
+ + +
+
+ Custom Content +
+
+ The default version of this configurator section + displays a background color selector here. +
+
+ + +
+ + + +
+
+
+ \\\\\\\`, + styleUrls: ["./custom-configurator-section.example.component.less"], + changeDetection: ChangeDetectionStrategy.OnPush, + standalone: false, +}) +// Remember to declare this class in the parent module +export class CustomKpiDescriptionConfigurationComponent + implements OnInit, OnChanges, IHasChangeDetector, IHasForm +{ + // Ensure that the lateLoadKey value matches class name + public static lateLoadKey = "CustomKpiDescriptionConfigurationComponent"; + + @Input() componentId: string; + @Input() configurableUnits: boolean; + + @Input() label: string = ""; + @Input() units: string = ""; + + @Output() formReady = new EventEmitter(); + + public form: FormGroup; + public subtitle$: Observable; + + constructor( + public changeDetector: ChangeDetectorRef, + private formBuilder: FormBuilder + ) {} + + public ngOnInit(): void { + this.form = this.formBuilder.group({ + label: [this.label, [Validators.required]], + }); + + if (this.configurableUnits) { + this.form.addControl("units", this.formBuilder.control(this.units)); + } + + const label = this.form.get("label"); + // eslint-disable-next-line import/no-deprecated + const labelValue = label?.valueChanges.pipe(startWith(label?.value)); + + // eslint-disable-next-line import/no-deprecated + this.subtitle$ = combineLatest([ + labelValue?.pipe(map((t) => t || $localize\\\\\\\`no label\\\\\\\`)), + ]).pipe(map((labels) => labels.join(", "))); + + this.formReady.emit(this.form); + } + + public ngOnChanges(changes: SimpleChanges): void { + if (changes.label) { + this.form.patchValue({ label: changes.label.currentValue }); + } + if (changes.units) { + this.form.patchValue({ units: changes.units.currentValue }); + } + } +} + +/** + * A simple KPI data source to retrieve the average rating of Harry Potter and the Sorcerer's Stone (book) via googleapis + */ +@Injectable() +export class AverageRatingKpiDataSource + extends DataSourceService + implements OnDestroy +{ + // This is the ID we'll use to identify the provider + public static providerId = "AverageRatingKpiDataSource"; + + // Use this subject to communicate the data source's busy state + public busy = new BehaviorSubject(false); + + constructor(private http: HttpClient) { + super(); + } + + // In this example, getFilteredData is invoked every 10 minutes (Take a look at the refresher + // provider definition in the widget configuration below to see how the interval is set) + public async getFilteredData(): Promise { + this.busy.next(true); + return new Promise((resolve) => { + // *** Make a resource request to an external API (if needed) + this.http + .get("https://www.googleapis.com/books/v1/volumes/5MQFrgEACAAJ") + .pipe(finalize(() => this.busy.next(false))) + .subscribe({ + next: (data: any) => { + resolve({ + result: { + value: data.volumeInfo.averageRating, + }, + }); + }, + error: (error: HttpErrorResponse) => { + resolve({ + result: null, + error: { + type: error.status, + }, + }); + }, + }); + }); + } + + public ngOnDestroy(): void { + this.outputsSubject.complete(); + } +} + +/** + * A simple KPI data source to retrieve the ratings count of Harry Potter and the Sorcerer's Stone (book) via googleapis + */ +@Injectable() +export class RatingsCountKpiDataSource + extends DataSourceService + implements OnDestroy +{ + public static providerId = "RatingsCountKpiDataSource"; + + // Use this subject to communicate the data source's busy state + public busy = new BehaviorSubject(false); + + constructor(private http: HttpClient) { + super(); + } + + public async getFilteredData(): Promise { + this.busy.next(true); + return new Promise((resolve) => { + this.http + .get("https://www.googleapis.com/books/v1/volumes/5MQFrgEACAAJ") + .pipe(finalize(() => this.busy.next(false))) + .subscribe({ + next: (data: any) => { + resolve({ + result: { + value: data.volumeInfo.ratingsCount, + }, + }); + }, + error: (error: HttpErrorResponse) => { + resolve({ + result: null, + error: { + type: error.status, + }, + }); + }, + }); + }); + } + + public ngOnDestroy(): void { + this.outputsSubject.complete(); + } +} + +/** + * A component that instantiates the dashboard + */ +@Component({ + selector: "custom-configurator-section-example", + templateUrl: "./custom-configurator-section.example.component.html", + styleUrls: ["./custom-configurator-section.example.component.less"], + standalone: false, +}) +export class CustomConfiguratorSectionExampleComponent implements OnInit { + // This variable will hold all the data needed to define the layout and behavior of the widgets. + // Pass this to the dashboard component's dashboard input in the template. + public dashboard: IDashboard | undefined; + + // Angular gridster requires a configuration object even if it's empty. + // Pass this to the dashboard component's gridsterConfig input in the template. + public gridsterConfig: GridsterConfig = {}; + + // Boolean which dashboard takes in as an input if its true it allows you to move widgets around. + public editMode: boolean = false; + + constructor( + // WidgetTypesService provides the widget's necessary structure information + private widgetTypesService: WidgetTypesService, + + // In general, the ProviderRegistryService is used for making entities available for injection into dynamically loaded components. + private providerRegistry: ProviderRegistryService, + + // Inject the ComponentRegistryService to make our custom component available for late loading by the dashboards framework + private componentRegistry: ComponentRegistryService, + + private changeDetectorRef: ChangeDetectorRef + ) {} + + public ngOnInit(): void { + // Grab the widget's default template which will be needed as a parameter for setNode. + const widgetTemplate = this.widgetTypesService.getWidgetType("kpi", 1); + + // Replace the default KPI description configuration component with our custom one. + // Note: This could also be done in the parent module's constructor to give + // multiple dashboards access to the same custom configurator section. + this.widgetTypesService.setNode( + widgetTemplate, + "configurator", + WellKnownPathKey.TileDescriptionConfigComponentType, + CustomKpiDescriptionConfigurationComponent.lateLoadKey + ); + + // Register the custom configurator section with the component registry to make it available + // for late loading by the dashboards framework. + this.componentRegistry.registerByLateLoadKey( + CustomKpiDescriptionConfigurationComponent + ); + + // Register our data sources as dropdown options in the widget editor/configurator + // Note: This could also be done in the parent module's constructor so that + // multiple dashboards could have access to the same widget template modification. + this.widgetTypesService.setNode( + // This is the template we grabbed above with getWidgetType + widgetTemplate, + // We are setting the editor/configurator part of the widget template + "configurator", + // This indicates which node you are changing and we want to change + // the data source providers available for selection in the editor. + WellKnownPathKey.DataSourceProviders, + // We are setting the data sources available for selection in the editor + [ + AverageRatingKpiDataSource.providerId, + RatingsCountKpiDataSource.providerId, + ] + ); + + // Register the data sources available for injection into the KPI tiles. + // Note: Each tile of a KPI widget is assigned its own instance of a data source + this.providerRegistry.setProviders({ + [AverageRatingKpiDataSource.providerId]: { + provide: DATA_SOURCE, + useClass: AverageRatingKpiDataSource, + // Any dependencies that need to be injected into the provider must be listed here + deps: [HttpClient], + }, + [RatingsCountKpiDataSource.providerId]: { + provide: DATA_SOURCE, + useClass: RatingsCountKpiDataSource, + deps: [HttpClient], + }, + }); + + this.initializeDashboard(); + } + + public initializeDashboard(): void { + // We're using a static configuration object for this example (see widgetConfig at the bottom of the file), + // but this is where the widget's configuration could potentially be populated from a database + const kpiWidget = widgetConfig; + const widgetIndex: IWidgets = { + // Complete the KPI widget with information coming from its type definition + [kpiWidget.id]: + this.widgetTypesService.mergeWithWidgetType(kpiWidget), + }; + + // Setting the widget dimensions and position (this is for gridster) + const positions: Record = { + [kpiWidget.id]: { + cols: 4, + rows: 6, + y: 0, + x: 0, + }, + }; + + // Finally, assigning the variables we created above to the dashboard + this.dashboard = { + positions, + widgets: widgetIndex, + }; + } + + /** Used for restoring widgets state */ + public reInitializeDashboard(): void { + // destroys the components and their providers so the dashboard can re init data + this.dashboard = undefined; + this.changeDetectorRef.detectChanges(); + + this.initializeDashboard(); + } +} + +const widgetConfig: IWidget = { + id: "widget1", + type: "kpi", + pizzagna: { + [PizzagnaLayer.Configuration]: { + [DEFAULT_PIZZAGNA_ROOT]: { + providers: { + [WellKnownProviders.Refresher]: { + properties: { + // Configuring the refresher interval so that our data source is invoked every ten minutes + interval: 60 * 10, + enabled: true, + } as IRefresherProperties, + } as Partial, + }, + }, + header: { + properties: { + title: "Harry Potter and the Sorcerer's Stone", + subtitle: "By J. K. Rowling", + }, + }, + tiles: { + properties: { + nodes: ["kpi1"], + }, + }, + kpi1: { + id: "kpi1", + componentType: KpiComponent.lateLoadKey, + properties: { + widgetData: { + units: "out of 5 Stars", + label: "Average Rating", + }, + }, + providers: { + [WellKnownProviders.DataSource]: { + // Setting the data source providerId for the tile with id "kpi1" + providerId: AverageRatingKpiDataSource.providerId, + } as IProviderConfiguration, + [WellKnownProviders.Adapter]: { + providerId: NOVA_KPI_DATASOURCE_ADAPTER, + properties: { + componentId: "kpi1", + propertyPath: "widgetData", + }, + } as IProviderConfiguration, + }, + }, + }, + }, +}; +\\\`, + "tutorials/customization/configurator-section/custom-configurator-section-docs.component.ts": \\\`// © 2022 SolarWinds Worldwide, LLC. All rights reserved. +// +// Permission is hereby granted, free of charge, to any person obtaining a copy +// of this software and associated documentation files (the "Software"), to +// deal in the Software without restriction, including without limitation the +// rights to use, copy, modify, merge, publish, distribute, sublicense, and/or +// sell copies of the Software, and to permit persons to whom the Software is +// furnished to do so, subject to the following conditions: +// +// The above copyright notice and this permission notice shall be included in +// all copies or substantial portions of the Software. +// +// THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR +// IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, +// FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE +// AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER +// LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, +// OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN +// THE SOFTWARE. + +import { Component } from "@angular/core"; + +@Component({ + selector: "custom-configurator-section-docs", + templateUrl: "./custom-configurator-section-docs.component.html", + standalone: false, +}) +export class CustomConfiguratorSectionDocsComponent {} +\\\`, + "tutorials/customization/configurator-section/custom-configurator-section.module.ts": \\\`// © 2022 SolarWinds Worldwide, LLC. All rights reserved. +// +// Permission is hereby granted, free of charge, to any person obtaining a copy +// of this software and associated documentation files (the "Software"), to +// deal in the Software without restriction, including without limitation the +// rights to use, copy, modify, merge, publish, distribute, sublicense, and/or +// sell copies of the Software, and to permit persons to whom the Software is +// furnished to do so, subject to the following conditions: +// +// The above copyright notice and this permission notice shall be included in +// all copies or substantial portions of the Software. +// +// THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR +// IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, +// FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE +// AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER +// LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, +// OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN +// THE SOFTWARE. + +import { CommonModule } from "@angular/common"; +import { HttpClientModule } from "@angular/common/http"; +import { NgModule } from "@angular/core"; +import { ReactiveFormsModule } from "@angular/forms"; +import { RouterModule } from "@angular/router"; + +import { + NuiButtonModule, + NuiDocsModule, + NuiFormFieldModule, + NuiIconModule, + NuiMessageModule, + NuiSwitchModule, + NuiTextboxModule, + DEMO_PATH_TOKEN, +} from "@nova-ui/bits"; +import { + NuiDashboardConfiguratorModule, + NuiDashboardsModule, +} from "@nova-ui/dashboards"; + +import { + CustomConfiguratorSectionExampleComponent, + CustomKpiDescriptionConfigurationComponent, +} from "./custom-configurator-section/custom-configurator-section.example.component"; +import { CustomConfiguratorSectionDocsComponent } from "./custom-configurator-section-docs.component"; +import { getDemoFiles } from "../../../../../demo-files-factory"; + +const routes = [ + { + path: "", + component: CustomConfiguratorSectionDocsComponent, + data: { + srlc: { + hideIndicator: true, + }, + showThemeSwitcher: true, + }, + }, + { + path: "example", + component: CustomConfiguratorSectionExampleComponent, + data: { + srlc: { + hideIndicator: true, + }, + }, + }, +]; + +@NgModule({ + imports: [ + CommonModule, + ReactiveFormsModule, + HttpClientModule, + NuiDashboardsModule, + NuiDashboardConfiguratorModule, + NuiDocsModule, + NuiFormFieldModule, + NuiIconModule, + NuiMessageModule, + NuiSwitchModule, + NuiTextboxModule, + NuiButtonModule, + RouterModule.forChild(routes), + ], + declarations: [ + CustomConfiguratorSectionDocsComponent, + CustomKpiDescriptionConfigurationComponent, + CustomConfiguratorSectionExampleComponent, + ], + providers: [ + { + provide: DEMO_PATH_TOKEN, + useValue: getDemoFiles("configurator-section"), + }, + ], +}) +export default class CustomConfiguratorSectionModule {} +\\\`, + "tutorials/customization/customization.module.ts": \\\`// © 2022 SolarWinds Worldwide, LLC. All rights reserved. +// +// Permission is hereby granted, free of charge, to any person obtaining a copy +// of this software and associated documentation files (the "Software"), to +// deal in the Software without restriction, including without limitation the +// rights to use, copy, modify, merge, publish, distribute, sublicense, and/or +// sell copies of the Software, and to permit persons to whom the Software is +// furnished to do so, subject to the following conditions: +// +// The above copyright notice and this permission notice shall be included in +// all copies or substantial portions of the Software. +// +// THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR +// IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, +// FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE +// AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER +// LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, +// OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN +// THE SOFTWARE. + +import { NgModule, Type } from "@angular/core"; +import { RouterModule, Routes } from "@angular/router"; + +import { ConfiguratorHeadingService } from "@nova-ui/dashboards"; + +enum CustomizationModuleRoute { + ConfiguratorSection = "configurator-section", + Widget = "widget", + Formatter = "formatter", + DataSourceConfigurator = "data-source-configurator", +} + +const routes: Routes = [ + { + path: CustomizationModuleRoute.ConfiguratorSection, + loadChildren: async () => + import( + "./configurator-section/custom-configurator-section.module" + ) as object as Promise>, + }, + { + path: CustomizationModuleRoute.Widget, + loadChildren: async () => + import("./widget/custom-widget.module") as object as Promise< + Type + >, + }, + { + path: CustomizationModuleRoute.Formatter, + loadChildren: async () => + import("./formatter/custom-formatter.module") as object as Promise< + Type + >, + }, + { + path: CustomizationModuleRoute.DataSourceConfigurator, + loadChildren: async () => + import( + "./data-source-configurator/custom-data-source-configurator.module" + ) as object as Promise>, + }, +]; + +@NgModule({ + imports: [RouterModule.forChild(routes)], + providers: [ConfiguratorHeadingService], +}) +export default class CustomizationModule {} +\\\`, + "tutorials/customization/data-source-configurator/custom-data-source-configurator-docs.component.ts": \\\`// © 2022 SolarWinds Worldwide, LLC. All rights reserved. +// +// Permission is hereby granted, free of charge, to any person obtaining a copy +// of this software and associated documentation files (the "Software"), to +// deal in the Software without restriction, including without limitation the +// rights to use, copy, modify, merge, publish, distribute, sublicense, and/or +// sell copies of the Software, and to permit persons to whom the Software is +// furnished to do so, subject to the following conditions: +// +// The above copyright notice and this permission notice shall be included in +// all copies or substantial portions of the Software. +// +// THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR +// IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, +// FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE +// AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER +// LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, +// OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN +// THE SOFTWARE. + +import { Component } from "@angular/core"; + +@Component({ + selector: "nui-custom-data-source-configurator-docs", + templateUrl: "./custom-data-source-configurator-docs.component.html", + standalone: false, +}) +export class CustomDataSourceConfiguratorDocComponent {} +\\\`, + "tutorials/customization/data-source-configurator/custom-data-source-configurator.module.ts": \\\`// © 2022 SolarWinds Worldwide, LLC. All rights reserved. +// +// Permission is hereby granted, free of charge, to any person obtaining a copy +// of this software and associated documentation files (the "Software"), to +// deal in the Software without restriction, including without limitation the +// rights to use, copy, modify, merge, publish, distribute, sublicense, and/or +// sell copies of the Software, and to permit persons to whom the Software is +// furnished to do so, subject to the following conditions: +// +// The above copyright notice and this permission notice shall be included in +// all copies or substantial portions of the Software. +// +// THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR +// IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, +// FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE +// AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER +// LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, +// OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN +// THE SOFTWARE. + +import { NgModule } from "@angular/core"; +import { ReactiveFormsModule } from "@angular/forms"; +import { RouterModule, Routes } from "@angular/router"; + +// eslint-disable-next-line max-len +import { + NuiButtonModule, + NuiDocsModule, + NuiFormFieldModule, + NuiIconModule, + NuiMessageModule, + NuiSelectV2Module, + NuiSwitchModule, + NuiTextboxModule, + NuiValidationMessageModule, + DEMO_PATH_TOKEN, +} from "@nova-ui/bits"; +import { + NuiDashboardConfiguratorModule, + NuiDashboardsModule, +} from "@nova-ui/dashboards"; + +import { CustomDataSourceConfiguratorDocComponent } from "./custom-data-source-configurator-docs.component"; +import { + CustomDataSourceConfiguratorExampleComponent, + HarryPotterDataSourceConfiguratorComponent, +} from "./example/custom-data-source-configurator-example.component"; +import { getDemoFiles } from "../../../../../demo-files-factory"; + +const routes: Routes = [ + { + path: "", + component: CustomDataSourceConfiguratorDocComponent, + data: { + srlc: { + hideIndicator: true, + }, + showThemeSwitcher: true, + }, + }, +]; + +@NgModule({ + imports: [ + RouterModule.forChild(routes), + NuiDocsModule, + NuiButtonModule, + NuiMessageModule, + NuiDashboardConfiguratorModule, + NuiDashboardsModule, + NuiFormFieldModule, + NuiTextboxModule, + NuiSwitchModule, + NuiSelectV2Module, + NuiValidationMessageModule, + NuiIconModule, + ReactiveFormsModule, + ], + declarations: [ + CustomDataSourceConfiguratorDocComponent, + CustomDataSourceConfiguratorExampleComponent, + HarryPotterDataSourceConfiguratorComponent, + ], + providers: [ + { + provide: DEMO_PATH_TOKEN, + useValue: getDemoFiles("data-source-configurator"), + }, + ], +}) +export default class CustomDataSourceConfiguratorModuleRoute {} +\\\`, + "tutorials/customization/data-source-configurator/example/custom-data-source-configurator-example.component.ts": \\\`// © 2022 SolarWinds Worldwide, LLC. All rights reserved. +// +// Permission is hereby granted, free of charge, to any person obtaining a copy +// of this software and associated documentation files (the "Software"), to +// deal in the Software without restriction, including without limitation the +// rights to use, copy, modify, merge, publish, distribute, sublicense, and/or +// sell copies of the Software, and to permit persons to whom the Software is +// furnished to do so, subject to the following conditions: +// +// The above copyright notice and this permission notice shall be included in +// all copies or substantial portions of the Software. +// +// THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR +// IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, +// FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE +// AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER +// LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, +// OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN +// THE SOFTWARE. + +import { HttpClient, HttpErrorResponse } from "@angular/common/http"; +import { + ChangeDetectorRef, + Component, + Inject, + Injectable, + Injector, + OnDestroy, + OnInit, +} from "@angular/core"; +import { FormBuilder, Validators } from "@angular/forms"; +import { GridsterConfig, GridsterItem } from "angular-gridster2"; +import { BehaviorSubject } from "rxjs"; +import { finalize } from "rxjs/operators"; + +import { + DataSourceService, + EventBus, + IEvent, + IFilteringOutputs, + LoggerService, +} from "@nova-ui/bits"; +import { + ComponentRegistryService, + ConfiguratorHeadingService, + DataSourceConfigurationV2Component, + DATA_SOURCE, + DEFAULT_PIZZAGNA_ROOT, + IConfigurable, + IDashboard, + IKpiData, + IProperties, + IProviderConfiguration, + IRefresherProperties, + IWidget, + IWidgets, + KpiComponent, + NOVA_KPI_DATASOURCE_ADAPTER, + PizzagnaLayer, + PIZZAGNA_EVENT_BUS, + ProviderRegistryService, + WellKnownPathKey, + WellKnownProviders, + WidgetTypesService, +} from "@nova-ui/dashboards"; + +/** + * This component will serve as the data source accordion in the configurator. + */ +@Component({ + selector: "harry-potter-data-source-configurator", + styleUrls: ["./custom-data-source-configurator-example.component.less"], + template: \\\\\\\` + +
+ +
+ Data Source +
+ Harry Potter Books +
+
+
+
+ + + + {{ book.title }} + + + +
+
+ + + + {{ metric.label }} + + + +
+
+ \\\\\\\`, + standalone: false, +}) +@Injectable() +export class HarryPotterDataSourceConfiguratorComponent + extends DataSourceConfigurationV2Component + implements OnInit +{ + // This lateLoadKey allows the component to be able to be registered by the componentRegistry + public static lateLoadKey = "HarryPotterDataSourceConfiguratorComponent"; + + // Array of books that will populate the book select + public books = [ + { + id: "5MQFrgEACAAJ", + title: $localize\\\\\\\`Harry Potter and the Sorcerer's Stone\\\\\\\`, + }, + { + id: "5iTebBW-w7QC", + title: $localize\\\\\\\`Harry Potter and the Chamber of Secrets\\\\\\\`, + }, + ]; + + // Array of metrics that will populate the metric select + public metrics = [ + { + id: "averageRating", + label: $localize\\\\\\\`Average Rating\\\\\\\`, + }, + { + id: "ratingsCount", + label: $localize\\\\\\\`Ratings Count\\\\\\\`, + }, + ]; + + // These need to be injected because DataSourceConfigurationV2Component uses them + constructor( + changeDetector: ChangeDetectorRef, + configuratorHeading: ConfiguratorHeadingService, + formBuilder: FormBuilder, + providerRegistryService: ProviderRegistryService, + @Inject(PIZZAGNA_EVENT_BUS) eventBus: EventBus, + injector: Injector, + logger: LoggerService + ) { + super( + changeDetector, + configuratorHeading, + formBuilder, + providerRegistryService, + eventBus, + injector, + logger + ); + } + + // Overriding 'ngOnInit' to add custom controls to the 'properties' form group + public ngOnInit(): void { + super.ngOnInit(); + + // Overriding the 'properties' control on the form to create a form group that accommodates our custom properties + this.form.setControl( + "properties", + this.formBuilder.group({ + bookId: [this.properties?.bookId ?? "", Validators.required], + metric: [this.properties?.metric ?? "", Validators.required], + }) + ); + // The default data source control has a required validator we're removing that validator here since we aren't using it. + this.form.setControl("dataSource", this.formBuilder.control(null)); + // Here we set the providerId to our only data source so when a new tile gets created it will default to it. + this.form.get("providerId")?.setValue(AcmeKpiDataSource.providerId); + // Here we subscribe to the form and if there are any changes we invoke the data source + this.form.valueChanges.subscribe((value) => { + if (!value.providerId) { + return; + } + this.invokeDataSource(value); + }); + } +} + +/** + * A simple KPI data source to retrieve the average rating of Harry Potter and the Sorcerer's Stone (book) via googleapis + */ +@Injectable() +export class AcmeKpiDataSource + extends DataSourceService + implements OnDestroy, IConfigurable +{ + // This is the ID we'll use to identify the provider + public static providerId = "AcmeKpiDataSource"; + + // Use this subject to communicate the data source's busy state + public busy = new BehaviorSubject(false); + + public properties: IProperties; + + constructor(private http: HttpClient) { + super(); + } + + // This function MUST be implemented in order to receive property updates from our configurator + public updateConfiguration(properties: IProperties): void { + // Saving the properties because we will need it for this data source. + this.properties = properties; + } + + // In this example, getFilteredData is invoked every 10 minutes (Take a look at the refresher + // provider definition in the widget configuration below to see how the interval is set) + public async getFilteredData(): Promise { + // For loading indicator to show + this.busy.next(true); + return new Promise((resolve) => { + // *** Make a resource request to an external API (if needed) + this.http + .get( + \\\\\\\`https://www.googleapis.com/books/v1/volumes/\\\\\\\${this.properties?.bookId}\\\\\\\` + ) + // For loading indicator to be hidden + .pipe(finalize(() => this.busy.next(false))) + .subscribe({ + next: (data: any) => { + resolve({ + result: { + value: data.volumeInfo[this.properties?.metric], + }, + }); + }, + error: (error: HttpErrorResponse) => { + resolve({ + result: null, + error: { + type: error.status, + }, + }); + }, + }); + }); + } + + public ngOnDestroy(): void { + this.outputsSubject.complete(); + } +} + +/** + * A component that instantiates the dashboard + */ +@Component({ + selector: "custom-data-source-configurator-example", + templateUrl: "./custom-data-source-configurator-example.component.html", + styleUrls: ["./custom-data-source-configurator-example.component.less"], + standalone: false, +}) +export class CustomDataSourceConfiguratorExampleComponent implements OnInit { + // This variable will hold all the data needed to define the layout and behavior of the widgets. + // Pass this to the dashboard component's dashboard input in the template. + public dashboard: IDashboard; + + // Angular gridster requires a configuration object even if it's empty. + // Pass this to the dashboard component's gridsterConfig input in the template. + public gridsterConfig: GridsterConfig = {}; + + // Boolean which dashboard takes in as an input if its true it allows you to move widgets around. + public editMode: boolean = false; + + constructor( + // WidgetTypesService provides the widget's necessary structure information + private widgetTypesService: WidgetTypesService, + + // In general, the ProviderRegistryService is used for making entities available for injection into dynamically loaded components. + private providerRegistry: ProviderRegistryService, + + // Inject the ComponentRegistryService to make our custom component available for late loading by the dashboards framework + private componentRegistry: ComponentRegistryService + ) {} + + public ngOnInit(): void { + // Registering the new data source configurator so it can be used. + this.componentRegistry.registerByLateLoadKey( + HarryPotterDataSourceConfiguratorComponent + ); + // Registering the data source for injection into the KPI tile. + // Note: Each tile of a KPI widget is assigned its own instance of the data source + this.providerRegistry.setProviders({ + [AcmeKpiDataSource.providerId]: { + provide: DATA_SOURCE, + useClass: AcmeKpiDataSource, + // Any dependencies that need to be injected into the provider must be listed here + deps: [HttpClient], + }, + }); + + const kpiWidgetTemplate = this.widgetTypesService.getWidgetType( + "kpi", + 1 + ); + + this.widgetTypesService.setNode( + // This is the template we grabbed above with getWidgetType + kpiWidgetTemplate, + // We are setting the editor/configurator part of the widget template + "configurator", + // This is the path to go to the data source config component type. + WellKnownPathKey.DataSourceConfigComponentType, + // We are changing it to use the component we just created above instead of the default. + HarryPotterDataSourceConfiguratorComponent.lateLoadKey + ); + + // We're using a static configuration object for this example, but this is where + // the widget's configuration could potentially be populated from a database + const kpiWidget = widgetConfig; + const widgetIndex: IWidgets = { + // Complete the KPI widget with information coming from its type definition + [kpiWidget.id]: + this.widgetTypesService.mergeWithWidgetType(kpiWidget), + }; + + // Setting the widget dimensions and position (this is for gridster) + const positions: Record = { + [kpiWidget.id]: { + cols: 4, + rows: 6, + y: 0, + x: 0, + }, + }; + + // Finally, assigning the variables we created above to the dashboard + this.dashboard = { + positions, + widgets: widgetIndex, + }; + } +} + +const widgetConfig: IWidget = { + id: "widget1", + type: "kpi", + pizzagna: { + [PizzagnaLayer.Configuration]: { + [DEFAULT_PIZZAGNA_ROOT]: { + providers: { + [WellKnownProviders.Refresher]: { + properties: { + // Configuring the refresher interval so that our data source is invoked every ten minutes + interval: 60 * 10, + enabled: true, + } as IRefresherProperties, + } as Partial, + }, + }, + header: { + properties: { + title: "Harry Potter and the Sorcerer's Stone", + subtitle: "By J. K. Rowling", + }, + }, + tiles: { + properties: { + nodes: ["kpi1"], + }, + }, + kpi1: { + id: "kpi1", + componentType: KpiComponent.lateLoadKey, + properties: { + widgetData: { + units: "out of 5 Stars", + label: "Average Rating", + }, + }, + providers: { + [WellKnownProviders.DataSource]: { + // Setting the data source providerId for the tile with id "kpi1" + providerId: AcmeKpiDataSource.providerId, + properties: { + bookId: "5MQFrgEACAAJ", + metric: "averageRating", + }, + } as IProviderConfiguration, + [WellKnownProviders.Adapter]: { + providerId: NOVA_KPI_DATASOURCE_ADAPTER, + properties: { + componentId: "kpi1", + propertyPath: "widgetData", + }, + } as IProviderConfiguration, + }, + }, + }, + }, +}; +\\\`, + "tutorials/customization/formatter/custom-formatter.module.ts": \\\`// © 2022 SolarWinds Worldwide, LLC. All rights reserved. +// +// Permission is hereby granted, free of charge, to any person obtaining a copy +// of this software and associated documentation files (the "Software"), to +// deal in the Software without restriction, including without limitation the +// rights to use, copy, modify, merge, publish, distribute, sublicense, and/or +// sell copies of the Software, and to permit persons to whom the Software is +// furnished to do so, subject to the following conditions: +// +// The above copyright notice and this permission notice shall be included in +// all copies or substantial portions of the Software. +// +// THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR +// IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, +// FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE +// AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER +// LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, +// OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN +// THE SOFTWARE. + +import { NgModule } from "@angular/core"; +import { ReactiveFormsModule } from "@angular/forms"; +import { RouterModule, Routes } from "@angular/router"; + +// eslint-disable-next-line max-len +import { + NuiButtonModule, + NuiDocsModule, + NuiFormFieldModule, + NuiIconModule, + NuiMessageModule, + NuiSelectV2Module, + NuiSwitchModule, + NuiTextboxModule, + NuiValidationMessageModule, + DEMO_PATH_TOKEN, +} from "@nova-ui/bits"; +import { NuiDashboardsModule } from "@nova-ui/dashboards"; + +import { CustomDonutContentFormatterDocComponent } from "./donut-content-formatter-example/custom-donut-content-formatter-docs.component"; +import { + CustomDonutContentFormatterComponent, + CustomDonutContentFormatterConfiguratorComponent, + CustomDonutContentFormatterExampleComponent, +} from "./donut-content-formatter-example/custom-donut-content-formatter-example.component"; +import { CustomFormatterDocComponent } from "./formatter-example/custom-formatter-docs.component"; +import { + CustomFormatterComponent, + CustomFormatterConfiguratorComponent, + CustomFormatterExampleComponent, +} from "./formatter-example/custom-formatter-example.component"; +import { getDemoFiles } from "../../../../../demo-files-factory"; + +const routes: Routes = [ + { + path: "table-formatter", + component: CustomFormatterDocComponent, + data: { + srlc: { + hideIndicator: true, + }, + showThemeSwitcher: true, + }, + }, + { + path: "donut-content-formatter", + component: CustomDonutContentFormatterDocComponent, + data: { + srlc: { + hideIndicator: true, + }, + showThemeSwitcher: true, + }, + }, +]; + +@NgModule({ + imports: [ + RouterModule.forChild(routes), + NuiDocsModule, + NuiButtonModule, + NuiMessageModule, + NuiDashboardsModule, + NuiFormFieldModule, + NuiTextboxModule, + NuiSwitchModule, + NuiSelectV2Module, + NuiValidationMessageModule, + NuiIconModule, + ReactiveFormsModule, + ], + declarations: [ + CustomDonutContentFormatterComponent, + CustomDonutContentFormatterExampleComponent, + CustomDonutContentFormatterConfiguratorComponent, + CustomDonutContentFormatterDocComponent, + CustomFormatterDocComponent, + CustomFormatterExampleComponent, + CustomFormatterConfiguratorComponent, + CustomFormatterComponent, + ], + providers: [ + { + provide: DEMO_PATH_TOKEN, + useValue: getDemoFiles("formatter"), + }, + ], +}) +export default class CustomFormatterModuleRoute {} +\\\`, + "tutorials/customization/formatter/donut-content-formatter-example/custom-donut-content-formatter-docs.component.ts": \\\`// © 2022 SolarWinds Worldwide, LLC. All rights reserved. +// +// Permission is hereby granted, free of charge, to any person obtaining a copy +// of this software and associated documentation files (the "Software"), to +// deal in the Software without restriction, including without limitation the +// rights to use, copy, modify, merge, publish, distribute, sublicense, and/or +// sell copies of the Software, and to permit persons to whom the Software is +// furnished to do so, subject to the following conditions: +// +// The above copyright notice and this permission notice shall be included in +// all copies or substantial portions of the Software. +// +// THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR +// IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, +// FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE +// AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER +// LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, +// OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN +// THE SOFTWARE. + +import { Component } from "@angular/core"; + +@Component({ + selector: "nui-custom-donut-content-formatter-docs", + templateUrl: "./custom-donut-content-formatter-docs.component.html", + standalone: false, +}) +export class CustomDonutContentFormatterDocComponent {} +\\\`, + "tutorials/customization/formatter/donut-content-formatter-example/custom-donut-content-formatter-example.component.ts": \\\`// © 2022 SolarWinds Worldwide, LLC. All rights reserved. +// +// Permission is hereby granted, free of charge, to any person obtaining a copy +// of this software and associated documentation files (the "Software"), to +// deal in the Software without restriction, including without limitation the +// rights to use, copy, modify, merge, publish, distribute, sublicense, and/or +// sell copies of the Software, and to permit persons to whom the Software is +// furnished to do so, subject to the following conditions: +// +// The above copyright notice and this permission notice shall be included in +// all copies or substantial portions of the Software. +// +// THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR +// IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, +// FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE +// AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER +// LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, +// OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN +// THE SOFTWARE. + +import { HttpClient } from "@angular/common/http"; +import { + ChangeDetectorRef, + Component, + Injectable, + Input, + OnChanges, + OnDestroy, + OnInit, + SimpleChanges, +} from "@angular/core"; +import { FormBuilder, FormGroup, Validators } from "@angular/forms"; +import { GridsterConfig, GridsterItem } from "angular-gridster2"; +import { Subject } from "rxjs"; +import { takeUntil, tap } from "rxjs/operators"; + +import { + DataSourceService, + IconService, + IDataSource, + IFilteringOutputs, + LoggerService, +} from "@nova-ui/bits"; +import { + ChartAssist, + IAccessors, + IChartAssistEvent, + IChartAssistSeries, +} from "@nova-ui/charts"; +import { + ComponentRegistryService, + ConfiguratorHeadingService, + DATA_SOURCE, + DonutChartFormatterConfiguratorComponent, + DonutContentPercentageConfigurationComponent, + DonutContentPercentageFormatterComponent, + DonutContentSumFormatterComponent, + IDashboard, + IFormatterDefinition, + IHasChangeDetector, + IProperties, + IProportionalWidgetChartOptions, + IProportionalWidgetConfig, + IProviderConfiguration, + IWidget, + IWidgets, + LegendPlacement, + PizzagnaLayer, + ProportionalWidgetChartTypes, + ProviderRegistryService, + WellKnownPathKey, + WellKnownProviders, + WidgetTypesService, +} from "@nova-ui/dashboards"; + +export enum Units { + Days = "Day(s)", + Weeks = "Week(s)", + Hours = "Hour(s)", +} + +@Component({ + selector: "custom-donut-content-formatter", + host: { class: "d-flex flex-column align-items-center" }, + template: \\\\\\\` +
+ + {{ chartMetric || properties?.currentMetric || data[0].id }} +
+
+ {{ chartContent }} +
+
+ {{ units }} +
+
\\\\\\\`, + styleUrls: ["./custom-donut-content-formatter-example.component.less"], + standalone: false, +}) +export class CustomDonutContentFormatterComponent + implements IHasChangeDetector, OnInit, OnChanges +{ + public static lateLoadKey = "CustomDonutContentFormatterComponent"; + + // Used to emphasize the chart series when user interacts either with the chart legend, or chart segments. + public emphasizedSeriesData: IChartAssistSeries | undefined; + + // Current raw value of the metric to display + public currentMetricData: number; + + // Metric value rendered inside the template, when user selects a metric, and gets automatically recalculated depending on selected units + public chartContent: number; + + // Metric value rendered inside the template, when user interacts with either chart legend, or chart segments + public chartMetric: number; + + // Units which user can select from the configuration + public units: Units = Units.Days; + + private readonly destroy$ = new Subject(); + + constructor(public changeDetector: ChangeDetectorRef) {} + + // The data we receive from the chart, including metrics names and their values + @Input() data: IChartAssistSeries[]; + + // We use this chart assist instance to subscribe to the events triggered when an interaction with the chart occurs + @Input() chartAssist: ChartAssist; + + // These are the current properties from pizzagna. Used to use data set at the configuration layer + @Input() properties: IProperties; + + public ngOnChanges(changes: SimpleChanges): void { + if (changes.properties || !this.properties) { + // If current metric is not in the list of metrics any more we fall back to the very first one from the list we get from the datasource + this.currentMetricData = + this.data.find( + (item) => item.id === this.properties?.currentMetric + )?.data[0] || this.data[0].data[0]; + + // We either take the selected value, or fall back to the preselected default one + this.units = this.properties?.units || this.units; + } + + this.setContentValue(); + } + + public ngOnInit(): void { + // Here 'chartAssistSubject' is the entity that emits events every time user interacts with either chart legend, or chart segments. + // Subscribing to properly react on these kind of events + this.chartAssist.chartAssistSubject + .pipe( + tap( + (data: IChartAssistEvent) => + (this.emphasizedSeriesData = this.data.find( + (item) => item.id === data.payload.seriesId + )) + ), + tap(() => this.setContentValue()), + tap(() => this.setMetricValue()), + takeUntil(this.destroy$) + ) + .subscribe(); + } + + public getConvertedData(emphData: number): number { + // Recalculating data depending on the units user selected from the configuration view + switch (this.units) { + case Units.Weeks: + return this.emphasizedSeriesData + ? this.convertToWeeks(emphData) + : this.convertToWeeks(this.currentMetricData); + + case Units.Hours: + return this.emphasizedSeriesData + ? this.convertToHours(emphData) + : this.convertToHours(this.currentMetricData); + + default: + return this.emphasizedSeriesData + ? emphData + : this.currentMetricData; + } + } + + public setContentValue(): void { + this.chartContent = this.getConvertedData( + this.emphasizedSeriesData?.data[0] + ); + } + + public setMetricValue(): void { + this.chartMetric = this.emphasizedSeriesData + ? this.data.find( + (item) => + this.getConvertedData(item.data[0]) === + this.getConvertedData(this.emphasizedSeriesData?.data[0]) + )?.id + : // if metric was not initially selected we fall back to the very first one + this.properties?.currentMetric || this.data[0].id; + } + + private convertToWeeks(days: number | undefined): number { + return days ? Number((days / 7).toFixed(2)) : 0; + } + + private convertToHours(days: number | undefined): number { + return days ? Number((days * 24).toFixed(2)) : 0; + } +} + +@Component({ + selector: "custom-donut-content-formatter-configurator", + styleUrls: ["./custom-donut-content-formatter-example.component.less"], + template: \\\\\\\` +
+
+ + + + {{ itemValue?.name }} + + + + This field is required + + +
+
+ + + + {{ itemValue }} + + + + This field is required + + +
+
+ \\\\\\\`, + standalone: false, +}) +export class CustomDonutContentFormatterConfiguratorComponent + extends DonutChartFormatterConfiguratorComponent + implements OnChanges, OnInit, IHasChangeDetector +{ + public static lateLoadKey = "CustomFormatterConfiguratorComponent"; + + constructor( + changeDetector: ChangeDetectorRef, + formBuilder: FormBuilder, + logger: LoggerService, + public iconService: IconService, + public configuratorHeading: ConfiguratorHeadingService + ) { + super(changeDetector, formBuilder, logger); + } + + public availableUnits: Units[] = [Units.Days, Units.Hours, Units.Weeks]; + + protected addCustomFormControls(form: FormGroup): void { + form.addControl( + "units", + this.formBuilder.control(Units.Days, Validators.required) + ); + } +} + +/** + * A component that instantiates the dashboard + */ +@Component({ + selector: "custom-donut-content-formatter-example", + templateUrl: "./custom-donut-content-formatter-example.component.html", + styleUrls: ["./custom-donut-content-formatter-example.component.less"], + standalone: false, +}) +export class CustomDonutContentFormatterExampleComponent implements OnInit { + public editMode: boolean = false; + // This variable will hold all the data needed to define the layout and behavior of the widgets. + // Pass this to the dashboard component's dashboard input in the template. + public dashboard: IDashboard | undefined; + + // Angular gridster requires a configuration object even if it's empty. + // Pass this to the dashboard component's gridsterConfig input in the template. + public gridsterConfig: GridsterConfig = {}; + + constructor( + // WidgetTypesService provides the widget's necessary structure information + private widgetTypesService: WidgetTypesService, + // In general, the ProviderRegistryService is used for making entities available for injection into dynamically loaded components. + private providerRegistry: ProviderRegistryService, + // Inject the ComponentRegistryService to make our custom component available for late loading by the dashboards framework + private componentRegistry: ComponentRegistryService, + private changeDetectorRef: ChangeDetectorRef + ) { + // Register the custom configurator component with the component registry to make it available + // for late loading by the dashboard framework. + this.componentRegistry.registerByLateLoadKey( + CustomDonutContentFormatterConfiguratorComponent + ); + // Register the custom formatter component with the component registry to make it available + // for late loading by the dashboard framework. + this.componentRegistry.registerByLateLoadKey( + CustomDonutContentFormatterComponent + ); + + // Grab the widget's default template which will be needed as a parameter for setNode below. + const proportional = this.widgetTypesService.getWidgetType( + "proportional", + 1 + ); + + const donutFormatters: IFormatterDefinition[] = [ + { + componentType: DonutContentSumFormatterComponent.lateLoadKey, + label: $localize\\\\\\\`Sum\\\\\\\`, + } as IFormatterDefinition, + { + componentType: + DonutContentPercentageFormatterComponent.lateLoadKey, + label: $localize\\\\\\\`Percentage\\\\\\\`, + configurationComponent: + DonutContentPercentageConfigurationComponent.lateLoadKey, + } as IFormatterDefinition, + { + componentType: CustomDonutContentFormatterComponent.lateLoadKey, + label: $localize\\\\\\\`Custom\\\\\\\`, + // This is a custom configurator that will pop up below the formatter once it gets selected + configurationComponent: + CustomDonutContentFormatterConfiguratorComponent.lateLoadKey, + } as IFormatterDefinition, + ]; + + this.widgetTypesService.setNode( + // This is the template we grabbed above with getWidgetType + proportional, + // We are setting the editor/configurator part of the widget template + "configurator", + // This indicates which node you are changing and we want to change the formatters available for selection in the editor. + WellKnownPathKey.Formatters, + // We are setting the available formatters with the array we created above. + donutFormatters + ); + + // This sets the donut chart's datasource to have the StatusesExampleDatasource so the drop down is filled similar to the line above. + this.widgetTypesService.setNode( + proportional, + "configurator", + WellKnownPathKey.DataSourceProviders, + [StatusesExampleDatasource.providerId] + ); + + // Registering the data source for injection into the widget. + this.providerRegistry.setProviders({ + [StatusesExampleDatasource.providerId]: { + provide: DATA_SOURCE, + useClass: StatusesExampleDatasource, + // Any dependencies that need to be injected into the provider must be listed here + deps: [], + }, + }); + } + + public ngOnInit(): void { + this.initializeDashboard(); + } + + /** Used for restoring widgets state */ + public reInitializeDashboard(): void { + // destroys the components and their providers so the dashboard can re init data + this.dashboard = undefined; + this.changeDetectorRef.detectChanges(); + + this.initializeDashboard(); + } + + public initializeDashboard(): void { + // We're using a static configuration object for this example, but this is where + // the widget's configuration could potentially be populated from a database + const proportionalWidget = widgetConfig; + const widgetIndex: IWidgets = { + // Enhance the widget with information coming from it's type definition + [proportionalWidget.id]: + this.widgetTypesService.mergeWithWidgetType(proportionalWidget), + }; + + // Setting the widget dimensions and position (this is for gridster) + const positions: Record = { + [proportionalWidget.id]: { + cols: 12, + rows: 6, + y: 0, + x: 0, + }, + }; + + // Finally, assigning the variables we created above to the dashboard + this.dashboard = { + positions, + widgets: widgetIndex, + }; + } +} + +export interface IStatusesWidgetData { + id: string; + name: string; + data: number[]; +} + +export const randomStatusesWidgetData: IStatusesWidgetData[] = [ + { + id: "Down", + name: "Down", + data: [Math.round(Math.random() * 100)], + }, + { + id: "Critical", + name: "Critical", + data: [Math.round(Math.random() * 100)], + }, + { + id: "Warning", + name: "Warning", + data: [Math.round(Math.random() * 100)], + }, + { + id: "Unknown", + name: "Unknown", + data: [Math.round(Math.random() * 100)], + }, + { + id: "Up", + name: "Up", + data: [Math.round(Math.random() * 100)], + }, + { + id: "Unmanaged", + name: "Unmanaged", + data: [Math.round(Math.random() * 100)], + }, +]; + +@Injectable() +export class StatusesExampleDatasource + extends DataSourceService + implements IDataSource, OnDestroy +{ + public static providerId = "StatusesExampleDatasource"; + + public busy = new Subject(); + + constructor(private http: HttpClient) { + super(); + } + + public async getFilteredData(): Promise { + this.busy.next(true); + + return new Promise((resolve) => { + setTimeout(() => { + resolve({ + result: randomStatusesWidgetData, + }); + this.busy.next(false); + }, 1000); + }); + } + + public ngOnDestroy(): void { + this.outputsSubject.complete(); + } +} + +export const widgetConfig: IWidget = { + id: "proportionalWidgetId", + type: "proportional", + pizzagna: { + [PizzagnaLayer.Configuration]: { + header: { + properties: { + title: "Proportional Widget!", + subtitle: "Proportional widget with legend formatters", + }, + }, + chart: { + providers: { + [WellKnownProviders.DataSource]: { + providerId: StatusesExampleDatasource.providerId, + } as IProviderConfiguration, + }, + properties: { + configuration: { + interactive: true, + chartOptions: { + type: ProportionalWidgetChartTypes.DonutChart, + legendPlacement: LegendPlacement.Right, + contentFormatter: { + componentType: + CustomDonutContentFormatterComponent.lateLoadKey, + properties: { + // here you can set the default value for the metric you receive. If not set the first one from the list will be taken + currentMetric: "Down", + // here you set the default value for your custom controls. If not set the first one from the list will be taken + units: Units.Weeks, + }, + }, + } as IProportionalWidgetChartOptions, + chartColors: [ + "var(--nui-color-chart-eight)", + "var(--nui-color-chart-nine)", + "var(--nui-color-chart-ten)", + ], + } as IProportionalWidgetConfig, + }, + }, + }, + }, +}; +\\\`, + "tutorials/customization/formatter/formatter-example/custom-formatter-docs.component.ts": \\\`// © 2022 SolarWinds Worldwide, LLC. All rights reserved. +// +// Permission is hereby granted, free of charge, to any person obtaining a copy +// of this software and associated documentation files (the "Software"), to +// deal in the Software without restriction, including without limitation the +// rights to use, copy, modify, merge, publish, distribute, sublicense, and/or +// sell copies of the Software, and to permit persons to whom the Software is +// furnished to do so, subject to the following conditions: +// +// The above copyright notice and this permission notice shall be included in +// all copies or substantial portions of the Software. +// +// THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR +// IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, +// FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE +// AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER +// LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, +// OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN +// THE SOFTWARE. + +import { Component } from "@angular/core"; + +@Component({ + selector: "nui-custom-formatter-docs", + templateUrl: "./custom-formatter-docs.component.html", + standalone: false, +}) +export class CustomFormatterDocComponent {} +\\\`, + "tutorials/customization/formatter/formatter-example/custom-formatter-example.component.ts": \\\`// © 2022 SolarWinds Worldwide, LLC. All rights reserved. +// +// Permission is hereby granted, free of charge, to any person obtaining a copy +// of this software and associated documentation files (the "Software"), to +// deal in the Software without restriction, including without limitation the +// rights to use, copy, modify, merge, publish, distribute, sublicense, and/or +// sell copies of the Software, and to permit persons to whom the Software is +// furnished to do so, subject to the following conditions: +// +// The above copyright notice and this permission notice shall be included in +// all copies or substantial portions of the Software. +// +// THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR +// IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, +// FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE +// AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER +// LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, +// OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN +// THE SOFTWARE. + +import { ListRange } from "@angular/cdk/collections"; +import { ChangeDetectorRef, Component, Input, OnInit } from "@angular/core"; +import { FormBuilder, FormGroup, Validators } from "@angular/forms"; +import { GridsterConfig, GridsterItem } from "angular-gridster2"; +import isEqual from "lodash/isEqual"; +import orderBy from "lodash/orderBy"; +import { BehaviorSubject } from "rxjs"; + +import { + DataSourceService, + IconService, + IDataField, + INovaFilteringOutputs, + INovaFilters, + ISorterFilter, + LoggerService, +} from "@nova-ui/bits"; +import { + ComponentRegistryService, + ConfiguratorHeadingService, + DATA_SOURCE, + FormatterConfiguratorComponent, + IDashboard, + IDataSourceOutput, + IFormatterDefinition, + IHasChangeDetector, + ITableWidgetColumnConfig, + ITableWidgetSorterConfig, + IWidget, + IWidgets, + PizzagnaLayer, + ProviderRegistryService, + RawFormatterComponent, + TableFormatterRegistryService, + WellKnownPathKey, + WellKnownProviders, + WidgetTypesService, +} from "@nova-ui/dashboards"; + +export const BREW_API_URL = "https://api.punkapi.com/v2/beers"; + +@Component({ + selector: "custom-formatter", + host: { class: "d-flex" }, + template: \\\\\\\` +
+
+ +
+
+ {{ data.value }} +
+
+ \\\\\\\`, + styleUrls: ["./custom-formatter-example.component.less"], + standalone: false, +}) +export class CustomFormatterComponent implements IHasChangeDetector { + public static lateLoadKey = "CustomFormatterComponent"; + + constructor(public changeDetector: ChangeDetectorRef) {} + + @Input() public data: any; + @Input() public icon: string; + @Input() public threshold: string; + + public isAboveThreshold(): boolean { + return parseFloat(this.threshold) <= this.data.value; + } +} + +@Component({ + selector: "custom-formatter-configurator", + styleUrls: ["./custom-formatter-example.component.less"], + template: \\\\\\\` +
+
+ + + + {{ item.label }} + + + + This field is required + + +
+
+ + + + + + + + This field is required + + +
+
+ + + + + This field is required + + +
+
+ +
+
+ +
+ + +
+ + + Select Item + +
+ \\\\\\\`, + standalone: false, +}) +export class CustomFormatterConfiguratorComponent + extends FormatterConfiguratorComponent + implements OnInit, IHasChangeDetector +{ + public static lateLoadKey = "CustomFormatterConfiguratorComponent"; + + constructor( + changeDetector: ChangeDetectorRef, + configuratorHeading: ConfiguratorHeadingService, + formBuilder: FormBuilder, + logger: LoggerService, + public iconService: IconService + ) { + super(changeDetector, configuratorHeading, formBuilder, logger); + } + + public formatterFormGroup: FormGroup; + // This array is where the icon names will be stored + public options: string[] = []; + + public ngOnInit(): void { + for (const icon of this.iconService.icons) { + if (icon.category === "severity") { + this.options.push(icon.name); + } + } + } + + protected addCustomFormControls(form: FormGroup): void { + form.addControl( + "icon", + this.formBuilder.control("", Validators.required) + ); + form.addControl( + "threshold", + this.formBuilder.control(null, Validators.required) + ); + } +} + +/** + * A component that instantiates the dashboard + */ +@Component({ + selector: "custom-formatter-example", + templateUrl: "./custom-formatter-example.component.html", + styleUrls: ["./custom-formatter-example.component.less"], + standalone: false, +}) +export class CustomFormatterExampleComponent implements OnInit { + public editMode: boolean = false; + // This variable will hold all the data needed to define the layout and behavior of the widgets. + // Pass this to the dashboard component's dashboard input in the template. + public dashboard: IDashboard | undefined; + + // Angular gridster requires a configuration object even if it's empty. + // Pass this to the dashboard component's gridsterConfig input in the template. + public gridsterConfig: GridsterConfig = {}; + + constructor( + // WidgetTypesService provides the widget's necessary structure information + private widgetTypesService: WidgetTypesService, + // In general, the ProviderRegistryService is used for making entities available for injection into dynamically loaded components. + private providerRegistry: ProviderRegistryService, + // Inject the ComponentRegistryService to make our custom component available for late loading by the dashboards framework + private componentRegistry: ComponentRegistryService, + private tableFormatterRegistryService: TableFormatterRegistryService, + private changeDetectorRef: ChangeDetectorRef + ) { + // Register the custom configurator component with the component registry to make it available + // for late loading by the dashboard framework. + this.componentRegistry.registerByLateLoadKey( + CustomFormatterConfiguratorComponent + ); + // Register the custom formatter component with the component registry to make it available + // for late loading by the dashboard framework. + this.componentRegistry.registerByLateLoadKey(CustomFormatterComponent); + + // Grab the widget's default template which will be needed as a parameter for setNode below. + const table = this.widgetTypesService.getWidgetType("table", 1); + + const tableFormatters: IFormatterDefinition[] = [ + { + // This will be the component that will format the data + componentType: RawFormatterComponent.lateLoadKey, + // This is the label for what the formatter is selected in the drop down + label: $localize\\\\\\\`:table formatter|:No formatter\\\\\\\`, + // This says what datatype the formatter supports. If the value node is null, it accepts any data type. + dataTypes: { + // @ts-ignore: Ignoring compiler error to keep the same flow + value: null, + }, + }, + { + componentType: CustomFormatterComponent.lateLoadKey, + label: $localize\\\\\\\`:table formatter|:Custom formatter\\\\\\\`, + // This is a custom configurator that will pop up below the formatter once it gets selected + configurationComponent: + CustomFormatterConfiguratorComponent.lateLoadKey, + // This says what data types the formatter supports. + // In this case, it supports abv values only. + // If you look below in the table data source you'll see where we define our column's data types. + dataTypes: { + value: ["abv"], + }, + }, + ]; + + // Registering the formatters + this.tableFormatterRegistryService.addItems(tableFormatters); + + // This sets the table's datasource to have the BeerDataSource so the drop down is filled similar to the line above. + this.widgetTypesService.setNode( + table, + "configurator", + WellKnownPathKey.DataSourceProviders, + [BeerDataSource.providerId] + ); + + // Registering the data source for injection into the widget. + this.providerRegistry.setProviders({ + [BeerDataSource.providerId]: { + provide: DATA_SOURCE, + useClass: BeerDataSource, + // Any dependencies that need to be injected into the provider must be listed here + deps: [], + }, + }); + } + + public ngOnInit(): void { + this.initializeDashboard(); + } + + /** Used for restoring widgets state */ + public reInitializeDashboard(): void { + // destroys the components and their providers so the dashboard can re init data + this.dashboard = undefined; + this.changeDetectorRef.detectChanges(); + + this.initializeDashboard(); + } + + public initializeDashboard(): void { + // We're using a static configuration object for this example, but this is where + // the widget's configuration could potentially be populated from a database + const tableWidget = widgetConfig; + const widgetIndex: IWidgets = { + // Enhance the widget with information coming from it's type definition + [tableWidget.id]: + this.widgetTypesService.mergeWithWidgetType(tableWidget), + }; + + // Setting the widget dimensions and position (this is for gridster) + const positions: Record = { + [tableWidget.id]: { + cols: 12, + rows: 6, + y: 0, + x: 0, + }, + }; + + // Finally, assigning the variables we created above to the dashboard + this.dashboard = { + positions, + widgets: widgetIndex, + }; + } +} + +export interface IBrewInfo { + id: number; + name: string; + tagline: string; + first_brewed: string; + description: string; + brewers_tips: string; + abv: number; +} + +export interface IBrewDatasourceResponse { + brewInfo: IBrewInfo[]; + total: number; +} + +export class BeerDataSource extends DataSourceService { + public static providerId = "BeerDataSource"; + + private cache = Array.from({ length: 0 }); + private lastSortValue?: ISorterFilter; + private lastVirtualScroll?: ListRange; + // For simplicity, the totalItems value is hard-coded here, but in a real-world scenario the value would likely be retrieved via an async backend call + private totalItems: number = 325; + + public page: number = 1; + public busy = new BehaviorSubject(false); + + public dataFields: Array = [ + { id: "id", label: "No", dataType: "number" }, + { id: "name", label: "Name", dataType: "string" }, + { id: "tagline", label: "Tagline", dataType: "string" }, + { id: "first_brewed", label: "First Brewed", dataType: "string" }, + { id: "description", label: "Description", dataType: "string" }, + { id: "brewers_tips", label: "Brewer's Tips", dataType: "string" }, + // We are giving this field a custom data type of 'abv' so the dropdown in the custom formatter configurator can use it to filter out other data types + { id: "abv", label: "Alcohol By Volume", dataType: "abv" }, + ]; + + constructor(private logger: LoggerService) { + super(); + } + + public async getFilteredData( + filters: INovaFilters + ): Promise> { + const start = filters.virtualScroll?.value?.start ?? 0; + const end = filters.virtualScroll?.value?.end ?? 0; + const delta = end - start; + + // Note: We should start with a clean cache every time first page is requested + if (start === 0) { + this.cache = []; + } + + // This condition handles sorting. We want to sort columns without fetching another chunk of data. + // Since the data is being fetched when scrolled, we compare virtual scroll indexes here in the condition as well. + if (filters.sorter?.value) { + if ( + !isEqual(this.lastSortValue, filters.sorter.value) && + filters.virtualScroll?.value.start === 0 && + !!this.lastVirtualScroll + ) { + const totalPages = Math.ceil( + delta ? this.totalItems / delta : 1 + ); + const itemsPerPage: number = Math.max( + delta < 80 ? delta : 80, + 1 + ); + let response: Array | null = null; + let map: IBrewDatasourceResponse; + + if (filters.sorter?.value?.direction === "desc") { + this.cache = []; + for (let i = 0; i < this.page; ++i) { + response = await ( + await fetch( + \\\\\\\`\\\\\\\${BREW_API_URL}/?page=\\\\\\\${ + totalPages - i || 1 + }&per_page=\\\\\\\${itemsPerPage}\\\\\\\` + ) + ).json(); + + // since the last page contains only 5 items we need to fetch another page to give virtual scroll enough space to work + if (response && response.length < itemsPerPage) { + this.page++; + } + map = { + brewInfo: response?.map((result: IBrewInfo) => ({ + id: result.id, + name: result.name, + tagline: result.tagline, + first_brewed: result.first_brewed, + description: result.description, + brewers_tips: result.brewers_tips, + })), + total: response?.length, + } as IBrewDatasourceResponse; + this.cache = + totalPages - i !== 0 + ? this.cache.concat(map.brewInfo) + : this.cache; + } + } + + if (filters.sorter?.value?.direction === "asc") { + this.cache = []; + for (let i = 0; i < this.page; i++) { + response = await ( + await fetch( + \\\\\\\`\\\\\\\${BREW_API_URL}/?page=\\\\\\\${ + i + 1 + }&per_page=\\\\\\\${itemsPerPage}\\\\\\\` + ) + ).json(); + map = { + brewInfo: response?.map((result: IBrewInfo) => ({ + id: result.id, + name: result.name, + tagline: result.tagline, + first_brewed: result.first_brewed, + description: result.description, + brewers_tips: result.brewers_tips, + })), + total: response?.length, + } as IBrewDatasourceResponse; + this.cache = this.cache.concat(map.brewInfo); + } + } + + this.lastSortValue = filters.sorter?.value; + this.lastVirtualScroll = filters.virtualScroll?.value; + + return { + result: { + repeat: { + itemsSource: this.sortData(this.cache, filters), + }, + paginator: { total: this.totalItems }, + dataFields: this.dataFields, + }, + }; + } + } + + this.busy.next(true); + return new Promise((resolve) => { + setTimeout(() => { + this.getData(start, end, filters).then( + (response: INovaFilteringOutputs) => { + if (!response) { + return; + } + + this.cache = this.cache.concat(response.brewInfo); + + this.dataSubject.next(this.cache); + resolve({ + result: { + repeat: { + itemsSource: this.sortData( + this.cache, + filters + ), + }, + paginator: { total: this.totalItems }, + dataFields: this.dataFields, + }, + }); + + this.lastSortValue = filters.sorter?.value; + this.lastVirtualScroll = filters.virtualScroll?.value; + this.busy.next(false); + } + ); + }, 500); + }); + } + + public async getData( + start: number = 0, + end: number = 20, + filters: INovaFilters + ): Promise { + const delta = end - start; + const totalPages = Math.ceil(delta ? this.totalItems / delta : 1); + let response: Array | null = null; + // The api.punk.com is able to return only 80 items per page + const itemsPerPage: number = Math.max(delta < 80 ? delta : 80, 1); + + if (filters.sorter?.value?.direction === "asc") { + response = await ( + await fetch( + \\\\\\\`\\\\\\\${BREW_API_URL}/?page=\\\\\\\${this.page}&per_page=\\\\\\\${itemsPerPage}\\\\\\\` + ) + ).json(); + } + + if (filters.sorter?.value?.direction === "desc") { + response = await ( + await fetch( + \\\\\\\`\\\\\\\${BREW_API_URL}/?page=\\\\\\\${ + totalPages - this.page + }&per_page=\\\\\\\${itemsPerPage}\\\\\\\` + ) + ).json(); + } + + if (!filters.sorter) { + response = await ( + await fetch( + \\\\\\\`\\\\\\\${BREW_API_URL}/?page=\\\\\\\${this.page}&per_page=\\\\\\\${itemsPerPage}\\\\\\\` + ) + ).json(); + } + return { + brewInfo: response?.map((result: IBrewInfo, i: number) => ({ + id: result.id, + abv: result.abv, + name: result.name, + tagline: result.tagline, + first_brewed: result.first_brewed, + description: result.description, + brewers_tips: result.brewers_tips, + })), + total: response?.length, + } as IBrewDatasourceResponse; + } + + private sortData(data: IBrewInfo[], filters: INovaFilters) { + return orderBy( + data, + filters.sorter?.value?.sortBy, + filters.sorter?.value?.direction as "desc" | "asc" + ); + } +} + +export const widgetConfig: IWidget = { + id: "tableWidgetId", + type: "table", + pizzagna: { + [PizzagnaLayer.Configuration]: { + header: { + properties: { + title: "Stupendous Suds", + subtitle: "Try These Brilliant Brews", + }, + }, + table: { + providers: { + [WellKnownProviders.DataSource]: { + providerId: BeerDataSource.providerId, + }, + }, + properties: { + configuration: { + columns: [ + { + id: "column1", + label: "Beer Name", + isActive: true, + width: 185, + formatter: { + componentType: + RawFormatterComponent.lateLoadKey, + properties: { + dataFieldIds: { + value: "name", + }, + }, + }, + }, + { + id: "column2", + label: "Tagline", + isActive: true, + width: 250, + formatter: { + componentType: + RawFormatterComponent.lateLoadKey, + properties: { + dataFieldIds: { + value: "tagline", + }, + }, + }, + }, + { + id: "column3", + label: "Alcohol By Volume", + isActive: true, + width: 150, + formatter: { + componentType: + CustomFormatterComponent.lateLoadKey, + properties: { + dataFieldIds: { + value: "abv", + }, + icon: "severity_error", + threshold: "5", + }, + }, + }, + { + id: "column4", + label: "Description", + isActive: true, + formatter: { + componentType: + RawFormatterComponent.lateLoadKey, + properties: { + dataFieldIds: { + value: "description", + }, + }, + }, + }, + ] as ITableWidgetColumnConfig[], + sorterConfiguration: { + descendantSorting: false, + sortBy: "", + } as ITableWidgetSorterConfig, + hasVirtualScroll: true, + }, + }, + }, + }, + }, +}; +\\\`, + "tutorials/customization/widget/custom-widget-docs.component.ts": \\\`// © 2022 SolarWinds Worldwide, LLC. All rights reserved. +// +// Permission is hereby granted, free of charge, to any person obtaining a copy +// of this software and associated documentation files (the "Software"), to +// deal in the Software without restriction, including without limitation the +// rights to use, copy, modify, merge, publish, distribute, sublicense, and/or +// sell copies of the Software, and to permit persons to whom the Software is +// furnished to do so, subject to the following conditions: +// +// The above copyright notice and this permission notice shall be included in +// all copies or substantial portions of the Software. +// +// THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR +// IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, +// FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE +// AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER +// LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, +// OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN +// THE SOFTWARE. + +import { Component } from "@angular/core"; + +@Component({ + selector: "custom-widget-docs", + templateUrl: "./custom-widget-docs.component.html", + standalone: false, +}) +export class CustomWidgetDocsComponent {} +\\\`, + "tutorials/customization/widget/custom-widget.component.ts": \\\`// © 2022 SolarWinds Worldwide, LLC. All rights reserved. +// +// Permission is hereby granted, free of charge, to any person obtaining a copy +// of this software and associated documentation files (the "Software"), to +// deal in the Software without restriction, including without limitation the +// rights to use, copy, modify, merge, publish, distribute, sublicense, and/or +// sell copies of the Software, and to permit persons to whom the Software is +// furnished to do so, subject to the following conditions: +// +// The above copyright notice and this permission notice shall be included in +// all copies or substantial portions of the Software. +// +// THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR +// IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, +// FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE +// AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER +// LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, +// OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN +// THE SOFTWARE. + +import { + ChangeDetectionStrategy, + ChangeDetectorRef, + Component, + EventEmitter, + HostBinding, + Input, + OnChanges, + OnInit, + Output, + SimpleChanges, +} from "@angular/core"; +import { FormBuilder, FormGroup, Validators } from "@angular/forms"; +import { GridsterConfig, GridsterItem } from "angular-gridster2"; + +import { IMenuItem } from "@nova-ui/bits"; +import { + ComponentRegistryService, + ConfiguratorHeadingService, + DEFAULT_PIZZAGNA_ROOT, + EVENT_PROXY, + FormStackComponent, + IConverterFormPartsProperties, + IDashboard, + IHasChangeDetector, + IHasForm, + IProviderConfiguration, + IWidget, + IWidgets, + IWidgetTypeDefinition, + NOVA_GENERIC_CONVERTER, + NOVA_TITLE_AND_DESCRIPTION_CONVERTER, + PizzagnaLayer, + refresher, + StackComponent, + TitleAndDescriptionConfigurationComponent, + WellKnownPathKey, + WellKnownProviders, + widgetBodyContentNodes, + WidgetConfiguratorSectionComponent, + WidgetTypesService, + WIDGET_BODY, + WIDGET_HEADER, + WIDGET_LOADING, +} from "@nova-ui/dashboards"; + +// The custom widget type name we'll use +const CUSTOM_WIDGET_TYPENAME = "example-custom-widget"; +// The path key we'll use for image selection in the configurator definition +const IMAGE_SELECTION_CONFIGURATOR_PATH_KEY = "imageSelection"; + +@Component({ + selector: "custom-widget-body", + // A simple template for our custom widget + template: \\\\\\\`\\\\\\\`, + styleUrls: ["./custom-widget.component.less"], + changeDetection: ChangeDetectionStrategy.OnPush, + standalone: false, +}) +// Remember to declare this class in the parent module +export class CustomWidgetBodyContentComponent implements IHasChangeDetector { + // Ensure that the lateLoadKey value matches class name + public static lateLoadKey = "CustomWidgetBodyContentComponent"; + + // Optionally, providing an input for styling of the host element + @Input() @HostBinding("class") public elementClass = ""; + + // We'll map this input with the configurator form using the NOVA_GENERIC_CONVERTER. + // See the customWidget definition at the bottom of the file. + @Input() public imageSource: string; + + // Injecting the ChangeDetectorRef to implement IHasChangeDetector. + // This allows the dashboard framework to reliably propagate component property changes to the DOM. + constructor(public changeDetector: ChangeDetectorRef) {} +} + +/** + * A custom configurator section component for selecting the image source for the custom widget + */ +@Component({ + selector: "custom-configurator-section", + template: \\\\\\\` + + + + +
+ + +
+ Image Selection +
+ {{ imageDisplayValue }} +
+
+
+
+ + + + + {{ item.title }} + + + +
+
+ \\\\\\\`, + styleUrls: ["./custom-widget.component.less"], + changeDetection: ChangeDetectionStrategy.OnPush, + standalone: false, +}) +// Remember to declare this class in the parent module +export class CustomConfiguratorSectionComponent + implements OnInit, OnChanges, IHasChangeDetector, IHasForm +{ + // Ensure that the lateLoadKey value matches the class name + public static lateLoadKey = "CustomConfiguratorSectionComponent"; + + /** + * This input serves as the itemsSource a user can select an image from. + */ + @Input() imageItems: IMenuItem[] = []; + /** + * This property holds the currently selected image source string. + */ + @Input() imageSource: string; + + /** + * An output for emitting formReady to allow the immediate parent formGroup component to register us as a form control + * in the larger form. In this case, the immediate parent would be the WidgetConfiguratorSectionComponent as specified + * in the customWidget configurator definition at the bottom of this file. + */ + @Output() formReady = new EventEmitter(); + + public form: FormGroup; + public imageDisplayValue: string; + + constructor( + public changeDetector: ChangeDetectorRef, + private formBuilder: FormBuilder, + public configuratorHeading: ConfiguratorHeadingService + ) {} + + public ngOnInit(): void { + // Initializing the form + this.form = this.formBuilder.group({ + // Note: When using the NOVA_GENERIC_CONVERTER, the form control name, in this case 'imageSource', must match the input name on + // this component as well as that of the corresponding property on the custom widget body component. + imageSource: [{}, [Validators.required]], + }); + + // Emitting the formReady as described above. + this.formReady.emit(this.form); + } + + public ngOnChanges(changes: SimpleChanges): void { + if (changes.imageSource && !changes.imageSource.isFirstChange()) { + const previousValue: string = changes.imageSource.previousValue; + if (previousValue !== this.imageSource) { + // Setting the display value according to the current imageSource value + this.imageDisplayValue = this.imageItems.find( + (item: IMenuItem) => item.url === this.imageSource + )?.title; + + // Updating the form when the imageSource input gets updated + this.form.get("imageSource")?.setValue(this.imageSource); + } + } + } + + public onChanged(newValue: string): void { + // Keeping the display value updated as the user changes the dropdown selection + this.imageDisplayValue = this.imageItems.find( + (item: IMenuItem) => item.url === newValue + )?.title; + } +} + +/** + * The component that instantiates the dashboard + */ +@Component({ + selector: "custom-widget", + templateUrl: "./custom-widget.component.html", + styleUrls: ["./custom-widget.component.less"], + standalone: false, +}) +export class CustomWidgetComponent implements OnInit { + // This variable will hold all the data needed to define the layout and behavior of the widgets. + // Pass this to the dashboard component's dashboard input in the template. + public dashboard: IDashboard | undefined; + + // Angular gridster requires a configuration object even if it's empty. + // Pass this to the dashboard component's gridsterConfig input in the template. + public gridsterConfig: GridsterConfig = {}; + + // Boolean which dashboard takes in as an input if its true it allows you to move widgets around. + public editMode: boolean = false; + + constructor( + // WidgetTypesService provides the widget's necessary structure information + private widgetTypesService: WidgetTypesService, + + // Inject the ComponentRegistryService to make our custom component available for late loading by the dashboards framework + private componentRegistry: ComponentRegistryService, + private changeDetectorRef: ChangeDetectorRef + ) {} + + public ngOnInit(): void { + // Register the custom widget type and custom components + // Note: This could also be done in the parent module's constructor so that + // multiple dashboards could have access to the same registrations. + this.prepareNovaDashboards(); + + // Register some image items as dropdown options in the widget editor/configurator + // Note: This could also be done in the parent module's constructor so that + // multiple dashboards could have access to the same dropdown options. + this.registerImageOptions(); + + // Initialize our current instance of a dashboard with an instance of our custom widget + this.initializeDashboard(); + } + + /** Used for restoring widgets state */ + public reInitializeDashboard(): void { + // destroys the components and their providers so the dashboard can re init data + this.dashboard = undefined; + this.changeDetectorRef.detectChanges(); + + this.initializeDashboard(); + } + + public initializeDashboard(): void { + // We're using a static configuration object for this example (see widgetConfig at the bottom of the file), + // but this is where the widget's configuration could potentially be populated from a database + const widget = widgetConfig; + + // Create an index of widgets complete with structure and configuration to assign to the dashboard + const widgets: IWidgets = { + // Complete the custom widget with structure information coming from its type definition + [widget.id]: this.widgetTypesService.mergeWithWidgetType(widget), + }; + + // Setting the widget dimensions and position (this is for gridster) + const positions: Record = { + [widget.id]: { + cols: 4, + rows: 11, + y: 0, + x: 0, + }, + }; + + // Finally, assigning the variables we created above to the dashboard + this.dashboard = { positions, widgets }; + } + + private prepareNovaDashboards() { + // Register the custom widget type + this.widgetTypesService.registerWidgetType( + CUSTOM_WIDGET_TYPENAME, + 1, + customWidget + ); + + // Register the custom widget body component with the component registry to make it available + // for late loading by the dashboard framework. + this.componentRegistry.registerByLateLoadKey( + CustomWidgetBodyContentComponent + ); + + // Register the custom configurator section with the component registry to make it available + // for late loading by the dashboard framework. + this.componentRegistry.registerByLateLoadKey( + CustomConfiguratorSectionComponent + ); + } + + private registerImageOptions() { + // Grab the widget's default template which will be needed as a parameter for setNode below. + const widgetTemplate = this.widgetTypesService.getWidgetType( + CUSTOM_WIDGET_TYPENAME, + 1 + ); + + // Register some image items as dropdown options in the widget editor/configurator + this.widgetTypesService.setNode( + // This is the template we grabbed above with getWidgetType + widgetTemplate, + // We are setting the editor/configurator part of the widget template + "configurator", + // This indicates which node you are changing and we want to change the image items available for selection in the editor. + // For reference, see the 'paths' property of the custom widget's IWidgetTypeDefinition at the bottom of this file. + IMAGE_SELECTION_CONFIGURATOR_PATH_KEY, + // We are setting the image items available for selection in the editor. 'imageItems' is defined + // at the bottom of this file. + imageItems + ); + } +} + +/*************************************************************************************************** + * This is the type definition of our custom widget + ***************************************************************************************************/ +const customWidget: IWidgetTypeDefinition = { + /*************************************************************************************************** + * Paths to important settings in this type definition + ***************************************************************************************************/ + paths: { + widget: { + [WellKnownPathKey.Root]: DEFAULT_PIZZAGNA_ROOT, + }, + configurator: { + [WellKnownPathKey.Root]: DEFAULT_PIZZAGNA_ROOT, + // for the custom configuration component, this is the path for the list of image items available for selection + [IMAGE_SELECTION_CONFIGURATOR_PATH_KEY]: + "imageSelection.properties.imageItems", + }, + }, + /*************************************************************************************************** + * Widget section describes the structural part of the custom widget + ***************************************************************************************************/ + widget: { + [PizzagnaLayer.Structure]: { + [DEFAULT_PIZZAGNA_ROOT]: { + id: DEFAULT_PIZZAGNA_ROOT, + // base layout of the widget - all components referenced herein will be stacked in a column + componentType: StackComponent.lateLoadKey, + providers: { + // When enabled, this provider emits the REFRESH event on the pizzagna event bus every X seconds + [WellKnownProviders.Refresher]: refresher(), + // event proxy manages the transmission of events between widget and dashboard such as the WIDGET_EDIT and WIDGET_REMOVE events + [WellKnownProviders.EventProxy]: EVENT_PROXY, + }, + properties: { + // these values reference child components in the widget structure defined below + nodes: ["header", "loading", "body"], + }, + }, + // standard widget header + header: WIDGET_HEADER, + // this is the loading bar below the header + loading: WIDGET_LOADING, + // the body node + body: WIDGET_BODY, + + // retrieving the definitions for the body content nodes. the argument corresponds to the main content node key + ...widgetBodyContentNodes("mainContent"), + + // the component that supplies the content of our custom widget + mainContent: { + id: "mainContent", + componentType: CustomWidgetBodyContentComponent.lateLoadKey, + properties: { + elementClass: "d-flex w-100 justify-content-center", + }, + }, + }, + [PizzagnaLayer.Configuration]: { + [DEFAULT_PIZZAGNA_ROOT]: { + id: DEFAULT_PIZZAGNA_ROOT, + providers: { + // default refresher configuration + [WellKnownProviders.Refresher]: refresher(false, 60), + }, + }, + // default header configuration + header: { + properties: { + title: $localize\\\\\\\`Empty Custom Widget\\\\\\\`, + }, + }, + }, + }, + /*************************************************************************************************** + * Configurator section describes the form that's used to configure the widget + ***************************************************************************************************/ + configurator: { + [PizzagnaLayer.Structure]: { + [DEFAULT_PIZZAGNA_ROOT]: { + id: DEFAULT_PIZZAGNA_ROOT, + // base layout of the configurator - all form components referenced herein will be stacked in a column + componentType: FormStackComponent.lateLoadKey, + properties: { + elementClass: + "flex-grow-1 overflow-auto nui-scroll-shadows", + // these values reference child components laid out in this form (defined below) + nodes: ["presentation", "customConfig"], + }, + }, + // /presentation + presentation: { + id: "presentation", + componentType: WidgetConfiguratorSectionComponent.lateLoadKey, + properties: { + headerText: $localize\\\\\\\`Presentation\\\\\\\`, + nodes: ["titleAndDescription"], + }, + }, + // /presentation/titleAndDescription + titleAndDescription: { + id: "titleAndDescription", + componentType: + TitleAndDescriptionConfigurationComponent.lateLoadKey, + providers: { + converter: { + providerId: NOVA_TITLE_AND_DESCRIPTION_CONVERTER, + } as IProviderConfiguration, + }, + }, + // /customConfig + customConfig: { + id: "customConfig", + componentType: WidgetConfiguratorSectionComponent.lateLoadKey, + properties: { + headerText: $localize\\\\\\\`Custom Widget Configuration\\\\\\\`, + nodes: ["imageSelection"], + }, + }, + // /customConfig/imageSelection + imageSelection: { + id: "imageSelection", + // Here's where we set the configurator to use our custom configurator section + componentType: CustomConfiguratorSectionComponent.lateLoadKey, + properties: { + // This corresponds to the 'imageItems' input on the custom configurator section component + // which defines the list of image items to pick from. The empty value shown here is overridden + // in the 'registerImageOptions' method above. + imageItems: [] as IMenuItem[], + }, + providers: { + // Using the generic converter to map the selected image source between the widget and the form + [WellKnownProviders.Converter]: { + providerId: NOVA_GENERIC_CONVERTER, + properties: { + formParts: [ + { + // Setting up the generic converter to update the 'imageSource' property of the custom widget 'mainContent' component + previewPath: "mainContent.properties", + // Note: To use the NOVA_GENERIC_CONVERTER, the linked properties must have the same name between the configurator + // section component and the widget 'mainContent' component. Additionally, the property name must match the formControl + // name used in the configurator section component. In this case, the common name among all three is 'imageSource'. + keys: ["imageSource"], + }, + ] as IConverterFormPartsProperties[], + }, + } as IProviderConfiguration, + }, + }, + }, + }, +}; + +// For this example, we're using static items for the image selection dropdown. In a more realistic scenario, +// the items available for selection might come from a backend database. +const imageItems = [ + { + title: "Harry Potter Book Cover", + url: "https://imgc.allpostersimages.com/img/print/u-g-F8PQ9I0.jpg?w=550&h=550&p=0", + }, + { + title: "Harry Potter Movie Poster", + url: "https://images-na.ssl-images-amazon.com/images/I/81gpmMdKOHL._AC_SY741_.jpg", + }, +] as IMenuItem[]; + +// We're using a static configuration object for this example. In a more realistic scenario, +// a widget's configuration would likely be stored in a database. +const widgetConfig: IWidget = { + id: "widget1", + // This custom type is registered in the 'prepareNovaDashboards' method above. + type: CUSTOM_WIDGET_TYPENAME, + pizzagna: { + [PizzagnaLayer.Configuration]: { + header: { + // Setting the initial property values for the WidgetHeaderComponent + properties: { + title: "Harry Potter and the Sorcerer's Stone", + subtitle: "By J. K. Rowling", + }, + }, + mainContent: { + properties: { + // Setting the initial value for the 'imageSource' property on our custom widget body + imageSource: imageItems[0].url, + }, + }, + }, + }, +}; +\\\`, + "tutorials/customization/widget/custom-widget.module.ts": \\\`// © 2022 SolarWinds Worldwide, LLC. All rights reserved. +// +// Permission is hereby granted, free of charge, to any person obtaining a copy +// of this software and associated documentation files (the "Software"), to +// deal in the Software without restriction, including without limitation the +// rights to use, copy, modify, merge, publish, distribute, sublicense, and/or +// sell copies of the Software, and to permit persons to whom the Software is +// furnished to do so, subject to the following conditions: +// +// The above copyright notice and this permission notice shall be included in +// all copies or substantial portions of the Software. +// +// THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR +// IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, +// FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE +// AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER +// LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, +// OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN +// THE SOFTWARE. + +import { CommonModule } from "@angular/common"; +import { HttpClientModule } from "@angular/common/http"; +import { NgModule } from "@angular/core"; +import { ReactiveFormsModule } from "@angular/forms"; +import { RouterModule } from "@angular/router"; + +import { + NuiButtonModule, + NuiDocsModule, + NuiFormFieldModule, + NuiIconModule, + NuiImageModule, + NuiMessageModule, + NuiSelectV2Module, + NuiSwitchModule, + DEMO_PATH_TOKEN, +} from "@nova-ui/bits"; +import { + NuiDashboardConfiguratorModule, + NuiDashboardsModule, +} from "@nova-ui/dashboards"; + +import { CustomWidgetDocsComponent } from "./custom-widget-docs.component"; +import { + CustomConfiguratorSectionComponent, + CustomWidgetBodyContentComponent, + CustomWidgetComponent, +} from "./custom-widget.component"; +import { getDemoFiles } from "../../../../../demo-files-factory"; + +const routes = [ + { + path: "", + component: CustomWidgetDocsComponent, + data: { + srlc: { + hideIndicator: true, + }, + showThemeSwitcher: true, + }, + }, + { + path: "example", + component: CustomWidgetComponent, + data: { + srlc: { + hideIndicator: true, + }, + }, + }, +]; + +@NgModule({ + imports: [ + CommonModule, + ReactiveFormsModule, + HttpClientModule, + NuiDashboardsModule, + NuiDashboardConfiguratorModule, + NuiDocsModule, + NuiFormFieldModule, + NuiIconModule, + NuiImageModule, + NuiMessageModule, + NuiSelectV2Module, + NuiSwitchModule, + NuiButtonModule, + RouterModule.forChild(routes), + ], + declarations: [ + CustomWidgetDocsComponent, + CustomConfiguratorSectionComponent, + CustomWidgetBodyContentComponent, + CustomWidgetComponent, + ], + providers: [ + { + provide: DEMO_PATH_TOKEN, + useValue: getDemoFiles("widget"), + }, + ], +}) +export default class CustomWidgetModule {} +\\\`, + "tutorials/data-source-setup/data-source-setup-docs.component.ts": \\\`// © 2022 SolarWinds Worldwide, LLC. All rights reserved. +// +// Permission is hereby granted, free of charge, to any person obtaining a copy +// of this software and associated documentation files (the "Software"), to +// deal in the Software without restriction, including without limitation the +// rights to use, copy, modify, merge, publish, distribute, sublicense, and/or +// sell copies of the Software, and to permit persons to whom the Software is +// furnished to do so, subject to the following conditions: +// +// The above copyright notice and this permission notice shall be included in +// all copies or substantial portions of the Software. +// +// THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR +// IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, +// FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE +// AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER +// LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, +// OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN +// THE SOFTWARE. + +import { Component } from "@angular/core"; + +@Component({ + selector: "nui-dashboard-data-source-docs", + templateUrl: "./data-source-setup-docs.component.html", + standalone: false, +}) +export class DataSourceDocsComponent {} +\\\`, + "tutorials/data-source-setup/data-source-setup.component.ts": \\\`// © 2022 SolarWinds Worldwide, LLC. All rights reserved. +// +// Permission is hereby granted, free of charge, to any person obtaining a copy +// of this software and associated documentation files (the "Software"), to +// deal in the Software without restriction, including without limitation the +// rights to use, copy, modify, merge, publish, distribute, sublicense, and/or +// sell copies of the Software, and to permit persons to whom the Software is +// furnished to do so, subject to the following conditions: +// +// The above copyright notice and this permission notice shall be included in +// all copies or substantial portions of the Software. +// +// THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR +// IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, +// FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE +// AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER +// LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, +// OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN +// THE SOFTWARE. + +import { HttpClient, HttpErrorResponse } from "@angular/common/http"; +import { Component, Injectable, OnDestroy, OnInit } from "@angular/core"; +import { GridsterConfig, GridsterItem } from "angular-gridster2"; +import { BehaviorSubject } from "rxjs"; +import { finalize } from "rxjs/operators"; + +import { DataSourceService, IFilteringOutputs } from "@nova-ui/bits"; +import { + DATA_SOURCE, + DEFAULT_PIZZAGNA_ROOT, + IDashboard, + IKpiData, + IProviderConfiguration, + IRefresherProperties, + IWidget, + IWidgets, + KpiComponent, + NOVA_KPI_DATASOURCE_ADAPTER, + PizzagnaLayer, + ProviderRegistryService, + WellKnownProviders, + WidgetTypesService, +} from "@nova-ui/dashboards"; + +/** + * A simple KPI data source to retrieve the average rating of Harry Potter and the Sorcerer's Stone (book) via googleapis + */ +@Injectable() +export class AverageRatingKpiDataSource + extends DataSourceService + implements OnDestroy +{ + // This is the ID we'll use to identify the provider + public static providerId = "AverageRatingKpiDataSource"; + + // Use this subject to communicate the data source's busy state + public busy = new BehaviorSubject(false); + + constructor(private http: HttpClient) { + super(); + } + + // In this example, getFilteredData is invoked every 10 minutes (Take a look at the refresher + // provider definition in the widget configuration below to see how the interval is set) + public async getFilteredData(): Promise { + this.busy.next(true); + return new Promise((resolve) => { + // *** Make a resource request to an external API (if needed) + this.http + .get("https://www.googleapis.com/books/v1/volumes/5MQFrgEACAAJ") + .pipe(finalize(() => this.busy.next(false))) + .subscribe({ + next: (data: any) => { + resolve({ + result: { + value: data.volumeInfo.averageRating, + }, + }); + }, + error: (error: HttpErrorResponse) => { + resolve({ + result: null, + error: { + type: error.status, + }, + }); + }, + }); + }); + } + + public ngOnDestroy(): void { + this.outputsSubject.complete(); + } +} + +/** + * A component that instantiates the dashboard + */ +@Component({ + selector: "data-source-setup", + templateUrl: "./data-source-setup.component.html", + styleUrls: ["./data-source-setup.component.less"], + standalone: false, +}) +export class DataSourceSetupComponent implements OnInit { + // This variable will hold all the data needed to define the layout and behavior of the widgets. + // Pass this to the dashboard component's dashboard input in the template. + public dashboard: IDashboard; + + // Angular gridster requires a configuration object even if it's empty. + // Pass this to the dashboard component's gridsterConfig input in the template. + public gridsterConfig: GridsterConfig = {}; + + constructor( + // WidgetTypesService provides the widget's necessary structure information + private widgetTypesService: WidgetTypesService, + + // In general, the ProviderRegistryService is used for making entities available for injection into dynamically loaded components. + private providerRegistry: ProviderRegistryService + ) {} + + public ngOnInit(): void { + // Registering the data source for injection into the KPI tile. + // Note: Each tile of a KPI widget is assigned its own instance of the data source + this.providerRegistry.setProviders({ + [AverageRatingKpiDataSource.providerId]: { + provide: DATA_SOURCE, + useClass: AverageRatingKpiDataSource, + // Any dependencies that need to be injected into the provider must be listed here + deps: [HttpClient], + }, + }); + // We're using a static configuration object for this example, but this is where + // the widget's configuration could potentially be populated from a database + const kpiWidget = widgetConfig; + const widgetIndex: IWidgets = { + // Complete the KPI widget with information coming from its type definition + [kpiWidget.id]: + this.widgetTypesService.mergeWithWidgetType(kpiWidget), + }; + + // Setting the widget dimensions and position (this is for gridster) + const positions: Record = { + [kpiWidget.id]: { + cols: 4, + rows: 6, + y: 0, + x: 0, + }, + }; + + // Finally, assigning the variables we created above to the dashboard + this.dashboard = { + positions, + widgets: widgetIndex, + }; + } +} + +const widgetConfig: IWidget = { + id: "widget1", + type: "kpi", + pizzagna: { + [PizzagnaLayer.Configuration]: { + [DEFAULT_PIZZAGNA_ROOT]: { + providers: { + [WellKnownProviders.Refresher]: { + properties: { + // Configuring the refresher interval so that our data source is invoked every ten minutes + interval: 60 * 10, + enabled: true, + } as IRefresherProperties, + } as Partial, + }, + }, + header: { + properties: { + title: "Harry Potter and the Sorcerer's Stone", + subtitle: "By J. K. Rowling", + }, + }, + tiles: { + properties: { + nodes: ["kpi1"], + }, + }, + kpi1: { + id: "kpi1", + componentType: KpiComponent.lateLoadKey, + properties: { + widgetData: { + units: "out of 5 Stars", + label: "Average Rating", + }, + }, + providers: { + [WellKnownProviders.DataSource]: { + // Setting the data source providerId for the tile with id "kpi1" + providerId: AverageRatingKpiDataSource.providerId, + } as IProviderConfiguration, + [WellKnownProviders.Adapter]: { + providerId: NOVA_KPI_DATASOURCE_ADAPTER, + properties: { + componentId: "kpi1", + propertyPath: "widgetData", + }, + } as IProviderConfiguration, + }, + }, + }, + }, +}; +\\\`, + "tutorials/data-source-setup/data-source-setup.module.ts": \\\`// © 2022 SolarWinds Worldwide, LLC. All rights reserved. +// +// Permission is hereby granted, free of charge, to any person obtaining a copy +// of this software and associated documentation files (the "Software"), to +// deal in the Software without restriction, including without limitation the +// rights to use, copy, modify, merge, publish, distribute, sublicense, and/or +// sell copies of the Software, and to permit persons to whom the Software is +// furnished to do so, subject to the following conditions: +// +// The above copyright notice and this permission notice shall be included in +// all copies or substantial portions of the Software. +// +// THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR +// IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, +// FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE +// AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER +// LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, +// OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN +// THE SOFTWARE. + +import { CommonModule } from "@angular/common"; +import { HttpClientModule } from "@angular/common/http"; +import { NgModule } from "@angular/core"; +import { RouterModule } from "@angular/router"; + +import { + NuiDocsModule, + NuiMessageModule, + DEMO_PATH_TOKEN, +} from "@nova-ui/bits"; +import { NuiDashboardsModule } from "@nova-ui/dashboards"; + +import { DataSourceDocsComponent } from "./data-source-setup-docs.component"; +import { DataSourceSetupComponent } from "./data-source-setup.component"; +import { getDemoFiles } from "../../../../demo-files-factory"; + +const routes = [ + { + path: "", + component: DataSourceDocsComponent, + data: { + srlc: { + hideIndicator: true, + }, + showThemeSwitcher: true, + }, + }, + { + path: "example", + component: DataSourceSetupComponent, + data: { + srlc: { + hideIndicator: true, + }, + }, + }, +]; + +@NgModule({ + imports: [ + CommonModule, + HttpClientModule, + NuiDashboardsModule, + NuiDocsModule, + NuiMessageModule, + RouterModule.forChild(routes), + ], + declarations: [DataSourceDocsComponent, DataSourceSetupComponent], + providers: [ + { + provide: DEMO_PATH_TOKEN, + useValue: getDemoFiles("data-source-setup"), + }, + ], +}) +export default class DataSourceSetupModule {} +\\\`, + "tutorials/dynamic-header-links/dynamic-header-links-docs.component.ts": \\\`// © 2022 SolarWinds Worldwide, LLC. All rights reserved. +// +// Permission is hereby granted, free of charge, to any person obtaining a copy +// of this software and associated documentation files (the "Software"), to +// deal in the Software without restriction, including without limitation the +// rights to use, copy, modify, merge, publish, distribute, sublicense, and/or +// sell copies of the Software, and to permit persons to whom the Software is +// furnished to do so, subject to the following conditions: +// +// The above copyright notice and this permission notice shall be included in +// all copies or substantial portions of the Software. +// +// THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR +// IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, +// FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE +// AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER +// LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, +// OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN +// THE SOFTWARE. + +import { Component } from "@angular/core"; + +@Component({ + selector: "nui-dynamic-header-links-docs", + templateUrl: "./dynamic-header-links-docs.component.html", + standalone: false, +}) +export class DynamicHeaderLinksDocsComponent {} +\\\`, + "tutorials/dynamic-header-links/dynamic-header-links-docs.module.ts": \\\`// © 2022 SolarWinds Worldwide, LLC. All rights reserved. +// +// Permission is hereby granted, free of charge, to any person obtaining a copy +// of this software and associated documentation files (the "Software"), to +// deal in the Software without restriction, including without limitation the +// rights to use, copy, modify, merge, publish, distribute, sublicense, and/or +// sell copies of the Software, and to permit persons to whom the Software is +// furnished to do so, subject to the following conditions: +// +// The above copyright notice and this permission notice shall be included in +// all copies or substantial portions of the Software. +// +// THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR +// IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, +// FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE +// AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER +// LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, +// OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN +// THE SOFTWARE. + +import { NgModule } from "@angular/core"; +import { RouterModule } from "@angular/router"; + +import { DynamicHeaderLinksDocsComponent } from "./dynamic-header-links-docs.component"; + +const routes = [ + { + path: "", + component: DynamicHeaderLinksDocsComponent, + data: { + srlc: { + hideIndicator: true, + }, + showThemeSwitcher: true, + }, + }, +]; + +@NgModule({ + imports: [RouterModule.forChild(routes)], + declarations: [DynamicHeaderLinksDocsComponent], +}) +export default class DynamicHeaderLinksDocsModule {} +\\\`, + "tutorials/hello-dashboards/hello-dashboards-docs.component.ts": \\\`// © 2022 SolarWinds Worldwide, LLC. All rights reserved. +// +// Permission is hereby granted, free of charge, to any person obtaining a copy +// of this software and associated documentation files (the "Software"), to +// deal in the Software without restriction, including without limitation the +// rights to use, copy, modify, merge, publish, distribute, sublicense, and/or +// sell copies of the Software, and to permit persons to whom the Software is +// furnished to do so, subject to the following conditions: +// +// The above copyright notice and this permission notice shall be included in +// all copies or substantial portions of the Software. +// +// THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR +// IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, +// FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE +// AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER +// LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, +// OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN +// THE SOFTWARE. + +import { Component } from "@angular/core"; + +@Component({ + selector: "nui-dashboard-hello-dashboards-docs", + templateUrl: "./hello-dashboards-docs.component.html", + standalone: false, +}) +export class HelloDashboardsDocsComponent {} +\\\`, + "tutorials/hello-dashboards/hello-dashboards-example/hello-dashboards-example.component.ts": \\\`// © 2022 SolarWinds Worldwide, LLC. All rights reserved. +// +// Permission is hereby granted, free of charge, to any person obtaining a copy +// of this software and associated documentation files (the "Software"), to +// deal in the Software without restriction, including without limitation the +// rights to use, copy, modify, merge, publish, distribute, sublicense, and/or +// sell copies of the Software, and to permit persons to whom the Software is +// furnished to do so, subject to the following conditions: +// +// The above copyright notice and this permission notice shall be included in +// all copies or substantial portions of the Software. +// +// THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR +// IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, +// FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE +// AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER +// LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, +// OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN +// THE SOFTWARE. + +import { Component, OnInit } from "@angular/core"; +import { GridsterConfig, GridsterItem } from "angular-gridster2"; + +import { + IDashboard, + IWidget, + IWidgets, + KpiComponent, + PizzagnaLayer, + WidgetTypesService, +} from "@nova-ui/dashboards"; + +/** + * A component that instantiates the dashboard + */ +@Component({ + selector: "hello-dashboards-example", + templateUrl: "./hello-dashboards-example.component.html", + styleUrls: ["./hello-dashboards-example.component.less"], + standalone: false, +}) +export class HelloDashboardsExampleComponent implements OnInit { + // This variable will have all the data needed to render the widgets widgets. + // Pass this to the dashboard component's dashboard input. + public dashboard: IDashboard; + // Angular gridster requires a configuration object even if its empty. + // Pass this to the dashboard component's gridsterConfig input. + public gridsterConfig: GridsterConfig = {}; + + // WidgetTypesService provides the widget's necessary structure information + constructor(private widgetTypesService: WidgetTypesService) {} + + public ngOnInit(): void { + // Here we are hard-coding the widget config for this example, but this is where you + // could potentially populate the widget's configuration from a database + const kpiWidget = widgetConfig; + const widgetIndex: IWidgets = { + // Complete the KPI widget with information coming from its type definition + [kpiWidget.id]: + this.widgetTypesService.mergeWithWidgetType(kpiWidget), + }; + // Setting widget position and dimensions (this is for gridster) + const positions: Record = { + [kpiWidget.id]: { + cols: 4, + rows: 6, + y: 0, + x: 0, + }, + }; + // Finally, assigning the variables we created above to the dashboard + this.dashboard = { + positions, + widgets: widgetIndex, + }; + } +} + +// In a real-world scenario, this configuration would typically be fetched from a database or at least live in another file +const widgetConfig: IWidget = { + id: "widget1", + type: "kpi", + pizzagna: { + [PizzagnaLayer.Configuration]: { + header: { + properties: { + title: "Hello, KPI Widget!", + subtitle: "A Venue for Meaningful Values", + }, + }, + tiles: { + properties: { + nodes: ["kpi1"], + }, + }, + kpi1: { + id: "kpi1", + componentType: KpiComponent.lateLoadKey, + properties: { + widgetData: { + id: "totalStorage", + value: 1, + label: "Total storage", + units: "TB", + }, + }, + }, + }, + }, +}; +\\\`, + "tutorials/hello-dashboards/hello-dashboards.module.ts": \\\`// © 2022 SolarWinds Worldwide, LLC. All rights reserved. +// +// Permission is hereby granted, free of charge, to any person obtaining a copy +// of this software and associated documentation files (the "Software"), to +// deal in the Software without restriction, including without limitation the +// rights to use, copy, modify, merge, publish, distribute, sublicense, and/or +// sell copies of the Software, and to permit persons to whom the Software is +// furnished to do so, subject to the following conditions: +// +// The above copyright notice and this permission notice shall be included in +// all copies or substantial portions of the Software. +// +// THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR +// IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, +// FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE +// AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER +// LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, +// OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN +// THE SOFTWARE. + +import { CommonModule } from "@angular/common"; +import { NgModule } from "@angular/core"; +import { RouterModule } from "@angular/router"; + +import { + NuiDocsModule, + NuiMessageModule, + DEMO_PATH_TOKEN, +} from "@nova-ui/bits"; +import { NuiDashboardsModule } from "@nova-ui/dashboards"; + +import { HelloDashboardsDocsComponent } from "./hello-dashboards-docs.component"; +import { HelloDashboardsExampleComponent } from "./hello-dashboards-example/hello-dashboards-example.component"; +import { getDemoFiles } from "../../../../demo-files-factory"; + +const routes = [ + { + path: "", + component: HelloDashboardsDocsComponent, + data: { + srlc: { + hideIndicator: true, + }, + showThemeSwitcher: true, + }, + }, + { + path: "example", + component: HelloDashboardsExampleComponent, + data: { + srlc: { + hideIndicator: true, + }, + }, + }, +]; + +@NgModule({ + imports: [ + CommonModule, + NuiDashboardsModule, + NuiDocsModule, + NuiMessageModule, + RouterModule.forChild(routes), + ], + declarations: [ + HelloDashboardsDocsComponent, + HelloDashboardsExampleComponent, + ], + providers: [ + { + provide: DEMO_PATH_TOKEN, + useValue: getDemoFiles("hello-dashboards"), + }, + ], +}) +export default class HelloDashboardsModule {} +\\\`, + "tutorials/persistence-handler-setup/persistence-handler-setup-docs.component.ts": \\\`// © 2022 SolarWinds Worldwide, LLC. All rights reserved. +// +// Permission is hereby granted, free of charge, to any person obtaining a copy +// of this software and associated documentation files (the "Software"), to +// deal in the Software without restriction, including without limitation the +// rights to use, copy, modify, merge, publish, distribute, sublicense, and/or +// sell copies of the Software, and to permit persons to whom the Software is +// furnished to do so, subject to the following conditions: +// +// The above copyright notice and this permission notice shall be included in +// all copies or substantial portions of the Software. +// +// THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR +// IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, +// FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE +// AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER +// LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, +// OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN +// THE SOFTWARE. + +import { Component } from "@angular/core"; + +@Component({ + selector: "nui-dashboard-persistence-handler-setup-docs", + templateUrl: "./persistence-handler-setup-docs.component.html", + standalone: false, +}) +export class PersistenceHandlerSetupDocsComponent {} +\\\`, + "tutorials/persistence-handler-setup/persistence-handler-setup.component.ts": \\\`// © 2022 SolarWinds Worldwide, LLC. All rights reserved. +// +// Permission is hereby granted, free of charge, to any person obtaining a copy +// of this software and associated documentation files (the "Software"), to +// deal in the Software without restriction, including without limitation the +// rights to use, copy, modify, merge, publish, distribute, sublicense, and/or +// sell copies of the Software, and to permit persons to whom the Software is +// furnished to do so, subject to the following conditions: +// +// The above copyright notice and this permission notice shall be included in +// all copies or substantial portions of the Software. +// +// THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR +// IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, +// FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE +// AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER +// LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, +// OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN +// THE SOFTWARE. + +import { HttpClient, HttpErrorResponse } from "@angular/common/http"; +import { + ChangeDetectorRef, + Component, + Injectable, + OnDestroy, + OnInit, +} from "@angular/core"; +import { GridsterConfig, GridsterItem } from "angular-gridster2"; +import { BehaviorSubject, Observable, Subject } from "rxjs"; +import { finalize } from "rxjs/operators"; + +import { + DataSourceService, + IFilteringOutputs, + ToastService, + uuid, +} from "@nova-ui/bits"; +import { + DATA_SOURCE, + DEFAULT_PIZZAGNA_ROOT, + IDashboard, + IDashboardPersistenceHandler, + IKpiData, + IProviderConfiguration, + IRefresherProperties, + IWidget, + IWidgets, + KpiComponent, + NOVA_KPI_DATASOURCE_ADAPTER, + PizzagnaLayer, + ProviderRegistryService, + WellKnownPathKey, + WellKnownProviders, + WidgetTypesService, +} from "@nova-ui/dashboards"; + +/** + * A simple persistence handler that is tied to the widget editor directive + */ +@Injectable() +// The realizer of IDashboardPersistenceHandler may implement a trySubmit and/or a tryRemove method. +export class PersistenceHandler implements IDashboardPersistenceHandler { + // This variable is just to show how to handle error handling. + private persistenceSucceeded: boolean = true; + + // The example uses the toast service to demonstrate the + // invocation of each of the persistence handler callbacks + constructor(private toastService: ToastService) { + // toastService options to let it sit on the page for 2 seconds. + this.toastService.setConfig({ + timeOut: 2000, + }); + } + + // This method will be invoked anytime the widget editor form gets submitted. + public trySubmit = (widget: IWidget): Observable => { + // Since we are working asynchronously, we'll return a subject. So, after the submit attempt + // succeeds or fails, we can let the subscriber know the result. + const subject = new Subject(); + + if (!widget.id) { + // Creates an id if the widget has no id. + // (This step will make more sense in the context of the widget cloning tutorial + // in which we handle the persistence of a newly created widget.) + widget.id = uuid(); + } + + // For this example, we're using a setTimeout to mock an asynchronous persistence request to a backend + setTimeout(() => { + if (this.persistenceSucceeded) { + // Passes along the new widget after one second. + subject.next(widget); + // Toast on the page on success. + this.toastService.success({ + title: $localize\\\\\\\`Submit succeeded.\\\\\\\`, + }); + } else { + const errorText = $localize\\\\\\\`Submit failed.\\\\\\\`; + // Toast on the page on failure. + this.toastService.error({ title: errorText }); + // Makes the subject say there is an error. + subject.error(errorText); + } + // Completes the subject so whoever subscribes to it knows its finished. + subject.complete(); + }, 1000); + + // Returns the subject as an observable. + return subject.asObservable(); + }; + + // This method will be invoked anytime there's a widget removal attempt. + public tryRemove = (widgetId: string): Observable => { + const subject = new Subject(); + + setTimeout(() => { + if (this.persistenceSucceeded) { + // Pass through the id of the widget that was removed. + subject.next(widgetId); + this.toastService.success({ + title: $localize\\\\\\\`Removal succeeded.\\\\\\\`, + }); + } else { + const errorText = $localize\\\\\\\`Removal failed.\\\\\\\`; + this.toastService.error({ title: errorText }); + subject.error(errorText); + } + subject.complete(); + }, 1000); + + return subject.asObservable(); + }; +} + +/** + * A component that instantiates the dashboard + */ +@Component({ + selector: "persistence-handler-setup", + templateUrl: "./persistence-handler-setup.component.html", + styleUrls: ["./persistence-handler-setup.component.less"], + // Here we provide our persistence handler at the component level; this can also be done in the module. + providers: [PersistenceHandler], + standalone: false, +}) +export class PersistenceHandlerSetupComponent implements OnInit { + // This variable will hold all the data needed to define the layout and behavior of the widgets. + // Pass this to the dashboard component's dashboard input in the template. + public dashboard: IDashboard | undefined; + + // Angular gridster requires a configuration object even if it's empty. + // Pass this to the dashboard component's gridsterConfig input in the template. + public gridsterConfig: GridsterConfig = {}; + + // Boolean which dashboard takes in as an input if its true it allows you to move widgets around. + public editMode: boolean = false; + + constructor( + // WidgetTypesService provides the widget's necessary structure information + private widgetTypesService: WidgetTypesService, + + // In general, the ProviderRegistryService is used for making entities available for injection into dynamically loaded components. + private providerRegistry: ProviderRegistryService, + + // We are injecting the PersistenceHandler we created and assigning it to a property we use in the template. + public persistenceHandler: PersistenceHandler, + private changeDetectorRef: ChangeDetectorRef + ) {} + + public ngOnInit(): void { + // Grabbing the widget's default template which will be needed as a parameter for setNode + const widgetTemplate = this.widgetTypesService.getWidgetType("kpi", 1); + // Registering our data sources as dropdown options in the widget editor/configurator + // Note: This could also be done in the parent module's constructor so that + // multiple dashboards could have access to the same widget template modification. + this.widgetTypesService.setNode( + // This is the template we grabbed above with getWidgetType + widgetTemplate, + // We are setting the editor/configurator part of the widget template + "configurator", + // This indicates which node you are changing and we want to change + // the data source providers available for selection in the editor. + WellKnownPathKey.DataSourceProviders, + // We are setting the data sources available for selection in the editor + [ + AverageRatingKpiDataSource.providerId, + RatingsCountKpiDataSource.providerId, + ] + ); + + // Registering the data sources available for injection into the KPI tiles. + // Note: Each tile of a KPI widget is assigned its own instance of a data source + this.providerRegistry.setProviders({ + [AverageRatingKpiDataSource.providerId]: { + provide: DATA_SOURCE, + useClass: AverageRatingKpiDataSource, + // Any dependencies that need to be injected into the provider must be listed here + deps: [HttpClient], + }, + [RatingsCountKpiDataSource.providerId]: { + provide: DATA_SOURCE, + useClass: RatingsCountKpiDataSource, + deps: [HttpClient], + }, + }); + + this.initializeDashboard(); + } + + /** Used for restoring widgets state */ + public reInitializeDashboard(): void { + // destroys the components and their providers so the dashboard can re init data + this.dashboard = undefined; + this.changeDetectorRef.detectChanges(); + + this.initializeDashboard(); + } + + public initializeDashboard(): void { + // We're using a static configuration object for this example (see widgetConfig at the bottom of the file), + // but this is where the widget's configuration could potentially be populated from a database + const kpiWidget = widgetConfig; + const widgetIndex: IWidgets = { + // Complete the KPI widget with information coming from its type definition + [kpiWidget.id]: + this.widgetTypesService.mergeWithWidgetType(kpiWidget), + }; + + // Setting the widget dimensions and position (this is for gridster) + const positions: Record = { + [kpiWidget.id]: { + cols: 4, + rows: 6, + y: 0, + x: 0, + }, + }; + + // Finally, assigning the variables we created above to the dashboard + this.dashboard = { + positions, + widgets: widgetIndex, + }; + } +} + +/** + * A simple KPI data source to retrieve the average rating of Harry Potter and the Sorcerer's Stone (book) via googleapis + */ +@Injectable() +export class AverageRatingKpiDataSource + extends DataSourceService + implements OnDestroy +{ + // This is the ID we'll use to identify the provider + public static providerId = "AverageRatingKpiDataSource"; + + // Use this subject to communicate the data source's busy state + public busy = new BehaviorSubject(false); + + constructor(private http: HttpClient) { + super(); + } + + // In this example, getFilteredData is invoked every 10 minutes (Take a look at the refresher + // provider definition in the widget configuration below to see how the interval is set) + public async getFilteredData(): Promise { + this.busy.next(true); + return new Promise((resolve) => { + // *** Make a resource request to an external API (if needed) + this.http + .get("https://www.googleapis.com/books/v1/volumes/5MQFrgEACAAJ") + .pipe(finalize(() => this.busy.next(false))) + .subscribe({ + next: (data: any) => { + resolve({ + result: { + value: data.volumeInfo.averageRating, + }, + }); + }, + error: (error: HttpErrorResponse) => { + resolve({ + result: null, + error: { + type: error.status, + }, + }); + }, + }); + }); + } + + public ngOnDestroy(): void { + this.outputsSubject.complete(); + } +} + +/** + * A simple KPI data source to retrieve the ratings count of Harry Potter and the Sorcerer's Stone (book) via googleapis + */ +@Injectable() +export class RatingsCountKpiDataSource + extends DataSourceService + implements OnDestroy +{ + public static providerId = "RatingsCountKpiDataSource"; + + // Use this subject to communicate the data source's busy state + public busy = new BehaviorSubject(false); + + constructor(private http: HttpClient) { + super(); + } + + public async getFilteredData(): Promise { + this.busy.next(true); + return new Promise((resolve) => { + this.http + .get("https://www.googleapis.com/books/v1/volumes/5MQFrgEACAAJ") + .pipe(finalize(() => this.busy.next(false))) + .subscribe({ + next: (data: any) => { + resolve({ + result: { + value: data.volumeInfo.ratingsCount, + }, + }); + }, + error: (error: HttpErrorResponse) => { + resolve({ + result: null, + error: { + type: error.status, + }, + }); + }, + }); + }); + } + + public ngOnDestroy(): void { + this.outputsSubject.complete(); + } +} + +const widgetConfig: IWidget = { + id: "widget1", + type: "kpi", + pizzagna: { + [PizzagnaLayer.Configuration]: { + [DEFAULT_PIZZAGNA_ROOT]: { + providers: { + [WellKnownProviders.Refresher]: { + properties: { + // Configuring the refresher interval so that our data source is invoked every ten minutes + interval: 60 * 10, + enabled: true, + } as IRefresherProperties, + } as Partial, + }, + }, + header: { + properties: { + title: "Harry Potter and the Sorcerer's Stone", + subtitle: "By J. K. Rowling", + }, + }, + tiles: { + properties: { + nodes: ["kpi1"], + }, + }, + kpi1: { + id: "kpi1", + componentType: KpiComponent.lateLoadKey, + properties: { + widgetData: { + units: "out of 5 Stars", + label: "Average Rating", + }, + }, + providers: { + [WellKnownProviders.DataSource]: { + // Setting the data source providerId for the tile with id "kpi1" + providerId: AverageRatingKpiDataSource.providerId, + } as IProviderConfiguration, + [WellKnownProviders.Adapter]: { + providerId: NOVA_KPI_DATASOURCE_ADAPTER, + properties: { + componentId: "kpi1", + propertyPath: "widgetData", + }, + } as IProviderConfiguration, + }, + }, + }, + }, +}; +\\\`, + "tutorials/persistence-handler-setup/persistence-handler-setup.module.ts": \\\`// © 2022 SolarWinds Worldwide, LLC. All rights reserved. +// +// Permission is hereby granted, free of charge, to any person obtaining a copy +// of this software and associated documentation files (the "Software"), to +// deal in the Software without restriction, including without limitation the +// rights to use, copy, modify, merge, publish, distribute, sublicense, and/or +// sell copies of the Software, and to permit persons to whom the Software is +// furnished to do so, subject to the following conditions: +// +// The above copyright notice and this permission notice shall be included in +// all copies or substantial portions of the Software. +// +// THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR +// IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, +// FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE +// AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER +// LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, +// OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN +// THE SOFTWARE. + +import { CommonModule } from "@angular/common"; +import { HttpClientModule } from "@angular/common/http"; +import { NgModule } from "@angular/core"; +import { RouterModule } from "@angular/router"; + +import { + NuiButtonModule, + NuiDocsModule, + NuiMessageModule, + NuiSwitchModule, + NuiToastModule, + DEMO_PATH_TOKEN, +} from "@nova-ui/bits"; +import { NuiDashboardsModule } from "@nova-ui/dashboards"; + +import { PersistenceHandlerSetupDocsComponent } from "./persistence-handler-setup-docs.component"; +import { PersistenceHandlerSetupComponent } from "./persistence-handler-setup.component"; +import { getDemoFiles } from "../../../../demo-files-factory"; + +const routes = [ + { + path: "", + component: PersistenceHandlerSetupDocsComponent, + data: { + srlc: { + hideIndicator: true, + }, + showThemeSwitcher: true, + }, + }, + { + path: "example", + component: PersistenceHandlerSetupComponent, + data: { + srlc: { + hideIndicator: true, + }, + }, + }, +]; + +@NgModule({ + imports: [ + CommonModule, + HttpClientModule, + NuiDashboardsModule, + NuiDocsModule, + NuiMessageModule, + NuiSwitchModule, + NuiToastModule, + NuiButtonModule, + RouterModule.forChild(routes), + ], + declarations: [ + PersistenceHandlerSetupDocsComponent, + PersistenceHandlerSetupComponent, + ], + providers: [ + { + provide: DEMO_PATH_TOKEN, + useValue: getDemoFiles("persistence-handler-setup"), + }, + ], +}) +export default class PersistenceHandlerSetupModule {} +\\\`, + "tutorials/tutorials.module.ts": \\\`// © 2022 SolarWinds Worldwide, LLC. All rights reserved. +// +// Permission is hereby granted, free of charge, to any person obtaining a copy +// of this software and associated documentation files (the "Software"), to +// deal in the Software without restriction, including without limitation the +// rights to use, copy, modify, merge, publish, distribute, sublicense, and/or +// sell copies of the Software, and to permit persons to whom the Software is +// furnished to do so, subject to the following conditions: +// +// The above copyright notice and this permission notice shall be included in +// all copies or substantial portions of the Software. +// +// THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR +// IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, +// FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE +// AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER +// LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, +// OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN +// THE SOFTWARE. + +import { NgModule, Type } from "@angular/core"; +import { RouterModule, Routes } from "@angular/router"; + +import { ConfiguratorHeadingService } from "@nova-ui/dashboards"; + +export enum TutorialsModuleRoute { + HelloDashboards = "hello-dashboards", + DataSource = "data-source-setup", + WidgetEditor = "widget-editor-setup", + SubmitHandler = "persistence-handler-setup", + WidgetCreation = "widget-creation", + Customization = "customization", + WidgetErrorHandling = "widget-error-handling", + DynamicHeaderLinks = "dynamic-header-links", +} + +const routes: Routes = [ + { + path: TutorialsModuleRoute.HelloDashboards, + loadChildren: async () => + import( + "./hello-dashboards/hello-dashboards.module" + ) as object as Promise>, + }, + { + path: TutorialsModuleRoute.DataSource, + loadChildren: async () => + import( + "./data-source-setup/data-source-setup.module" + ) as object as Promise>, + }, + { + path: TutorialsModuleRoute.WidgetEditor, + loadChildren: async () => + import( + "./widget-editor-setup/widget-editor-setup.module" + ) as object as Promise>, + }, + { + path: TutorialsModuleRoute.SubmitHandler, + loadChildren: async () => + import( + "./persistence-handler-setup/persistence-handler-setup.module" + ) as object as Promise>, + }, + { + path: TutorialsModuleRoute.WidgetCreation, + loadChildren: async () => + import( + "./widget-creation/widget-creation.module" + ) as object as Promise>, + }, + { + path: TutorialsModuleRoute.Customization, + loadChildren: async () => + import("./customization/customization.module") as object as Promise< + Type + >, + }, + { + path: TutorialsModuleRoute.WidgetErrorHandling, + loadChildren: async () => + import( + "./widget-error-handling/widget-error-handling.module" + ) as object as Promise>, + }, + { + path: TutorialsModuleRoute.DynamicHeaderLinks, + loadChildren: async () => + import( + "./dynamic-header-links/dynamic-header-links-docs.module" + ) as object as Promise>, + }, +]; + +@NgModule({ + imports: [RouterModule.forChild(routes)], + providers: [ConfiguratorHeadingService], +}) +export default class TutorialsModule {} +\\\`, + "tutorials/widget-creation/widget-creation-docs.component.ts": \\\`// © 2022 SolarWinds Worldwide, LLC. All rights reserved. +// +// Permission is hereby granted, free of charge, to any person obtaining a copy +// of this software and associated documentation files (the "Software"), to +// deal in the Software without restriction, including without limitation the +// rights to use, copy, modify, merge, publish, distribute, sublicense, and/or +// sell copies of the Software, and to permit persons to whom the Software is +// furnished to do so, subject to the following conditions: +// +// The above copyright notice and this permission notice shall be included in +// all copies or substantial portions of the Software. +// +// THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR +// IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, +// FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE +// AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER +// LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, +// OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN +// THE SOFTWARE. + +import { Component } from "@angular/core"; + +@Component({ + selector: "nui-dashboard-widget-creation-docs", + templateUrl: "./widget-creation-docs.component.html", + standalone: false, +}) +export class WidgetCreationDocsComponent {} +\\\`, + "tutorials/widget-creation/widget-creation.component.ts": \\\`// © 2022 SolarWinds Worldwide, LLC. All rights reserved. +// +// Permission is hereby granted, free of charge, to any person obtaining a copy +// of this software and associated documentation files (the "Software"), to +// deal in the Software without restriction, including without limitation the +// rights to use, copy, modify, merge, publish, distribute, sublicense, and/or +// sell copies of the Software, and to permit persons to whom the Software is +// furnished to do so, subject to the following conditions: +// +// The above copyright notice and this permission notice shall be included in +// all copies or substantial portions of the Software. +// +// THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR +// IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, +// FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE +// AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER +// LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, +// OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN +// THE SOFTWARE. + +import { HttpClient, HttpErrorResponse } from "@angular/common/http"; +import { + Component, + EventEmitter, + Injectable, + OnDestroy, + OnInit, + Output, + ViewChild, +} from "@angular/core"; +import { GridsterConfig, GridsterItem } from "angular-gridster2"; +import { BehaviorSubject, Observable, Subject } from "rxjs"; +import { finalize, take, takeUntil } from "rxjs/operators"; + +import { + DataSourceService, + IFilteringOutputs, + ToastService, + uuid, +} from "@nova-ui/bits"; +import { + DashboardComponent, + DATA_SOURCE, + DEFAULT_PIZZAGNA_ROOT, + IDashboard, + IDashboardPersistenceHandler, + IDataSourceOutput, + IKpiData, + IProviderConfiguration, + IRefresherProperties, + IWidget, + IWidgets, + IWidgetSelector, + IWidgetTemplateSelector, + KpiComponent, + NOVA_KPI_DATASOURCE_ADAPTER, + PizzagnaLayer, + ProviderRegistryService, + WellKnownPathKey, + WellKnownProviders, + WidgetClonerService, + WidgetTypesService, +} from "@nova-ui/dashboards"; + +// Interface of a widget item +interface IWidgetItem { + name: string; + widget: IWidget; +} + +// This component acts as the first step, or page, in the wizard where the user selects a wizard type to create. +// It's recommended to have this component in a different file. For this tutorial, it's included in the same +// file for simplicity. +@Component({ + selector: "widget-template-selection", + styleUrls: ["./widget-creation.component.less"], + template: \\\\\\\` +
+ + +
+ + +
+
{{ item.name }}
+
+
+ \\\\\\\`, + standalone: false, +}) +export class WidgetTemplateSelectionComponent + implements IWidgetTemplateSelector, OnInit +{ + // This output will notify the wizard that a widget has been selected. + @Output() public widgetSelected = new EventEmitter(); + + public widgetItems: IWidgetItem[] = []; + public widgetSelection: IWidgetItem[]; + + constructor(private widgetTypesService: WidgetTypesService) {} + + public ngOnInit(): void { + // Here we combine the widget structure from the WidgetTypesService with the corresponding widget + // configuration to create an array of widget objects for the itemSource on the repeat component. + this.widgetItems = [ + { + name: "Fully Configured KPI Widget", + widget: this.widgetTypesService.mergeWithWidgetType( + fullKpiWidgetConfig + ), + }, + { + name: "Unconfigured Proportional Widget", + // Note that 'partialPropWidgetConfig' sets 'metadata.needsConfiguration' to true. + // When this widget is selected in the wizard, the 'Create Widget' button will be hidden + // to guide the user to the second step where they can complete the configuration. + widget: this.widgetTypesService.mergeWithWidgetType( + partialPropWidgetConfig + ), + }, + ]; + + // You can optionally auto-select a widget by doing the following + // this.onSelect([this.widgetItems[0]]); + } + + public onSelect(selectedItems: any[]): void { + // We emit the selected widget to communicate the selection to the configurator + this.widgetSelected.emit(selectedItems[0].widget); + this.widgetSelection = selectedItems; + } +} + +/** + * A simple persistence handler that is tied to the widget editor directive + */ +@Injectable() +// The realizer of IDashboardPersistenceHandler may implement a trySubmit and/or a tryRemove method. +export class PersistenceHandler implements IDashboardPersistenceHandler { + // This variable is just to show how to handle error handling. + private persistenceSucceeded: boolean = true; + + // The example uses the toast service to demonstrate the + // invocation of each of the persistence handler callbacks + constructor(private toastService: ToastService) { + // toastService options to let it sit on the page for 2 seconds. + this.toastService.setConfig({ + timeOut: 2000, + }); + } + + // This method will be invoked anytime the widget editor form gets submitted. + public trySubmit = (widget: IWidget): Observable => { + // Since we are working asynchronously, we'll return a subject. So, after the submit attempt + // succeeds or fails, we can let the subscriber know the result. + const subject = new Subject(); + + if (!widget.id) { + // Creates an id if the widget has no id. + // (This step will make more sense in the context of the widget cloning tutorial + // in which we handle the persistence of a newly created widget.) + widget.id = uuid(); + } + + // For this example, we're using a setTimeout to mock an asynchronous persistence request to a backend + setTimeout(() => { + if (this.persistenceSucceeded) { + // Passes along the new widget after one second. + subject.next(widget); + // Toast on the page on success. + this.toastService.success({ + title: $localize\\\\\\\`Submit succeeded.\\\\\\\`, + }); + } else { + const errorText = $localize\\\\\\\`Submit failed.\\\\\\\`; + // Toast on the page on failure. + this.toastService.error({ title: errorText }); + // Makes the subject say there is an error. + subject.error(errorText); + } + // Completes the subject so whoever subscribes to it knows its finished. + subject.complete(); + }, 1000); + + // Returns the subject as an observable. + return subject.asObservable(); + }; + + // This method will be invoked anytime there's a widget removal attempt. + public tryRemove = (widgetId: string): Observable => { + const subject = new Subject(); + + setTimeout(() => { + if (this.persistenceSucceeded) { + // Pass through the id of the widget that was removed. + subject.next(widgetId); + this.toastService.success({ + title: $localize\\\\\\\`Removal success\\\\\\\`, + }); + } else { + const errorText = $localize\\\\\\\`Removal failed.\\\\\\\`; + this.toastService.error({ title: errorText }); + subject.error(errorText); + } + subject.complete(); + }, 1000); + + return subject.asObservable(); + }; +} + +/** + * A component that instantiates the dashboard + */ +@Component({ + selector: "widget-creation", + templateUrl: "./widget-creation.component.html", + styleUrls: ["./widget-creation.component.less"], + // Here we provide our persistence handler at the component level; this can also be done in the module. + providers: [PersistenceHandler], + standalone: false, +}) +export class WidgetCreationComponent implements OnInit { + // The WidgetClonerService will need this for updating the dashboard + @ViewChild(DashboardComponent, { static: true }) + dashboardComponent: DashboardComponent; + // This variable will hold all the data needed to define the layout and behavior of the widgets. + // Pass this to the dashboard component's dashboard input in the template. + public dashboard: IDashboard; + + // Angular gridster requires a configuration object even if it's empty. + // Pass this to the dashboard component's gridsterConfig input in the template. + public gridsterConfig: GridsterConfig = { + // These values will be used to set the initial widget dimensions on creation. + // If not set, they each default to 6. + defaultItemCols: 3, + defaultItemRows: 5, + }; + + // Boolean the dashboard takes in as an input; if it's set to true + // the dashboard allows you to resize widgets and move them around. + public editMode: boolean = false; + + // Subject used for auto-unsubscribing from subscriptions on component destruction + private readonly destroy$ = new Subject(); + + constructor( + // WidgetTypesService provides the widget's necessary structure information + private widgetTypesService: WidgetTypesService, + + // In general, the ProviderRegistryService is used for making entities available for injection into dynamically loaded components. + private providerRegistry: ProviderRegistryService, + + // Injecting the PersistenceHandler we created and assigning it to a property we use in the template. + public persistenceHandler: PersistenceHandler, + + // Injecting the cloner service which is needed for opening up the cloner wizard. + private widgetClonerService: WidgetClonerService + ) {} + + public ngOnInit(): void { + // Grabbing the widget's default template which will be needed as a parameter for setNode + const kpiTemplate = this.widgetTypesService.getWidgetType("kpi", 1); + const proportionalTemplate = this.widgetTypesService.getWidgetType( + "proportional", + 1 + ); + + // Registering our data sources as dropdown options in the widget editor/configurator + // Note: This could also be done in the parent module's constructor so that + // multiple dashboards could have access to the same widget template modification. + this.widgetTypesService.setNode( + // This is the template we grabbed above with getWidgetType + proportionalTemplate, + // Setting the editor/configurator part of the widget template + "configurator", + // This indicates which node you are changing and we want to change + // the data source providers available for selection in the editor. + WellKnownPathKey.DataSourceProviders, + // Setting the data sources available for selection in the editor + [RandomCitiesProportionalDataSource.providerId] + ); + + // Same as above, but for the KPI data sources + this.widgetTypesService.setNode( + kpiTemplate, + "configurator", + WellKnownPathKey.DataSourceProviders, + [ + AverageRatingKpiDataSource.providerId, + RatingsCountKpiDataSource.providerId, + ] + ); + + // Registering the data sources available for injection into the KPI tiles and proportional widget. + // Note: Each tile of a KPI widget is assigned its own instance of a data source. + this.providerRegistry.setProviders({ + [AverageRatingKpiDataSource.providerId]: { + provide: DATA_SOURCE, + useClass: AverageRatingKpiDataSource, + // Any dependencies that need to be injected into the provider must be listed here + deps: [HttpClient], + }, + [RatingsCountKpiDataSource.providerId]: { + provide: DATA_SOURCE, + useClass: RatingsCountKpiDataSource, + deps: [HttpClient], + }, + [RandomCitiesProportionalDataSource.providerId]: { + provide: DATA_SOURCE, + useClass: RandomCitiesProportionalDataSource, + deps: [], + }, + }); + + this.initializeDashboard(); + } + + public onCreateWidget(): void { + const widgetSelector: IWidgetSelector = { + // Template ref of the dashboard component. + dashboardComponent: this.dashboardComponent, + // A trySubmit function; in this case, we use the trySubmit from the PersistenceHandler created in the previous tutorial. + trySubmit: this.persistenceHandler.trySubmit, + // WidgetTemplateSelectionComponent will act as step one of the wizard to allow the user to select which widget will be cloned. + widgetSelectionComponentType: WidgetTemplateSelectionComponent, + }; + this.widgetClonerService + .open(widgetSelector) + .pipe( + // Auto-unsubscribe after one emission or on component destruction + take(1), + takeUntil(this.destroy$) + ) + .subscribe(); + } + + public initializeDashboard(): void { + // We're using a static configuration object for this example (see widgetConfig at the bottom of the file), + // but this is where the widget's configuration could potentially be populated from a database + const kpiWidget = fullKpiWidgetConfig; + const widgetIndex: IWidgets = { + // Complete the KPI widget with information coming from its type definition + [kpiWidget.id]: + this.widgetTypesService.mergeWithWidgetType(kpiWidget), + }; + + // Setting the widget dimensions and position (this is for gridster) + // Note: If no position is given for a widget the 'defaultItemCols' and 'defaultItemRows' properties + // from the gridsterConfig will be used for the dimensions + const positions: Record = { + [kpiWidget.id]: { + cols: 3, + rows: 5, + y: 0, + x: 0, + }, + }; + + // Finally, assigning the variables we created above to the dashboard + this.dashboard = { + positions, + widgets: widgetIndex, + }; + } +} + +// Interface for each data point in a proportional widget. +interface IProportionalWidgetData { + id: string; + name: string; + data: number[]; + icon: string; + link: string; + value: string; +} + +@Injectable() +export class RandomCitiesProportionalDataSource implements OnDestroy { + public static providerId = "RandomCitiesProportionalDataSource"; + + public outputsSubject = new Subject< + IDataSourceOutput + >(); + + // Every time applyFilters gets ran we are changing the data source. + public applyFilters(): void { + setTimeout(() => { + this.outputsSubject.next({ + result: this.getRandomProportionalWidgetData(), + }); + }, 1000); + } + + public ngOnDestroy(): void { + this.outputsSubject.complete(); + } + + private getRandomProportionalWidgetData(): IProportionalWidgetData[] { + return [ + { + id: "Down", + name: "Down", + data: [Math.round(Math.random() * 100)], + icon: "status_down", + link: "https://en.wikipedia.org/wiki/Brno", + value: "Brno", + }, + { + id: "Critical", + name: "Critical", + data: [Math.round(Math.random() * 100)], + icon: "status_critical", + link: "https://en.wikipedia.org/wiki/Kyiv", + value: "Kyiv", + }, + { + id: "Warning", + name: "Warning", + data: [Math.round(Math.random() * 100)], + icon: "status_warning", + link: "https://en.wikipedia.org/wiki/Austin", + value: "Austin", + }, + { + id: "Unknown", + name: "Unknown", + data: [Math.round(Math.random() * 100)], + icon: "status_unknown", + link: "https://en.wikipedia.org/wiki/Lisbon", + value: "Lisbon", + }, + { + id: "Up", + name: "Up", + data: [Math.round(Math.random() * 100)], + icon: "status_up", + link: "https://en.wikipedia.org/wiki/Sydney", + value: "Sydney", + }, + { + id: "Unmanaged", + name: "Unmanaged", + data: [Math.round(Math.random() * 100)], + icon: "status_unmanaged", + link: "https://en.wikipedia.org/wiki/Nur-Sultan", + value: "Nur-Sultan", + }, + ]; + } +} + +/** + * A simple KPI data source to retrieve the average rating of Harry Potter and the Sorcerer's Stone (book) via googleapis + */ +@Injectable() +export class AverageRatingKpiDataSource + extends DataSourceService + implements OnDestroy +{ + // This is the ID we'll use to identify the provider + public static providerId = "AverageRatingKpiDataSource"; + + // Use this subject to communicate the data source's busy state + public busy = new BehaviorSubject(false); + + constructor(private http: HttpClient) { + super(); + } + + // In this example, getFilteredData is invoked every 10 minutes (Take a look at the refresher + // provider definition in the widget configuration below to see how the interval is set) + public async getFilteredData(): Promise { + this.busy.next(true); + return new Promise((resolve) => { + // *** Make a resource request to an external API (if needed) + this.http + .get("https://www.googleapis.com/books/v1/volumes/5MQFrgEACAAJ") + .pipe(finalize(() => this.busy.next(false))) + .subscribe({ + next: (data: any) => { + resolve({ + result: { + value: data.volumeInfo.averageRating, + }, + }); + }, + error: (error: HttpErrorResponse) => { + resolve({ + result: null, + error: { + type: error.status, + }, + }); + }, + }); + }); + } + + public ngOnDestroy(): void { + this.outputsSubject.complete(); + } +} + +/** + * A simple KPI data source to retrieve the ratings count of Harry Potter and the Sorcerer's Stone (book) via googleapis + */ +@Injectable() +export class RatingsCountKpiDataSource + extends DataSourceService + implements OnDestroy +{ + public static providerId = "RatingsCountKpiDataSource"; + + // Use this subject to communicate the data source's busy state + public busy = new BehaviorSubject(false); + + constructor(private http: HttpClient) { + super(); + } + + public async getFilteredData(): Promise { + this.busy.next(true); + return new Promise((resolve) => { + this.http + .get("https://www.googleapis.com/books/v1/volumes/5MQFrgEACAAJ") + .pipe(finalize(() => this.busy.next(false))) + .subscribe({ + next: (data: any) => { + resolve({ + result: { + value: data.volumeInfo.ratingsCount, + }, + }); + }, + error: (error: HttpErrorResponse) => { + resolve({ + result: null, + error: { + type: error.status, + }, + }); + }, + }); + }); + } + + public ngOnDestroy(): void { + this.outputsSubject.complete(); + } +} + +const fullKpiWidgetConfig: IWidget = { + id: "widget1", + type: "kpi", + pizzagna: { + [PizzagnaLayer.Configuration]: { + [DEFAULT_PIZZAGNA_ROOT]: { + providers: { + [WellKnownProviders.Refresher]: { + properties: { + // Configuring the refresher interval so that our data source is invoked every ten minutes + interval: 60 * 10, + enabled: true, + } as IRefresherProperties, + } as Partial, + }, + }, + header: { + properties: { + title: "Harry Potter and the Sorcerer's Stone", + subtitle: "By J. K. Rowling", + }, + }, + tiles: { + properties: { + nodes: ["kpi1"], + }, + }, + kpi1: { + id: "kpi1", + componentType: KpiComponent.lateLoadKey, + properties: { + widgetData: { + units: \\\\\\\`out of 5 Stars\\\\\\\`, + label: \\\\\\\`Average Rating\\\\\\\`, + }, + }, + providers: { + [WellKnownProviders.DataSource]: { + // Setting the data source providerId for the tile with id "kpi1" + providerId: AverageRatingKpiDataSource.providerId, + } as IProviderConfiguration, + [WellKnownProviders.Adapter]: { + providerId: NOVA_KPI_DATASOURCE_ADAPTER, + properties: { + componentId: "kpi1", + propertyPath: "widgetData", + }, + } as IProviderConfiguration, + }, + }, + }, + }, +}; + +const partialPropWidgetConfig: IWidget = { + id: "widget2", + type: "proportional", + metadata: { + // Set 'needsConfiguration' to true if the widget needs further configuration before it can be + // placed on the dashboard. The "Create Widget" button will be hidden in the wizard when this + // widget is selected. + needsConfiguration: true, + }, + pizzagna: { + [PizzagnaLayer.Configuration]: { + header: { + properties: { + title: "*New Proportional Widget*", + }, + }, + }, + }, +}; +\\\`, + "tutorials/widget-creation/widget-creation.module.ts": \\\`// © 2022 SolarWinds Worldwide, LLC. All rights reserved. +// +// Permission is hereby granted, free of charge, to any person obtaining a copy +// of this software and associated documentation files (the "Software"), to +// deal in the Software without restriction, including without limitation the +// rights to use, copy, modify, merge, publish, distribute, sublicense, and/or +// sell copies of the Software, and to permit persons to whom the Software is +// furnished to do so, subject to the following conditions: +// +// The above copyright notice and this permission notice shall be included in +// all copies or substantial portions of the Software. +// +// THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR +// IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, +// FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE +// AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER +// LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, +// OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN +// THE SOFTWARE. + +import { CommonModule } from "@angular/common"; +import { HttpClientModule } from "@angular/common/http"; +import { NgModule } from "@angular/core"; +import { RouterModule } from "@angular/router"; + +import { + NuiButtonModule, + NuiDocsModule, + NuiImageModule, + NuiMessageModule, + NuiRepeatModule, + NuiSwitchModule, + NuiToastModule, + DEMO_PATH_TOKEN, +} from "@nova-ui/bits"; +import { NuiDashboardsModule } from "@nova-ui/dashboards"; + +import { WidgetCreationDocsComponent } from "./widget-creation-docs.component"; +import { + WidgetCreationComponent, + WidgetTemplateSelectionComponent, +} from "./widget-creation.component"; +import { getDemoFiles } from "../../../../demo-files-factory"; + +const routes = [ + { + path: "", + component: WidgetCreationDocsComponent, + data: { + srlc: { + hideIndicator: true, + }, + showThemeSwitcher: true, + }, + }, + { + path: "example", + component: WidgetCreationComponent, + data: { + srlc: { + hideIndicator: true, + }, + }, + }, +]; + +@NgModule({ + imports: [ + CommonModule, + HttpClientModule, + NuiDashboardsModule, + NuiDocsModule, + NuiMessageModule, + NuiSwitchModule, + NuiToastModule, + NuiButtonModule, + NuiRepeatModule, + NuiImageModule, + RouterModule.forChild(routes), + ], + declarations: [ + WidgetCreationDocsComponent, + WidgetCreationComponent, + WidgetTemplateSelectionComponent, + ], + providers: [ + { + provide: DEMO_PATH_TOKEN, + useValue: getDemoFiles("widget-creation"), + }, + ], +}) +export default class WidgetCreationModule {} +\\\`, + "tutorials/widget-editor-setup/widget-editor-setup-docs.component.ts": \\\`// © 2022 SolarWinds Worldwide, LLC. All rights reserved. +// +// Permission is hereby granted, free of charge, to any person obtaining a copy +// of this software and associated documentation files (the "Software"), to +// deal in the Software without restriction, including without limitation the +// rights to use, copy, modify, merge, publish, distribute, sublicense, and/or +// sell copies of the Software, and to permit persons to whom the Software is +// furnished to do so, subject to the following conditions: +// +// The above copyright notice and this permission notice shall be included in +// all copies or substantial portions of the Software. +// +// THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR +// IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, +// FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE +// AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER +// LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, +// OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN +// THE SOFTWARE. + +import { Component } from "@angular/core"; + +@Component({ + selector: "nui-dashboard-widget-editor-docs", + templateUrl: "./widget-editor-setup-docs.component.html", + standalone: false, +}) +export class WidgetEditorDocsComponent {} +\\\`, + "tutorials/widget-editor-setup/widget-editor-setup.component.ts": \\\`// © 2022 SolarWinds Worldwide, LLC. All rights reserved. +// +// Permission is hereby granted, free of charge, to any person obtaining a copy +// of this software and associated documentation files (the "Software"), to +// deal in the Software without restriction, including without limitation the +// rights to use, copy, modify, merge, publish, distribute, sublicense, and/or +// sell copies of the Software, and to permit persons to whom the Software is +// furnished to do so, subject to the following conditions: +// +// The above copyright notice and this permission notice shall be included in +// all copies or substantial portions of the Software. +// +// THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR +// IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, +// FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE +// AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER +// LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, +// OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN +// THE SOFTWARE. + +import { HttpClient, HttpErrorResponse } from "@angular/common/http"; +import { + ChangeDetectorRef, + Component, + Injectable, + OnDestroy, + OnInit, +} from "@angular/core"; +import { GridsterConfig, GridsterItem } from "angular-gridster2"; +import { BehaviorSubject } from "rxjs"; +import { finalize } from "rxjs/operators"; + +import { DataSourceService, IFilteringOutputs } from "@nova-ui/bits"; +import { + DATA_SOURCE, + DEFAULT_PIZZAGNA_ROOT, + IDashboard, + IKpiData, + IProviderConfiguration, + IRefresherProperties, + IWidget, + IWidgets, + KpiComponent, + NOVA_KPI_DATASOURCE_ADAPTER, + PizzagnaLayer, + ProviderRegistryService, + WellKnownPathKey, + WellKnownProviders, + WidgetTypesService, +} from "@nova-ui/dashboards"; + +/** + * A simple KPI data source to retrieve the average rating of Harry Potter and the Sorcerer's Stone (book) via googleapis + */ +@Injectable() +export class AverageRatingKpiDataSource + extends DataSourceService + implements OnDestroy +{ + // This is the ID we'll use to identify the provider + public static providerId = "AverageRatingKpiDataSource"; + + // Use this subject to communicate the data source's busy state + public busy = new BehaviorSubject(false); + + constructor(private http: HttpClient) { + super(); + } + + // In this example, getFilteredData is invoked every 10 minutes (Take a look at the refresher + // provider definition in the widget configuration below to see how the interval is set) + public async getFilteredData(): Promise { + this.busy.next(true); + return new Promise((resolve) => { + // *** Make a resource request to an external API (if needed) + this.http + .get("https://www.googleapis.com/books/v1/volumes/5MQFrgEACAAJ") + .pipe(finalize(() => this.busy.next(false))) + .subscribe({ + next: (data: any) => { + resolve({ + result: { + value: data.volumeInfo.averageRating, + }, + }); + }, + error: (error: HttpErrorResponse) => { + resolve({ + result: null, + error: { + type: error.status, + }, + }); + }, + }); + }); + } + + public ngOnDestroy(): void { + this.outputsSubject.complete(); + } +} + +/** + * A simple KPI data source to retrieve the ratings count of Harry Potter and the Sorcerer's Stone (book) via googleapis + */ +@Injectable() +export class RatingsCountKpiDataSource + extends DataSourceService + implements OnDestroy +{ + public static providerId = "RatingsCountKpiDataSource"; + + // Use this subject to communicate the data source's busy state + public busy = new BehaviorSubject(false); + + constructor(private http: HttpClient) { + super(); + } + + public async getFilteredData(): Promise { + this.busy.next(true); + return new Promise((resolve) => { + this.http + .get("https://www.googleapis.com/books/v1/volumes/5MQFrgEACAAJ") + .pipe(finalize(() => this.busy.next(false))) + .subscribe({ + next: (data: any) => { + resolve({ + result: { + value: data.volumeInfo.ratingsCount, + }, + }); + }, + error: (error: HttpErrorResponse) => { + resolve({ + result: null, + error: { + type: error.status, + }, + }); + }, + }); + }); + } + + public ngOnDestroy(): void { + this.outputsSubject.complete(); + } +} + +/** + * A component that instantiates the dashboard + */ +@Component({ + selector: "widget-editor-setup", + templateUrl: "./widget-editor-setup.component.html", + styleUrls: ["./widget-editor-setup.component.less"], + standalone: false, +}) +export class WidgetEditorSetupComponent implements OnInit { + // This variable will hold all the data needed to define the layout and behavior of the widgets. + // Pass this to the dashboard component's dashboard input in the template. + public dashboard: IDashboard | undefined; + + // Angular gridster requires a configuration object even if it's empty. + // Pass this to the dashboard component's gridsterConfig input in the template. + public gridsterConfig: GridsterConfig = {}; + + // Boolean which dashboard takes in as an input if its true it allows you to move widgets around. + public editMode: boolean = false; + + constructor( + // WidgetTypesService provides the widget's necessary structure information + private widgetTypesService: WidgetTypesService, + + // In general, the ProviderRegistryService is used for making entities available for injection into dynamically loaded components. + private providerRegistry: ProviderRegistryService, + private changeDetectorRef: ChangeDetectorRef + ) {} + + public ngOnInit(): void { + // Grabbing the widget's default template which will be needed as a parameter for setNode + const widgetTemplate = this.widgetTypesService.getWidgetType("kpi", 1); + // Registering our data sources as dropdown options in the widget editor/configurator + // Note: This could also be done in the parent module's constructor so that + // multiple dashboards could have access to the same widget template modification. + this.widgetTypesService.setNode( + // This is the template we grabbed above with getWidgetType + widgetTemplate, + // We are setting the editor/configurator part of the widget template + "configurator", + // This indicates which node you are changing and we want to change + // the data source providers available for selection in the editor. + WellKnownPathKey.DataSourceProviders, + // We are setting the data sources available for selection in the editor + [ + AverageRatingKpiDataSource.providerId, + RatingsCountKpiDataSource.providerId, + ] + ); + + // Registering the data sources available for injection into the KPI tiles. + // Note: Each tile of a KPI widget is assigned its own instance of a data source + this.providerRegistry.setProviders({ + [AverageRatingKpiDataSource.providerId]: { + provide: DATA_SOURCE, + useClass: AverageRatingKpiDataSource, + // Any dependencies that need to be injected into the provider must be listed here + deps: [HttpClient], + }, + [RatingsCountKpiDataSource.providerId]: { + provide: DATA_SOURCE, + useClass: RatingsCountKpiDataSource, + deps: [HttpClient], + }, + }); + + this.initializeDashboard(); + } + + /** Used for restoring widgets state */ + public reInitializeDashboard(): void { + // destroys the components and their providers so the dashboard can re init data + this.dashboard = undefined; + this.changeDetectorRef.detectChanges(); + + this.initializeDashboard(); + } + + public initializeDashboard(): void { + // We're using a static configuration object for this example (see widgetConfig at the bottom of the file), + // but this is where the widget's configuration could potentially be populated from a database + const kpiWidget = widgetConfig; + const widgetIndex: IWidgets = { + // Complete the KPI widget with information coming from its type definition + [kpiWidget.id]: + this.widgetTypesService.mergeWithWidgetType(kpiWidget), + }; + + // Setting the widget dimensions and position (this is for gridster) + const positions: Record = { + [kpiWidget.id]: { + cols: 4, + rows: 6, + y: 0, + x: 0, + }, + }; + + // Finally, assigning the variables we created above to the dashboard + this.dashboard = { + positions, + widgets: widgetIndex, + }; + } +} + +const widgetConfig: IWidget = { + id: "widget1", + type: "kpi", + pizzagna: { + [PizzagnaLayer.Configuration]: { + [DEFAULT_PIZZAGNA_ROOT]: { + providers: { + [WellKnownProviders.Refresher]: { + properties: { + // Configuring the refresher interval so that our data source is invoked every ten minutes + interval: 60 * 10, + enabled: true, + } as IRefresherProperties, + } as Partial, + }, + }, + header: { + properties: { + title: "Harry Potter and the Sorcerer's Stone", + subtitle: "By J. K. Rowling", + }, + }, + tiles: { + properties: { + nodes: ["kpi1"], + }, + }, + kpi1: { + id: "kpi1", + componentType: KpiComponent.lateLoadKey, + properties: { + widgetData: { + units: "out of 5 Stars", + label: "Average Rating", + }, + }, + providers: { + [WellKnownProviders.DataSource]: { + // Setting the data source providerId for the tile with id "kpi1" + providerId: AverageRatingKpiDataSource.providerId, + } as IProviderConfiguration, + [WellKnownProviders.Adapter]: { + providerId: NOVA_KPI_DATASOURCE_ADAPTER, + properties: { + componentId: "kpi1", + propertyPath: "widgetData", + }, + } as IProviderConfiguration, + }, + }, + }, + }, +}; +\\\`, + "tutorials/widget-editor-setup/widget-editor-setup.module.ts": \\\`// © 2022 SolarWinds Worldwide, LLC. All rights reserved. +// +// Permission is hereby granted, free of charge, to any person obtaining a copy +// of this software and associated documentation files (the "Software"), to +// deal in the Software without restriction, including without limitation the +// rights to use, copy, modify, merge, publish, distribute, sublicense, and/or +// sell copies of the Software, and to permit persons to whom the Software is +// furnished to do so, subject to the following conditions: +// +// The above copyright notice and this permission notice shall be included in +// all copies or substantial portions of the Software. +// +// THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR +// IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, +// FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE +// AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER +// LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, +// OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN +// THE SOFTWARE. + +import { CommonModule } from "@angular/common"; +import { HttpClientModule } from "@angular/common/http"; +import { NgModule } from "@angular/core"; +import { RouterModule } from "@angular/router"; + +import { + NuiButtonModule, + NuiDocsModule, + NuiMessageModule, + NuiSwitchModule, + DEMO_PATH_TOKEN, +} from "@nova-ui/bits"; +import { NuiDashboardsModule } from "@nova-ui/dashboards"; + +import { WidgetEditorDocsComponent } from "./widget-editor-setup-docs.component"; +import { WidgetEditorSetupComponent } from "./widget-editor-setup.component"; +import { getDemoFiles } from "../../../../demo-files-factory"; + +const routes = [ + { + path: "", + component: WidgetEditorDocsComponent, + data: { + srlc: { + hideIndicator: true, + }, + showThemeSwitcher: true, + }, + }, + { + path: "example", + component: WidgetEditorSetupComponent, + data: { + srlc: { + hideIndicator: true, + }, + }, + }, +]; + +@NgModule({ + imports: [ + CommonModule, + HttpClientModule, + NuiDashboardsModule, + NuiDocsModule, + NuiMessageModule, + NuiSwitchModule, + NuiButtonModule, + RouterModule.forChild(routes), + ], + declarations: [WidgetEditorDocsComponent, WidgetEditorSetupComponent], + providers: [ + { + provide: DEMO_PATH_TOKEN, + useValue: getDemoFiles("widget-editor-setup"), + }, + ], +}) +export default class WidgetEditorSetupModule {} +\\\`, + "tutorials/widget-error-handling/widget-error-handling-docs.component.ts": \\\`// © 2022 SolarWinds Worldwide, LLC. All rights reserved. +// +// Permission is hereby granted, free of charge, to any person obtaining a copy +// of this software and associated documentation files (the "Software"), to +// deal in the Software without restriction, including without limitation the +// rights to use, copy, modify, merge, publish, distribute, sublicense, and/or +// sell copies of the Software, and to permit persons to whom the Software is +// furnished to do so, subject to the following conditions: +// +// The above copyright notice and this permission notice shall be included in +// all copies or substantial portions of the Software. +// +// THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR +// IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, +// FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE +// AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER +// LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, +// OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN +// THE SOFTWARE. + +import { Component } from "@angular/core"; + +@Component({ + selector: "nui-widget-error-handling-docs", + templateUrl: "./widget-error-handling-docs.component.html", + standalone: false, +}) +export class WidgetErrorHandlingDocsComponent { + public fallbackAdapter = \\\\\\\` +@Injectable() +export class StatusContentFallbackAdapter implements OnDestroy, IHasComponent { + + protected readonly destroy$ = new Subject(); + protected componentId: string; + + constructor(@Inject(PIZZAGNA_EVENT_BUS) protected eventBus: EventBus, + protected pizzagnaService: PizzagnaService) { + this.eventBus.getStream(DATA_SOURCE_OUTPUT) + .pipe(takeUntil(this.destroy$)).subscribe((event: IEvent>) => { + this.handleDataSourceOutput(event); + }); + } + + public ngOnDestroy(): void { + this.destroy$.next(); + this.destroy$.complete(); + } + + public setComponent(component: any, componentId: string) { + this.componentId = componentId; + } + + protected handleDataSourceOutput(event: IEvent>) { + this.pizzagnaService.setProperty({ + componentId: this.componentId, + propertyPath: ["fallbackKey"], + pizzagnaKey: PizzagnaLayer.Data, + }, typeof event.payload?.error?.type !== "undefined" ? event.payload?.error?.type.toString() : undefined); + } +}\\\\\\\`; + public errorsMap = \\\\\\\` +export const ERROR_FALLBACK_MAP: Record = { + [HttpStatusCode.Unknown]: ErrorNodeKey.ErrorUnknown, + [HttpStatusCode.Forbidden]: ErrorNodeKey.ErrorForbidden, + [HttpStatusCode.NotFound]: ErrorNodeKey.ErrorNotFound, +}; +\\\\\\\`; + public errorNodes = \\\\\\\` +export const ERROR_NODES: Record = { + [ErrorNodeKey.ErrorUnknown]: { + id: ErrorNodeKey.ErrorUnknown, + componentType: WidgetErrorComponent.lateLoadKey, + properties: { + image: "no-data-to-show", + title: $localize\\\\\\\\\\\\\\\`Whoops, something went wrong\\\\\\\\\\\\\\\`, + description: $localize\\\\\\\\\\\\\\\`There was an unexpected error.\\\\\\\\\\\\\\\`, + } as IWidgetErrorDisplayProperties, + }, + [ErrorNodeKey.ErrorForbidden]: { + id: ErrorNodeKey.ErrorForbidden, + componentType: WidgetErrorComponent.lateLoadKey, + properties: { + image: "no-data-to-show", + title: $localize\\\\\\\\\\\\\\\`403 - Forbidden\\\\\\\\\\\\\\\`, + description: $localize\\\\\\\\\\\\\\\`The requested action was forbidden.\\\\\\\\\\\\\\\`, + } as IWidgetErrorDisplayProperties, + }, + [ErrorNodeKey.ErrorNotFound]: { + id: ErrorNodeKey.ErrorNotFound, + componentType: WidgetErrorComponent.lateLoadKey, + properties: { + image: "no-data-to-show", + title: $localize\\\\\\\\\\\\\\\`404 - Not Found\\\\\\\\\\\\\\\`, + description: $localize\\\\\\\\\\\\\\\`The requested resource could not be found.\\\\\\\\\\\\\\\`, + } as IWidgetErrorDisplayProperties, + }, +};\\\\\\\`; + public widgetBodyContentNodesSignature = \\\\\\\` +/** + * Retrieves an index of the basic widget body content nodes including fallback nodes + * + * @param mainContentNodeKey The key corresponding to the main body content node + * @param fallbackAdapterId The id for the adapter responsible for activating fallback content in case of an error + * @param fallbackMap A map of node keys to fallback content definitions + * @param fallbackNodes An index of fallback content definitions + * + * @returns An index of component configurations + */ +export function widgetBodyContentNodes( + mainContentNodeKey: string, + fallbackAdapterId = NOVA_STATUS_CONTENT_FALLBACK_ADAPTER, + fallbackMap: Record = ERROR_FALLBACK_MAP, + fallbackNodes: Record = ERROR_NODES +): Record { ... } +\\\\\\\`; +} +\\\`, + "tutorials/widget-error-handling/widget-error-handling.component.ts": \\\`// © 2022 SolarWinds Worldwide, LLC. All rights reserved. +// +// Permission is hereby granted, free of charge, to any person obtaining a copy +// of this software and associated documentation files (the "Software"), to +// deal in the Software without restriction, including without limitation the +// rights to use, copy, modify, merge, publish, distribute, sublicense, and/or +// sell copies of the Software, and to permit persons to whom the Software is +// furnished to do so, subject to the following conditions: +// +// The above copyright notice and this permission notice shall be included in +// all copies or substantial portions of the Software. +// +// THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR +// IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, +// FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE +// AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER +// LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, +// OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN +// THE SOFTWARE. + +import { HttpClient, HttpErrorResponse } from "@angular/common/http"; +import { + ChangeDetectorRef, + Component, + Injectable, + OnDestroy, + OnInit, +} from "@angular/core"; +import { GridsterConfig, GridsterItem } from "angular-gridster2"; +import { BehaviorSubject } from "rxjs"; +import { finalize } from "rxjs/operators"; + +import { DataSourceService, IFilteringOutputs } from "@nova-ui/bits"; +import { + DATA_SOURCE, + DEFAULT_PIZZAGNA_ROOT, + HttpStatusCode, + IDashboard, + IKpiData, + IProviderConfiguration, + IRefresherProperties, + IWidget, + IWidgets, + KpiComponent, + NOVA_KPI_DATASOURCE_ADAPTER, + PizzagnaLayer, + ProviderRegistryService, + WellKnownPathKey, + WellKnownProviders, + WidgetTypesService, +} from "@nova-ui/dashboards"; + +/** + * A simple KPI data source to retrieve the average rating of Harry Potter and the Sorcerer's Stone (book) via googleapis + */ +@Injectable() +export class AverageRatingKpiDataSource + extends DataSourceService + implements OnDestroy +{ + // This is the ID we'll use to identify the provider + public static providerId = "AverageRatingKpiDataSource"; + + // Use this subject to communicate the data source's busy state + public busy = new BehaviorSubject(false); + + constructor(private http: HttpClient) { + super(); + } + + // In this example, getFilteredData is invoked every 10 minutes (Take a look at the refresher + // provider definition in the widget configuration below to see how the interval is set) + public async getFilteredData(): Promise { + this.busy.next(true); + return new Promise((resolve) => { + // *** Make a resource request to an external API (if needed) + this.http + .get("https://www.googleapis.com/books/v1/volumes/5MQFrgEACAAJ") + .pipe(finalize(() => this.busy.next(false))) + .subscribe({ + next: (data: any) => { + resolve({ + result: { + value: data.volumeInfo.averageRating, + }, + }); + }, + error: (error: HttpErrorResponse) => { + resolve({ + result: null, + error: { + type: error.status, + }, + }); + }, + }); + }); + } + + public ngOnDestroy(): void { + this.outputsSubject.complete(); + } +} + +/** + * A simple KPI data source to retrieve the average rating of Harry Potter and the Sorcerer's Stone (book) via googleapis + */ +@Injectable() +export class ErrorUnknownDataSource + extends DataSourceService + implements OnDestroy +{ + // This is the ID we'll use to identify the provider + public static providerId = "ErrorUnknownDataSource"; + + // Use this subject to communicate the data source's busy state + public busy = new BehaviorSubject(false); + + // In this example, getFilteredData is invoked every 10 minutes (Take a look at the refresher + // provider definition in the widget configuration below to see how the interval is set) + public async getFilteredData(): Promise { + this.busy.next(true); + const mockError = { + result: null, + error: { type: HttpStatusCode.Unknown }, + }; + this.busy.next(false); + return mockError; + } + + public ngOnDestroy(): void { + this.outputsSubject.complete(); + } +} + +/** + * A simple KPI data source to retrieve the ratings count of Harry Potter and the Sorcerer's Stone (book) via googleapis + */ +@Injectable() +export class ErrorForbiddenDataSource + extends DataSourceService + implements OnDestroy +{ + public static providerId = "ErrorForbiddenDataSource"; + + // Use this subject to communicate the data source's busy state + public busy = new BehaviorSubject(false); + + constructor(private http: HttpClient) { + super(); + } + + public async getFilteredData(): Promise { + this.busy.next(true); + // generate a 403 + return new Promise((resolve) => { + this.http + .get( + "http://www.mocky.io/v2/5ecc724a3200000f0023614a?mocky-delay=4000ms" + ) + .pipe(finalize(() => this.busy.next(false))) + .subscribe({ + error: (error: HttpErrorResponse) => { + resolve({ + result: null, + error: { + type: error.status, + }, + }); + }, + }); + }); + } + + public ngOnDestroy(): void { + this.outputsSubject.complete(); + } +} + +/** + * A simple KPI data source to retrieve the ratings count of Harry Potter and the Sorcerer's Stone (book) via googleapis + */ +@Injectable() +export class ErrorNotFoundDataSource + extends DataSourceService + implements OnDestroy +{ + public static providerId = "ErrorNotFoundDataSource"; + + // Use this subject to communicate the data source's busy state + public busy = new BehaviorSubject(false); + + constructor(private http: HttpClient) { + super(); + } + + public async getFilteredData(): Promise { + this.busy.next(true); + // generate a 404 + return new Promise((resolve) => { + this.http + .get( + "http://www.mocky.io/v2/5ec6bfd93200007800d75100?mocky-delay=1000ms" + ) + .pipe(finalize(() => this.busy.next(false))) + .subscribe({ + error: (error: HttpErrorResponse) => { + resolve({ + result: null, + error: { + type: error.status, + }, + }); + }, + }); + }); + } + + public ngOnDestroy(): void { + this.outputsSubject.complete(); + } +} + +/** + * A component that instantiates the dashboard + */ +@Component({ + selector: "widget-error-handling", + templateUrl: "./widget-error-handling.component.html", + styleUrls: ["./widget-error-handling.component.less"], + standalone: false, +}) +export class WidgetErrorHandlingComponent implements OnInit { + // This variable will hold all the data needed to define the layout and behavior of the widgets. + // Pass this to the dashboard component's dashboard input in the template. + public dashboard: IDashboard | undefined; + + // Angular gridster requires a configuration object even if it's empty. + // Pass this to the dashboard component's gridsterConfig input in the template. + public gridsterConfig: GridsterConfig = {}; + + // Boolean which dashboard takes in as an input if its true it allows you to move widgets around. + public editMode: boolean = false; + + constructor( + // WidgetTypesService provides the widget's necessary structure information + private widgetTypesService: WidgetTypesService, + + // In general, the ProviderRegistryService is used for making entities available for injection into dynamically loaded components. + private providerRegistry: ProviderRegistryService, + private changeDetectorRef: ChangeDetectorRef + ) {} + + public ngOnInit(): void { + // Grab the widget's default template which will be needed as a parameter for setNode. + const widgetTemplate = this.widgetTypesService.getWidgetType("kpi", 1); + // Register our data sources as dropdown options in the widget editor/configurator + // Note: This could also be done in the parent module's constructor so that + // multiple dashboards could have access to the same widget template modification. + this.widgetTypesService.setNode( + // This is the template we grabbed above with getWidgetType + widgetTemplate, + // We are setting the editor/configurator part of the widget template + "configurator", + // This indicates which node you are changing and we want to change + // the data source providers available for selection in the editor. + WellKnownPathKey.DataSourceProviders, + // We are setting the data sources available for selection in the editor + [ + ErrorUnknownDataSource.providerId, + ErrorForbiddenDataSource.providerId, + ErrorNotFoundDataSource.providerId, + AverageRatingKpiDataSource.providerId, + ] + ); + + // Register the data sources available for injection into the KPI tiles. + // Note: Each tile of a KPI widget is assigned its own instance of a data source + this.providerRegistry.setProviders({ + [ErrorUnknownDataSource.providerId]: { + provide: DATA_SOURCE, + useClass: ErrorUnknownDataSource, + // Any dependencies that need to be injected into the provider must be listed here + deps: [HttpClient], + }, + [ErrorForbiddenDataSource.providerId]: { + provide: DATA_SOURCE, + useClass: ErrorForbiddenDataSource, + deps: [HttpClient], + }, + [ErrorNotFoundDataSource.providerId]: { + provide: DATA_SOURCE, + useClass: ErrorNotFoundDataSource, + deps: [HttpClient], + }, + [AverageRatingKpiDataSource.providerId]: { + provide: DATA_SOURCE, + useClass: AverageRatingKpiDataSource, + // Any dependencies that need to be injected into the provider must be listed here + deps: [HttpClient], + }, + }); + + this.initializeDashboard(); + } + + /** Used for restoring widgets state */ + public reInitializeDashboard(): void { + // destroys the components and their providers so the dashboard can re init data + this.dashboard = undefined; + this.changeDetectorRef.detectChanges(); + + this.initializeDashboard(); + } + + public initializeDashboard(): void { + // We're using a static configuration object for this example (see widgetConfig at the bottom of the file), + // but this is where the widget's configuration could potentially be populated from a database + const kpiWidget = widgetConfig; + const widgetIndex: IWidgets = { + // Complete the KPI widget with information coming from its type definition + [kpiWidget.id]: + this.widgetTypesService.mergeWithWidgetType(kpiWidget), + }; + + // Setting the widget dimensions and position (this is for gridster) + const positions: Record = { + [kpiWidget.id]: { + cols: 4, + rows: 6, + y: 0, + x: 0, + }, + }; + + // Finally, assigning the variables we created above to the dashboard + this.dashboard = { + positions, + widgets: widgetIndex, + }; + } +} + +const widgetConfig: IWidget = { + id: "widget1", + type: "kpi", + pizzagna: { + [PizzagnaLayer.Configuration]: { + [DEFAULT_PIZZAGNA_ROOT]: { + providers: { + [WellKnownProviders.Refresher]: { + properties: { + // Configuring the refresher interval so that our data source is invoked every ten minutes + interval: 60 * 10, + enabled: true, + } as IRefresherProperties, + } as Partial, + }, + }, + header: { + properties: { + title: "Harry Potter and the Sorcerer's Stone", + subtitle: "By J. K. Rowling", + }, + }, + tiles: { + properties: { + nodes: ["kpi1"], + }, + }, + kpi1: { + id: "kpi1", + componentType: KpiComponent.lateLoadKey, + properties: { + widgetData: { + units: "out of 5 Stars", + label: "Average Rating", + }, + }, + providers: { + [WellKnownProviders.DataSource]: { + // Setting the data source providerId for the tile with id "kpi1" + providerId: ErrorUnknownDataSource.providerId, + } as IProviderConfiguration, + [WellKnownProviders.Adapter]: { + providerId: NOVA_KPI_DATASOURCE_ADAPTER, + properties: { + componentId: "kpi1", + propertyPath: "widgetData", + }, + } as IProviderConfiguration, + }, + }, + }, + }, +}; +\\\`, + "tutorials/widget-error-handling/widget-error-handling.module.ts": \\\`// © 2022 SolarWinds Worldwide, LLC. All rights reserved. +// +// Permission is hereby granted, free of charge, to any person obtaining a copy +// of this software and associated documentation files (the "Software"), to +// deal in the Software without restriction, including without limitation the +// rights to use, copy, modify, merge, publish, distribute, sublicense, and/or +// sell copies of the Software, and to permit persons to whom the Software is +// furnished to do so, subject to the following conditions: +// +// The above copyright notice and this permission notice shall be included in +// all copies or substantial portions of the Software. +// +// THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR +// IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, +// FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE +// AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER +// LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, +// OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN +// THE SOFTWARE. + +import { CommonModule } from "@angular/common"; +import { HttpClientModule } from "@angular/common/http"; +import { NgModule } from "@angular/core"; +import { ReactiveFormsModule } from "@angular/forms"; +import { RouterModule } from "@angular/router"; + +import { + NuiButtonModule, + NuiDocsModule, + NuiFormFieldModule, + NuiIconModule, + NuiMessageModule, + NuiSwitchModule, + NuiTextboxModule, + DEMO_PATH_TOKEN, +} from "@nova-ui/bits"; +import { + NuiDashboardConfiguratorModule, + NuiDashboardsModule, +} from "@nova-ui/dashboards"; + +import { WidgetErrorHandlingDocsComponent } from "./widget-error-handling-docs.component"; +import { WidgetErrorHandlingComponent } from "./widget-error-handling.component"; +import { getDemoFiles } from "../../../../demo-files-factory"; + +const routes = [ + { + path: "", + component: WidgetErrorHandlingDocsComponent, + data: { + srlc: { + hideIndicator: true, + }, + showThemeSwitcher: true, + }, + }, + { + path: "example", + component: WidgetErrorHandlingComponent, + data: { + srlc: { + hideIndicator: true, + }, + }, + }, +]; + +@NgModule({ + imports: [ + CommonModule, + ReactiveFormsModule, + HttpClientModule, + NuiButtonModule, + NuiDashboardsModule, + NuiDashboardConfiguratorModule, + NuiDocsModule, + NuiFormFieldModule, + NuiIconModule, + NuiMessageModule, + NuiIconModule, + NuiTextboxModule, + NuiIconModule, + NuiSwitchModule, + RouterModule.forChild(routes), + ], + declarations: [ + WidgetErrorHandlingDocsComponent, + WidgetErrorHandlingComponent, + ], + providers: [ + { + provide: DEMO_PATH_TOKEN, + useValue: getDemoFiles("widget-error-handling"), + }, + ], +}) +export default class WidgetErrorHandlingModule {} +\\\`, + "types.ts": \\\`// © 2022 SolarWinds Worldwide, LLC. All rights reserved. +// +// Permission is hereby granted, free of charge, to any person obtaining a copy +// of this software and associated documentation files (the "Software"), to +// deal in the Software without restriction, including without limitation the +// rights to use, copy, modify, merge, publish, distribute, sublicense, and/or +// sell copies of the Software, and to permit persons to whom the Software is +// furnished to do so, subject to the following conditions: +// +// The above copyright notice and this permission notice shall be included in +// all copies or substantial portions of the Software. +// +// THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR +// IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, +// FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE +// AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER +// LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, +// OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN +// THE SOFTWARE. + +export enum APOLLO_API_NAMESPACE { + COUNTRIES = "countries", +} +\\\`, + "widget-types/drilldown/drilldown-multi-request-widget/drilldown-multi-request-widget-example.component.ts": \\\`// © 2022 SolarWinds Worldwide, LLC. All rights reserved. +// +// Permission is hereby granted, free of charge, to any person obtaining a copy +// of this software and associated documentation files (the "Software"), to +// deal in the Software without restriction, including without limitation the +// rights to use, copy, modify, merge, publish, distribute, sublicense, and/or +// sell copies of the Software, and to permit persons to whom the Software is +// furnished to do so, subject to the following conditions: +// +// The above copyright notice and this permission notice shall be included in +// all copies or substantial portions of the Software. +// +// THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR +// IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, +// FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE +// AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER +// LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, +// OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN +// THE SOFTWARE. + +import { HttpClient } from "@angular/common/http"; +import { + ChangeDetectorRef, + Component, + Injectable, + OnDestroy, + OnInit, +} from "@angular/core"; +import { GridsterConfig, GridsterItem } from "angular-gridster2"; +import { Apollo, gql } from "apollo-angular"; +import { BehaviorSubject, Observable, of, Subject } from "rxjs"; +// eslint-disable-next-line import/no-deprecated +import { finalize, map, switchMap, tap } from "rxjs/operators"; + +import { + DataSourceService, + IconStatus, + IDataField, + IFilters, + INovaFilters, +} from "@nova-ui/bits"; +import { + DATA_SOURCE, + DEFAULT_PIZZAGNA_ROOT, + IDashboard, + IDrilldownComponentsConfiguration, + IListWidgetConfiguration, + IProviderConfiguration, + IWidget, + IWidgets, + ListGroupItemComponent, + ListLeafItemComponent, + NOVA_DRILLDOWN_DATASOURCE_ADAPTER, + PizzagnaLayer, + ProviderRegistryService, + WellKnownPathKey, + WellKnownProviders, + WidgetTypesService, +} from "@nova-ui/dashboards"; + +import { APOLLO_API_NAMESPACE } from "../../../types"; + +/** + * A simple KPI data source to retrieve the average rating of Harry Potter and the Sorcerer's Stone (book) via googleapis + */ +@Injectable() +export class DrilldownDataSource + extends DataSourceService + implements OnDestroy +{ + // This is the ID we'll use to identify the provider + public static providerId = "DrilldownDataSource"; + + // Use this subject to communicate the data source's busy state + public busy = new BehaviorSubject(false); + + public dataFields: Partial[] = [ + { id: "Region", label: "Region name" }, + { id: "Subregion", label: "Subregion name" }, + ]; + + private drillState: string[] = []; + private groupBy: string[]; + private cache: any; + private lastDrillState: string[] = []; + private leafGroup: string = "country"; + private applyFilters$ = new Subject(); + + constructor(private http: HttpClient, private apollo: Apollo) { + super(); + + // TODO: remove Partial in vNext after marking dataType field as optional - NUI-5838 + ( + this.dataFieldsConfig.dataFields$ as BehaviorSubject< + Partial[] + > + ).next(this.dataFields); + + this.applyFilters$ + // eslint-disable-next-line import/no-deprecated + .pipe(switchMap((filters) => this.getData(filters))) + .subscribe(async (res) => { + this.outputsSubject.next(await this.getFilteredData(res)); + }); + } + + private groupedDataHistory: any[] = []; + + // In this example, getFilteredData is invoked every 10 minutes (Take a look at the refresher + // provider definition in the widget configuration below to see how the interval is set) + public async getFilteredData(data: any): Promise { + return of(data) + .pipe( + map((entries) => { + if (this.isDrillDown()) { + const activeDrillLvl = this.drillState.length; + const group = this.groupBy[activeDrillLvl]; + const lastGroupedValue = + this.getTransformedDataForGroup( + entries, + group, + getLast(this.drillState) + ); + + this.groupedDataHistory.push(lastGroupedValue); + + return lastGroupedValue; + } + + const mapIconsToEntries = entries.map((item: any) => ({ + ...item, + icon: "virtual-host", + icon_status: IconStatus.Up, + })); + this.groupedDataHistory.push(mapIconsToEntries); + const widgetInput = this.getOutput(entries); + + return widgetInput; + }) + ) + .toPromise(); + } + + public ngOnDestroy(): void { + this.outputsSubject.complete(); + } + + // redefine parent method + public async applyFilters(): Promise { + this.applyFilters$.next(this.getFilters()); + } + + private getQuery(key: string, value: string) { + const groupToRequestMap: Record = { + Region: \\\\\\\`{ Region { name } }\\\\\\\`, + Subregion: \\\\\\\`{ Subregion(filter: { region: { name: "\\\\\\\${value}" } } ) { name } }\\\\\\\`, + Country: \\\\\\\`{ Country(filter: { subregion: { name: "\\\\\\\${value}" } } ) { name capital } }\\\\\\\`, + }; + + return gql\\\\\\\` + \\\\\\\${groupToRequestMap[key]} + \\\\\\\`; + } + + private getData(filters: INovaFilters): Observable { + this.drillState = filters.drillstate?.value; + this.groupBy = filters.group?.value; + const group = this.groupBy[this.drillState.length]; + const isDrillUp = this.drillState.length < this.lastDrillState.length; + + this.lastDrillState = [...this.drillState]; + + if (!this.drillState.length) { + this.groupedDataHistory.length = 0; + } + + this.busy.next(true); + + if (this.cache && (isDrillUp || this.isHome())) { + return of(this.cache).pipe( + map((data) => data.data[group]), + finalize(() => this.busy.next(false)) + ); + } else { + return this.apollo + .use(APOLLO_API_NAMESPACE.COUNTRIES) + .query({ + query: this.getQuery( + group || this.leafGroup, + getLast(this.drillState) + ), + }) + .pipe( + tap( + (data) => + (this.cache = { + data: { ...this.cache?.data, ...data?.data }, + }) + ), + map((data) => data.data[group || this.leafGroup]), + finalize(() => this.busy.next(false)) + ); + } + } + + private getTransformedDataForGroup( + data: any, + group: string, + drillStateValue: string + ) { + const fallback: string = \\\\\\\`No \\\\\\\${group} for \\\\\\\${drillStateValue}\\\\\\\`; + const dataArr = Object.values(data).map((val: any) => ({ + id: val.name || fallback, + label: val.name || fallback, + statuses: [ + { key: "state_ok", value: val.name?.length }, + { + key: "status_unreachable", + value: generateNumberUpTo(100000), + }, + { key: "status_warning", value: generateNumberUpTo(10000) }, + { key: "status_unknown", value: generateNumberUpTo(1000) }, + ], + })); + + return dataArr; + } + + private isHome(): boolean { + return this.drillState.length === 0; + } + + private isDrillDown(): boolean { + return this.drillState.length !== this.groupBy.length; + } + + private getOutput(data: any) { + if (this.isHome()) { + this.groupedDataHistory.length = 0; + } + + const lastHistoryValue = getLast(this.groupedDataHistory); + + if (!lastHistoryValue) { + return data; + } + + return lastHistoryValue[getLast(this.drillState)] || lastHistoryValue; + } +} + +@Component({ + selector: "drilldown-multi-request-widget-example", + templateUrl: "./drilldown-multi-request-widget-example.component.html", + styleUrls: ["./drilldown-multi-request-widget-example.component.less"], + standalone: false, +}) +export class DrilldownMultiRequestWidgetExampleComponent implements OnInit { + // This variable will hold all the data needed to define the layout and behavior of the widgets. + // Pass this to the dashboard component's dashboard input in the template. + public dashboard: IDashboard | undefined; + + // Angular gridster requires a configuration object even if it's empty. + // Pass this to the dashboard component's gridsterConfig input in the template. + public gridsterConfig: GridsterConfig = {}; + + // Boolean passed as an input to the dashboard. When true, widgets can be moved, resized, removed, or edited + public editMode: boolean = false; + + constructor( + // WidgetTypesService provides the widget's necessary structure information + private widgetTypesService: WidgetTypesService, + private providerRegistry: ProviderRegistryService, + private changeDetectorRef: ChangeDetectorRef + ) {} + + public ngOnInit(): void { + // this.prepareNovaDashboards(); + this.initializeDashboard(); + const widgetTemplate = this.widgetTypesService.getWidgetType( + "drilldown", + 1 + ); + this.widgetTypesService.setNode( + widgetTemplate, + "configurator", + WellKnownPathKey.DataSourceProviders, + [DrilldownDataSource.providerId] + ); + + // Registering the data source for injection into the KPI tile. + // Note: Each tile of a KPI widget is assigned its own instance of the data source + this.providerRegistry.setProviders({ + [DrilldownDataSource.providerId]: { + provide: DATA_SOURCE, + useClass: DrilldownDataSource, + // Any dependencies that need to be injected into the provider must be listed here + deps: [HttpClient, Apollo], + }, + }); + } + + /** Used for restoring widgets state */ + public reInitializeDashboard(): void { + // destroys the components and their providers so the dashboard can re init data + this.dashboard = undefined; + this.changeDetectorRef.detectChanges(); + + this.initializeDashboard(); + } + + public initializeDashboard(): void { + // We're using a static configuration object for this example, but this is where + // the widget's configuration could potentially be populated from a database + const drilldownWidget = widgetConfig; + const widgets: IWidgets = { + // Complete the widget with information coming from its type definition + [drilldownWidget.id]: + this.widgetTypesService.mergeWithWidgetType(drilldownWidget), + }; + + // Setting the widget dimensions and position (this is for gridster) + const positions: Record = { + [drilldownWidget.id]: { + cols: 10, + rows: 10, + y: 0, + x: 0, + }, + }; + + // Finally, assigning the variables we created above to the dashboard + this.dashboard = { positions, widgets }; + } +} + +const widgetConfig: IWidget = { + id: "drilldown", + type: "drilldown", + pizzagna: { + [PizzagnaLayer.Configuration]: { + header: { + properties: { + title: "Drilldown Widget", + subtitle: "Countries BY continent THEN currency", + }, + }, + [DEFAULT_PIZZAGNA_ROOT]: { + providers: { + [WellKnownProviders.DataSource]: { + // Setting the data source providerId for the tile with id "kpi1" + providerId: DrilldownDataSource.providerId, + properties: {}, + } as IProviderConfiguration, + }, + }, + listWidget: { + providers: { + [WellKnownProviders.Adapter]: { + providerId: NOVA_DRILLDOWN_DATASOURCE_ADAPTER, + properties: { + // widget + navigationBarId: "navigationBar", + componentId: "listWidget", + dataPath: "data", + + // adapter props + drillstate: [], + groups: ["Region", "Subregion"], + groupBy: ["Region", "Subregion"], + + // components + componentsConfig: { + group: { + componentType: + ListGroupItemComponent.lateLoadKey, + properties: { + dataFieldIds: { + id: "id", + label: "label", + statuses: "statuses", + }, + }, + itemProperties: { + canNavigate: true, + }, + }, + leaf: { + componentType: + ListLeafItemComponent.lateLoadKey, + properties: { + dataFieldIds: { + icon: "icon", + status: "icon_status", + detailedUrl: "capital", + label: "name", + }, + }, + itemProperties: { + canNavigate: false, + }, + }, + } as IDrilldownComponentsConfiguration, + }, + }, + }, + properties: { + configuration: { + // FORMAT: + // componentType: ListLeafItemComponent.lateLoadKey, + // properties: { + // dataFieldIds: { + // icon: "", + // status: "code", + // detailedUrl: "capital", + // label: "name", + // }, + // }, + // + } as IListWidgetConfiguration, + }, + }, + }, + }, +}; + +const getLast = (arr: any[]) => arr[arr.length - 1]; + +const generateNumberUpTo = (upperLimit: number): number => + Math.floor(Math.random() * upperLimit + 1); +\\\`, + "widget-types/drilldown/drilldown-widget/data-mock.ts": \\\`// © 2022 SolarWinds Worldwide, LLC. All rights reserved. +// +// Permission is hereby granted, free of charge, to any person obtaining a copy +// of this software and associated documentation files (the "Software"), to +// deal in the Software without restriction, including without limitation the +// rights to use, copy, modify, merge, publish, distribute, sublicense, and/or +// sell copies of the Software, and to permit persons to whom the Software is +// furnished to do so, subject to the following conditions: +// +// The above copyright notice and this permission notice shall be included in +// all copies or substantial portions of the Software. +// +// THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR +// IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, +// FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE +// AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER +// LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, +// OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN +// THE SOFTWARE. + +import { IconStatus } from "@nova-ui/bits"; + +export const GRAPH_DATA_MOCK = { + data: { + countries: [ + { + name: "Andorra", + code: "AD", + icon: "virtual-host", + icon_status: IconStatus.Up, + capital: "Andorra la Vella", + continent: { + name: "Europe", + }, + currency: "EUR", + languages: [ + { + name: "Catalan", + }, + ], + url: "https://en.wikipedia.org/wiki/Andorra", + }, + { + name: "United Arab Emirates", + code: "AE", + icon: "virtual-host", + icon_status: IconStatus.Up, + capital: "Abu Dhabi", + continent: { + name: "Asia", + }, + currency: "AED", + languages: [ + { + name: "Arabic", + }, + ], + url: "https://en.wikipedia.org/wiki/United_Arab_Emirates", + }, + { + name: "Afghanistan", + code: "AF", + icon: "virtual-host", + icon_status: IconStatus.Up, + capital: "Kabul", + continent: { + name: "Asia", + }, + currency: "AFN", + languages: [ + { + name: "Pashto", + }, + { + name: "Uzbek", + }, + { + name: "Turkmen", + }, + ], + url: "https://en.wikipedia.org/wiki/Afghanistan", + }, + { + name: "Antigua and Barbuda", + code: "AG", + icon: "virtual-host", + icon_status: IconStatus.Up, + capital: "Saint John's", + continent: { + name: "North America", + }, + currency: "XCD", + languages: [ + { + name: "English", + }, + ], + url: "https://en.wikipedia.org/wiki/Antigua_and_Barbuda", + }, + { + name: "Anguilla", + code: "AI", + icon: "virtual-host", + icon_status: IconStatus.Up, + capital: "The Valley", + continent: { + name: "North America", + }, + currency: "XCD", + languages: [ + { + name: "English", + }, + ], + url: "https://en.wikipedia.org/wiki/Anguilla", + }, + { + name: "Albania", + code: "AL", + icon: "virtual-host", + icon_status: IconStatus.Up, + capital: "Tirana", + continent: { + name: "Europe", + }, + currency: "ALL", + languages: [ + { + name: "Albanian", + }, + ], + url: "https://en.wikipedia.org/wiki/Albania", + }, + { + name: "Armenia", + code: "AM", + icon: "virtual-host", + icon_status: IconStatus.Up, + capital: "Yerevan", + continent: { + name: "Asia", + }, + currency: "AMD", + languages: [ + { + name: "Armenian", + }, + { + name: "Russian", + }, + ], + url: "https://en.wikipedia.org/wiki/Armenia", + }, + { + name: "Angola", + code: "AO", + icon: "virtual-host", + icon_status: IconStatus.Up, + capital: "Luanda", + continent: { + name: "Africa", + }, + currency: "AOA", + languages: [ + { + name: "Portuguese", + }, + ], + }, + { + name: "Antarctica", + code: "AQ", + icon: "virtual-host", + icon_status: IconStatus.Up, + capital: null, + continent: { + name: "Antarctica", + }, + currency: null, + languages: [], + }, + { + name: "Argentina", + code: "AR", + icon: "virtual-host", + icon_status: IconStatus.Up, + capital: "Buenos Aires", + continent: { + name: "South America", + }, + currency: "ARS", + languages: [ + { + name: "Spanish", + }, + { + name: "Guarani", + }, + ], + }, + { + name: "American Samoa", + code: "AS", + icon: "virtual-host", + icon_status: IconStatus.Up, + capital: "Pago Pago", + continent: { + name: "Oceania", + }, + currency: "USD", + languages: [ + { + name: "English", + }, + { + name: "Samoan", + }, + ], + }, + { + name: "Austria", + code: "AT", + icon: "virtual-host", + icon_status: IconStatus.Up, + capital: "Vienna", + continent: { + name: "Europe", + }, + currency: "EUR", + languages: [ + { + name: "German", + }, + ], + }, + { + name: "Australia", + code: "AU", + icon: "virtual-host", + icon_status: IconStatus.Up, + capital: "Canberra", + continent: { + name: "Oceania", + }, + currency: "AUD", + languages: [ + { + name: "English", + }, + ], + }, + { + name: "Aruba", + code: "AW", + icon: "virtual-host", + icon_status: IconStatus.Up, + capital: "Oranjestad", + continent: { + name: "North America", + }, + currency: "AWG", + languages: [ + { + name: "Dutch", + }, + { + name: "Panjabi / Punjabi", + }, + ], + }, + { + name: "Åland", + code: "AX", + icon: "virtual-host", + icon_status: IconStatus.Up, + capital: "Mariehamn", + continent: { + name: "Europe", + }, + currency: "EUR", + languages: [ + { + name: "Swedish", + }, + ], + }, + { + name: "Azerbaijan", + code: "AZ", + icon: "virtual-host", + icon_status: IconStatus.Up, + capital: "Baku", + continent: { + name: "Asia", + }, + currency: "AZN", + languages: [ + { + name: "Azerbaijani", + }, + ], + }, + { + name: "Bosnia and Herzegovina", + code: "BA", + icon: "virtual-host", + icon_status: IconStatus.Up, + capital: "Sarajevo", + continent: { + name: "Europe", + }, + currency: "BAM", + languages: [ + { + name: "Bosnian", + }, + { + name: "Croatian", + }, + { + name: "Serbian", + }, + ], + }, + { + name: "Barbados", + code: "BB", + icon: "virtual-host", + icon_status: IconStatus.Up, + capital: "Bridgetown", + continent: { + name: "North America", + }, + currency: "BBD", + languages: [ + { + name: "English", + }, + ], + }, + { + name: "Bangladesh", + code: "BD", + icon: "virtual-host", + icon_status: IconStatus.Up, + capital: "Dhaka", + continent: { + name: "Asia", + }, + currency: "BDT", + languages: [ + { + name: "Bengali", + }, + ], + }, + { + name: "Belgium", + code: "BE", + icon: "virtual-host", + icon_status: IconStatus.Up, + capital: "Brussels", + continent: { + name: "Europe", + }, + currency: "EUR", + languages: [ + { + name: "Dutch", + }, + { + name: "French", + }, + { + name: "German", + }, + ], + }, + { + name: "Burkina Faso", + code: "BF", + icon: "virtual-host", + icon_status: IconStatus.Up, + capital: "Ouagadougou", + continent: { + name: "Africa", + }, + currency: "XOF", + languages: [ + { + name: "French", + }, + { + name: "Peul", + }, + ], + }, + { + name: "Bulgaria", + code: "BG", + icon: "virtual-host", + icon_status: IconStatus.Up, + capital: "Sofia", + continent: { + name: "Europe", + }, + currency: "BGN", + languages: [ + { + name: "Bulgarian", + }, + ], + }, + { + name: "Bahrain", + code: "BH", + icon: "virtual-host", + icon_status: IconStatus.Up, + capital: "Manama", + continent: { + name: "Asia", + }, + currency: "BHD", + languages: [ + { + name: "Arabic", + }, + ], + }, + { + name: "Burundi", + code: "BI", + icon: "virtual-host", + icon_status: IconStatus.Up, + capital: "Bujumbura", + continent: { + name: "Africa", + }, + currency: "BIF", + languages: [ + { + name: "French", + }, + { + name: "Kirundi", + }, + ], + }, + { + name: "Benin", + code: "BJ", + icon: "virtual-host", + icon_status: IconStatus.Up, + capital: "Porto-Novo", + continent: { + name: "Africa", + }, + currency: "XOF", + languages: [ + { + name: "French", + }, + ], + }, + { + name: "Saint Barthélemy", + code: "BL", + icon: "virtual-host", + icon_status: IconStatus.Up, + capital: "Gustavia", + continent: { + name: "North America", + }, + currency: "EUR", + languages: [ + { + name: "French", + }, + ], + }, + { + name: "Bermuda", + code: "BM", + icon: "virtual-host", + icon_status: IconStatus.Up, + capital: "Hamilton", + continent: { + name: "North America", + }, + currency: "BMD", + languages: [ + { + name: "English", + }, + ], + }, + { + name: "Brunei", + code: "BN", + icon: "virtual-host", + icon_status: IconStatus.Up, + capital: "Bandar Seri Begawan", + continent: { + name: "Asia", + }, + currency: "BND", + languages: [ + { + name: "Malay", + }, + ], + }, + { + name: "Bolivia", + code: "BO", + icon: "virtual-host", + icon_status: IconStatus.Up, + capital: "Sucre", + continent: { + name: "South America", + }, + currency: "BOB,BOV", + languages: [ + { + name: "Spanish", + }, + { + name: "Aymara", + }, + { + name: "Quechua", + }, + ], + }, + { + name: "Bonaire", + code: "BQ", + icon: "virtual-host", + icon_status: IconStatus.Up, + capital: "Kralendijk", + continent: { + name: "North America", + }, + currency: "USD", + languages: [ + { + name: "Dutch", + }, + ], + }, + { + name: "Brazil", + code: "BR", + icon: "virtual-host", + icon_status: IconStatus.Up, + capital: "Brasília", + continent: { + name: "South America", + }, + currency: "BRL", + languages: [ + { + name: "Portuguese", + }, + ], + }, + { + name: "Bahamas", + code: "BS", + icon: "virtual-host", + icon_status: IconStatus.Up, + capital: "Nassau", + continent: { + name: "North America", + }, + currency: "BSD", + languages: [ + { + name: "English", + }, + ], + }, + { + name: "Bhutan", + code: "BT", + icon: "virtual-host", + icon_status: IconStatus.Up, + capital: "Thimphu", + continent: { + name: "Asia", + }, + currency: "BTN,INR", + languages: [ + { + name: "Dzongkha", + }, + ], + }, + { + name: "Bouvet Island", + code: "BV", + icon: "virtual-host", + icon_status: IconStatus.Up, + capital: null, + continent: { + name: "Antarctica", + }, + currency: "NOK", + languages: [ + { + name: "Norwegian", + }, + { + name: "Norwegian Bokmål", + }, + { + name: "Norwegian Nynorsk", + }, + ], + }, + { + name: "Botswana", + code: "BW", + icon: "virtual-host", + icon_status: IconStatus.Up, + capital: "Gaborone", + continent: { + name: "Africa", + }, + currency: "BWP", + languages: [ + { + name: "English", + }, + { + name: "Tswana", + }, + ], + }, + { + name: "Belarus", + code: "BY", + icon: "virtual-host", + icon_status: IconStatus.Up, + capital: "Minsk", + continent: { + name: "Europe", + }, + currency: "BYN", + languages: [ + { + name: "Belarusian", + }, + { + name: "Russian", + }, + ], + }, + { + name: "Belize", + code: "BZ", + icon: "virtual-host", + icon_status: IconStatus.Up, + capital: "Belmopan", + continent: { + name: "North America", + }, + currency: "BZD", + languages: [ + { + name: "English", + }, + { + name: "Spanish", + }, + ], + }, + { + name: "Canada", + code: "CA", + icon: "virtual-host", + icon_status: IconStatus.Up, + capital: "Ottawa", + continent: { + name: "North America", + }, + currency: "CAD", + languages: [ + { + name: "English", + }, + { + name: "French", + }, + ], + }, + { + name: "Cocos [Keeling] Islands", + code: "CC", + icon: "virtual-host", + icon_status: IconStatus.Up, + capital: "West Island", + continent: { + name: "Asia", + }, + currency: "AUD", + languages: [ + { + name: "English", + }, + ], + }, + { + name: "Democratic Republic of the Congo", + code: "CD", + icon: "virtual-host", + icon_status: IconStatus.Up, + capital: "Kinshasa", + continent: { + name: "Africa", + }, + currency: "CDF", + languages: [ + { + name: "French", + }, + { + name: "Lingala", + }, + { + name: "Kongo", + }, + { + name: "Swahili", + }, + { + name: "Luba-Katanga", + }, + ], + }, + { + name: "Central African Republic", + code: "CF", + icon: "virtual-host", + icon_status: IconStatus.Up, + capital: "Bangui", + continent: { + name: "Africa", + }, + currency: "XAF", + languages: [ + { + name: "French", + }, + { + name: "Sango", + }, + ], + }, + { + name: "Republic of the Congo", + code: "CG", + icon: "virtual-host", + icon_status: IconStatus.Up, + capital: "Brazzaville", + continent: { + name: "Africa", + }, + currency: "XAF", + languages: [ + { + name: "French", + }, + { + name: "Lingala", + }, + ], + }, + { + name: "Switzerland", + code: "CH", + icon: "virtual-host", + icon_status: IconStatus.Up, + capital: "Bern", + continent: { + name: "Europe", + }, + currency: "CHE,CHF,CHW", + languages: [ + { + name: "German", + }, + { + name: "French", + }, + { + name: "Italian", + }, + ], + }, + { + name: "Ivory Coast", + code: "CI", + icon: "virtual-host", + icon_status: IconStatus.Up, + capital: "Yamoussoukro", + continent: { + name: "Africa", + }, + currency: "XOF", + languages: [ + { + name: "French", + }, + ], + }, + { + name: "Cook Islands", + code: "CK", + icon: "virtual-host", + icon_status: IconStatus.Up, + capital: "Avarua", + continent: { + name: "Oceania", + }, + currency: "NZD", + languages: [ + { + name: "English", + }, + ], + }, + { + name: "Chile", + code: "CL", + icon: "virtual-host", + icon_status: IconStatus.Up, + capital: "Santiago", + continent: { + name: "South America", + }, + currency: "CLF,CLP", + languages: [ + { + name: "Spanish", + }, + ], + }, + { + name: "Cameroon", + code: "CM", + icon: "virtual-host", + icon_status: IconStatus.Up, + capital: "Yaoundé", + continent: { + name: "Africa", + }, + currency: "XAF", + languages: [ + { + name: "English", + }, + { + name: "French", + }, + ], + }, + { + name: "China", + code: "CN", + icon: "virtual-host", + icon_status: IconStatus.Up, + capital: "Beijing", + continent: { + name: "Asia", + }, + currency: "CNY", + languages: [ + { + name: "Chinese", + }, + ], + }, + { + name: "Colombia", + code: "CO", + icon: "virtual-host", + icon_status: IconStatus.Up, + capital: "Bogotá", + continent: { + name: "South America", + }, + currency: "COP", + languages: [ + { + name: "Spanish", + }, + ], + }, + { + name: "Costa Rica", + code: "CR", + icon: "virtual-host", + icon_status: IconStatus.Up, + capital: "San José", + continent: { + name: "North America", + }, + currency: "CRC", + languages: [ + { + name: "Spanish", + }, + ], + }, + { + name: "Cuba", + code: "CU", + icon: "virtual-host", + icon_status: IconStatus.Up, + capital: "Havana", + continent: { + name: "North America", + }, + currency: "CUC,CUP", + languages: [ + { + name: "Spanish", + }, + ], + }, + { + name: "Cape Verde", + code: "CV", + icon: "virtual-host", + icon_status: IconStatus.Up, + capital: "Praia", + continent: { + name: "Africa", + }, + currency: "CVE", + languages: [ + { + name: "Portuguese", + }, + ], + }, + { + name: "Curacao", + code: "CW", + icon: "virtual-host", + icon_status: IconStatus.Up, + capital: "Willemstad", + continent: { + name: "North America", + }, + currency: "ANG", + languages: [ + { + name: "Dutch", + }, + { + name: "Panjabi / Punjabi", + }, + { + name: "English", + }, + ], + }, + { + name: "Christmas Island", + code: "CX", + icon: "virtual-host", + icon_status: IconStatus.Up, + capital: "Flying Fish Cove", + continent: { + name: "Asia", + }, + currency: "AUD", + languages: [ + { + name: "English", + }, + ], + }, + { + name: "Cyprus", + code: "CY", + icon: "virtual-host", + icon_status: IconStatus.Up, + capital: "Nicosia", + continent: { + name: "Europe", + }, + currency: "EUR", + languages: [ + { + name: "Greek", + }, + { + name: "Turkish", + }, + { + name: "Armenian", + }, + ], + }, + { + name: "Czech Republic", + code: "CZ", + icon: "virtual-host", + icon_status: IconStatus.Up, + capital: "Prague", + continent: { + name: "Europe", + }, + currency: "CZK", + languages: [ + { + name: "Czech", + }, + { + name: "Slovak", + }, + ], + }, + { + name: "Germany", + code: "DE", + icon: "virtual-host", + icon_status: IconStatus.Up, + capital: "Berlin", + continent: { + name: "Europe", + }, + currency: "EUR", + languages: [ + { + name: "German", + }, + ], + }, + { + name: "Djibouti", + code: "DJ", + icon: "virtual-host", + icon_status: IconStatus.Up, + capital: "Djibouti", + continent: { + name: "Africa", + }, + currency: "DJF", + languages: [ + { + name: "French", + }, + { + name: "Arabic", + }, + ], + }, + { + name: "Denmark", + code: "DK", + icon: "virtual-host", + icon_status: IconStatus.Up, + capital: "Copenhagen", + continent: { + name: "Europe", + }, + currency: "DKK", + languages: [ + { + name: "Danish", + }, + ], + }, + { + name: "Dominica", + code: "DM", + icon: "virtual-host", + icon_status: IconStatus.Up, + capital: "Roseau", + continent: { + name: "North America", + }, + currency: "XCD", + languages: [ + { + name: "English", + }, + ], + }, + { + name: "Dominican Republic", + code: "DO", + icon: "virtual-host", + icon_status: IconStatus.Up, + capital: "Santo Domingo", + continent: { + name: "North America", + }, + currency: "DOP", + languages: [ + { + name: "Spanish", + }, + ], + }, + { + name: "Algeria", + code: "DZ", + icon: "virtual-host", + icon_status: IconStatus.Up, + capital: "Algiers", + continent: { + name: "Africa", + }, + currency: "DZD", + languages: [ + { + name: "Arabic", + }, + ], + }, + { + name: "Ecuador", + code: "EC", + icon: "virtual-host", + icon_status: IconStatus.Up, + capital: "Quito", + continent: { + name: "South America", + }, + currency: "USD", + languages: [ + { + name: "Spanish", + }, + ], + }, + { + name: "Estonia", + code: "EE", + icon: "virtual-host", + icon_status: IconStatus.Up, + capital: "Tallinn", + continent: { + name: "Europe", + }, + currency: "EUR", + languages: [ + { + name: "Estonian", + }, + ], + }, + { + name: "Egypt", + code: "EG", + icon: "virtual-host", + icon_status: IconStatus.Up, + capital: "Cairo", + continent: { + name: "Africa", + }, + currency: "EGP", + languages: [ + { + name: "Arabic", + }, + ], + }, + { + name: "Western Sahara", + code: "EH", + icon: "virtual-host", + icon_status: IconStatus.Up, + capital: "El Aaiún", + continent: { + name: "Africa", + }, + currency: "MAD,DZD,MRU", + languages: [ + { + name: "Spanish", + }, + ], + }, + { + name: "Eritrea", + code: "ER", + icon: "virtual-host", + icon_status: IconStatus.Up, + capital: "Asmara", + continent: { + name: "Africa", + }, + currency: "ERN", + languages: [ + { + name: "Tigrinya", + }, + { + name: "Arabic", + }, + { + name: "English", + }, + ], + }, + { + name: "Spain", + code: "ES", + icon: "virtual-host", + icon_status: IconStatus.Up, + capital: "Madrid", + continent: { + name: "Europe", + }, + currency: "EUR", + languages: [ + { + name: "Spanish", + }, + { + name: "Basque", + }, + { + name: "Catalan", + }, + { + name: "Galician", + }, + { + name: "Occitan", + }, + ], + }, + { + name: "Ethiopia", + code: "ET", + icon: "virtual-host", + icon_status: IconStatus.Up, + capital: "Addis Ababa", + continent: { + name: "Africa", + }, + currency: "ETB", + languages: [ + { + name: "Amharic", + }, + ], + }, + { + name: "Finland", + code: "FI", + icon: "virtual-host", + icon_status: IconStatus.Up, + capital: "Helsinki", + continent: { + name: "Europe", + }, + currency: "EUR", + languages: [ + { + name: "Finnish", + }, + { + name: "Swedish", + }, + ], + }, + { + name: "Fiji", + code: "FJ", + icon: "virtual-host", + icon_status: IconStatus.Up, + capital: "Suva", + continent: { + name: "Oceania", + }, + currency: "FJD", + languages: [ + { + name: "English", + }, + { + name: "Fijian", + }, + { + name: "Hindi", + }, + { + name: "Urdu", + }, + ], + }, + { + name: "Falkland Islands", + code: "FK", + icon: "virtual-host", + icon_status: IconStatus.Up, + capital: "Stanley", + continent: { + name: "South America", + }, + currency: "FKP", + languages: [ + { + name: "English", + }, + ], + }, + { + name: "Micronesia", + code: "FM", + icon: "virtual-host", + icon_status: IconStatus.Up, + capital: "Palikir", + continent: { + name: "Oceania", + }, + currency: "USD", + languages: [ + { + name: "English", + }, + ], + }, + { + name: "Faroe Islands", + code: "FO", + icon: "virtual-host", + icon_status: IconStatus.Up, + capital: "Tórshavn", + continent: { + name: "Europe", + }, + currency: "DKK", + languages: [ + { + name: "Faroese", + }, + ], + }, + { + name: "France", + code: "FR", + icon: "virtual-host", + icon_status: IconStatus.Up, + capital: "Paris", + continent: { + name: "Europe", + }, + currency: "EUR", + languages: [ + { + name: "French", + }, + ], + }, + { + name: "Gabon", + code: "GA", + icon: "virtual-host", + icon_status: IconStatus.Up, + capital: "Libreville", + continent: { + name: "Africa", + }, + currency: "XAF", + languages: [ + { + name: "French", + }, + ], + }, + { + name: "United Kingdom", + code: "GB", + icon: "virtual-host", + icon_status: IconStatus.Up, + capital: "London", + continent: { + name: "Europe", + }, + currency: "GBP", + languages: [ + { + name: "English", + }, + ], + }, + { + name: "Grenada", + code: "GD", + icon: "virtual-host", + icon_status: IconStatus.Up, + capital: "St. George's", + continent: { + name: "North America", + }, + currency: "XCD", + languages: [ + { + name: "English", + }, + ], + }, + { + name: "Georgia", + code: "GE", + icon: "virtual-host", + icon_status: IconStatus.Up, + capital: "Tbilisi", + continent: { + name: "Asia", + }, + currency: "GEL", + languages: [ + { + name: "Georgian", + }, + ], + }, + { + name: "French Guiana", + code: "GF", + icon: "virtual-host", + icon_status: IconStatus.Up, + capital: "Cayenne", + continent: { + name: "South America", + }, + currency: "EUR", + languages: [ + { + name: "French", + }, + ], + }, + { + name: "Guernsey", + code: "GG", + icon: "virtual-host", + icon_status: IconStatus.Up, + capital: "St. Peter Port", + continent: { + name: "Europe", + }, + currency: "GBP", + languages: [ + { + name: "English", + }, + { + name: "French", + }, + ], + }, + { + name: "Ghana", + code: "GH", + icon: "virtual-host", + icon_status: IconStatus.Up, + capital: "Accra", + continent: { + name: "Africa", + }, + currency: "GHS", + languages: [ + { + name: "English", + }, + ], + }, + { + name: "Gibraltar", + code: "GI", + icon: "virtual-host", + icon_status: IconStatus.Up, + capital: "Gibraltar", + continent: { + name: "Europe", + }, + currency: "GIP", + languages: [ + { + name: "English", + }, + ], + }, + { + name: "Greenland", + code: "GL", + icon: "virtual-host", + icon_status: IconStatus.Up, + capital: "Nuuk", + continent: { + name: "North America", + }, + currency: "DKK", + languages: [ + { + name: "Greenlandic", + }, + ], + }, + { + name: "Gambia", + code: "GM", + icon: "virtual-host", + icon_status: IconStatus.Up, + capital: "Banjul", + continent: { + name: "Africa", + }, + currency: "GMD", + languages: [ + { + name: "English", + }, + ], + }, + { + name: "Guinea", + code: "GN", + icon: "virtual-host", + icon_status: IconStatus.Up, + capital: "Conakry", + continent: { + name: "Africa", + }, + currency: "GNF", + languages: [ + { + name: "French", + }, + { + name: "Peul", + }, + ], + }, + { + name: "Guadeloupe", + code: "GP", + icon: "virtual-host", + icon_status: IconStatus.Up, + capital: "Basse-Terre", + continent: { + name: "North America", + }, + currency: "EUR", + languages: [ + { + name: "French", + }, + ], + }, + { + name: "Equatorial Guinea", + code: "GQ", + icon: "virtual-host", + icon_status: IconStatus.Up, + capital: "Malabo", + continent: { + name: "Africa", + }, + currency: "XAF", + languages: [ + { + name: "Spanish", + }, + { + name: "French", + }, + ], + }, + { + name: "Greece", + code: "GR", + icon: "virtual-host", + icon_status: IconStatus.Up, + capital: "Athens", + continent: { + name: "Europe", + }, + currency: "EUR", + languages: [ + { + name: "Greek", + }, + ], + }, + { + name: "South Georgia and the South Sandwich Islands", + code: "GS", + icon: "virtual-host", + icon_status: IconStatus.Up, + capital: "King Edward Point", + continent: { + name: "Antarctica", + }, + currency: "GBP", + languages: [ + { + name: "English", + }, + ], + }, + { + name: "Guatemala", + code: "GT", + icon: "virtual-host", + icon_status: IconStatus.Up, + capital: "Guatemala City", + continent: { + name: "North America", + }, + currency: "GTQ", + languages: [ + { + name: "Spanish", + }, + ], + }, + { + name: "Guam", + code: "GU", + icon: "virtual-host", + icon_status: IconStatus.Up, + capital: "Hagåtña", + continent: { + name: "Oceania", + }, + currency: "USD", + languages: [ + { + name: "English", + }, + { + name: "Chamorro", + }, + { + name: "Spanish", + }, + ], + }, + { + name: "Guinea-Bissau", + code: "GW", + icon: "virtual-host", + icon_status: IconStatus.Up, + capital: "Bissau", + continent: { + name: "Africa", + }, + currency: "XOF", + languages: [ + { + name: "Portuguese", + }, + ], + }, + { + name: "Guyana", + code: "GY", + icon: "virtual-host", + icon_status: IconStatus.Up, + capital: "Georgetown", + continent: { + name: "South America", + }, + currency: "GYD", + languages: [ + { + name: "English", + }, + ], + }, + { + name: "Hong Kong", + code: "HK", + icon: "virtual-host", + icon_status: IconStatus.Up, + capital: "City of Victoria", + continent: { + name: "Asia", + }, + currency: "HKD", + languages: [ + { + name: "Chinese", + }, + { + name: "English", + }, + ], + }, + { + name: "Heard Island and McDonald Islands", + code: "HM", + icon: "virtual-host", + icon_status: IconStatus.Up, + capital: null, + continent: { + name: "Antarctica", + }, + currency: "AUD", + languages: [ + { + name: "English", + }, + ], + }, + { + name: "Honduras", + code: "HN", + icon: "virtual-host", + icon_status: IconStatus.Up, + capital: "Tegucigalpa", + continent: { + name: "North America", + }, + currency: "HNL", + languages: [ + { + name: "Spanish", + }, + ], + }, + { + name: "Croatia", + code: "HR", + icon: "virtual-host", + icon_status: IconStatus.Up, + capital: "Zagreb", + continent: { + name: "Europe", + }, + currency: "HRK", + languages: [ + { + name: "Croatian", + }, + ], + }, + { + name: "Haiti", + code: "HT", + icon: "virtual-host", + icon_status: IconStatus.Up, + capital: "Port-au-Prince", + continent: { + name: "North America", + }, + currency: "HTG,USD", + languages: [ + { + name: "French", + }, + { + name: "Haitian", + }, + ], + }, + { + name: "Hungary", + code: "HU", + icon: "virtual-host", + icon_status: IconStatus.Up, + capital: "Budapest", + continent: { + name: "Europe", + }, + currency: "HUF", + languages: [ + { + name: "Hungarian", + }, + ], + }, + { + name: "Indonesia", + code: "ID", + icon: "virtual-host", + icon_status: IconStatus.Up, + capital: "Jakarta", + continent: { + name: "Asia", + }, + currency: "IDR", + languages: [ + { + name: "Indonesian", + }, + ], + }, + { + name: "Ireland", + code: "IE", + icon: "virtual-host", + icon_status: IconStatus.Up, + capital: "Dublin", + continent: { + name: "Europe", + }, + currency: "EUR", + languages: [ + { + name: "Irish", + }, + { + name: "English", + }, + ], + }, + { + name: "Israel", + code: "IL", + icon: "virtual-host", + icon_status: IconStatus.Up, + capital: "Jerusalem", + continent: { + name: "Asia", + }, + currency: "ILS", + languages: [ + { + name: "Hebrew", + }, + { + name: "Arabic", + }, + ], + }, + { + name: "Isle of Man", + code: "IM", + icon: "virtual-host", + icon_status: IconStatus.Up, + capital: "Douglas", + continent: { + name: "Europe", + }, + currency: "GBP", + languages: [ + { + name: "English", + }, + { + name: "Manx", + }, + ], + }, + { + name: "India", + code: "IN", + icon: "virtual-host", + icon_status: IconStatus.Up, + capital: "New Delhi", + continent: { + name: "Asia", + }, + currency: "INR", + languages: [ + { + name: "Hindi", + }, + { + name: "English", + }, + ], + }, + { + name: "British Indian Ocean Territory", + code: "IO", + icon: "virtual-host", + icon_status: IconStatus.Up, + capital: "Diego Garcia", + continent: { + name: "Asia", + }, + currency: "USD", + languages: [ + { + name: "English", + }, + ], + }, + { + name: "Iraq", + code: "IQ", + icon: "virtual-host", + icon_status: IconStatus.Up, + capital: "Baghdad", + continent: { + name: "Asia", + }, + currency: "IQD", + languages: [ + { + name: "Arabic", + }, + { + name: "Kurdish", + }, + ], + }, + { + name: "Iran", + code: "IR", + icon: "virtual-host", + icon_status: IconStatus.Up, + capital: "Tehran", + continent: { + name: "Asia", + }, + currency: "IRR", + languages: [ + { + name: "Persian", + }, + ], + }, + { + name: "Iceland", + code: "IS", + icon: "virtual-host", + icon_status: IconStatus.Up, + capital: "Reykjavik", + continent: { + name: "Europe", + }, + currency: "ISK", + languages: [ + { + name: "Icelandic", + }, + ], + }, + { + name: "Italy", + code: "IT", + icon: "virtual-host", + icon_status: IconStatus.Up, + capital: "Rome", + continent: { + name: "Europe", + }, + currency: "EUR", + languages: [ + { + name: "Italian", + }, + ], + }, + { + name: "Jersey", + code: "JE", + icon: "virtual-host", + icon_status: IconStatus.Up, + capital: "Saint Helier", + continent: { + name: "Europe", + }, + currency: "GBP", + languages: [ + { + name: "English", + }, + { + name: "French", + }, + ], + }, + { + name: "Jamaica", + code: "JM", + icon: "virtual-host", + icon_status: IconStatus.Up, + capital: "Kingston", + continent: { + name: "North America", + }, + currency: "JMD", + languages: [ + { + name: "English", + }, + ], + }, + { + name: "Jordan", + code: "JO", + icon: "virtual-host", + icon_status: IconStatus.Up, + capital: "Amman", + continent: { + name: "Asia", + }, + currency: "JOD", + languages: [ + { + name: "Arabic", + }, + ], + }, + { + name: "Japan", + code: "JP", + icon: "virtual-host", + icon_status: IconStatus.Up, + capital: "Tokyo", + continent: { + name: "Asia", + }, + currency: "JPY", + languages: [ + { + name: "Japanese", + }, + ], + }, + { + name: "Kenya", + code: "KE", + icon: "virtual-host", + icon_status: IconStatus.Up, + capital: "Nairobi", + continent: { + name: "Africa", + }, + currency: "KES", + languages: [ + { + name: "English", + }, + { + name: "Swahili", + }, + ], + }, + { + name: "Kyrgyzstan", + code: "KG", + icon: "virtual-host", + icon_status: IconStatus.Up, + capital: "Bishkek", + continent: { + name: "Asia", + }, + currency: "KGS", + languages: [ + { + name: "Kirghiz", + }, + { + name: "Russian", + }, + ], + }, + { + name: "Cambodia", + code: "KH", + icon: "virtual-host", + icon_status: IconStatus.Up, + capital: "Phnom Penh", + continent: { + name: "Asia", + }, + currency: "KHR", + languages: [ + { + name: "Cambodian", + }, + ], + }, + { + name: "Kiribati", + code: "KI", + icon: "virtual-host", + icon_status: IconStatus.Up, + capital: "South Tarawa", + continent: { + name: "Oceania", + }, + currency: "AUD", + languages: [ + { + name: "English", + }, + ], + }, + { + name: "Comoros", + code: "KM", + icon: "virtual-host", + icon_status: IconStatus.Up, + capital: "Moroni", + continent: { + name: "Africa", + }, + currency: "KMF", + languages: [ + { + name: "Arabic", + }, + { + name: "French", + }, + ], + }, + { + name: "Saint Kitts and Nevis", + code: "KN", + icon: "virtual-host", + icon_status: IconStatus.Up, + capital: "Basseterre", + continent: { + name: "North America", + }, + currency: "XCD", + languages: [ + { + name: "English", + }, + ], + }, + { + name: "North Korea", + code: "KP", + icon: "virtual-host", + icon_status: IconStatus.Up, + capital: "Pyongyang", + continent: { + name: "Asia", + }, + currency: "KPW", + languages: [ + { + name: "Korean", + }, + ], + }, + { + name: "South Korea", + code: "KR", + icon: "virtual-host", + icon_status: IconStatus.Up, + capital: "Seoul", + continent: { + name: "Asia", + }, + currency: "KRW", + languages: [ + { + name: "Korean", + }, + ], + }, + { + name: "Kuwait", + code: "KW", + icon: "virtual-host", + icon_status: IconStatus.Up, + capital: "Kuwait City", + continent: { + name: "Asia", + }, + currency: "KWD", + languages: [ + { + name: "Arabic", + }, + ], + }, + { + name: "Cayman Islands", + code: "KY", + icon: "virtual-host", + icon_status: IconStatus.Up, + capital: "George Town", + continent: { + name: "North America", + }, + currency: "KYD", + languages: [ + { + name: "English", + }, + ], + }, + { + name: "Kazakhstan", + code: "KZ", + icon: "virtual-host", + icon_status: IconStatus.Up, + capital: "Astana", + continent: { + name: "Asia", + }, + currency: "KZT", + languages: [ + { + name: "Kazakh", + }, + { + name: "Russian", + }, + ], + }, + { + name: "Laos", + code: "LA", + icon: "virtual-host", + icon_status: IconStatus.Up, + capital: "Vientiane", + continent: { + name: "Asia", + }, + currency: "LAK", + languages: [ + { + name: "Laotian", + }, + ], + }, + { + name: "Lebanon", + code: "LB", + icon: "virtual-host", + icon_status: IconStatus.Up, + capital: "Beirut", + continent: { + name: "Asia", + }, + currency: "LBP", + languages: [ + { + name: "Arabic", + }, + { + name: "French", + }, + ], + }, + { + name: "Saint Lucia", + code: "LC", + icon: "virtual-host", + icon_status: IconStatus.Up, + capital: "Castries", + continent: { + name: "North America", + }, + currency: "XCD", + languages: [ + { + name: "English", + }, + ], + }, + { + name: "Liechtenstein", + code: "LI", + icon: "virtual-host", + icon_status: IconStatus.Up, + capital: "Vaduz", + continent: { + name: "Europe", + }, + currency: "CHF", + languages: [ + { + name: "German", + }, + ], + }, + { + name: "Sri Lanka", + code: "LK", + icon: "virtual-host", + icon_status: IconStatus.Up, + capital: "Colombo", + continent: { + name: "Asia", + }, + currency: "LKR", + languages: [ + { + name: "Sinhalese", + }, + { + name: "Tamil", + }, + ], + }, + { + name: "Liberia", + code: "LR", + icon: "virtual-host", + icon_status: IconStatus.Up, + capital: "Monrovia", + continent: { + name: "Africa", + }, + currency: "LRD", + languages: [ + { + name: "English", + }, + ], + }, + { + name: "Lesotho", + code: "LS", + icon: "virtual-host", + icon_status: IconStatus.Up, + capital: "Maseru", + continent: { + name: "Africa", + }, + currency: "LSL,ZAR", + languages: [ + { + name: "English", + }, + { + name: "Southern Sotho", + }, + ], + }, + { + name: "Lithuania", + code: "LT", + icon: "virtual-host", + icon_status: IconStatus.Up, + capital: "Vilnius", + continent: { + name: "Europe", + }, + currency: "EUR", + languages: [ + { + name: "Lithuanian", + }, + ], + }, + { + name: "Luxembourg", + code: "LU", + icon: "virtual-host", + icon_status: IconStatus.Up, + capital: "Luxembourg", + continent: { + name: "Europe", + }, + currency: "EUR", + languages: [ + { + name: "French", + }, + { + name: "German", + }, + { + name: "Luxembourgish", + }, + ], + }, + { + name: "Latvia", + code: "LV", + icon: "virtual-host", + icon_status: IconStatus.Up, + capital: "Riga", + continent: { + name: "Europe", + }, + currency: "EUR", + languages: [ + { + name: "Latvian", + }, + ], + }, + { + name: "Libya", + code: "LY", + icon: "virtual-host", + icon_status: IconStatus.Up, + capital: "Tripoli", + continent: { + name: "Africa", + }, + currency: "LYD", + languages: [ + { + name: "Arabic", + }, + ], + }, + { + name: "Morocco", + code: "MA", + icon: "virtual-host", + icon_status: IconStatus.Up, + capital: "Rabat", + continent: { + name: "Africa", + }, + currency: "MAD", + languages: [ + { + name: "Arabic", + }, + ], + }, + { + name: "Monaco", + code: "MC", + icon: "virtual-host", + icon_status: IconStatus.Up, + capital: "Monaco", + continent: { + name: "Europe", + }, + currency: "EUR", + languages: [ + { + name: "French", + }, + ], + }, + { + name: "Moldova", + code: "MD", + icon: "virtual-host", + icon_status: IconStatus.Up, + capital: "Chișinău", + continent: { + name: "Europe", + }, + currency: "MDL", + languages: [ + { + name: "Romanian", + }, + ], + }, + { + name: "Montenegro", + code: "ME", + icon: "virtual-host", + icon_status: IconStatus.Up, + capital: "Podgorica", + continent: { + name: "Europe", + }, + currency: "EUR", + languages: [ + { + name: "Serbian", + }, + { + name: "Bosnian", + }, + { + name: "Albanian", + }, + { + name: "Croatian", + }, + ], + }, + { + name: "Saint Martin", + code: "MF", + icon: "virtual-host", + icon_status: IconStatus.Up, + capital: "Marigot", + continent: { + name: "North America", + }, + currency: "EUR", + languages: [ + { + name: "English", + }, + { + name: "French", + }, + { + name: "Dutch", + }, + ], + }, + { + name: "Madagascar", + code: "MG", + icon: "virtual-host", + icon_status: IconStatus.Up, + capital: "Antananarivo", + continent: { + name: "Africa", + }, + currency: "MGA", + languages: [ + { + name: "French", + }, + { + name: "Malagasy", + }, + ], + }, + { + name: "Marshall Islands", + code: "MH", + icon: "virtual-host", + icon_status: IconStatus.Up, + capital: "Majuro", + continent: { + name: "Oceania", + }, + currency: "USD", + languages: [ + { + name: "English", + }, + { + name: "Marshallese", + }, + ], + }, + { + name: "North Macedonia", + code: "MK", + icon: "virtual-host", + icon_status: IconStatus.Up, + capital: "Skopje", + continent: { + name: "Europe", + }, + currency: "MKD", + languages: [ + { + name: "Macedonian", + }, + ], + }, + { + name: "Mali", + code: "ML", + icon: "virtual-host", + icon_status: IconStatus.Up, + capital: "Bamako", + continent: { + name: "Africa", + }, + currency: "XOF", + languages: [ + { + name: "French", + }, + ], + }, + { + name: "Myanmar [Burma]", + code: "MM", + icon: "virtual-host", + icon_status: IconStatus.Up, + capital: "Naypyidaw", + continent: { + name: "Asia", + }, + currency: "MMK", + languages: [ + { + name: "Burmese", + }, + ], + }, + { + name: "Mongolia", + code: "MN", + icon: "virtual-host", + icon_status: IconStatus.Up, + capital: "Ulan Bator", + continent: { + name: "Asia", + }, + currency: "MNT", + languages: [ + { + name: "Mongolian", + }, + ], + }, + { + name: "Macao", + code: "MO", + icon: "virtual-host", + icon_status: IconStatus.Up, + capital: null, + continent: { + name: "Asia", + }, + currency: "MOP", + languages: [ + { + name: "Chinese", + }, + { + name: "Portuguese", + }, + ], + }, + { + name: "Northern Mariana Islands", + code: "MP", + icon: "virtual-host", + icon_status: IconStatus.Up, + capital: "Saipan", + continent: { + name: "Oceania", + }, + currency: "USD", + languages: [ + { + name: "English", + }, + { + name: "Chamorro", + }, + ], + }, + { + name: "Martinique", + code: "MQ", + icon: "virtual-host", + icon_status: IconStatus.Up, + capital: "Fort-de-France", + continent: { + name: "North America", + }, + currency: "EUR", + languages: [ + { + name: "French", + }, + ], + }, + { + name: "Mauritania", + code: "MR", + icon: "virtual-host", + icon_status: IconStatus.Up, + capital: "Nouakchott", + continent: { + name: "Africa", + }, + currency: "MRU", + languages: [ + { + name: "Arabic", + }, + ], + }, + { + name: "Montserrat", + code: "MS", + icon: "virtual-host", + icon_status: IconStatus.Up, + capital: "Plymouth", + continent: { + name: "North America", + }, + currency: "XCD", + languages: [ + { + name: "English", + }, + ], + }, + { + name: "Malta", + code: "MT", + icon: "virtual-host", + icon_status: IconStatus.Up, + capital: "Valletta", + continent: { + name: "Europe", + }, + currency: "EUR", + languages: [ + { + name: "Maltese", + }, + { + name: "English", + }, + ], + }, + { + name: "Mauritius", + code: "MU", + icon: "virtual-host", + icon_status: IconStatus.Up, + capital: "Port Louis", + continent: { + name: "Africa", + }, + currency: "MUR", + languages: [ + { + name: "English", + }, + ], + }, + { + name: "Maldives", + code: "MV", + icon: "virtual-host", + icon_status: IconStatus.Up, + capital: "Malé", + continent: { + name: "Asia", + }, + currency: "MVR", + languages: [ + { + name: "Divehi", + }, + ], + }, + { + name: "Malawi", + code: "MW", + icon: "virtual-host", + icon_status: IconStatus.Up, + capital: "Lilongwe", + continent: { + name: "Africa", + }, + currency: "MWK", + languages: [ + { + name: "English", + }, + { + name: "Chichewa", + }, + ], + }, + { + name: "Mexico", + code: "MX", + icon: "virtual-host", + icon_status: IconStatus.Up, + capital: "Mexico City", + continent: { + name: "North America", + }, + currency: "MXN", + languages: [ + { + name: "Spanish", + }, + ], + }, + { + name: "Malaysia", + code: "MY", + icon: "virtual-host", + icon_status: IconStatus.Up, + capital: "Kuala Lumpur", + continent: { + name: "Asia", + }, + currency: "MYR", + languages: [ + { + name: "Malay", + }, + ], + }, + { + name: "Mozambique", + code: "MZ", + icon: "virtual-host", + icon_status: IconStatus.Up, + capital: "Maputo", + continent: { + name: "Africa", + }, + currency: "MZN", + languages: [ + { + name: "Portuguese", + }, + ], + }, + { + name: "Namibia", + code: "NA", + icon: "virtual-host", + icon_status: IconStatus.Up, + capital: "Windhoek", + continent: { + name: "Africa", + }, + currency: "NAD,ZAR", + languages: [ + { + name: "English", + }, + { + name: "Afrikaans", + }, + ], + }, + { + name: "New Caledonia", + code: "NC", + icon: "virtual-host", + icon_status: IconStatus.Up, + capital: "Nouméa", + continent: { + name: "Oceania", + }, + currency: "XPF", + languages: [ + { + name: "French", + }, + ], + }, + { + name: "Niger", + code: "NE", + icon: "virtual-host", + icon_status: IconStatus.Up, + capital: "Niamey", + continent: { + name: "Africa", + }, + currency: "XOF", + languages: [ + { + name: "French", + }, + ], + }, + { + name: "Norfolk Island", + code: "NF", + icon: "virtual-host", + icon_status: IconStatus.Up, + capital: "Kingston", + continent: { + name: "Oceania", + }, + currency: "AUD", + languages: [ + { + name: "English", + }, + ], + }, + { + name: "Nigeria", + code: "NG", + icon: "virtual-host", + icon_status: IconStatus.Up, + capital: "Abuja", + continent: { + name: "Africa", + }, + currency: "NGN", + languages: [ + { + name: "English", + }, + ], + }, + { + name: "Nicaragua", + code: "NI", + icon: "virtual-host", + icon_status: IconStatus.Up, + capital: "Managua", + continent: { + name: "North America", + }, + currency: "NIO", + languages: [ + { + name: "Spanish", + }, + ], + }, + { + name: "Netherlands", + code: "NL", + icon: "virtual-host", + icon_status: IconStatus.Up, + capital: "Amsterdam", + continent: { + name: "Europe", + }, + currency: "EUR", + languages: [ + { + name: "Dutch", + }, + ], + }, + { + name: "Norway", + code: "NO", + icon: "virtual-host", + icon_status: IconStatus.Up, + capital: "Oslo", + continent: { + name: "Europe", + }, + currency: "NOK", + languages: [ + { + name: "Norwegian", + }, + { + name: "Norwegian Bokmål", + }, + { + name: "Norwegian Nynorsk", + }, + ], + }, + { + name: "Nepal", + code: "NP", + icon: "virtual-host", + icon_status: IconStatus.Up, + capital: "Kathmandu", + continent: { + name: "Asia", + }, + currency: "NPR", + languages: [ + { + name: "Nepali", + }, + ], + }, + { + name: "Nauru", + code: "NR", + icon: "virtual-host", + icon_status: IconStatus.Up, + capital: "Yaren", + continent: { + name: "Oceania", + }, + currency: "AUD", + languages: [ + { + name: "English", + }, + { + name: "Nauruan", + }, + ], + }, + { + name: "Niue", + code: "NU", + icon: "virtual-host", + icon_status: IconStatus.Up, + capital: "Alofi", + continent: { + name: "Oceania", + }, + currency: "NZD", + languages: [ + { + name: "English", + }, + ], + }, + { + name: "New Zealand", + code: "NZ", + icon: "virtual-host", + icon_status: IconStatus.Up, + capital: "Wellington", + continent: { + name: "Oceania", + }, + currency: "NZD", + languages: [ + { + name: "English", + }, + { + name: "Maori", + }, + ], + }, + { + name: "Oman", + code: "OM", + icon: "virtual-host", + icon_status: IconStatus.Up, + capital: "Muscat", + continent: { + name: "Asia", + }, + currency: "OMR", + languages: [ + { + name: "Arabic", + }, + ], + }, + { + name: "Panama", + code: "PA", + icon: "virtual-host", + icon_status: IconStatus.Up, + capital: "Panama City", + continent: { + name: "North America", + }, + currency: "PAB,USD", + languages: [ + { + name: "Spanish", + }, + ], + }, + { + name: "Peru", + code: "PE", + icon: "virtual-host", + icon_status: IconStatus.Up, + capital: "Lima", + continent: { + name: "South America", + }, + currency: "PEN", + languages: [ + { + name: "Spanish", + }, + ], + }, + { + name: "French Polynesia", + code: "PF", + icon: "virtual-host", + icon_status: IconStatus.Up, + capital: "Papeetē", + continent: { + name: "Oceania", + }, + currency: "XPF", + languages: [ + { + name: "French", + }, + ], + }, + { + name: "Papua New Guinea", + code: "PG", + icon: "virtual-host", + icon_status: IconStatus.Up, + capital: "Port Moresby", + continent: { + name: "Oceania", + }, + currency: "PGK", + languages: [ + { + name: "English", + }, + ], + }, + { + name: "Philippines", + code: "PH", + icon: "virtual-host", + icon_status: IconStatus.Up, + capital: "Manila", + continent: { + name: "Asia", + }, + currency: "PHP", + languages: [ + { + name: "English", + }, + ], + }, + { + name: "Pakistan", + code: "PK", + icon: "virtual-host", + icon_status: IconStatus.Up, + capital: "Islamabad", + continent: { + name: "Asia", + }, + currency: "PKR", + languages: [ + { + name: "English", + }, + { + name: "Urdu", + }, + ], + }, + { + name: "Poland", + code: "PL", + icon: "virtual-host", + icon_status: IconStatus.Up, + capital: "Warsaw", + continent: { + name: "Europe", + }, + currency: "PLN", + languages: [ + { + name: "Polish", + }, + ], + }, + { + name: "Saint Pierre and Miquelon", + code: "PM", + icon: "virtual-host", + icon_status: IconStatus.Up, + capital: "Saint-Pierre", + continent: { + name: "North America", + }, + currency: "EUR", + languages: [ + { + name: "French", + }, + ], + }, + { + name: "Pitcairn Islands", + code: "PN", + icon: "virtual-host", + icon_status: IconStatus.Up, + capital: "Adamstown", + continent: { + name: "Oceania", + }, + currency: "NZD", + languages: [ + { + name: "English", + }, + ], + }, + { + name: "Puerto Rico", + code: "PR", + icon: "virtual-host", + icon_status: IconStatus.Up, + capital: "San Juan", + continent: { + name: "North America", + }, + currency: "USD", + languages: [ + { + name: "Spanish", + }, + { + name: "English", + }, + ], + }, + { + name: "Palestine", + code: "PS", + icon: "virtual-host", + icon_status: IconStatus.Up, + capital: "Ramallah", + continent: { + name: "Asia", + }, + currency: "ILS", + languages: [ + { + name: "Arabic", + }, + ], + }, + { + name: "Portugal", + code: "PT", + icon: "virtual-host", + icon_status: IconStatus.Up, + capital: "Lisbon", + continent: { + name: "Europe", + }, + currency: "EUR", + languages: [ + { + name: "Portuguese", + }, + ], + }, + { + name: "Palau", + code: "PW", + icon: "virtual-host", + icon_status: IconStatus.Up, + capital: "Ngerulmud", + continent: { + name: "Oceania", + }, + currency: "USD", + languages: [ + { + name: "English", + }, + ], + }, + { + name: "Paraguay", + code: "PY", + icon: "virtual-host", + icon_status: IconStatus.Up, + capital: "Asunción", + continent: { + name: "South America", + }, + currency: "PYG", + languages: [ + { + name: "Spanish", + }, + { + name: "Guarani", + }, + ], + }, + { + name: "Qatar", + code: "QA", + icon: "virtual-host", + icon_status: IconStatus.Up, + capital: "Doha", + continent: { + name: "Asia", + }, + currency: "QAR", + languages: [ + { + name: "Arabic", + }, + ], + }, + { + name: "Réunion", + code: "RE", + icon: "virtual-host", + icon_status: IconStatus.Up, + capital: "Saint-Denis", + continent: { + name: "Africa", + }, + currency: "EUR", + languages: [ + { + name: "French", + }, + ], + }, + { + name: "Romania", + code: "RO", + icon: "virtual-host", + icon_status: IconStatus.Up, + capital: "Bucharest", + continent: { + name: "Europe", + }, + currency: "RON", + languages: [ + { + name: "Romanian", + }, + ], + }, + { + name: "Serbia", + code: "RS", + icon: "virtual-host", + icon_status: IconStatus.Up, + capital: "Belgrade", + continent: { + name: "Europe", + }, + currency: "RSD", + languages: [ + { + name: "Serbian", + }, + ], + }, + { + name: "Russia", + code: "RU", + icon: "virtual-host", + icon_status: IconStatus.Up, + capital: "Moscow", + continent: { + name: "Europe", + }, + currency: "RUB", + languages: [ + { + name: "Russian", + }, + ], + }, + { + name: "Rwanda", + code: "RW", + icon: "virtual-host", + icon_status: IconStatus.Up, + capital: "Kigali", + continent: { + name: "Africa", + }, + currency: "RWF", + languages: [ + { + name: "Rwandi", + }, + { + name: "English", + }, + { + name: "French", + }, + ], + }, + { + name: "Saudi Arabia", + code: "SA", + icon: "virtual-host", + icon_status: IconStatus.Up, + capital: "Riyadh", + continent: { + name: "Asia", + }, + currency: "SAR", + languages: [ + { + name: "Arabic", + }, + ], + }, + { + name: "Solomon Islands", + code: "SB", + icon: "virtual-host", + icon_status: IconStatus.Up, + capital: "Honiara", + continent: { + name: "Oceania", + }, + currency: "SBD", + languages: [ + { + name: "English", + }, + ], + }, + { + name: "Seychelles", + code: "SC", + icon: "virtual-host", + icon_status: IconStatus.Up, + capital: "Victoria", + continent: { + name: "Africa", + }, + currency: "SCR", + languages: [ + { + name: "French", + }, + { + name: "English", + }, + ], + }, + { + name: "Sudan", + code: "SD", + icon: "virtual-host", + icon_status: IconStatus.Up, + capital: "Khartoum", + continent: { + name: "Africa", + }, + currency: "SDG", + languages: [ + { + name: "Arabic", + }, + { + name: "English", + }, + ], + }, + { + name: "Sweden", + code: "SE", + icon: "virtual-host", + icon_status: IconStatus.Up, + capital: "Stockholm", + continent: { + name: "Europe", + }, + currency: "SEK", + languages: [ + { + name: "Swedish", + }, + ], + }, + { + name: "Singapore", + code: "SG", + icon: "virtual-host", + icon_status: IconStatus.Up, + capital: "Singapore", + continent: { + name: "Asia", + }, + currency: "SGD", + languages: [ + { + name: "English", + }, + { + name: "Malay", + }, + { + name: "Tamil", + }, + { + name: "Chinese", + }, + ], + }, + { + name: "Saint Helena", + code: "SH", + icon: "virtual-host", + icon_status: IconStatus.Up, + capital: "Jamestown", + continent: { + name: "Africa", + }, + currency: "SHP", + languages: [ + { + name: "English", + }, + ], + }, + { + name: "Slovenia", + code: "SI", + icon: "virtual-host", + icon_status: IconStatus.Up, + capital: "Ljubljana", + continent: { + name: "Europe", + }, + currency: "EUR", + languages: [ + { + name: "Slovenian", + }, + ], + }, + { + name: "Svalbard and Jan Mayen", + code: "SJ", + icon: "virtual-host", + icon_status: IconStatus.Up, + capital: "Longyearbyen", + continent: { + name: "Europe", + }, + currency: "NOK", + languages: [ + { + name: "Norwegian", + }, + ], + }, + { + name: "Slovakia", + code: "SK", + icon: "virtual-host", + icon_status: IconStatus.Up, + capital: "Bratislava", + continent: { + name: "Europe", + }, + currency: "EUR", + languages: [ + { + name: "Slovak", + }, + ], + }, + { + name: "Sierra Leone", + code: "SL", + icon: "virtual-host", + icon_status: IconStatus.Up, + capital: "Freetown", + continent: { + name: "Africa", + }, + currency: "SLL", + languages: [ + { + name: "English", + }, + ], + }, + { + name: "San Marino", + code: "SM", + icon: "virtual-host", + icon_status: IconStatus.Up, + capital: "City of San Marino", + continent: { + name: "Europe", + }, + currency: "EUR", + languages: [ + { + name: "Italian", + }, + ], + }, + { + name: "Senegal", + code: "SN", + icon: "virtual-host", + icon_status: IconStatus.Up, + capital: "Dakar", + continent: { + name: "Africa", + }, + currency: "XOF", + languages: [ + { + name: "French", + }, + ], + }, + { + name: "Somalia", + code: "SO", + icon: "virtual-host", + icon_status: IconStatus.Up, + capital: "Mogadishu", + continent: { + name: "Africa", + }, + currency: "SOS", + languages: [ + { + name: "Somalia", + }, + { + name: "Arabic", + }, + ], + }, + { + name: "Suriname", + code: "SR", + icon: "virtual-host", + icon_status: IconStatus.Up, + capital: "Paramaribo", + continent: { + name: "South America", + }, + currency: "SRD", + languages: [ + { + name: "Dutch", + }, + ], + }, + { + name: "South Sudan", + code: "SS", + icon: "virtual-host", + icon_status: IconStatus.Up, + capital: "Juba", + continent: { + name: "Africa", + }, + currency: "SSP", + languages: [ + { + name: "English", + }, + ], + }, + { + name: "São Tomé and Príncipe", + code: "ST", + icon: "virtual-host", + icon_status: IconStatus.Up, + capital: "São Tomé", + continent: { + name: "Africa", + }, + currency: "STN", + languages: [ + { + name: "Portuguese", + }, + ], + }, + { + name: "El Salvador", + code: "SV", + icon: "virtual-host", + icon_status: IconStatus.Up, + capital: "San Salvador", + continent: { + name: "North America", + }, + currency: "SVC,USD", + languages: [ + { + name: "Spanish", + }, + ], + }, + { + name: "Sint Maarten", + code: "SX", + icon: "virtual-host", + icon_status: IconStatus.Up, + capital: "Philipsburg", + continent: { + name: "North America", + }, + currency: "ANG", + languages: [ + { + name: "Dutch", + }, + { + name: "English", + }, + ], + }, + { + name: "Syria", + code: "SY", + icon: "virtual-host", + icon_status: IconStatus.Up, + capital: "Damascus", + continent: { + name: "Asia", + }, + currency: "SYP", + languages: [ + { + name: "Arabic", + }, + ], + }, + { + name: "Swaziland", + code: "SZ", + icon: "virtual-host", + icon_status: IconStatus.Up, + capital: "Lobamba", + continent: { + name: "Africa", + }, + currency: "SZL", + languages: [ + { + name: "English", + }, + { + name: "Swati", + }, + ], + }, + { + name: "Turks and Caicos Islands", + code: "TC", + icon: "virtual-host", + icon_status: IconStatus.Up, + capital: "Cockburn Town", + continent: { + name: "North America", + }, + currency: "USD", + languages: [ + { + name: "English", + }, + ], + }, + { + name: "Chad", + code: "TD", + icon: "virtual-host", + icon_status: IconStatus.Up, + capital: "N'Djamena", + continent: { + name: "Africa", + }, + currency: "XAF", + languages: [ + { + name: "French", + }, + { + name: "Arabic", + }, + ], + }, + { + name: "French Southern Territories", + code: "TF", + icon: "virtual-host", + icon_status: IconStatus.Up, + capital: "Port-aux-Français", + continent: { + name: "Antarctica", + }, + currency: "EUR", + languages: [ + { + name: "French", + }, + ], + }, + { + name: "Togo", + code: "TG", + icon: "virtual-host", + icon_status: IconStatus.Up, + capital: "Lomé", + continent: { + name: "Africa", + }, + currency: "XOF", + languages: [ + { + name: "French", + }, + ], + }, + { + name: "Thailand", + code: "TH", + icon: "virtual-host", + icon_status: IconStatus.Up, + capital: "Bangkok", + continent: { + name: "Asia", + }, + currency: "THB", + languages: [ + { + name: "Thai", + }, + ], + }, + { + name: "Tajikistan", + code: "TJ", + icon: "virtual-host", + icon_status: IconStatus.Up, + capital: "Dushanbe", + continent: { + name: "Asia", + }, + currency: "TJS", + languages: [ + { + name: "Tajik", + }, + { + name: "Russian", + }, + ], + }, + { + name: "Tokelau", + code: "TK", + icon: "virtual-host", + icon_status: IconStatus.Up, + capital: "Fakaofo", + continent: { + name: "Oceania", + }, + currency: "NZD", + languages: [ + { + name: "English", + }, + ], + }, + { + name: "East Timor", + code: "TL", + icon: "virtual-host", + icon_status: IconStatus.Up, + capital: "Dili", + continent: { + name: "Oceania", + }, + currency: "USD", + languages: [ + { + name: "Portuguese", + }, + ], + }, + { + name: "Turkmenistan", + code: "TM", + icon: "virtual-host", + icon_status: IconStatus.Up, + capital: "Ashgabat", + continent: { + name: "Asia", + }, + currency: "TMT", + languages: [ + { + name: "Turkmen", + }, + { + name: "Russian", + }, + ], + }, + { + name: "Tunisia", + code: "TN", + icon: "virtual-host", + icon_status: IconStatus.Up, + capital: "Tunis", + continent: { + name: "Africa", + }, + currency: "TND", + languages: [ + { + name: "Arabic", + }, + ], + }, + { + name: "Tonga", + code: "TO", + icon: "virtual-host", + icon_status: IconStatus.Up, + capital: "Nuku'alofa", + continent: { + name: "Oceania", + }, + currency: "TOP", + languages: [ + { + name: "English", + }, + { + name: "Tonga", + }, + ], + }, + { + name: "Turkey", + code: "TR", + icon: "virtual-host", + icon_status: IconStatus.Up, + capital: "Ankara", + continent: { + name: "Asia", + }, + currency: "TRY", + languages: [ + { + name: "Turkish", + }, + ], + }, + { + name: "Trinidad and Tobago", + code: "TT", + icon: "virtual-host", + icon_status: IconStatus.Up, + capital: "Port of Spain", + continent: { + name: "North America", + }, + currency: "TTD", + languages: [ + { + name: "English", + }, + ], + }, + { + name: "Tuvalu", + code: "TV", + icon: "virtual-host", + icon_status: IconStatus.Up, + capital: "Funafuti", + continent: { + name: "Oceania", + }, + currency: "AUD", + languages: [ + { + name: "English", + }, + ], + }, + { + name: "Taiwan", + code: "TW", + icon: "virtual-host", + icon_status: IconStatus.Up, + capital: "Taipei", + continent: { + name: "Asia", + }, + currency: "TWD", + languages: [ + { + name: "Chinese", + }, + ], + }, + { + name: "Tanzania", + code: "TZ", + icon: "virtual-host", + icon_status: IconStatus.Up, + capital: "Dodoma", + continent: { + name: "Africa", + }, + currency: "TZS", + languages: [ + { + name: "Swahili", + }, + { + name: "English", + }, + ], + }, + { + name: "Ukraine", + code: "UA", + icon: "virtual-host", + icon_status: IconStatus.Up, + capital: "Kyiv", + continent: { + name: "Europe", + }, + currency: "UAH", + languages: [ + { + name: "Ukrainian", + }, + ], + }, + { + name: "Uganda", + code: "UG", + icon: "virtual-host", + icon_status: IconStatus.Up, + capital: "Kampala", + continent: { + name: "Africa", + }, + currency: "UGX", + languages: [ + { + name: "English", + }, + { + name: "Swahili", + }, + ], + }, + { + name: "U.S. Minor Outlying Islands", + code: "UM", + icon: "virtual-host", + icon_status: IconStatus.Up, + capital: null, + continent: { + name: "Oceania", + }, + currency: "USD", + languages: [ + { + name: "English", + }, + ], + }, + { + name: "United States", + code: "US", + icon: "virtual-host", + icon_status: IconStatus.Up, + capital: "Washington D.C.", + continent: { + name: "North America", + }, + currency: "USD,USN,USS", + languages: [ + { + name: "English", + }, + ], + }, + { + name: "Uruguay", + code: "UY", + icon: "virtual-host", + icon_status: IconStatus.Up, + capital: "Montevideo", + continent: { + name: "South America", + }, + currency: "UYI,UYU", + languages: [ + { + name: "Spanish", + }, + ], + }, + { + name: "Uzbekistan", + code: "UZ", + icon: "virtual-host", + icon_status: IconStatus.Up, + capital: "Tashkent", + continent: { + name: "Asia", + }, + currency: "UZS", + languages: [ + { + name: "Uzbek", + }, + { + name: "Russian", + }, + ], + }, + { + name: "Vatican City", + code: "VA", + icon: "virtual-host", + icon_status: IconStatus.Up, + capital: "Vatican City", + continent: { + name: "Europe", + }, + currency: "EUR", + languages: [ + { + name: "Italian", + }, + { + name: "Latin", + }, + ], + }, + { + name: "Saint Vincent and the Grenadines", + code: "VC", + icon: "virtual-host", + icon_status: IconStatus.Up, + capital: "Kingstown", + continent: { + name: "North America", + }, + currency: "XCD", + languages: [ + { + name: "English", + }, + ], + }, + { + name: "Venezuela", + code: "VE", + icon: "virtual-host", + icon_status: IconStatus.Up, + capital: "Caracas", + continent: { + name: "South America", + }, + currency: "VES", + languages: [ + { + name: "Spanish", + }, + ], + }, + { + name: "British Virgin Islands", + code: "VG", + icon: "virtual-host", + icon_status: IconStatus.Up, + capital: "Road Town", + continent: { + name: "North America", + }, + currency: "USD", + languages: [ + { + name: "English", + }, + ], + }, + { + name: "U.S. Virgin Islands", + code: "VI", + icon: "virtual-host", + icon_status: IconStatus.Up, + capital: "Charlotte Amalie", + continent: { + name: "North America", + }, + currency: "USD", + languages: [ + { + name: "English", + }, + ], + }, + { + name: "Vietnam", + code: "VN", + icon: "virtual-host", + icon_status: IconStatus.Up, + capital: "Hanoi", + continent: { + name: "Asia", + }, + currency: "VND", + languages: [ + { + name: "Vietnamese", + }, + ], + }, + { + name: "Vanuatu", + code: "VU", + icon: "virtual-host", + icon_status: IconStatus.Up, + capital: "Port Vila", + continent: { + name: "Oceania", + }, + currency: "VUV", + languages: [ + { + name: "Bislama", + }, + { + name: "English", + }, + { + name: "French", + }, + ], + }, + { + name: "Wallis and Futuna", + code: "WF", + icon: "virtual-host", + icon_status: IconStatus.Up, + capital: "Mata-Utu", + continent: { + name: "Oceania", + }, + currency: "XPF", + languages: [ + { + name: "French", + }, + ], + }, + { + name: "Samoa", + code: "WS", + icon: "virtual-host", + icon_status: IconStatus.Up, + capital: "Apia", + continent: { + name: "Oceania", + }, + currency: "WST", + languages: [ + { + name: "Samoan", + }, + { + name: "English", + }, + ], + }, + { + name: "Kosovo", + code: "XK", + icon: "virtual-host", + icon_status: IconStatus.Up, + capital: "Pristina", + continent: { + name: "Europe", + }, + currency: "EUR", + languages: [ + { + name: "Albanian", + }, + { + name: "Serbian", + }, + ], + }, + { + name: "Yemen", + code: "YE", + icon: "virtual-host", + icon_status: IconStatus.Up, + capital: "Sana'a", + continent: { + name: "Asia", + }, + currency: "YER", + languages: [ + { + name: "Arabic", + }, + ], + }, + { + name: "Mayotte", + code: "YT", + icon: "virtual-host", + icon_status: IconStatus.Up, + capital: "Mamoudzou", + continent: { + name: "Africa", + }, + currency: "EUR", + languages: [ + { + name: "French", + }, + ], + }, + { + name: "South Africa", + code: "ZA", + icon: "virtual-host", + icon_status: IconStatus.Up, + capital: "Pretoria", + continent: { + name: "Africa", + }, + currency: "ZAR", + languages: [ + { + name: "Afrikaans", + }, + { + name: "English", + }, + { + name: "South Ndebele", + }, + { + name: "Southern Sotho", + }, + { + name: "Swati", + }, + { + name: "Tswana", + }, + { + name: "Tsonga", + }, + { + name: "Venda", + }, + { + name: "Xhosa", + }, + { + name: "Zulu", + }, + ], + }, + { + name: "Zambia", + code: "ZM", + icon: "virtual-host", + icon_status: IconStatus.Up, + capital: "Lusaka", + continent: { + name: "Africa", + }, + currency: "ZMW", + languages: [ + { + name: "English", + }, + ], + }, + { + name: "Zimbabwe", + code: "ZW", + icon: "virtual-host", + icon_status: IconStatus.Up, + capital: "Harare", + continent: { + name: "Africa", + }, + currency: "USD,ZAR,BWP,GBP,AUD,CNY,INR,JPY", + languages: [ + { + name: "English", + }, + { + name: "Shona", + }, + { + name: "North Ndebele", + }, + ], + }, + ], + }, +}; +\\\`, + "widget-types/drilldown/drilldown-widget/drilldown-widget-example.component.ts": \\\`// © 2022 SolarWinds Worldwide, LLC. All rights reserved. +// +// Permission is hereby granted, free of charge, to any person obtaining a copy +// of this software and associated documentation files (the "Software"), to +// deal in the Software without restriction, including without limitation the +// rights to use, copy, modify, merge, publish, distribute, sublicense, and/or +// sell copies of the Software, and to permit persons to whom the Software is +// furnished to do so, subject to the following conditions: +// +// The above copyright notice and this permission notice shall be included in +// all copies or substantial portions of the Software. +// +// THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR +// IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, +// FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE +// AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER +// LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, +// OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN +// THE SOFTWARE. + +import { HttpClient } from "@angular/common/http"; +import { + ChangeDetectorRef, + Component, + Injectable, + OnDestroy, + OnInit, +} from "@angular/core"; +import { GridsterConfig, GridsterItem } from "angular-gridster2"; +import { Apollo, gql } from "apollo-angular"; +import groupBy from "lodash/groupBy"; +import { BehaviorSubject, Observable, of } from "rxjs"; +import { catchError, delay, filter, map } from "rxjs/operators"; + +import { + DataSourceFeatures, + IconStatus, + IDataField, + IDataSource, + IDataSourceFeatures, + IDataSourceFeaturesConfiguration, + INovaFilters, + LoggerService, + ServerSideDataSource, + IFilters, +} from "@nova-ui/bits"; +import { + DATA_SOURCE, + DEFAULT_PIZZAGNA_ROOT, + IDashboard, + IDrilldownComponentsConfiguration, + IListWidgetConfiguration, + IProviderConfiguration, + IWidget, + IWidgets, + ListGroupItemComponent, + ListLeafItemComponent, + NOVA_DRILLDOWN_DATASOURCE_ADAPTER, + PizzagnaLayer, + ProviderRegistryService, + WellKnownPathKey, + WellKnownProviders, + WidgetTypesService, +} from "@nova-ui/dashboards"; + +import { DrilldownDataSource } from "./mock-data-source"; + +/** + * A simple KPI data source to retrieve the average rating of Harry Potter and the Sorcerer's Stone (book) via googleapis + */ +@Injectable() +export class DrilldownDataSourceRealApi + extends ServerSideDataSource + implements OnDestroy, IDataSource +{ + // This is the ID we'll use to identify the provider + public static providerId = "DrilldownDataSourceRealApi"; + + // Use this subject to communicate the data source's busy state + public busy = new BehaviorSubject(false); + public dataFields: Partial[] = [ + { id: "regionName", label: "Region name" }, + { id: "subregionName", label: "Subregion name" }, + ]; + + public features: IDataSourceFeaturesConfiguration; + private supportedFeatures: IDataSourceFeatures = { + search: { enabled: true }, + }; + + private drillState: string[] = []; + private groupBy: string[]; + + constructor( + private logger: LoggerService, + private http: HttpClient, + private apollo: Apollo + ) { + super(); + this.features = new DataSourceFeatures(this.supportedFeatures); + // TODO: remove Partial in vNext after marking dataType field as optional - NUI-5838 + ( + this.dataFieldsConfig.dataFields$ as BehaviorSubject< + Partial[] + > + ).next(this.dataFields); + } + + private groupedDataHistory: Array> = []; + + // In this example, getFilteredData is invoked every 10 minutes (Take a look at the refresher + // provider definition in the widget configuration below to see how the interval is set) + public async getFilteredData(data: IFilters): Promise { + return of(data) + .pipe( + filter(() => !!this.drillState), + map((countries) => { + const lastHistory = () => getLast(this.groupedDataHistory); + + if (!this.drillState.length && !this.groupBy.length) { + return countries; + } + + // adding "ROOT" as a root level for drilling + const fullDrillState = ["ROOT", ...this.drillState]; + const activeDrillLvl = fullDrillState.length; + const historyLvl = this.groupedDataHistory.length; + + // checking how many lvls we have to group for drilling, in case some are missed + const drillLvlDiff = activeDrillLvl - historyLvl; + + if (!drillLvlDiff) { + return lastHistory() || countries; + } + + const drillToGroup = fullDrillState.slice( + fullDrillState.length - drillLvlDiff + ); + + for (const drill of drillToGroup) { + const drillIdx = fullDrillState.findIndex( + (v) => v === drill + ); + const group = this.groupBy[drillIdx]; + + if (group) { + const dataToGroup = lastHistory() + ? lastHistory()[drill] + : countries; + const lastGroupedValue = groupBy( + dataToGroup, + group + ); + + this.groupedDataHistory.push(lastGroupedValue); + } + } + + // take last if we have all data grouped + if (this.groupBy.length === this.drillState.length) { + return lastHistory()[getLast(this.drillState)]; + } + + // get groping and transform to raw data format + return this.getGroupsWidgetData(lastHistory()); + }) + ) + .toPromise(); + } + + public ngOnDestroy(): void { + this.outputsSubject.complete(); + } + + // This method is expected to return all data needed for repeat/paginator/filterGroups in order to work. + // In case of custom filtering participants feel free to extend INovaFilteringOutputs. + protected getBackendData(filters: INovaFilters): Observable { + const mainRequest = this.apollo.watchQuery<{ countries: any }>({ + query: this.generateQuery(filters), + }); + + return mainRequest.valueChanges.pipe( + // mock delay + delay(300), + // data mapping, !DS specific! + map((res) => res.data.countries), + // adds mock icons to be displayed on leaf nodes !DS specific! + map((res: any[]) => + res.map((v) => ({ + ...v, + icon: "virtual-host", + icon_status: IconStatus.Up, + subregionName: + v.subregion?.name || "No Subregion Specified", + regionName: + v.subregion?.region?.name || "No Region Specified", + })) + ), + catchError((e) => { + this.logger.error(e); + return of({} as any); + }) + ); + } + + private generateQuery(filters: INovaFilters) { + const { search } = filters; + const searchValue = search?.value ? \\\\\\\`^[\\\\\\\${search.value}]*\\\\\\\` : ""; + + const queryString = \\\\\\\` + query { + countries(filter: {name: {regex: "\\\\\\\${searchValue}"} }) { + name + native + capital + languages { + name + } + currencies + subdivisions { + name + } + } + } + \\\\\\\`; + + return gql\\\\\\\` + \\\\\\\${queryString} + \\\\\\\`; + } + + // Overrides default ServerSideDataSource.beforeApplyFilters implementation + // to save some filters that are used internally + // -- !DS specific + protected beforeApplyFilters(filters: INovaFilters): void { + this.busy.next(true); + + this.drillState = filters.drillstate?.value; + this.groupBy = filters.group?.value; + + if (this.isHome()) { + this.groupedDataHistory.length = 0; + } + + if (this.isBack()) { + this.groupedDataHistory.length = this.groupedDataHistory.length - 1; + } + + if (this.getFilters()["search"] && this.filterChanged("search")) { + this.groupedDataHistory.length = 0; + } + } + + private getGroupsWidgetData(groupByObj: Record) { + return Object.keys(groupByObj).map((property) => ({ + id: property, + label: property, + // statuses that will be displayed on group item + statuses: [ + { key: "virtual-host", value: groupByObj[property].length }, + { + key: "acknowledge", + value: this.getPopulation(groupByObj[property]), + }, + ], + })); + } + + private isHome(): boolean { + return this.drillState?.length === 0; + } + + private isBack(): boolean { + return ( + this.groupedDataHistory?.length > this.drillState?.length && + !this.isHome() + ); + } + + /** + * Gets population for the country(ies) + */ + private getPopulation(countries: any[]) { + const totalPopulation = countries.reduce( + (acc, next) => (acc += next.population), + 0 + ); + return \\\\\\\`\\\\\\\${totalPopulation * Math.pow(10, -3)} k\\\\\\\`; + } +} + +@Component({ + selector: "drilldown-widget-example", + templateUrl: "./drilldown-widget-example.component.html", + styleUrls: ["./drilldown-widget-example.component.less"], + standalone: false, +}) +export class DrilldownWidgetExampleComponent implements OnInit { + // This variable will hold all the data needed to define the layout and behavior of the widgets. + // Pass this to the dashboard component's dashboard input in the template. + public dashboard: IDashboard | undefined; + + // Angular gridster requires a configuration object even if it's empty. + // Pass this to the dashboard component's gridsterConfig input in the template. + public gridsterConfig: GridsterConfig = {}; + + // Boolean passed as an input to the dashboard. When true, widgets can be moved, resized, removed, or edited + public editMode: boolean = false; + + constructor( + // WidgetTypesService provides the widget's necessary structure information + private widgetTypesService: WidgetTypesService, + private providerRegistry: ProviderRegistryService, + private changeDetectorRef: ChangeDetectorRef + ) {} + + public ngOnInit(): void { + // Registering the data source for injection into the KPI tile. + // Note: Each tile of a KPI widget is assigned its own instance of the data source + this.providerRegistry.setProviders({ + [DrilldownDataSource.providerId]: { + provide: DATA_SOURCE, + useClass: DrilldownDataSource, + // Any dependencies that need to be injected into the provider must be listed here + deps: [HttpClient], + }, + [DrilldownDataSourceRealApi.providerId]: { + provide: DATA_SOURCE, + useClass: DrilldownDataSourceRealApi, + // Any dependencies that need to be injected into the provider must be listed here + deps: [LoggerService, HttpClient, Apollo], + }, + }); + + this.initializeDashboard(); + const widgetTemplate = this.widgetTypesService.getWidgetType( + "drilldown", + 1 + ); + this.widgetTypesService.setNode( + widgetTemplate, + "configurator", + WellKnownPathKey.DataSourceProviders, + [ + DrilldownDataSourceRealApi.providerId, + DrilldownDataSource.providerId, + ] + ); + } + + public initializeDashboard(): void { + // We're using a static configuration object for this example, but this is where + // the widget's configuration could potentially be populated from a database + const drilldownWidget = widgetConfig; + const widgets: IWidgets = { + // Complete the widget with information coming from its type definition + [drilldownWidget.id]: + this.widgetTypesService.mergeWithWidgetType(drilldownWidget), + }; + + // Setting the widget dimensions and position (this is for gridster) + const positions: Record = { + [drilldownWidget.id]: { + cols: 10, + rows: 10, + y: 0, + x: 0, + }, + }; + + // Finally, assigning the variables we created above to the dashboard + this.dashboard = { positions, widgets }; + } + + public reInitializeDashboard(): void { + // destroys the components and their providers so the dashboard can re init data + this.dashboard = undefined; + this.changeDetectorRef.detectChanges(); + + const adapterProperties = + widgetConfig.pizzagna[PizzagnaLayer.Configuration].listWidget + .providers?.adapter?.properties; + + if (adapterProperties) { + adapterProperties.drillstate = []; + } + + this.initializeDashboard(); + } +} + +const widgetConfig: IWidget = { + id: "drilldown", + type: "drilldown", + pizzagna: { + [PizzagnaLayer.Configuration]: { + [DEFAULT_PIZZAGNA_ROOT]: { + providers: { + [WellKnownProviders.DataSource]: { + // Setting the data source providerId for the tile with id "kpi1" + providerId: DrilldownDataSourceRealApi.providerId, + properties: {}, + } as IProviderConfiguration, + }, + }, + header: { + properties: { + title: "Drilldown Widget", + subtitle: "Search is case sensitive!", + }, + }, + listWidget: { + providers: { + [WellKnownProviders.Adapter]: { + providerId: NOVA_DRILLDOWN_DATASOURCE_ADAPTER, + properties: { + // widget + navigationBarId: "navigationBar", + componentId: "listWidget", + dataPath: "data", + + // adapter props + drillstate: [], + groupBy: ["regionName", "subregionName"], + groups: ["regionName", "subregionName"], + + // components + componentsConfig: { + group: { + componentType: + ListGroupItemComponent.lateLoadKey, + properties: { + dataFieldIds: { + id: "id", + label: "label", + statuses: "statuses", + }, + }, + itemProperties: { + canNavigate: true, + }, + }, + leaf: { + componentType: + ListLeafItemComponent.lateLoadKey, + properties: { + dataFieldIds: { + icon: "icon", + status: "icon_status", + detailedUrl: "capital", + label: "name", + }, + }, + itemProperties: { + canNavigate: false, + }, + }, + } as IDrilldownComponentsConfiguration, + }, + }, + }, + properties: { + configuration: { + // FORMAT: + // componentType: ListLeafItemComponent.lateLoadKey, + // properties: { + // dataFieldIds: { + // icon: "", + // status: "code", + // detailedUrl: "capital", + // label: "name", + // }, + // }, + // + } as IListWidgetConfiguration, + }, + }, + }, + }, +}; + +const getLast = (arr: any[]) => arr[arr.length - 1]; +\\\`, + "widget-types/drilldown/drilldown-widget/mock-data-source.ts": \\\`// © 2022 SolarWinds Worldwide, LLC. All rights reserved. +// +// Permission is hereby granted, free of charge, to any person obtaining a copy +// of this software and associated documentation files (the "Software"), to +// deal in the Software without restriction, including without limitation the +// rights to use, copy, modify, merge, publish, distribute, sublicense, and/or +// sell copies of the Software, and to permit persons to whom the Software is +// furnished to do so, subject to the following conditions: +// +// The above copyright notice and this permission notice shall be included in +// all copies or substantial portions of the Software. +// +// THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR +// IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, +// FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE +// AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER +// LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, +// OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN +// THE SOFTWARE. + +import { Injectable, OnDestroy } from "@angular/core"; +import groupBy from "lodash/groupBy"; +import { BehaviorSubject, Observable, of, Subject } from "rxjs"; +import { + catchError, + delay, + finalize, + map, + // eslint-disable-next-line import/no-deprecated + switchMap, + tap, +} from "rxjs/operators"; + +import { + DataSourceService, + IDataField, + IDataSource, + IFilters, + INovaFilters, +} from "@nova-ui/bits"; + +import { GRAPH_DATA_MOCK } from "./data-mock"; + +/** + * A simple KPI data source to retrieve the average rating of Harry Potter and the Sorcerer's Stone (book) via googleapis + */ +@Injectable() +export class DrilldownDataSource + extends DataSourceService + implements IDataSource, OnDestroy +{ + // This is the ID we'll use to identify the provider + public static providerId = "DrilldownDataSource"; + + // Use this subject to communicate the data source's busy state + public busy = new BehaviorSubject(false); + public dataFields: Partial[] = [ + { id: "continent.name", label: "Continent name" }, + { id: "currency", label: "Currency" }, + ]; + + private drillState: string[] = []; + private groupBy: string[]; + private cache: any; + private applyFilters$ = new Subject(); + + constructor() { + super(); + + // TODO: remove Partial in vNext after marking dataType field as optional - NUI-5838 + ( + this.dataFieldsConfig.dataFields$ as BehaviorSubject< + Partial[] + > + ).next(this.dataFields); + + this.applyFilters$ + // eslint-disable-next-line import/no-deprecated + .pipe(switchMap((filters) => this.getData(filters))) + .subscribe(async (res) => { + this.outputsSubject.next(await this.getFilteredData(res)); + }); + } + + private groupedDataHistory: any[] = []; + + // In this example, getFilteredData is invoked every 10 minutes (Take a look at the refresher + // provider definition in the widget configuration below to see how the interval is set) + public async getFilteredData(data: any): Promise { + return of(data) + .pipe( + map((countries) => { + const widgetInput = this.getOutput(countries); + + if (this.isDrillDown()) { + const activeDrillLvl = this.drillState.length; + const group = this.groupBy[activeDrillLvl]; + const [lastGroupedValue, groupedData] = + this.getTransformedDataForGroup(widgetInput, group); + + this.groupedDataHistory.push(lastGroupedValue); + + return groupedData; + } + + return widgetInput; + }) + ) + .toPromise(); + } + + public ngOnDestroy(): void { + this.outputsSubject.complete(); + } + + // redefine parent method + public async applyFilters(): Promise { + this.applyFilters$.next(this.getFilters()); + } + + private getData(filters: INovaFilters): Observable { + this.drillState = filters.drillstate?.value; + this.groupBy = filters.group?.value; + + this.busy.next(true); + + return of(this.cache || GRAPH_DATA_MOCK).pipe( + delay(1000), + tap((data) => (this.cache = data)), + map((data) => data.data.countries), + catchError((e) => of([])), + finalize(() => this.busy.next(false)) + ); + } + + private getTransformedDataForGroup(data: any, groupName: string) { + const groupedDict = groupBy(data, groupName); + const dataArr = Object.keys(groupedDict).map((property) => ({ + id: property, + label: property, + // TODO: apply groups mapping here + statuses: [ + { key: "state_ok", value: groupedDict[property].length }, + { + key: "status_unreachable", + value: generateNumberUpTo(100000), + }, + { key: "status_warning", value: generateNumberUpTo(10000) }, + { key: "status_unknown", value: generateNumberUpTo(1000) }, + ], + })); + + return [groupedDict, dataArr]; + } + + private isHome(): boolean { + return !this.drillState || this.drillState.length === 0; + } + + private isBack(): boolean { + return ( + this.groupedDataHistory.length > this.drillState?.length && + !this.isHome() + ); + } + + private isDrillDown(): boolean { + return this.drillState?.length !== this.groupBy?.length; + } + + private getOutput(data: any) { + if (this.isHome()) { + this.groupedDataHistory.length = 0; + } + + if (this.isBack()) { + this.groupedDataHistory.length = this.groupedDataHistory.length - 1; + } + + const lastHistoryValue = getLast(this.groupedDataHistory); + + if (!lastHistoryValue) { + return data; + } + + return lastHistoryValue[getLast(this.drillState)] || lastHistoryValue; + } +} + +const getLast = (arr: any[]) => arr[arr.length - 1]; +const generateNumberUpTo = (upperLimit: number): number => + Math.floor(Math.random() * upperLimit + 1); +\\\`, + "widget-types/drilldown/drilldown-widget-docs.component.ts": \\\`// © 2022 SolarWinds Worldwide, LLC. All rights reserved. +// +// Permission is hereby granted, free of charge, to any person obtaining a copy +// of this software and associated documentation files (the "Software"), to +// deal in the Software without restriction, including without limitation the +// rights to use, copy, modify, merge, publish, distribute, sublicense, and/or +// sell copies of the Software, and to permit persons to whom the Software is +// furnished to do so, subject to the following conditions: +// +// The above copyright notice and this permission notice shall be included in +// all copies or substantial portions of the Software. +// +// THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR +// IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, +// FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE +// AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER +// LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, +// OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN +// THE SOFTWARE. + +import { Component, OnInit } from "@angular/core"; + +import { mapContentFile } from "../../../../demo-files-factory"; + +@Component({ + selector: "nui-drilldown-docs", + templateUrl: "./drilldown-widget-docs.component.html", + standalone: false, +}) +export class DrilldownDocsComponent implements OnInit { + public widgetFileText = ""; + public configuratorFileText = ""; + + public predefinedGroping = \\\\\\\` +listWidget: { + providers: { + [WellKnownProviders.Adapter]: { + providerId: NOVA_DRILLDOWN_DATASOURCE_ADAPTER, + properties: { + ... + // adapter props + drillstate: [], + groupBy: ["regionName", "subregionName"], + groups: ["regionName", "subregionName"], + ... + }, + }, + }, +}, +\\\\\\\`; + public featuredDeclaredText = \\\\\\\` + private supportedFeatures: IDataSourceFeatures = { + search: { enabled: true }, + };\\\\\\\`; + public featuresUsedText = \\\\\\\` + this.features = new DataSourceFeatures(this.supportedFeatures); + \\\\\\\`; + + public async ngOnInit(): Promise { + this.widgetFileText = await import( + "./../../../../../../src/lib/widget-types/drilldown/drilldown-widget" + ).then(mapContentFile); + this.configuratorFileText = await import( + "./../../../../../../src/lib/widget-types/drilldown/drilldown-configurator" + ).then(mapContentFile); + } +} +\\\`, + "widget-types/drilldown/drilldown-widget-docs.module.ts": \\\`// © 2022 SolarWinds Worldwide, LLC. All rights reserved. +// +// Permission is hereby granted, free of charge, to any person obtaining a copy +// of this software and associated documentation files (the "Software"), to +// deal in the Software without restriction, including without limitation the +// rights to use, copy, modify, merge, publish, distribute, sublicense, and/or +// sell copies of the Software, and to permit persons to whom the Software is +// furnished to do so, subject to the following conditions: +// +// The above copyright notice and this permission notice shall be included in +// all copies or substantial portions of the Software. +// +// THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR +// IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, +// FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE +// AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER +// LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, +// OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN +// THE SOFTWARE. + +import { NgModule } from "@angular/core"; +import { RouterModule, Routes } from "@angular/router"; + +// eslint-disable-next-line max-len +import { + NuiButtonModule, + NuiDocsModule, + NuiMessageModule, + NuiSwitchModule, + DEMO_PATH_TOKEN, +} from "@nova-ui/bits"; +import { NuiDashboardsModule } from "@nova-ui/dashboards"; + +import { DrilldownMultiRequestWidgetExampleComponent } from "./drilldown-multi-request-widget/drilldown-multi-request-widget-example.component"; +import { DrilldownWidgetExampleComponent } from "./drilldown-widget/drilldown-widget-example.component"; +import { DrilldownDocsComponent } from "./drilldown-widget-docs.component"; +import { getDemoFiles } from "../../../../demo-files-factory"; + +const routes: Routes = [ + { + path: "", + component: DrilldownDocsComponent, + data: { + srlc: { + hideIndicator: true, + }, + }, + }, + { + path: "example", + component: DrilldownWidgetExampleComponent, + data: { + srlc: { + hideIndicator: true, + }, + }, + }, + { + path: "multiple-requests", + component: DrilldownMultiRequestWidgetExampleComponent, + data: { + srlc: { + hideIndicator: true, + }, + }, + }, +]; + +@NgModule({ + imports: [ + RouterModule.forChild(routes), + NuiButtonModule, + NuiDocsModule, + NuiMessageModule, + NuiDashboardsModule, + NuiSwitchModule, + ], + declarations: [ + DrilldownDocsComponent, + DrilldownWidgetExampleComponent, + DrilldownMultiRequestWidgetExampleComponent, + ], + providers: [ + { + provide: DEMO_PATH_TOKEN, + useValue: getDemoFiles("drilldown"), + }, + ], +}) +export default class DrilldownDocsModule {} +\\\`, + "widget-types/embedded-content/embedded-content-docs.component.ts": \\\`// © 2022 SolarWinds Worldwide, LLC. All rights reserved. +// +// Permission is hereby granted, free of charge, to any person obtaining a copy +// of this software and associated documentation files (the "Software"), to +// deal in the Software without restriction, including without limitation the +// rights to use, copy, modify, merge, publish, distribute, sublicense, and/or +// sell copies of the Software, and to permit persons to whom the Software is +// furnished to do so, subject to the following conditions: +// +// The above copyright notice and this permission notice shall be included in +// all copies or substantial portions of the Software. +// +// THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR +// IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, +// FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE +// AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER +// LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, +// OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN +// THE SOFTWARE. + +import { Component, OnInit } from "@angular/core"; + +import { mapContentFile } from "../../../../demo-files-factory"; + +@Component({ + selector: "nui-embedded-content-docs", + templateUrl: "./embedded-content-docs.component.html", + standalone: false, +}) +export class EmbeddedContentDocsComponent implements OnInit { + public embeddedContentWidgetFileText = ""; + public embeddedContentConfiguratorFileText = ""; + + public async ngOnInit(): Promise { + this.embeddedContentWidgetFileText = await import( + "./../../../../../../src/lib/widget-types/embedded-content/embedded-content-widget" + ).then(mapContentFile); + this.embeddedContentWidgetFileText = await import( + "./../../../../../../src/lib/widget-types/embedded-content/embedded-content-configurator" + ).then(mapContentFile); + } +} +\\\`, + "widget-types/embedded-content/embedded-content-docs.module.ts": \\\`// © 2022 SolarWinds Worldwide, LLC. All rights reserved. +// +// Permission is hereby granted, free of charge, to any person obtaining a copy +// of this software and associated documentation files (the "Software"), to +// deal in the Software without restriction, including without limitation the +// rights to use, copy, modify, merge, publish, distribute, sublicense, and/or +// sell copies of the Software, and to permit persons to whom the Software is +// furnished to do so, subject to the following conditions: +// +// The above copyright notice and this permission notice shall be included in +// all copies or substantial portions of the Software. +// +// THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR +// IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, +// FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE +// AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER +// LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, +// OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN +// THE SOFTWARE. + +import { NgModule } from "@angular/core"; +import { RouterModule, Routes } from "@angular/router"; + +// eslint-disable-next-line max-len +import { + NuiButtonModule, + NuiDocsModule, + NuiMessageModule, + NuiSwitchModule, + DEMO_PATH_TOKEN, +} from "@nova-ui/bits"; +import { NuiDashboardsModule } from "@nova-ui/dashboards"; + +import { EmbeddedContentDocsComponent } from "./embedded-content-docs.component"; +import { EmbeddedContentWidgetExampleComponent } from "./embedded-content-widget-example/embedded-content-widget-example.component"; +import { getDemoFiles } from "../../../../demo-files-factory"; + +const routes: Routes = [ + { + path: "", + component: EmbeddedContentDocsComponent, + data: { + srlc: { + hideIndicator: true, + }, + }, + }, + { + path: "example", + component: EmbeddedContentWidgetExampleComponent, + data: { + srlc: { + hideIndicator: true, + }, + }, + }, +]; + +@NgModule({ + imports: [ + RouterModule.forChild(routes), + NuiButtonModule, + NuiDocsModule, + NuiMessageModule, + NuiDashboardsModule, + NuiSwitchModule, + ], + declarations: [ + EmbeddedContentDocsComponent, + EmbeddedContentWidgetExampleComponent, + ], + providers: [ + { + provide: DEMO_PATH_TOKEN, + useValue: getDemoFiles("embedded-content"), + }, + ], +}) +export default class EmbeddedContentDocsModule {} +\\\`, + "widget-types/embedded-content/embedded-content-widget-example/embedded-content-widget-example.component.ts": \\\`// © 2022 SolarWinds Worldwide, LLC. All rights reserved. +// +// Permission is hereby granted, free of charge, to any person obtaining a copy +// of this software and associated documentation files (the "Software"), to +// deal in the Software without restriction, including without limitation the +// rights to use, copy, modify, merge, publish, distribute, sublicense, and/or +// sell copies of the Software, and to permit persons to whom the Software is +// furnished to do so, subject to the following conditions: +// +// The above copyright notice and this permission notice shall be included in +// all copies or substantial portions of the Software. +// +// THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR +// IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, +// FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE +// AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER +// LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, +// OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN +// THE SOFTWARE. + +import { ChangeDetectorRef, Component, OnInit } from "@angular/core"; +import { GridsterConfig, GridsterItem } from "angular-gridster2"; + +import { + ComponentRegistryService, + EmbeddedContentComponent, + EmbeddedContentConfigurationComponent, + EmbeddedContentMode, + IDashboard, + IWidget, + IWidgets, + PizzagnaLayer, + WidgetTypesService, +} from "@nova-ui/dashboards"; + +@Component({ + selector: "embedded-content-widget-example", + templateUrl: "./embedded-content-widget-example.component.html", + styleUrls: ["./embedded-content-widget-example.component.less"], + standalone: false, +}) +export class EmbeddedContentWidgetExampleComponent implements OnInit { + // This variable will hold all the data needed to define the layout and behavior of the widgets. + // Pass this to the dashboard component's dashboard input in the template. + public dashboard: IDashboard | undefined; + + // Angular gridster requires a configuration object even if it's empty. + // Pass this to the dashboard component's gridsterConfig input in the template. + public gridsterConfig: GridsterConfig = {}; + + // Boolean passed as an input to the dashboard. When true, widgets can be moved, resized, removed, or edited + public editMode: boolean = false; + + constructor( + // WidgetTypesService provides the widget's necessary structure information + private widgetTypesService: WidgetTypesService, + + private componentRegistry: ComponentRegistryService, + private changeDetectorRef: ChangeDetectorRef + ) {} + + public ngOnInit(): void { + this.prepareNovaDashboards(); + this.initializeDashboard(); + } + + /** Used for restoring widgets state */ + public reInitializeDashboard(): void { + // destroys the components and their providers so the dashboard can re init data + this.dashboard = undefined; + this.changeDetectorRef.detectChanges(); + + this.initializeDashboard(); + } + + public initializeDashboard(): void { + // We're using a static configuration object for this example, but this is where + // the widget's configuration could potentially be populated from a database + const embeddedContentWidget = widgetConfig; + const widgets: IWidgets = { + // Complete the widget with information coming from its type definition + [embeddedContentWidget.id]: + this.widgetTypesService.mergeWithWidgetType( + embeddedContentWidget + ), + }; + + // Setting the widget dimensions and position (this is for gridster) + const positions: Record = { + [embeddedContentWidget.id]: { + cols: 10, + rows: 10, + y: 0, + x: 0, + }, + }; + + // Finally, assigning the variables we created above to the dashboard + this.dashboard = { positions, widgets }; + } + + private prepareNovaDashboards() { + this.componentRegistry.registerByLateLoadKey(EmbeddedContentComponent); + this.componentRegistry.registerByLateLoadKey( + EmbeddedContentConfigurationComponent + ); + } +} + +const widgetConfig: IWidget = { + id: "embeddedContentWidgetId", + type: "embedded-content", + pizzagna: { + [PizzagnaLayer.Configuration]: { + header: { + properties: { + title: "Embedded Content Widget", + subtitle: "", + }, + }, + mainContent: { + properties: { + sanitized: true, + mode: EmbeddedContentMode.URL, + customEmbeddedContent: "https://www.ventusky.com/", + }, + }, + }, + }, +}; +\\\`, + "widget-types/kpi/kpi-docs.component.ts": \\\`// © 2022 SolarWinds Worldwide, LLC. All rights reserved. +// +// Permission is hereby granted, free of charge, to any person obtaining a copy +// of this software and associated documentation files (the "Software"), to +// deal in the Software without restriction, including without limitation the +// rights to use, copy, modify, merge, publish, distribute, sublicense, and/or +// sell copies of the Software, and to permit persons to whom the Software is +// furnished to do so, subject to the following conditions: +// +// The above copyright notice and this permission notice shall be included in +// all copies or substantial portions of the Software. +// +// THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR +// IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, +// FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE +// AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER +// LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, +// OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN +// THE SOFTWARE. + +import { Component, OnInit } from "@angular/core"; + +import { mapContentFile } from "../../../../demo-files-factory"; + +@Component({ + selector: "nui-kpi-docs", + templateUrl: "./kpi-docs.component.html", + standalone: false, +}) +export class KpiDocsComponent implements OnInit { + public kpiWidgetFileText = ""; + public kpiConfiguratorFileText = ""; + + public async ngOnInit(): Promise { + this.kpiWidgetFileText = await import( + "./../../../../../../src/lib/widget-types/kpi/kpi-widget" + ).then(mapContentFile); + this.kpiConfiguratorFileText = await import( + "./../../../../../../src/lib/widget-types/kpi/kpi-configurator" + ).then(mapContentFile); + } +} +\\\`, + "widget-types/kpi/kpi-docs.module.ts": \\\`// © 2022 SolarWinds Worldwide, LLC. All rights reserved. +// +// Permission is hereby granted, free of charge, to any person obtaining a copy +// of this software and associated documentation files (the "Software"), to +// deal in the Software without restriction, including without limitation the +// rights to use, copy, modify, merge, publish, distribute, sublicense, and/or +// sell copies of the Software, and to permit persons to whom the Software is +// furnished to do so, subject to the following conditions: +// +// The above copyright notice and this permission notice shall be included in +// all copies or substantial portions of the Software. +// +// THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR +// IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, +// FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE +// AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER +// LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, +// OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN +// THE SOFTWARE. + +import { NgModule } from "@angular/core"; +import { RouterModule, Routes } from "@angular/router"; + +import { + NuiButtonModule, + NuiDocsModule, + NuiMessageModule, + NuiSwitchModule, + DEMO_PATH_TOKEN, +} from "@nova-ui/bits"; +import { + KpiColorComparatorsRegistryService, + NuiDashboardsModule, +} from "@nova-ui/dashboards"; + +import { KpiDocsComponent } from "./kpi-docs.component"; +import { KpiSyncBrokerExampleComponent } from "./kpi-sync-broker/kpi-sync-broker-example.component"; +import { KpiSyncBrokerDocsComponent } from "./kpi-sync-broker-docs.component"; +import { KpiSyncBrokerForAllTilesExampleComponent } from "./kpi-sync-broker-for-all-tiles/kpi-sync-broker-for-all-tiles-example.component"; +import { KpiWidgetExampleComponent } from "./kpi-widget/kpi-widget-example.component"; +import { KpiWidgetBackgroundColorExampleComponent } from "./kpi-widget-background-color/kpi-widget-background-color-example.component"; +import { KpiWidgetBackgroundColorDocsComponent } from "./kpi-widget-background-color-docs.component"; +import { KpiWidgetInteractiveExampleComponent } from "./kpi-widget-interactive/kpi-widget-interactive-example.component"; +import { getDemoFiles } from "../../../../demo-files-factory"; + +const routes: Routes = [ + { + path: "", + component: KpiDocsComponent, + data: { + srlc: { + hideIndicator: true, + }, + showThemeSwitcher: true, + }, + }, + { + path: "example", + component: KpiWidgetExampleComponent, + data: { + srlc: { + hideIndicator: true, + }, + }, + }, + { + path: "background-color", + component: KpiWidgetBackgroundColorDocsComponent, + data: { + srlc: { + hideIndicator: true, + }, + }, + }, + { + path: "sync-broker", + component: KpiSyncBrokerDocsComponent, + data: { + srlc: { + hideIndicator: true, + }, + }, + }, +]; + +@NgModule({ + imports: [ + RouterModule.forChild(routes), + NuiButtonModule, + NuiDocsModule, + NuiMessageModule, + NuiDashboardsModule, + NuiSwitchModule, + ], + declarations: [ + KpiDocsComponent, + KpiWidgetExampleComponent, + KpiWidgetInteractiveExampleComponent, + KpiWidgetBackgroundColorDocsComponent, + KpiWidgetBackgroundColorExampleComponent, + KpiSyncBrokerDocsComponent, + KpiSyncBrokerExampleComponent, + KpiSyncBrokerForAllTilesExampleComponent, + ], + providers: [ + KpiColorComparatorsRegistryService, + { + provide: DEMO_PATH_TOKEN, + useValue: getDemoFiles("kpi"), + }, + ], +}) +export default class KpiDocsModule { + constructor( + private comparatorsRegistry: KpiColorComparatorsRegistryService + ) { + this.backgroundColorDocsSetup(); + } + + private backgroundColorDocsSetup() { + this.comparatorsRegistry.registerComparators({ + "!=": { + comparatorFn: (actual: any, reference: any) => + // eslint-disable-next-line eqeqeq + actual != reference, + label: "Not equal", + }, + }); + } +} +\\\`, + "widget-types/kpi/kpi-sync-broker/kpi-sync-broker-example.component.ts": \\\`// © 2022 SolarWinds Worldwide, LLC. All rights reserved. +// +// Permission is hereby granted, free of charge, to any person obtaining a copy +// of this software and associated documentation files (the "Software"), to +// deal in the Software without restriction, including without limitation the +// rights to use, copy, modify, merge, publish, distribute, sublicense, and/or +// sell copies of the Software, and to permit persons to whom the Software is +// furnished to do so, subject to the following conditions: +// +// The above copyright notice and this permission notice shall be included in +// all copies or substantial portions of the Software. +// +// THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR +// IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, +// FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE +// AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER +// LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, +// OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN +// THE SOFTWARE. + +import { HttpClient, HttpErrorResponse } from "@angular/common/http"; +import { + ChangeDetectorRef, + Component, + Injectable, + OnDestroy, + OnInit, +} from "@angular/core"; +import { GridsterConfig, GridsterItem } from "angular-gridster2"; +import keyBy from "lodash/keyBy"; +import { BehaviorSubject, of } from "rxjs"; +import { delay, finalize, take } from "rxjs/operators"; + +import { DataSourceService, IFilteringOutputs } from "@nova-ui/bits"; +import { + DATA_SOURCE, + IDashboard, + IKpiData, + IProviderConfiguration, + IWidget, + KpiComponent, + NOVA_KPI_DATASOURCE_ADAPTER, + NOVA_KPI_SCALE_SYNC_BROKER, + PizzagnaLayer, + ProviderRegistryService, + WellKnownPathKey, + WellKnownProviders, + WidgetTypesService, +} from "@nova-ui/dashboards"; + +/** + * A simple KPI data source to retrieve the average rating of Harry Potter and the Sorcerer's Stone (book) via googleapis + */ +@Injectable() +export class AverageRatingKpiDataSource + extends DataSourceService + implements OnDestroy +{ + public static providerId = "AverageRatingKpiDataSource"; + public busy = new BehaviorSubject(false); + + constructor(private http: HttpClient) { + super(); + } + + public async getFilteredData(): Promise { + this.busy.next(true); + return new Promise((resolve) => { + // *** Make a resource request to an external API (if needed) + this.http + .get("https://www.googleapis.com/books/v1/volumes/5MQFrgEACAAJ") + .pipe(finalize(() => this.busy.next(false))) + .subscribe({ + next: (data: any) => { + resolve({ + result: { + value: data.volumeInfo.averageRating, + }, + }); + }, + error: (error: HttpErrorResponse) => { + resolve({ + result: null, + error: { + type: error.status, + }, + }); + }, + }); + }); + } + + public ngOnDestroy(): void { + this.outputsSubject.complete(); + } +} + +/** + * A simple KPI data source to retrieve the ratings count of Harry Potter and the Sorcerer's Stone (book) via googleapis + */ +@Injectable() +export class RatingsCountKpiDataSource + extends DataSourceService + implements OnDestroy +{ + public static providerId = "RatingsCountKpiDataSource"; + + // Use this subject to communicate the data source's busy state + public busy = new BehaviorSubject(false); + + constructor(private http: HttpClient) { + super(); + } + + public async getFilteredData(): Promise { + this.busy.next(true); + return new Promise((resolve) => { + this.http + .get("https://www.googleapis.com/books/v1/volumes/5MQFrgEACAAJ") + .pipe( + delay(2000), + finalize(() => this.busy.next(false)) + ) + .subscribe({ + next: (data: any) => { + resolve({ + result: { + value: data.volumeInfo.ratingsCount, + }, + }); + }, + error: (error: HttpErrorResponse) => { + resolve({ + result: null, + error: { + type: error.status, + }, + }); + }, + }); + }); + } + + public ngOnDestroy(): void { + this.outputsSubject.complete(); + } +} +/** + * A simple KPI data source to retrieve the ratings count of Harry Potter and the Sorcerer's Stone (book) via googleapis + */ +@Injectable() +export class MockKpiDataSource + extends DataSourceService + implements OnDestroy +{ + public static providerId = "MockKpiDataSource"; + + // Use this subject to communicate the data source's busy state + public busy = new BehaviorSubject(false); + + constructor() { + super(); + } + + public async getFilteredData(): Promise { + this.busy.next(true); + return new Promise((resolve) => { + of(3381342) + .pipe( + delay(5000), + take(1), + finalize(() => this.busy.next(false)) + ) + .subscribe({ + next: (data: any) => { + resolve({ + result: { + value: data, + }, + }); + }, + }); + }); + } + + public ngOnDestroy(): void { + this.outputsSubject.complete(); + } +} + +/** + * A component that instantiates the dashboard + */ +@Component({ + selector: "kpi-sync-broker-example", + templateUrl: "./kpi-sync-broker-example.component.html", + styleUrls: ["./kpi-sync-broker-example.component.less"], + standalone: false, +}) +export class KpiSyncBrokerExampleComponent implements OnInit { + public dashboard: IDashboard | undefined; + public gridsterConfig: GridsterConfig = {}; + public editMode: boolean = false; + + constructor( + private widgetTypesService: WidgetTypesService, + private providerRegistry: ProviderRegistryService, + private changeDetectorRef: ChangeDetectorRef + ) {} + + public ngOnInit(): void { + this.setupDashboard(); + + this.initializeDashboard(); + } + + private setupDashboard() { + const widgetTemplate = this.widgetTypesService.getWidgetType("kpi", 1); + + this.widgetTypesService.setNode( + widgetTemplate, + "configurator", + WellKnownPathKey.DataSourceProviders, + [ + AverageRatingKpiDataSource.providerId, + RatingsCountKpiDataSource.providerId, + MockKpiDataSource.providerId, + ] + ); + + this.providerRegistry.setProviders({ + [AverageRatingKpiDataSource.providerId]: { + provide: DATA_SOURCE, + useClass: AverageRatingKpiDataSource, + deps: [HttpClient], + }, + [RatingsCountKpiDataSource.providerId]: { + provide: DATA_SOURCE, + useClass: RatingsCountKpiDataSource, + deps: [HttpClient], + }, + [MockKpiDataSource.providerId]: { + provide: DATA_SOURCE, + useClass: MockKpiDataSource, + deps: [], + }, + }); + } + + /** Used for restoring widgets state */ + public reInitializeDashboard(): void { + // destroys the components and their providers so the dashboard can re init data + this.dashboard = undefined; + this.changeDetectorRef.detectChanges(); + + this.initializeDashboard(); + } + + private initializeDashboard(): void { + const widgetsWithStructure = widgetsConfig.map((w) => + this.widgetTypesService.mergeWithWidgetType(w) + ); + const widgetsIndex = keyBy(widgetsWithStructure, (w: IWidget) => w.id); + + const positions: Record = { + kpiWidgetId: { + cols: 3, + rows: 6, + y: 0, + x: 0, + }, + kpiWidgetId2: { + cols: 3, + rows: 6, + y: 0, + x: 0, + }, + }; + + this.dashboard = { + positions, + widgets: widgetsIndex, + }; + } +} + +const widgetsConfig: IWidget[] = [ + { + id: "kpiWidgetId", + type: "kpi", + pizzagna: { + [PizzagnaLayer.Configuration]: { + header: { + properties: { + title: "NO Sync Broker", + subtitle: "Values sizes are being not synced", + }, + }, + tiles: { + properties: { + nodes: ["kpi1", "kpi2", "kpi3"], + }, + }, + kpi1: { + id: "kpi1", + componentType: KpiComponent.lateLoadKey, + properties: { + widgetData: { + units: \\\\\\\`out of 5 Stars\\\\\\\`, + label: \\\\\\\`Average Rating\\\\\\\`, + backgroundColor: "lightpink", + }, + }, + providers: { + [WellKnownProviders.DataSource]: { + providerId: AverageRatingKpiDataSource.providerId, + } as IProviderConfiguration, + [WellKnownProviders.Adapter]: { + providerId: NOVA_KPI_DATASOURCE_ADAPTER, + properties: { + componentId: "kpi1", + propertyPath: "widgetData", + }, + } as IProviderConfiguration, + }, + }, + kpi2: { + id: "kpi2", + componentType: KpiComponent.lateLoadKey, + properties: { + widgetData: { + label: \\\\\\\`Another label which might be a pretty long one\\\\\\\`, + units: \\\\\\\`Which comes from somewhere\\\\\\\`, + backgroundColor: "skyblue", + }, + }, + providers: { + [WellKnownProviders.DataSource]: { + providerId: RatingsCountKpiDataSource.providerId, + } as IProviderConfiguration, + [WellKnownProviders.Adapter]: { + providerId: NOVA_KPI_DATASOURCE_ADAPTER, + properties: { + componentId: "kpi2", + propertyPath: "widgetData", + }, + } as IProviderConfiguration, + }, + }, + kpi3: { + id: "kpi3", + componentType: KpiComponent.lateLoadKey, + properties: { + widgetData: { + label: \\\\\\\`Random\\\\\\\`, + units: \\\\\\\`Data\\\\\\\`, + }, + }, + providers: { + [WellKnownProviders.DataSource]: { + providerId: MockKpiDataSource.providerId, + } as IProviderConfiguration, + [WellKnownProviders.Adapter]: { + providerId: NOVA_KPI_DATASOURCE_ADAPTER, + properties: { + componentId: "kpi3", + propertyPath: "widgetData", + }, + } as IProviderConfiguration, + }, + }, + }, + }, + }, + { + id: "kpiWidgetId2", + type: "kpi", + pizzagna: { + [PizzagnaLayer.Configuration]: { + header: { + properties: { + title: "WITH Sync Broker", + subtitle: + "Now the values of label, units, and value are being synced", + }, + }, + tiles: { + properties: { + nodes: ["kpi4", "kpi5", "kpi6"], + }, + providers: { + // This is where and how you set the sync broker provider + kpiScaleSyncBroker: { + providerId: NOVA_KPI_SCALE_SYNC_BROKER, + properties: { + scaleSyncConfig: [ + // You can decide which values to keep in sync. For instance, you can leave only 'label' id in the array below + { id: "value" }, + { id: "label" }, + { id: "units" }, + ], + }, + }, + }, + }, + kpi4: { + id: "kpi4", + componentType: KpiComponent.lateLoadKey, + properties: { + widgetData: { + units: \\\\\\\`out of 5 Stars\\\\\\\`, + label: \\\\\\\`Average Rating\\\\\\\`, + backgroundColor: "lightpink", + }, + }, + providers: { + [WellKnownProviders.DataSource]: { + providerId: AverageRatingKpiDataSource.providerId, + } as IProviderConfiguration, + [WellKnownProviders.Adapter]: { + providerId: NOVA_KPI_DATASOURCE_ADAPTER, + properties: { + componentId: "kpi4", + propertyPath: "widgetData", + }, + } as IProviderConfiguration, + }, + }, + kpi5: { + id: "kpi5", + componentType: KpiComponent.lateLoadKey, + properties: { + widgetData: { + label: \\\\\\\`Another label which might be a pretty long one\\\\\\\`, + units: \\\\\\\`Which comes from somewhere\\\\\\\`, + backgroundColor: "skyblue", + }, + }, + providers: { + [WellKnownProviders.DataSource]: { + providerId: RatingsCountKpiDataSource.providerId, + } as IProviderConfiguration, + [WellKnownProviders.Adapter]: { + providerId: NOVA_KPI_DATASOURCE_ADAPTER, + properties: { + componentId: "kpi5", + propertyPath: "widgetData", + }, + } as IProviderConfiguration, + }, + }, + kpi6: { + id: "kpi6", + componentType: KpiComponent.lateLoadKey, + properties: { + widgetData: { + label: \\\\\\\`Random\\\\\\\`, + units: \\\\\\\`Data\\\\\\\`, + }, + }, + providers: { + [WellKnownProviders.DataSource]: { + providerId: MockKpiDataSource.providerId, + } as IProviderConfiguration, + [WellKnownProviders.Adapter]: { + providerId: NOVA_KPI_DATASOURCE_ADAPTER, + properties: { + componentId: "kpi6", + propertyPath: "widgetData", + }, + } as IProviderConfiguration, + }, + }, + }, + }, + }, +]; +\\\`, + "widget-types/kpi/kpi-sync-broker-docs.component.ts": \\\`// © 2022 SolarWinds Worldwide, LLC. All rights reserved. +// +// Permission is hereby granted, free of charge, to any person obtaining a copy +// of this software and associated documentation files (the "Software"), to +// deal in the Software without restriction, including without limitation the +// rights to use, copy, modify, merge, publish, distribute, sublicense, and/or +// sell copies of the Software, and to permit persons to whom the Software is +// furnished to do so, subject to the following conditions: +// +// The above copyright notice and this permission notice shall be included in +// all copies or substantial portions of the Software. +// +// THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR +// IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, +// FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE +// AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER +// LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, +// OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN +// THE SOFTWARE. + +import { Component } from "@angular/core"; + +@Component({ + selector: "kpi-sync-broker-docs", + templateUrl: "./kpi-sync-broker-docs.component.html", + standalone: false, +}) +export class KpiSyncBrokerDocsComponent { + public kpiScaleSyncBroker = \\\\\\\` +"tiles": { + "providers": { + kpiScaleSyncBroker: { + providerId: NOVA_KPI_SCALE_SYNC_BROKER, + properties: { + scaleSyncConfig: [ + { id: "value" }, + { id: "label" }, + { id: "units" }, + ], + }, + }, + }, +}, +\\\\\\\`; + + public defineScaleBrokerOnDashboardSetup = \\\\\\\` +// To add the sync broker globally to all the kpi tiles you may start with setting up the broker config +// Here you define which values to keep in sync +const brokerConfig = { + providerId: NOVA_KPI_SCALE_SYNC_BROKER, + properties: { + scaleSyncConfig: [ + { id: "value" }, + { id: "label" }, + { id: "units" }, + ], + }, + }; + +// And here is how you set the sync broker for every KPI widget in the dashboard. +// Later, you will be able to override this setting for each separate KPI widget in the configuration (just like it is shown in the third +// width of the example with the 'kpiWidgetId3') +this.widgetTypesService.setNode( + widgetTemplate, + "widget", + "tiles.providers.kpiScaleSyncBroker", + brokerConfig +); +\\\\\\\`; +} +\\\`, + "widget-types/kpi/kpi-sync-broker-for-all-tiles/kpi-sync-broker-for-all-tiles-example.component.ts": \\\`// © 2022 SolarWinds Worldwide, LLC. All rights reserved. +// +// Permission is hereby granted, free of charge, to any person obtaining a copy +// of this software and associated documentation files (the "Software"), to +// deal in the Software without restriction, including without limitation the +// rights to use, copy, modify, merge, publish, distribute, sublicense, and/or +// sell copies of the Software, and to permit persons to whom the Software is +// furnished to do so, subject to the following conditions: +// +// The above copyright notice and this permission notice shall be included in +// all copies or substantial portions of the Software. +// +// THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR +// IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, +// FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE +// AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER +// LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, +// OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN +// THE SOFTWARE. + +import { HttpClient, HttpErrorResponse } from "@angular/common/http"; +import { + ChangeDetectorRef, + Component, + Injectable, + OnDestroy, + OnInit, +} from "@angular/core"; +import { GridsterConfig, GridsterItem } from "angular-gridster2"; +import keyBy from "lodash/keyBy"; +import { BehaviorSubject, of } from "rxjs"; +import { delay, finalize, take } from "rxjs/operators"; + +import { DataSourceService, IFilteringOutputs } from "@nova-ui/bits"; +import { + DATA_SOURCE, + IDashboard, + IKpiData, + IProviderConfiguration, + IWidget, + KpiComponent, + NOVA_KPI_DATASOURCE_ADAPTER, + NOVA_KPI_SCALE_SYNC_BROKER, + PizzagnaLayer, + ProviderRegistryService, + WellKnownPathKey, + WellKnownProviders, + WidgetTypesService, +} from "@nova-ui/dashboards"; + +/** + * A simple KPI data source to retrieve the average rating of Harry Potter and the Sorcerer's Stone (book) via googleapis + */ +@Injectable() +export class AverageRatingKpiDataSource + extends DataSourceService + implements OnDestroy +{ + public static providerId = "AverageRatingKpiDataSource"; + public busy = new BehaviorSubject(false); + + constructor(private http: HttpClient) { + super(); + } + + public async getFilteredData(): Promise { + this.busy.next(true); + return new Promise((resolve) => { + // *** Make a resource request to an external API (if needed) + this.http + .get("https://www.googleapis.com/books/v1/volumes/5MQFrgEACAAJ") + .pipe(finalize(() => this.busy.next(false))) + .subscribe({ + next: (data: any) => { + resolve({ + result: { + value: data.volumeInfo.averageRating, + }, + }); + }, + error: (error: HttpErrorResponse) => { + resolve({ + result: null, + error: { + type: error.status, + }, + }); + }, + }); + }); + } + + public ngOnDestroy(): void { + this.outputsSubject.complete(); + } +} + +/** + * A simple KPI data source to retrieve the ratings count of Harry Potter and the Sorcerer's Stone (book) via googleapis + */ +@Injectable() +export class RatingsCountKpiDataSource + extends DataSourceService + implements OnDestroy +{ + public static providerId = "RatingsCountKpiDataSource"; + + // Use this subject to communicate the data source's busy state + public busy = new BehaviorSubject(false); + + constructor(private http: HttpClient) { + super(); + } + + public async getFilteredData(): Promise { + this.busy.next(true); + return new Promise((resolve) => { + this.http + .get("https://www.googleapis.com/books/v1/volumes/5MQFrgEACAAJ") + .pipe( + delay(2000), + finalize(() => this.busy.next(false)) + ) + .subscribe({ + next: (data: any) => { + resolve({ + result: { + value: data.volumeInfo.ratingsCount, + }, + }); + }, + error: (error: HttpErrorResponse) => { + resolve({ + result: null, + error: { + type: error.status, + }, + }); + }, + }); + }); + } + + public ngOnDestroy(): void { + this.outputsSubject.complete(); + } +} +/** + * A simple KPI data source to retrieve the ratings count of Harry Potter and the Sorcerer's Stone (book) via googleapis + */ +@Injectable() +export class MockKpiDataSource + extends DataSourceService + implements OnDestroy +{ + public static providerId = "MockKpiDataSource"; + + // Use this subject to communicate the data source's busy state + public busy = new BehaviorSubject(false); + public value: number = 3381342; + + constructor() { + super(); + } + + public async getFilteredData(): Promise { + this.busy.next(true); + return new Promise((resolve) => { + of(this.value) + .pipe( + delay(5000), + take(1), + finalize(() => this.busy.next(false)) + ) + .subscribe({ + next: (data: any) => { + resolve({ + result: { + value: data, + }, + }); + }, + }); + }); + } + + public ngOnDestroy(): void { + this.outputsSubject.complete(); + } +} + +/** + * A component that instantiates the dashboard + */ +@Component({ + selector: "kpi-sync-broker-for-all-tiles-example", + templateUrl: "./kpi-sync-broker-for-all-tiles-example.component.html", + styleUrls: ["./kpi-sync-broker-for-all-tiles-example.component.less"], + standalone: false, +}) +export class KpiSyncBrokerForAllTilesExampleComponent implements OnInit { + public dashboard: IDashboard | undefined; + public gridsterConfig: GridsterConfig = {}; + public editMode: boolean = false; + + constructor( + private widgetTypesService: WidgetTypesService, + private providerRegistry: ProviderRegistryService, + private changeDetectorRef: ChangeDetectorRef + ) {} + + public ngOnInit(): void { + this.setupDashboard(); + + this.initializeDashboard(); + } + + /** Used for restoring widgets state */ + public reInitializeDashboard(): void { + // destroys the components and their providers so the dashboard can re init data + this.dashboard = undefined; + this.changeDetectorRef.detectChanges(); + + this.initializeDashboard(); + } + + private setupDashboard() { + // To add the sync broker globally to all the kpi tiles you may start with setting up the broker config + // Here you define which values to keep in sync + const brokerConfig = { + providerId: NOVA_KPI_SCALE_SYNC_BROKER, + properties: { + scaleSyncConfig: [ + { id: "value" }, + { id: "label" }, + { id: "units" }, + ], + }, + }; + const widgetTemplate = this.widgetTypesService.getWidgetType("kpi", 1); + + this.widgetTypesService.setNode( + widgetTemplate, + "configurator", + WellKnownPathKey.DataSourceProviders, + [ + AverageRatingKpiDataSource.providerId, + RatingsCountKpiDataSource.providerId, + MockKpiDataSource.providerId, + ] + ); + + // And here is how you set the sync broker for every KPI widget in the dashboard. + // Later, you will be able to override this setting for each separate KPI widget in the configuration (just like it is shown in the third + // width of the example with the 'kpiWidgetId3') + this.widgetTypesService.setNode( + widgetTemplate, + "widget", + "tiles.providers.kpiScaleSyncBroker", + brokerConfig + ); + + this.providerRegistry.setProviders({ + [AverageRatingKpiDataSource.providerId]: { + provide: DATA_SOURCE, + useClass: AverageRatingKpiDataSource, + deps: [HttpClient], + }, + [RatingsCountKpiDataSource.providerId]: { + provide: DATA_SOURCE, + useClass: RatingsCountKpiDataSource, + deps: [HttpClient], + }, + [MockKpiDataSource.providerId]: { + provide: DATA_SOURCE, + useClass: MockKpiDataSource, + deps: [], + }, + }); + } + + private initializeDashboard(): void { + const widgetsWithStructure = widgetsConfig.map((w) => + this.widgetTypesService.mergeWithWidgetType(w) + ); + const widgetsIndex = keyBy(widgetsWithStructure, (w: IWidget) => w.id); + + const positions: Record = { + kpiWidgetId: { + cols: 3, + rows: 6, + y: 0, + x: 0, + }, + kpiWidgetId2: { + cols: 3, + rows: 6, + y: 0, + x: 3, + }, + kpiWidgetId3: { + cols: 3, + rows: 6, + y: 0, + x: 6, + }, + }; + + this.dashboard = { + positions, + widgets: widgetsIndex, + }; + } +} + +const widgetsConfig: IWidget[] = [ + { + id: "kpiWidgetId", + type: "kpi", + pizzagna: { + [PizzagnaLayer.Configuration]: { + header: { + properties: { + title: "Sync Broker Applied for ALL Widgets", + subtitle: "Values are being synced", + }, + }, + tiles: { + properties: { + nodes: ["kpi1", "kpi2", "kpi3"], + }, + }, + kpi1: { + id: "kpi1", + componentType: KpiComponent.lateLoadKey, + properties: { + widgetData: { + units: \\\\\\\`out of 5 Stars\\\\\\\`, + label: \\\\\\\`Average Rating\\\\\\\`, + backgroundColor: "lightpink", + }, + }, + providers: { + [WellKnownProviders.DataSource]: { + providerId: AverageRatingKpiDataSource.providerId, + } as IProviderConfiguration, + [WellKnownProviders.Adapter]: { + providerId: NOVA_KPI_DATASOURCE_ADAPTER, + properties: { + componentId: "kpi1", + propertyPath: "widgetData", + }, + } as IProviderConfiguration, + }, + }, + kpi2: { + id: "kpi2", + componentType: KpiComponent.lateLoadKey, + properties: { + widgetData: { + label: \\\\\\\`Another label which might be a pretty long one\\\\\\\`, + units: \\\\\\\`Which comes from somewhere\\\\\\\`, + backgroundColor: "skyblue", + }, + }, + providers: { + [WellKnownProviders.DataSource]: { + providerId: RatingsCountKpiDataSource.providerId, + } as IProviderConfiguration, + [WellKnownProviders.Adapter]: { + providerId: NOVA_KPI_DATASOURCE_ADAPTER, + properties: { + componentId: "kpi2", + propertyPath: "widgetData", + }, + } as IProviderConfiguration, + }, + }, + kpi3: { + id: "kpi3", + componentType: KpiComponent.lateLoadKey, + properties: { + widgetData: { + label: \\\\\\\`Random\\\\\\\`, + units: \\\\\\\`Data\\\\\\\`, + }, + }, + providers: { + [WellKnownProviders.DataSource]: { + providerId: MockKpiDataSource.providerId, + } as IProviderConfiguration, + [WellKnownProviders.Adapter]: { + providerId: NOVA_KPI_DATASOURCE_ADAPTER, + properties: { + componentId: "kpi3", + propertyPath: "widgetData", + }, + } as IProviderConfiguration, + }, + }, + }, + }, + }, + { + id: "kpiWidgetId2", + type: "kpi", + pizzagna: { + [PizzagnaLayer.Configuration]: { + header: { + properties: { + title: "Sync Broker Applied for ALL Widgets", + subtitle: + "Now the values of label, units, and value are being synced", + }, + }, + tiles: { + properties: { + nodes: ["kpi1", "kpi2", "kpi3"], + }, + }, + kpi1: { + id: "kpi1", + componentType: KpiComponent.lateLoadKey, + properties: { + widgetData: { + units: \\\\\\\`out of 5 Stars\\\\\\\`, + label: \\\\\\\`Average Rating\\\\\\\`, + backgroundColor: "lightpink", + }, + }, + providers: { + [WellKnownProviders.DataSource]: { + providerId: AverageRatingKpiDataSource.providerId, + } as IProviderConfiguration, + [WellKnownProviders.Adapter]: { + providerId: NOVA_KPI_DATASOURCE_ADAPTER, + properties: { + componentId: "kpi1", + propertyPath: "widgetData", + }, + } as IProviderConfiguration, + }, + }, + kpi2: { + id: "kpi2", + componentType: KpiComponent.lateLoadKey, + properties: { + widgetData: { + label: \\\\\\\`Another label which might be a pretty long one\\\\\\\`, + units: \\\\\\\`Which comes from somewhere\\\\\\\`, + backgroundColor: "skyblue", + }, + }, + providers: { + [WellKnownProviders.DataSource]: { + providerId: RatingsCountKpiDataSource.providerId, + } as IProviderConfiguration, + [WellKnownProviders.Adapter]: { + providerId: NOVA_KPI_DATASOURCE_ADAPTER, + properties: { + componentId: "kpi2", + propertyPath: "widgetData", + }, + } as IProviderConfiguration, + }, + }, + kpi3: { + id: "kpi3", + componentType: KpiComponent.lateLoadKey, + properties: { + widgetData: { + label: \\\\\\\`Random\\\\\\\`, + units: \\\\\\\`Data\\\\\\\`, + }, + }, + providers: { + [WellKnownProviders.DataSource]: { + providerId: MockKpiDataSource.providerId, + } as IProviderConfiguration, + [WellKnownProviders.Adapter]: { + providerId: NOVA_KPI_DATASOURCE_ADAPTER, + properties: { + componentId: "kpi3", + propertyPath: "widgetData", + }, + } as IProviderConfiguration, + }, + }, + }, + }, + }, + { + id: "kpiWidgetId3", + type: "kpi", + pizzagna: { + [PizzagnaLayer.Configuration]: { + header: { + properties: { + title: "Here We Sync Only Labels and Units", + subtitle: + "Now only the label, and units are being synced", + }, + }, + tiles: { + properties: { + nodes: ["kpi1", "kpi2", "kpi3"], + }, + providers: { + // This is where and how you can override the globally set broker config + kpiScaleSyncBroker: { + providerId: NOVA_KPI_SCALE_SYNC_BROKER, + properties: { + scaleSyncConfig: [ + { id: "label" }, + { id: "units" }, + ], + }, + }, + }, + }, + kpi1: { + id: "kpi1", + componentType: KpiComponent.lateLoadKey, + properties: { + widgetData: { + units: \\\\\\\`out of 5 Stars\\\\\\\`, + label: \\\\\\\`Average Rating\\\\\\\`, + backgroundColor: "lightpink", + }, + }, + providers: { + [WellKnownProviders.DataSource]: { + providerId: AverageRatingKpiDataSource.providerId, + } as IProviderConfiguration, + [WellKnownProviders.Adapter]: { + providerId: NOVA_KPI_DATASOURCE_ADAPTER, + properties: { + componentId: "kpi1", + propertyPath: "widgetData", + }, + } as IProviderConfiguration, + }, + }, + kpi2: { + id: "kpi2", + componentType: KpiComponent.lateLoadKey, + properties: { + widgetData: { + label: \\\\\\\`Another label which might be a pretty long one\\\\\\\`, + units: \\\\\\\`Which comes from somewhere\\\\\\\`, + backgroundColor: "skyblue", + }, + }, + providers: { + [WellKnownProviders.DataSource]: { + providerId: RatingsCountKpiDataSource.providerId, + } as IProviderConfiguration, + [WellKnownProviders.Adapter]: { + providerId: NOVA_KPI_DATASOURCE_ADAPTER, + properties: { + componentId: "kpi2", + propertyPath: "widgetData", + }, + } as IProviderConfiguration, + }, + }, + kpi3: { + id: "kpi3", + componentType: KpiComponent.lateLoadKey, + properties: { + widgetData: { + label: \\\\\\\`Random\\\\\\\`, + units: \\\\\\\`Data\\\\\\\`, + }, + }, + providers: { + [WellKnownProviders.DataSource]: { + providerId: MockKpiDataSource.providerId, + } as IProviderConfiguration, + [WellKnownProviders.Adapter]: { + providerId: NOVA_KPI_DATASOURCE_ADAPTER, + properties: { + componentId: "kpi3", + propertyPath: "widgetData", + }, + } as IProviderConfiguration, + }, + }, + }, + }, + }, +]; +\\\`, + "widget-types/kpi/kpi-widget/kpi-widget-example.component.ts": \\\`// © 2022 SolarWinds Worldwide, LLC. All rights reserved. +// +// Permission is hereby granted, free of charge, to any person obtaining a copy +// of this software and associated documentation files (the "Software"), to +// deal in the Software without restriction, including without limitation the +// rights to use, copy, modify, merge, publish, distribute, sublicense, and/or +// sell copies of the Software, and to permit persons to whom the Software is +// furnished to do so, subject to the following conditions: +// +// The above copyright notice and this permission notice shall be included in +// all copies or substantial portions of the Software. +// +// THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR +// IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, +// FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE +// AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER +// LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, +// OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN +// THE SOFTWARE. + +import { HttpClient, HttpErrorResponse } from "@angular/common/http"; +import { Component, Injectable, OnDestroy, OnInit } from "@angular/core"; +import { GridsterConfig, GridsterItem } from "angular-gridster2"; +import { BehaviorSubject } from "rxjs"; +import { finalize } from "rxjs/operators"; + +import { DataSourceService, IFilteringOutputs } from "@nova-ui/bits"; +import { + DATA_SOURCE, + DEFAULT_PIZZAGNA_ROOT, + IDashboard, + IKpiData, + IProviderConfiguration, + IRefresherProperties, + IWidget, + IWidgets, + KpiComponent, + NOVA_KPI_DATASOURCE_ADAPTER, + PizzagnaLayer, + ProviderRegistryService, + WellKnownPathKey, + WellKnownProviders, + WidgetTypesService, +} from "@nova-ui/dashboards"; + +/** + * A simple KPI data source to retrieve the average rating of Harry Potter and the Sorcerer's Stone (book) via googleapis + */ +@Injectable() +export class AverageRatingKpiDataSource + extends DataSourceService + implements OnDestroy +{ + // This is the ID we'll use to identify the provider + public static providerId = "AverageRatingKpiDataSource"; + + // Use this subject to communicate the data source's busy state + public busy = new BehaviorSubject(false); + + constructor(private http: HttpClient) { + super(); + } + + // In this example, getFilteredData is invoked every 10 minutes (Take a look at the refresher + // provider definition in the widget configuration below to see how the interval is set) + public async getFilteredData(): Promise { + this.busy.next(true); + return new Promise((resolve) => { + // *** Make a resource request to an external API (if needed) + this.http + .get("https://www.googleapis.com/books/v1/volumes/5MQFrgEACAAJ") + .pipe(finalize(() => this.busy.next(false))) + .subscribe({ + next: (data: any) => { + resolve({ + result: { + value: data.volumeInfo.averageRating, + }, + }); + }, + error: (error: HttpErrorResponse) => { + resolve({ + result: null, + error: { + type: error.status, + }, + }); + }, + }); + }); + } + + public ngOnDestroy(): void { + this.outputsSubject.complete(); + } +} + +/** + * A component that instantiates the dashboard + */ +@Component({ + selector: "kpi-widget-example", + templateUrl: "./kpi-widget-example.component.html", + styleUrls: ["./kpi-widget-example.component.less"], + standalone: false, +}) +export class KpiWidgetExampleComponent implements OnInit { + // This variable will hold all the data needed to define the layout and behavior of the widgets. + // Pass this to the dashboard component's dashboard input in the template. + public dashboard: IDashboard; + + // Angular gridster requires a configuration object even if it's empty. + // Pass this to the dashboard component's gridsterConfig input in the template. + public gridsterConfig: GridsterConfig = {}; + + // Boolean passed as an input to the dashboard. When true, widgets can be moved, resized, removed, or edited + public editMode: boolean = false; + + constructor( + // WidgetTypesService provides the widget's necessary structure information + private widgetTypesService: WidgetTypesService, + + // In general, the ProviderRegistryService is used for making entities available for injection into dynamically loaded components. + private providerRegistry: ProviderRegistryService + ) {} + + public ngOnInit(): void { + // Grabbing the widget's default template which will be needed as a parameter for setNode + const widgetTemplate = this.widgetTypesService.getWidgetType("kpi", 1); + // Registering our data sources as dropdown options in the widget editor/configurator + // Note: This could also be done in the parent module's constructor so that + // multiple dashboards could have access to the same widget template modification. + this.widgetTypesService.setNode( + // This is the template we grabbed above with getWidgetType + widgetTemplate, + // We are setting the editor/configurator part of the widget template + "configurator", + // This indicates which node you are changing and we want to change + // the data source providers available for selection in the editor. + WellKnownPathKey.DataSourceProviders, + // We are setting the data sources available for selection in the editor + [AverageRatingKpiDataSource.providerId] + ); + + // Registering the data source for injection into the KPI tile. + // Note: Each tile of a KPI widget is assigned its own instance of the data source + this.providerRegistry.setProviders({ + [AverageRatingKpiDataSource.providerId]: { + provide: DATA_SOURCE, + useClass: AverageRatingKpiDataSource, + // Any dependencies that need to be injected into the provider must be listed here + deps: [HttpClient], + }, + }); + + this.initializeDashboard(); + } + + public initializeDashboard(): void { + // We're using a static configuration object for this example, but this is where + // the widget's configuration could potentially be populated from a database + const kpiWidget = widgetConfig; + const widgetIndex: IWidgets = { + // Complete the KPI widget with information coming from its type definition + [kpiWidget.id]: + this.widgetTypesService.mergeWithWidgetType(kpiWidget), + }; + + // Setting the widget dimensions and position (this is for gridster) + const positions: Record = { + [kpiWidget.id]: { + cols: 4, + rows: 6, + y: 0, + x: 0, + }, + }; + + // Finally, assigning the variables we created above to the dashboard + this.dashboard = { + positions, + widgets: widgetIndex, + }; + } +} + +const widgetConfig: IWidget = { + id: "kpiWidgetId", + type: "kpi", + pizzagna: { + [PizzagnaLayer.Configuration]: { + [DEFAULT_PIZZAGNA_ROOT]: { + providers: { + [WellKnownProviders.Refresher]: { + properties: { + // Configuring the refresher interval so that our data source is invoked every ten minutes + interval: 60 * 10, + enabled: true, + } as IRefresherProperties, + } as Partial, + }, + }, + header: { + properties: { + title: "Harry Potter and the Sorcerer's Stone", + subtitle: "By J. K. Rowling", + }, + }, + tiles: { + properties: { + nodes: ["kpi1"], + }, + }, + kpi1: { + id: "kpi1", + componentType: KpiComponent.lateLoadKey, + properties: { + widgetData: { + units: \\\\\\\`out of 5 Stars\\\\\\\`, + label: \\\\\\\`Average Rating\\\\\\\`, + }, + }, + providers: { + [WellKnownProviders.DataSource]: { + // Setting the data source providerId for the tile with id "kpi1" + providerId: AverageRatingKpiDataSource.providerId, + } as IProviderConfiguration, + [WellKnownProviders.Adapter]: { + providerId: NOVA_KPI_DATASOURCE_ADAPTER, + properties: { + componentId: "kpi1", + propertyPath: "widgetData", + }, + } as IProviderConfiguration, + }, + }, + }, + }, +}; +\\\`, + "widget-types/kpi/kpi-widget-background-color/kpi-widget-background-color-example.component.ts": \\\`// © 2022 SolarWinds Worldwide, LLC. All rights reserved. +// +// Permission is hereby granted, free of charge, to any person obtaining a copy +// of this software and associated documentation files (the "Software"), to +// deal in the Software without restriction, including without limitation the +// rights to use, copy, modify, merge, publish, distribute, sublicense, and/or +// sell copies of the Software, and to permit persons to whom the Software is +// furnished to do so, subject to the following conditions: +// +// The above copyright notice and this permission notice shall be included in +// all copies or substantial portions of the Software. +// +// THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR +// IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, +// FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE +// AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER +// LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, +// OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN +// THE SOFTWARE. + +import { HttpClient, HttpErrorResponse } from "@angular/common/http"; +import { + ChangeDetectorRef, + Component, + Injectable, + OnDestroy, + OnInit, +} from "@angular/core"; +import { GridsterConfig, GridsterItem } from "angular-gridster2"; +import { BehaviorSubject } from "rxjs"; +import { finalize } from "rxjs/operators"; + +import { DataSourceService, IFilteringOutputs } from "@nova-ui/bits"; +import { + DATA_SOURCE, + DEFAULT_KPI_BACKGROUND_COLORS, + IDashboard, + IKpiColorRules, + IKpiData, + IProviderConfiguration, + IWidget, + IWidgets, + KpiComponent, + NOVA_KPI_COLOR_PRIORITIZER, + NOVA_KPI_DATASOURCE_ADAPTER, + PizzagnaLayer, + ProviderRegistryService, + WellKnownPathKey, + WellKnownProviders, + WidgetTypesService, +} from "@nova-ui/dashboards"; + +/** + * A simple KPI data source to retrieve the average rating of Harry Potter and the Sorcerer's Stone (book) via googleapis + */ +@Injectable() +export class AverageRatingKpiDataSource + extends DataSourceService + implements OnDestroy +{ + public static providerId = "AverageRatingKpiDataSource"; + public busy = new BehaviorSubject(false); + + constructor(private http: HttpClient) { + super(); + } + + public async getFilteredData(): Promise { + this.busy.next(true); + return new Promise((resolve) => { + // *** Make a resource request to an external API (if needed) + this.http + .get("https://www.googleapis.com/books/v1/volumes/5MQFrgEACAAJ") + .pipe(finalize(() => this.busy.next(false))) + .subscribe({ + next: (data: any) => { + resolve({ + result: { + value: data.volumeInfo.averageRating, + // setting the color on the dataSource "Sea Green", + // uncomment to get the background color update from the "Data" layer + // backgroundColor: "var(--nui-color-chart-three)", + }, + }); + }, + error: (error: HttpErrorResponse) => { + resolve({ + result: null, + error: { + type: error.status, + }, + }); + }, + }); + }); + } + + public ngOnDestroy(): void { + this.outputsSubject.complete(); + } +} + +/** + * A component that instantiates the dashboard + */ +@Component({ + selector: "kpi-widget-background-color-example", + templateUrl: "./kpi-widget-background-color-example.component.html", + styleUrls: ["./kpi-widget-background-color-example.component.less"], + standalone: false, +}) +export class KpiWidgetBackgroundColorExampleComponent implements OnInit { + public dashboard: IDashboard | undefined; + public gridsterConfig: GridsterConfig = {}; + public editMode: boolean = false; + + constructor( + private widgetTypesService: WidgetTypesService, + private providerRegistry: ProviderRegistryService, + private changeDetectorRef: ChangeDetectorRef + ) {} + + public ngOnInit(): void { + this.setupDashboard(); + + // KPI tile default color setup + this.setupDefaultColorStructure(); + + // Sets the custom pallette to the 'Description' section + this.setupCustomPalletteDescription(); + + // Sets the custom pallette to the 'Background color rules' section + this.setupCustomPalletteRules(); + + this.initializeDashboard(); + } + + /** Used for restoring widgets state */ + public reInitializeDashboard(): void { + // destroys the components and their providers so the dashboard can re init data + this.dashboard = undefined; + this.changeDetectorRef.detectChanges(); + + this.initializeDashboard(); + } + + private setupCustomPalletteDescription() { + const kpiWidgetTemplate = this.widgetTypesService.getWidgetType( + "kpi", + 1 + ); + this.widgetTypesService.setNode( + kpiWidgetTemplate, + "configurator", + WellKnownPathKey.TileDescriptionBackgroundColors, + [ + { color: "var(--nui-color-chart-one)", label: "Blue" }, + { + color: "var(--nui-color-chart-one-light)", + label: "Blue Light", + }, + { + color: "var(--nui-color-chart-one-dark)", + label: "Blue Dark", + }, + ] + ); + } + + private setupCustomPalletteRules() { + const kpiWidgetTemplate = this.widgetTypesService.getWidgetType( + "kpi", + 1 + ); + this.widgetTypesService.setNode( + kpiWidgetTemplate, + "configurator", + WellKnownPathKey.TileBackgroundColorRulesBackgroundColors, + [ + { color: "red", label: "Native Red" }, + ...DEFAULT_KPI_BACKGROUND_COLORS, + ] + ); + } + + private setupDefaultColorStructure() { + const widgetTemplate = this.widgetTypesService.getWidgetType("kpi", 1); + this.widgetTypesService.setNode( + widgetTemplate, + "widget", + "tiles.properties.template.properties.widgetData.backgroundColor", + "red" + ); + } + + private setupDashboard() { + const widgetTemplate = this.widgetTypesService.getWidgetType("kpi", 1); + this.widgetTypesService.setNode( + widgetTemplate, + "configurator", + WellKnownPathKey.DataSourceProviders, + [AverageRatingKpiDataSource.providerId] + ); + + this.providerRegistry.setProviders({ + [AverageRatingKpiDataSource.providerId]: { + provide: DATA_SOURCE, + useClass: AverageRatingKpiDataSource, + deps: [HttpClient], + }, + }); + } + + private initializeDashboard(): void { + const kpiWidget = widgetConfig; + const widgetIndex: IWidgets = { + [kpiWidget.id]: + this.widgetTypesService.mergeWithWidgetType(kpiWidget), + }; + + const positions: Record = { + [kpiWidget.id]: { + cols: 4, + rows: 6, + y: 0, + x: 0, + }, + }; + + this.dashboard = { + positions, + widgets: widgetIndex, + }; + } +} + +const widgetConfig: IWidget = { + id: "kpiWidgetId", + type: "kpi", + pizzagna: { + [PizzagnaLayer.Configuration]: { + header: { + properties: { + title: "Harry Potter and the Sorcerer's Stone", + subtitle: "By J. K. Rowling", + }, + }, + tiles: { + properties: { + nodes: ["kpi1"], + }, + }, + kpi1: { + id: "kpi1", + componentType: KpiComponent.lateLoadKey, + properties: { + widgetData: { + units: \\\\\\\`out of 5 Stars\\\\\\\`, + label: \\\\\\\`Average Rating\\\\\\\`, + // Configuration color "Blue" + backgroundColor: "var(--nui-color-chart-one)", + }, + }, + providers: { + [WellKnownProviders.DataSource]: { + providerId: AverageRatingKpiDataSource.providerId, + } as IProviderConfiguration, + [WellKnownProviders.Adapter]: { + providerId: NOVA_KPI_DATASOURCE_ADAPTER, + properties: { + componentId: "kpi1", + propertyPath: "widgetData", + }, + } as IProviderConfiguration, + [WellKnownProviders.KpiColorPrioritizer]: { + providerId: NOVA_KPI_COLOR_PRIORITIZER, + properties: { + // Color Prioritizer Rules + // settings rules - if the value is more than "2" display "Violet" color + rules: [ + { + comparisonType: ">", + value: 2, + color: "var(--nui-color-chart-four)", + }, + ] as IKpiColorRules[], + }, + } as IProviderConfiguration, + }, + }, + }, + }, +}; +\\\`, + "widget-types/kpi/kpi-widget-background-color-docs.component.ts": \\\`// © 2022 SolarWinds Worldwide, LLC. All rights reserved. +// +// Permission is hereby granted, free of charge, to any person obtaining a copy +// of this software and associated documentation files (the "Software"), to +// deal in the Software without restriction, including without limitation the +// rights to use, copy, modify, merge, publish, distribute, sublicense, and/or +// sell copies of the Software, and to permit persons to whom the Software is +// furnished to do so, subject to the following conditions: +// +// The above copyright notice and this permission notice shall be included in +// all copies or substantial portions of the Software. +// +// THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR +// IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, +// FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE +// AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER +// LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, +// OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN +// THE SOFTWARE. + +import { Component } from "@angular/core"; + +@Component({ + selector: "nui-kpi-background-color-docs", + templateUrl: "./kpi-widget-background-color-docs.component.html", + standalone: false, +}) +export class KpiWidgetBackgroundColorDocsComponent { + public comparatorsRegistryCode = \\\\\\\` + this.comparatorsRegistry.registerComparators({ + "!=": { + comparatorFn: (actual: any, reference: any) => actual != reference, + label: "Not equal", + }, + }); + \\\\\\\`; +} +\\\`, + "widget-types/kpi/kpi-widget-interactive/kpi-widget-interactive-example.component.ts": \\\`// © 2022 SolarWinds Worldwide, LLC. All rights reserved. +// +// Permission is hereby granted, free of charge, to any person obtaining a copy +// of this software and associated documentation files (the "Software"), to +// deal in the Software without restriction, including without limitation the +// rights to use, copy, modify, merge, publish, distribute, sublicense, and/or +// sell copies of the Software, and to permit persons to whom the Software is +// furnished to do so, subject to the following conditions: +// +// The above copyright notice and this permission notice shall be included in +// all copies or substantial portions of the Software. +// +// THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR +// IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, +// FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE +// AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER +// LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, +// OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN +// THE SOFTWARE. + +import { HttpClient, HttpErrorResponse } from "@angular/common/http"; +import { Component, Injectable, OnDestroy, OnInit } from "@angular/core"; +import { GridsterConfig, GridsterItem } from "angular-gridster2"; +import { BehaviorSubject } from "rxjs"; +import { finalize } from "rxjs/operators"; + +import { DataSourceService, IFilteringOutputs } from "@nova-ui/bits"; +import { + DATA_SOURCE, + IDashboard, + IKpiData, + IProviderConfiguration, + IWidget, + IWidgets, + KpiComponent, + NOVA_KPI_DATASOURCE_ADAPTER, + NOVA_URL_INTERACTION_HANDLER, + PizzagnaLayer, + ProviderRegistryService, + WellKnownPathKey, + WellKnownProviders, + WidgetTypesService, +} from "@nova-ui/dashboards"; + +/** + * A simple KPI data source to retrieve the average rating of Harry Potter and the Sorcerer's Stone (book) via googleapis + */ +@Injectable() +export class BookRatingDataSource + extends DataSourceService + implements OnDestroy +{ + // This is the ID we'll use to identify the provider + public static providerId = "BookRatingDataSource"; + + // Use this subject to communicate the data source's busy state + public busy = new BehaviorSubject(false); + + constructor(private http: HttpClient) { + super(); + } + + // In this example, getFilteredData is invoked every 10 minutes (Take a look at the refresher + // provider definition in the widget configuration below to see how the interval is set) + public async getFilteredData(): Promise { + this.busy.next(true); + return new Promise((resolve) => { + // *** Make a resource request to an external API (if needed) + this.http + .get("https://www.googleapis.com/books/v1/volumes/zpvysRGsBlwC") + .pipe(finalize(() => this.busy.next(false))) + .subscribe({ + next: (data: any) => { + resolve({ + result: { + value: data.volumeInfo.averageRating, + link: data.volumeInfo.infoLink, + }, + }); + }, + error: (error: HttpErrorResponse) => { + resolve({ + result: null, + error: { + type: error.status, + }, + }); + }, + }); + }); + } + + public ngOnDestroy(): void { + this.outputsSubject.complete(); + } +} + +/** + * A component that instantiates the dashboard + */ +@Component({ + selector: "kpi-widget-interactive-example", + templateUrl: "./kpi-widget-interactive-example.component.html", + styleUrls: ["./kpi-widget-interactive-example.component.less"], + standalone: false, +}) +export class KpiWidgetInteractiveExampleComponent implements OnInit { + // This variable will hold all the data needed to define the layout and behavior of the widgets. + // Pass this to the dashboard component's dashboard input in the template. + public dashboard: IDashboard; + + // Angular gridster requires a configuration object even if it's empty. + // Pass this to the dashboard component's gridsterConfig input in the template. + public gridsterConfig: GridsterConfig = {}; + + // Boolean passed as an input to the dashboard. When true, widgets can be moved, resized, removed, or edited + public editMode: boolean = false; + + constructor( + // WidgetTypesService provides the widget's necessary structure information + private widgetTypesService: WidgetTypesService, + + // In general, the ProviderRegistryService is used for making entities available for injection into dynamically loaded components. + private providerRegistry: ProviderRegistryService + ) {} + + public ngOnInit(): void { + // Grabbing the widget's default template which will be needed as a parameter for setNode + const widgetTemplate = this.widgetTypesService.getWidgetType("kpi", 1); + // Registering our data sources as dropdown options in the widget editor/configurator + // Note: This could also be done in the parent module's constructor so that + // multiple dashboards could have access to the same widget template modification. + this.widgetTypesService.setNode( + // This is the template we grabbed above with getWidgetType + widgetTemplate, + // We are setting the editor/configurator part of the widget template + "configurator", + // This indicates which node you are changing and we want to change + // the data source providers available for selection in the editor. + WellKnownPathKey.DataSourceProviders, + // We are setting the data sources available for selection in the editor + [BookRatingDataSource.providerId] + ); + + // Registering the data source for injection into the KPI tile. + // Note: Each tile of a KPI widget is assigned its own instance of the data source + this.providerRegistry.setProviders({ + [BookRatingDataSource.providerId]: { + provide: DATA_SOURCE, + useClass: BookRatingDataSource, + // Any dependencies that need to be injected into the provider must be listed here + deps: [HttpClient], + }, + }); + + this.initializeDashboard(); + } + + public initializeDashboard(): void { + // We're using a static configuration object for this example, but this is where + // the widget's configuration could potentially be populated from a database + const kpiWidget = widgetConfig; + const widgetIndex: IWidgets = { + // Complete the KPI widget with information coming from its type definition + [kpiWidget.id]: + this.widgetTypesService.mergeWithWidgetType(kpiWidget), + }; + + // Setting the widget dimensions and position (this is for gridster) + const positions: Record = { + [kpiWidget.id]: { + cols: 4, + rows: 6, + y: 0, + x: 0, + }, + }; + + // Finally, assigning the variables we created above to the dashboard + this.dashboard = { + positions, + widgets: widgetIndex, + }; + } +} + +const widgetConfig: IWidget = { + id: "kpiWidgetId", + type: "kpi", + pizzagna: { + [PizzagnaLayer.Configuration]: { + header: { + properties: { + title: "Harry Potter and the Order of the Phoenix", + subtitle: "By: J. K. Rowling", + }, + }, + tiles: { + providers: { + interaction: { + // Configuring the UrlInteractionHandler for interactions on the tiles + providerId: NOVA_URL_INTERACTION_HANDLER, + properties: { + // the 'url' property tells the handler what link to use when interaction occurs on the series + url: "\\\\\\\${data.link}", + }, + }, + }, + properties: { + nodes: ["kpi1"], + }, + }, + kpi1: { + id: "kpi1", + componentType: KpiComponent.lateLoadKey, + properties: { + widgetData: { + units: \\\\\\\`out of 5 stars\\\\\\\`, + label: \\\\\\\`Average Rating\\\\\\\`, + value: 0, + // the link property that is passed to the UrlInteractionHandler when the title is clicked + // this will be updated in BookRatingDataSource's 'getFilteredData' call. + link: "http://www.google.com", + }, + }, + providers: { + [WellKnownProviders.DataSource]: { + // Setting the data source providerId for the tile with id "kpi1" + providerId: BookRatingDataSource.providerId, + } as IProviderConfiguration, + [WellKnownProviders.Adapter]: { + providerId: NOVA_KPI_DATASOURCE_ADAPTER, + properties: { + componentId: "kpi1", + propertyPath: "widgetData", + }, + } as IProviderConfiguration, + }, + }, + }, + }, +}; +\\\`, + "widget-types/proportional/models.ts": \\\`export interface IMockBeerReview { + id: string; + name: string; + data: number[]; + icon: string; + link?: string; + value: string; + color?: string; +} +\\\`, + "widget-types/proportional/proportional-docs.component.ts": \\\`// © 2022 SolarWinds Worldwide, LLC. All rights reserved. +// +// Permission is hereby granted, free of charge, to any person obtaining a copy +// of this software and associated documentation files (the "Software"), to +// deal in the Software without restriction, including without limitation the +// rights to use, copy, modify, merge, publish, distribute, sublicense, and/or +// sell copies of the Software, and to permit persons to whom the Software is +// furnished to do so, subject to the following conditions: +// +// The above copyright notice and this permission notice shall be included in +// all copies or substantial portions of the Software. +// +// THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR +// IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, +// FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE +// AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER +// LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, +// OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN +// THE SOFTWARE. + +import { Component, OnInit } from "@angular/core"; + +import { mapContentFile } from "../../../../demo-files-factory"; + +@Component({ + selector: "nui-proportional-docs", + templateUrl: "./proportional-docs.component.html", + standalone: false, +}) +export class ProportionalDocsComponent implements OnInit { + public proportionalWidgetFileText = ""; + public proportionalConfiguratorFileText = ""; + + public async ngOnInit(): Promise { + this.proportionalWidgetFileText = await import( + "./../../../../../../src/lib/widget-types/proportional/proportional-widget" + ).then(mapContentFile); + this.proportionalConfiguratorFileText = await import( + "./../../../../../../src/lib/widget-types/proportional/proportional-configurator" + ).then(mapContentFile); + } +} +\\\`, + "widget-types/proportional/proportional-docs.module.ts": \\\`// © 2022 SolarWinds Worldwide, LLC. All rights reserved. +// +// Permission is hereby granted, free of charge, to any person obtaining a copy +// of this software and associated documentation files (the "Software"), to +// deal in the Software without restriction, including without limitation the +// rights to use, copy, modify, merge, publish, distribute, sublicense, and/or +// sell copies of the Software, and to permit persons to whom the Software is +// furnished to do so, subject to the following conditions: +// +// The above copyright notice and this permission notice shall be included in +// all copies or substantial portions of the Software. +// +// THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR +// IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, +// FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE +// AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER +// LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, +// OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN +// THE SOFTWARE. + +import { NgModule } from "@angular/core"; +import { RouterModule, Routes } from "@angular/router"; + +import { + NuiButtonModule, + NuiDocsModule, + NuiMessageModule, + NuiSwitchModule, + DEMO_PATH_TOKEN, +} from "@nova-ui/bits"; +import { NuiDashboardsModule } from "@nova-ui/dashboards"; + +import { ProportionalDocsComponent } from "./proportional-docs.component"; +import { ProportionalDonutContentDocsComponent } from "./proportional-donut-content-docs.component"; +import { ProportionalWidgetDonutContentFormattersExampleComponent } from "./proportional-donut-content-formatters/proportional-donut-content-formatters-example.component"; +import { ProportionalWidgetExampleComponent } from "./proportional-widget/proportional-widget-example.component"; +import { ProportionalWidgetInteractiveExampleComponent } from "./proportional-widget-interactive/proportional-widget-interactive-example.component"; +import { getDemoFiles } from "../../../../demo-files-factory"; + +const routes: Routes = [ + { + path: "", + component: ProportionalDocsComponent, + data: { + srlc: { + hideIndicator: true, + }, + showThemeSwitcher: true, + }, + }, + { + path: "example", + component: ProportionalWidgetExampleComponent, + data: { + srlc: { + hideIndicator: true, + }, + }, + }, + { + path: "donut-content-formatters", + component: ProportionalDonutContentDocsComponent, + data: { + srlc: { + hideIndicator: true, + }, + }, + }, + { + path: "donut-content-formatters-example", + component: ProportionalWidgetDonutContentFormattersExampleComponent, + data: { + srlc: { + hideIndicator: true, + }, + }, + }, + { + path: "proportional-widget-interactive-example", + component: ProportionalWidgetInteractiveExampleComponent, + data: { + srlc: { + hideIndicator: true, + }, + }, + }, +]; + +@NgModule({ + imports: [ + RouterModule.forChild(routes), + NuiButtonModule, + NuiDocsModule, + NuiDashboardsModule, + NuiMessageModule, + NuiSwitchModule, + ], + declarations: [ + ProportionalDocsComponent, + ProportionalWidgetExampleComponent, + ProportionalWidgetInteractiveExampleComponent, + ProportionalWidgetDonutContentFormattersExampleComponent, + ProportionalDonutContentDocsComponent, + ], + providers: [ + { + provide: DEMO_PATH_TOKEN, + useValue: getDemoFiles("proportional"), + }, + ], +}) +export default class ProportionalDocsModule {} +\\\`, + "widget-types/proportional/proportional-donut-content-docs.component.ts": \\\`// © 2022 SolarWinds Worldwide, LLC. All rights reserved. +// +// Permission is hereby granted, free of charge, to any person obtaining a copy +// of this software and associated documentation files (the "Software"), to +// deal in the Software without restriction, including without limitation the +// rights to use, copy, modify, merge, publish, distribute, sublicense, and/or +// sell copies of the Software, and to permit persons to whom the Software is +// furnished to do so, subject to the following conditions: +// +// The above copyright notice and this permission notice shall be included in +// all copies or substantial portions of the Software. +// +// THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR +// IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, +// FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE +// AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER +// LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, +// OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN +// THE SOFTWARE. + +import { Component } from "@angular/core"; + +@Component({ + selector: "nui-proportional-donut-content-docs", + templateUrl: "./proportional-donut-content-docs.component.html", + standalone: false, +}) +export class ProportionalDonutContentDocsComponent { + public dataSourceDataFieldsConfig = \\\\\\\` +public dataFieldsConfig: IProportionalDataFieldsConfig = { + dataFields$: new BehaviorSubject(this.dataFields), + chartSeriesDataFields$: new BehaviorSubject(this.chartSeriesDataFields), +}; + \\\\\\\`; + + public widgetConfigSlice = \\\\\\\` +"properties": { + "configuration": { + "chartOptions": { + donutContentConfig: { + formatter: { + componentType: SiUnitsFormatterComponent.lateLoadKey, + }, + aggregator: { + aggregatorType: sumAggregator.aggregatorType, + }, + }, + } + } +} + + \\\\\\\`; +} +\\\`, + "widget-types/proportional/proportional-donut-content-formatters/proportional-donut-content-formatters-example.component.ts": \\\`// © 2022 SolarWinds Worldwide, LLC. All rights reserved. +// +// Permission is hereby granted, free of charge, to any person obtaining a copy +// of this software and associated documentation files (the "Software"), to +// deal in the Software without restriction, including without limitation the +// rights to use, copy, modify, merge, publish, distribute, sublicense, and/or +// sell copies of the Software, and to permit persons to whom the Software is +// furnished to do so, subject to the following conditions: +// +// The above copyright notice and this permission notice shall be included in +// all copies or substantial portions of the Software. +// +// THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR +// IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, +// FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE +// AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER +// LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, +// OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN +// THE SOFTWARE. + +import { + ChangeDetectorRef, + Component, + Injectable, + OnDestroy, + OnInit, +} from "@angular/core"; +import { GridsterConfig, GridsterItem } from "angular-gridster2"; +import { BehaviorSubject } from "rxjs"; + +import { + DataSourceService, + IDataField, + IDataSource, + IFilteringOutputs, +} from "@nova-ui/bits"; +import { IAccessors, IChartAssistSeries } from "@nova-ui/charts"; +import { + DATA_SOURCE, + DEFAULT_LEGEND_FORMATTERS, + DEFAULT_PIZZAGNA_ROOT, + DEFAULT_PROPORTIONAL_CONTENT_AGGREGATORS, + DEFAULT_PROPORTIONAL_CONTENT_FORMATTERS, + DONUT_CONTENT_CONFIGURATION_SLICE, + IDashboard, + IDonutContentConfig, + IProportionalDataFieldsConfig, + IProportionalWidgetChartOptions, + IProportionalWidgetConfig, + IProviderConfiguration, + IWidget, + IWidgets, + LegendPlacement, + PizzagnaLayer, + ProportionalContentAggregatorsRegistryService, + ProportionalDonutContentFormattersRegistryService, + ProportionalLegendFormattersRegistryService, + ProportionalWidgetChartTypes, + ProviderRegistryService, + SiUnitsFormatterComponent, + sumAggregator, + WellKnownPathKey, + WellKnownProviders, + WidgetTypesService, +} from "@nova-ui/dashboards"; + +import { IMockBeerReview } from "../models"; + +/** + * A simple proportional data source to retrieve beer review counts by city + */ +@Injectable() +export class BeerReviewCountsByCityMockDataSource + extends DataSourceService> + implements IDataSource>, OnDestroy +{ + public static providerId = "BeerReviewCountsByCityMockDataSource"; + public busy = new BehaviorSubject(false); + + protected dataFields: IDataField[] = [ + { + id: "Brno", + label: "Brno", + // @ts-ignore + dataType: null, + }, + { + id: "kyiv", + label: "Kyiv", + // @ts-ignore + dataType: null, + }, + { + id: "austin", + label: "Austin", + // @ts-ignore + dataType: null, + }, + { + id: "lisbon", + label: "Lisbon", + // @ts-ignore + dataType: null, + }, + { + id: "sydney", + label: "Sydney", + // @ts-ignore + dataType: null, + }, + { + id: "nur-sultan", + label: "Nur-Sultan", + // @ts-ignore + dataType: null, + }, + ]; + protected chartSeriesDataFields: IDataField[] = [ + // default field in the chart series that is used for the aggregation + { + id: "data[0]", + label: "data", + // @ts-ignore + dataType: null, + }, + // any custom field in the chart series that is used for the aggregation + { + id: "customDonutContent", + label: "Custom Donut Content", + // @ts-ignore + dataType: null, + }, + ]; + + /** + * DataSource needs to implement the "IDataFieldsConfig" for this scenario. + * + * It's necessary to provide the "chartSeriesDataFields", + * that's why proportional widget dataSource has it's own interface for that - IProportionalDataFieldsConfig. + * + * dataFields$ - stands for possible series fields + * chartSeriesDataFields$ - stands for the fields IN the series + * + * see declaration of "dataFields" and "chartSeriesDataFields" for the example. + */ + public dataFieldsConfig: IProportionalDataFieldsConfig = { + dataFields$: new BehaviorSubject(this.dataFields), + chartSeriesDataFields$: new BehaviorSubject( + this.chartSeriesDataFields + ), + }; + + public async getFilteredData(): Promise { + this.busy.next(true); + return new Promise((resolve) => { + setTimeout(() => { + this.outputsSubject.next({ + result: getMockBeerReviewCountsByCity(), + }); + this.busy.next(false); + }, 300); + }); + } + + public ngOnDestroy(): void { + this.outputsSubject.complete(); + } +} + +/** + * A component that instantiates the dashboard + */ +@Component({ + selector: "proportional-widget-donut-content-formatters-example", + templateUrl: "./proportional-donut-content-formatters-example.component.html", + styleUrls: [ + "./proportional-donut-content-formatters-example.component.less", + ], + standalone: false, +}) +export class ProportionalWidgetDonutContentFormattersExampleComponent + implements OnInit +{ + public dashboard: IDashboard | undefined; + + // Angular gridster requires a configuration object even if it's empty. + // Pass this to the dashboard component's gridsterConfig input in the template. + public gridsterConfig: GridsterConfig = {}; + + // Boolean passed as an input to the dashboard. When true, widgets can be moved, resized, removed, or edited + public editMode: boolean = false; + + constructor( + // WidgetTypesService provides the widget's necessary structure information + private widgetTypesService: WidgetTypesService, + // In general, the ProviderRegistryService is used for making entities available for injection into dynamically loaded components. + private providerRegistry: ProviderRegistryService, + // registry for adding the formatter for donut content + contentFormattersRegistry: ProportionalDonutContentFormattersRegistryService, + // registry for adding the formatter for proportional legend + legendFormattersRegistry: ProportionalLegendFormattersRegistryService, + // registry for adding the aggregators for donut content + aggregatorRegistry: ProportionalContentAggregatorsRegistryService, + private changeDetectorRef: ChangeDetectorRef + ) { + // on the dashboard startup, it's necessary to add possible content formatters, legend formatters and content aggregators to the registry. + // using registry is a way for setting the available formatters. + legendFormattersRegistry.addItems(DEFAULT_LEGEND_FORMATTERS); + contentFormattersRegistry.addItems( + DEFAULT_PROPORTIONAL_CONTENT_FORMATTERS + ); + aggregatorRegistry.addItems(DEFAULT_PROPORTIONAL_CONTENT_AGGREGATORS); + } + + public ngOnInit(): void { + // Grabbing the widget's default template which will be needed as a parameter for setNode + const widgetTemplate = this.widgetTypesService.getWidgetType( + "proportional", + 1 + ); + + // Registering our data sources as dropdown options in the widget editor/configurator + // Note: This could also be done in the parent module's constructor so that + // multiple dashboards could have access to the same widget template modification. + this.widgetTypesService.setNode( + // This is the template we grabbed above with getWidgetType + widgetTemplate, + // We are setting the editor/configurator part of the widget template + "configurator", + // This indicates which node you are changing and we want to change + // the data source providers available for selection in the editor. + WellKnownPathKey.DataSourceProviders, + // We are setting the data sources available for selection in the editor + [BeerReviewCountsByCityMockDataSource.providerId] + ); + + // Setup of the configurator is done here + this.setupConfigurator(); + + // Registering the data source for injection into the Proportional widget. + this.providerRegistry.setProviders({ + [BeerReviewCountsByCityMockDataSource.providerId]: { + provide: DATA_SOURCE, + useClass: BeerReviewCountsByCityMockDataSource, + deps: [], + }, + }); + + this.initializeDashboard(); + } + + /** Used for restoring widgets state */ + public reInitializeDashboard(): void { + // destroys the components and their providers so the dashboard can re init data + this.dashboard = undefined; + this.changeDetectorRef.detectChanges(); + + this.initializeDashboard(); + } + + private initializeDashboard(): void { + // We're using a static configuration object for this example, but this is where + // the widget's configuration could potentially be populated from a database + const widgetIndex: IWidgets = { + // Complete the proportional widget with information coming from its type definition + [widgetConfig.id]: + this.widgetTypesService.mergeWithWidgetType(widgetConfig), + }; + + // Setting the widget dimensions and position (this is for gridster) + const positions: Record = { + [widgetConfig.id]: { + cols: 6, + rows: 6, + y: 0, + x: 0, + }, + }; + + // Finally, assigning the variables we created above to the dashboard + this.dashboard = { + positions, + widgets: widgetIndex, + }; + } + + /** + * Sets up the configurator sections for proportional donut + */ + private setupConfigurator() { + const widgetTemplate = this.widgetTypesService.getWidgetType( + "proportional", + 1 + ); + + // remove old "presentation", "chartOptionsEditor" and "donutContentConfiguration" sections from the configurator + delete widgetTemplate.configurator?.structure?.presentation; + delete widgetTemplate.configurator?.structure?.chartOptionsEditor; + delete widgetTemplate.configurator?.structure + ?.donutContentConfiguration; + + // add new "presentation" section + this.widgetTypesService.setNode( + widgetTemplate, + "configurator", + "presentation", + DONUT_CONTENT_CONFIGURATION_SLICE.presentation + ); + // add new "chartOptionsEditor" section + this.widgetTypesService.setNode( + widgetTemplate, + "configurator", + "chartOptionsEditor", + DONUT_CONTENT_CONFIGURATION_SLICE.chartOptionsEditor + ); + // add new "donutContentConfiguration" section + this.widgetTypesService.setNode( + widgetTemplate, + "configurator", + "donutContentConfiguration", + DONUT_CONTENT_CONFIGURATION_SLICE.donutContentConfiguration + ); + } +} + +const widgetConfig: IWidget = { + id: "proportionalWidgetId", + type: "proportional", + pizzagna: { + [PizzagnaLayer.Configuration]: { + [DEFAULT_PIZZAGNA_ROOT]: { + providers: {}, + }, + header: { + properties: { + title: "Beer Review Tally by City", + subtitle: "These People Love Beer", + }, + }, + chart: { + providers: { + [WellKnownProviders.DataSource]: { + // Setting the data source providerId for the chart + providerId: + BeerReviewCountsByCityMockDataSource.providerId, + } as IProviderConfiguration, + }, + properties: { + configuration: { + chartOptions: { + type: ProportionalWidgetChartTypes.DonutChart, + legendPlacement: LegendPlacement.Right, + // old configuration looks like this + // contentFormatter: { + // componentType: DonutContentSumFormatterComponent.lateLoadKey, + // }, + + // NEW configuration looks like this + donutContentConfig: { + formatter: { + componentType: + SiUnitsFormatterComponent.lateLoadKey, + }, + aggregator: { + aggregatorType: + sumAggregator.aggregatorType, + properties: { + // example of a default metric to be used for the percentage calculation + // activeMetricId: "austin", + }, + }, + } as IDonutContentConfig, + } as IProportionalWidgetChartOptions, + } as IProportionalWidgetConfig, + }, + }, + }, + }, +}; + +export function getMockBeerReviewCountsByCity(): IMockBeerReview[] { + return [ + { + id: "Brno", + name: "Brno", + data: [Math.round(Math.random() * 1000000)], + icon: "status_down", + link: "https://en.wikipedia.org/wiki/Brno", + value: "Brno", + customDonutContent: "Custom Brno", + }, + { + id: "kyiv", + name: "Kyiv", + data: [Math.round(Math.random() * 1000000)], + icon: "status_critical", + link: "https://en.wikipedia.org/wiki/Kyiv", + value: "Kyiv", + customDonutContent: "Custom Kyiv", + }, + { + id: "austin", + name: "Austin", + data: [Math.round(Math.random() * 1000000)], + icon: "status_warning", + link: "https://en.wikipedia.org/wiki/Austin", + value: "Austin", + customDonutContent: "Custom Austin", + }, + { + id: "lisbon", + name: "Lisbon", + data: [Math.round(Math.random() * 1000000)], + icon: "status_unknown", + link: "https://en.wikipedia.org/wiki/Lisbon", + value: "Lisbon", + customDonutContent: "Custom Lisbon", + }, + { + id: "sydney", + name: "Sydney", + data: [Math.round(Math.random() * 1000000)], + icon: "status_up", + link: "https://en.wikipedia.org/wiki/Sydney", + value: "Sydney", + customDonutContent: "Custom Sydney", + }, + { + id: "nur-sultan", + name: "Nur-Sultan", + data: [Math.round(Math.random() * 1000000)], + icon: "status_unmanaged", + link: "https://en.wikipedia.org/wiki/Nur-Sultan", + value: "Nur-Sultan", + customDonutContent: "Custom Nur-Sultan", + }, + ].sort((a, b) => a.data[0] - b.data[0]); +} +\\\`, + "widget-types/proportional/proportional-widget/proportional-widget-example.component.ts": \\\`// © 2022 SolarWinds Worldwide, LLC. All rights reserved. +// +// Permission is hereby granted, free of charge, to any person obtaining a copy +// of this software and associated documentation files (the "Software"), to +// deal in the Software without restriction, including without limitation the +// rights to use, copy, modify, merge, publish, distribute, sublicense, and/or +// sell copies of the Software, and to permit persons to whom the Software is +// furnished to do so, subject to the following conditions: +// +// The above copyright notice and this permission notice shall be included in +// all copies or substantial portions of the Software. +// +// THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR +// IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, +// FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE +// AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER +// LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, +// OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN +// THE SOFTWARE. + +import { + ChangeDetectorRef, + Component, + Injectable, + OnDestroy, + OnInit, +} from "@angular/core"; +import { GridsterConfig, GridsterItem } from "angular-gridster2"; +import { BehaviorSubject } from "rxjs"; + +import { + DataSourceService, + IDataSource, + IFilteringOutputs, +} from "@nova-ui/bits"; +import { + DATA_SOURCE, + DEFAULT_PIZZAGNA_ROOT, + IDashboard, + IProportionalWidgetChartOptions, + IProportionalWidgetConfig, + IProportionalWidgetData, + IProviderConfiguration, + IRefresherProperties, + IWidget, + IWidgets, + LegendPlacement, + PizzagnaLayer, + ProportionalWidgetChartTypes, + ProviderRegistryService, + WellKnownPathKey, + WellKnownProviders, + WidgetTypesService, +} from "@nova-ui/dashboards"; + +import { IMockBeerReview } from "../models"; + +/** + * A simple proportional data source to retrieve beer review counts by city + */ +@Injectable() +export class BeerReviewCountsByCityMockDataSource + extends DataSourceService + implements IDataSource, OnDestroy +{ + // This is the ID we'll use to identify the provider + public static providerId = "BeerReviewCountsByCityMockDataSource"; + public busy = new BehaviorSubject(false); + + public async getFilteredData(): Promise { + this.busy.next(true); + return new Promise((resolve) => { + setTimeout(() => { + this.outputsSubject.next({ + result: getMockBeerReviewCountsByCity(), + }); + this.busy.next(false); + }, 300); + }); + } + + public ngOnDestroy(): void { + this.outputsSubject.complete(); + } +} + +/** + * A component that instantiates the dashboard + */ +@Component({ + selector: "proportional-widget-example", + templateUrl: "./proportional-widget-example.component.html", + styleUrls: ["./proportional-widget-example.component.less"], + standalone: false, +}) +export class ProportionalWidgetExampleComponent implements OnInit { + // This variable will hold all the data needed to define the layout and behavior of the widgets. + // Pass this to the dashboard component's dashboard input in the template. + public dashboard: IDashboard | undefined; + + // Angular gridster requires a configuration object even if it's empty. + // Pass this to the dashboard component's gridsterConfig input in the template. + public gridsterConfig: GridsterConfig = {}; + + // Boolean passed as an input to the dashboard. When true, widgets can be moved, resized, removed, or edited + public editMode: boolean = false; + + constructor( + // WidgetTypesService provides the widget's necessary structure information + private widgetTypesService: WidgetTypesService, + // In general, the ProviderRegistryService is used for making entities available for injection into dynamically loaded components. + private providerRegistry: ProviderRegistryService, + private changeDetectorRef: ChangeDetectorRef + ) {} + + public ngOnInit(): void { + // Grabbing the widget's default template which will be needed as a parameter for setNode + const widgetTemplate = this.widgetTypesService.getWidgetType( + "proportional", + 1 + ); + + // Registering our data sources as dropdown options in the widget editor/configurator + // Note: This could also be done in the parent module's constructor so that + // multiple dashboards could have access to the same widget template modification. + this.widgetTypesService.setNode( + // This is the template we grabbed above with getWidgetType + widgetTemplate, + // We are setting the editor/configurator part of the widget template + "configurator", + // This indicates which node you are changing and we want to change + // the data source providers available for selection in the editor. + WellKnownPathKey.DataSourceProviders, + // We are setting the data sources available for selection in the editor + [BeerReviewCountsByCityMockDataSource.providerId] + ); + + // Registering the data source for injection into the Proportional widget. + this.providerRegistry.setProviders({ + [BeerReviewCountsByCityMockDataSource.providerId]: { + provide: DATA_SOURCE, + useClass: BeerReviewCountsByCityMockDataSource, + deps: [], + }, + }); + + this.initializeDashboard(); + } + + /** Used for restoring widgets state */ + public reInitializeDashboard(): void { + // destroys the components and their providers so the dashboard can re init data + this.dashboard = undefined; + this.changeDetectorRef.detectChanges(); + + this.initializeDashboard(); + } + + public initializeDashboard(): void { + // We're using a static configuration object for this example, but this is where + // the widget's configuration could potentially be populated from a database + const widgetIndex: IWidgets = { + // Complete the proportional widget with information coming from its type definition + [widgetConfig.id]: + this.widgetTypesService.mergeWithWidgetType(widgetConfig), + }; + + // Setting the widget dimensions and position (this is for gridster) + const positions: Record = { + [widgetConfig.id]: { + cols: 5, + rows: 6, + y: 0, + x: 0, + }, + }; + + // Finally, assigning the variables we created above to the dashboard + this.dashboard = { + positions, + widgets: widgetIndex, + }; + } +} + +const widgetConfig: IWidget = { + id: "proportionalWidgetId", + type: "proportional", + pizzagna: { + [PizzagnaLayer.Configuration]: { + [DEFAULT_PIZZAGNA_ROOT]: { + providers: { + [WellKnownProviders.Refresher]: { + properties: { + // Configuring the refresher interval so that our data source is invoked every ten minutes + interval: 60 * 10, + enabled: true, + } as IRefresherProperties, + } as Partial, + }, + }, + header: { + properties: { + title: "Beer Review Tally by City", + subtitle: "These People Love Beer", + }, + }, + chart: { + providers: { + [WellKnownProviders.DataSource]: { + // Setting the data source providerId for the chart + providerId: + BeerReviewCountsByCityMockDataSource.providerId, + } as IProviderConfiguration, + }, + properties: { + configuration: { + chartOptions: { + type: ProportionalWidgetChartTypes.DonutChart, + legendPlacement: LegendPlacement.Right, + } as IProportionalWidgetChartOptions, + // You can optionally define custom colors for the chart by setting the 'chartColors' configuration property + // "chartColors": [ + // "var(--nui-color-chart-five)", + // "var(--nui-color-chart-six)", + // "var(--nui-color-chart-seven)", + // "var(--nui-color-chart-eight)", + // "var(--nui-color-chart-nine)", + // "var(--nui-color-chart-ten)", + // ], + // or use-mapped structure + chartColors: { + Brno: "var(--nui-color-chart-five)", + kyiv: "var(--nui-color-chart-six)", + austin: "var(--nui-color-chart-seven)", + lisbon: "var(--nui-color-chart-eight)", + sydney: "var(--nui-color-chart-nine)", + "nur-sultan": "var(--nui-color-chart-ten)", + }, + prioritizeWidgetColors: false, + } as IProportionalWidgetConfig, + }, + }, + }, + }, +}; + +export function getMockBeerReviewCountsByCity(): IMockBeerReview[] { + return [ + { + id: "Brno", + name: "Brno", + data: [Math.round(Math.random() * 100000)], + icon: "status_down", + link: "https://en.wikipedia.org/wiki/Brno", + value: "Brno", + color: "var(--nui-color-chart-one)", + }, + { + id: "kyiv", + name: "Kyiv", + data: [Math.round(Math.random() * 100000)], + icon: "status_critical", + link: "https://en.wikipedia.org/wiki/Kyiv", + value: "Kyiv", + color: "var(--nui-color-chart-two)", + }, + { + id: "austin", + name: "Austin", + data: [Math.round(Math.random() * 100000)], + icon: "status_warning", + link: "https://en.wikipedia.org/wiki/Austin", + value: "Austin", + color: "var(--nui-color-chart-three)", + }, + { + id: "lisbon", + name: "Lisbon", + data: [Math.round(Math.random() * 100000)], + icon: "status_unknown", + link: "https://en.wikipedia.org/wiki/Lisbon", + value: "Lisbon", + color: "var(--nui-color-chart-four)", + }, + { + id: "sydney", + name: "Sydney", + data: [Math.round(Math.random() * 100000)], + icon: "status_up", + link: "https://en.wikipedia.org/wiki/Sydney", + value: "Sydney", + color: "var(--nui-color-chart-five)", + }, + { + id: "nur-sultan", + name: "Nur-Sultan", + data: [Math.round(Math.random() * 100000)], + icon: "status_unmanaged", + link: "https://en.wikipedia.org/wiki/Nur-Sultan", + value: "Nur-Sultan", + color: "var(--nui-color-chart-six)", + }, + ].sort((a, b) => a.data[0] - b.data[0]); +} +\\\`, + "widget-types/proportional/proportional-widget-interactive/proportional-widget-interactive-example.component.ts": \\\`// © 2022 SolarWinds Worldwide, LLC. All rights reserved. +// +// Permission is hereby granted, free of charge, to any person obtaining a copy +// of this software and associated documentation files (the "Software"), to +// deal in the Software without restriction, including without limitation the +// rights to use, copy, modify, merge, publish, distribute, sublicense, and/or +// sell copies of the Software, and to permit persons to whom the Software is +// furnished to do so, subject to the following conditions: +// +// The above copyright notice and this permission notice shall be included in +// all copies or substantial portions of the Software. +// +// THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR +// IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, +// FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE +// AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER +// LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, +// OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN +// THE SOFTWARE. + +import { + ChangeDetectorRef, + Component, + Injectable, + OnDestroy, + OnInit, +} from "@angular/core"; +import { GridsterConfig, GridsterItem } from "angular-gridster2"; +import keyBy from "lodash/keyBy"; +import { BehaviorSubject } from "rxjs"; + +import { + DataSourceService, + IDataSource, + IFilteringOutputs, +} from "@nova-ui/bits"; +import { + DATA_SOURCE, + DEFAULT_PIZZAGNA_ROOT, + IDashboard, + IProportionalWidgetChartOptions, + IProportionalWidgetConfig, + IProportionalWidgetData, + IProviderConfiguration, + IWidget, + LegendPlacement, + NOVA_URL_INTERACTION_HANDLER, + PizzagnaLayer, + ProportionalWidgetChartTypes, + ProviderRegistryService, + WellKnownPathKey, + WellKnownProviders, + WidgetTypesService, +} from "@nova-ui/dashboards"; + +import { IMockBeerReview } from "../models"; + +/** + * A simple proportional data source to retrieve beer review counts by city + */ +@Injectable() +export class ReviewCountsByCityMockDataSource + extends DataSourceService + implements IDataSource, OnDestroy +{ + // This is the ID we'll use to identify the provider + public static providerId = "ReviewCountsByCityMockDataSource"; + public busy = new BehaviorSubject(false); + + public async getFilteredData(): Promise { + this.busy.next(true); + return new Promise((resolve) => { + setTimeout(() => { + this.outputsSubject.next({ + result: getMockBeerReviewCountsByCity(), + }); + this.busy.next(false); + }, 300); + }); + } + + public ngOnDestroy(): void { + this.outputsSubject.complete(); + } +} + +/** + * A component that instantiates the dashboard + */ +@Component({ + selector: "proportional-widget-interactive-example", + templateUrl: "./proportional-widget-interactive-example.component.html", + styleUrls: ["./proportional-widget-interactive-example.component.less"], + standalone: false, +}) +export class ProportionalWidgetInteractiveExampleComponent implements OnInit { + // This variable will hold all the data needed to define the layout and behavior of the widgets. + // Pass this to the dashboard component's dashboard input in the template. + public dashboard: IDashboard | undefined; + + // Angular gridster requires a configuration object even if it's empty. + // Pass this to the dashboard component's gridsterConfig input in the template. + public gridsterConfig: GridsterConfig = {}; + + // Boolean passed as an input to the dashboard. When true, widgets can be moved, resized, removed, or edited + public editMode: boolean = false; + + constructor( + // WidgetTypesService provides the widget's necessary structure information + private widgetTypesService: WidgetTypesService, + // In general, the ProviderRegistryService is used for making entities available for injection into dynamically loaded components. + private providerRegistry: ProviderRegistryService, + private changeDetectorRef: ChangeDetectorRef + ) {} + + public ngOnInit(): void { + // Grabbing the widget's default template which will be needed as a parameter for setNode + const widgetTemplate = this.widgetTypesService.getWidgetType( + "proportional", + 1 + ); + + // Registering our data sources as dropdown options in the widget editor/configurator + // Note: This could also be done in the parent module's constructor so that + // multiple dashboards could have access to the same widget template modification. + this.widgetTypesService.setNode( + // This is the template we grabbed above with getWidgetType + widgetTemplate, + // We are setting the editor/configurator part of the widget template + "configurator", + // This indicates which node you are changing and we want to change + // the data source providers available for selection in the editor. + WellKnownPathKey.DataSourceProviders, + // We are setting the data sources available for selection in the editor + [ReviewCountsByCityMockDataSource.providerId] + ); + + // Registering the data source for injection into the Proportional widget. + this.providerRegistry.setProviders({ + [ReviewCountsByCityMockDataSource.providerId]: { + provide: DATA_SOURCE, + useClass: ReviewCountsByCityMockDataSource, + deps: [], + }, + }); + + this.initializeDashboard(); + } + + /** Used for restoring widgets state */ + public reInitializeDashboard(): void { + // destroys the components and their providers so the dashboard can re init data + this.dashboard = undefined; + this.changeDetectorRef.detectChanges(); + + this.initializeDashboard(); + } + + public initializeDashboard(): void { + // We're using a static configuration object for this example, but this is where + // the widget's configuration could potentially be populated from a database + const widgetsWithStructure = widgetConfigs.map((w) => + this.widgetTypesService.mergeWithWidgetType(w) + ); + const widgetsIndex = keyBy(widgetsWithStructure, (w: IWidget) => w.id); + + // Setting the widget dimensions and position (this is for gridster) + const positions: Record = { + [widgetConfigs[0].id]: { + cols: 6, + rows: 6, + y: 0, + x: 0, + }, + [widgetConfigs[1].id]: { + cols: 6, + rows: 6, + y: 0, + x: 6, + }, + }; + + // Finally, assigning the variables we created above to the dashboard + this.dashboard = { + positions, + widgets: widgetsIndex, + }; + } +} + +const widgetConfigs: IWidget[] = [ + { + id: "widget1", + type: "proportional", + pizzagna: { + [PizzagnaLayer.Configuration]: { + [DEFAULT_PIZZAGNA_ROOT]: { + providers: { + // Configuring the UrlInteractionHandler to handle interactions + [WellKnownProviders.InteractionHandler]: { + providerId: NOVA_URL_INTERACTION_HANDLER, + properties: { + // the 'url' property tells the handler what link to use when interaction occurs on the series + // if the series does not have a link we are passing one to the handler + url: "\\\\\\\${data.link || 'https://en.wikipedia.org/wiki/'+data.id}", + // by default the link is opened in the current window, set 'newWindow' to true to open in a new tab instead + // newWindow: true, + }, + }, + }, + }, + header: { + properties: { + title: "Proportional Widget", + subtitle: "With interaction handler", + }, + }, + chart: { + providers: { + [WellKnownProviders.DataSource]: { + // Setting the data source providerId for the chart + providerId: + ReviewCountsByCityMockDataSource.providerId, + } as IProviderConfiguration, + }, + properties: { + configuration: { + // Setting the interactive to true + interactive: true, + chartOptions: { + type: ProportionalWidgetChartTypes.VerticalBarChart, + legendPlacement: LegendPlacement.Bottom, + } as IProportionalWidgetChartOptions, + prioritizeWidgetColors: false, + } as IProportionalWidgetConfig, + }, + }, + }, + }, + }, + { + id: "widget2", + type: "proportional", + pizzagna: { + [PizzagnaLayer.Configuration]: { + header: { + properties: { + title: "Proportional Widget", + subtitle: "Without interaction handler", + }, + }, + chart: { + providers: { + [WellKnownProviders.DataSource]: { + // Setting the data source providerId for the chart + providerId: + ReviewCountsByCityMockDataSource.providerId, + } as IProviderConfiguration, + }, + properties: { + configuration: { + // interactive set to false so series without links are not styled like a link + interactive: false, + chartOptions: { + type: ProportionalWidgetChartTypes.HorizontalBarChart, + legendPlacement: LegendPlacement.Bottom, + } as IProportionalWidgetChartOptions, + prioritizeWidgetColors: false, + } as IProportionalWidgetConfig, + }, + }, + }, + }, + }, +]; + +export function getMockBeerReviewCountsByCity(): IMockBeerReview[] { + return [ + { + id: "Brno", + name: "Brno", + data: [Math.round(Math.random() * 100000)], + icon: "status_down", + link: "https://en.wikipedia.org/wiki/Brno", + value: "Brno", + color: "var(--nui-color-chart-one)", + }, + { + id: "kyiv", + name: "Kyiv", + data: [Math.round(Math.random() * 100000)], + icon: "status_critical", + link: "https://en.wikipedia.org/wiki/Kyiv", + value: "Kyiv", + color: "var(--nui-color-chart-two)", + }, + { + id: "austin", + name: "Austin", + data: [Math.round(Math.random() * 100000)], + icon: "status_warning", + value: "Austin", + color: "var(--nui-color-chart-three)", + }, + { + id: "lisbon", + name: "Lisbon", + data: [Math.round(Math.random() * 100000)], + icon: "status_unknown", + link: "https://en.wikipedia.org/wiki/Lisbon", + value: "Lisbon", + color: "var(--nui-color-chart-four)", + }, + { + id: "sydney", + name: "Sydney", + data: [Math.round(Math.random() * 100000)], + icon: "status_up", + value: "Sydney", + color: "var(--nui-color-chart-five)", + }, + { + id: "nur-sultan", + name: "Nur-Sultan", + data: [Math.round(Math.random() * 100000)], + icon: "status_unmanaged", + value: "Nur-Sultan", + color: "var(--nui-color-chart-six)", + }, + ].sort((a, b) => a.data[0] - b.data[0]); +} +\\\`, + "widget-types/risk-score/risk-score-docs.component.ts": \\\`// © 2023 SolarWinds Worldwide, LLC. All rights reserved. +// +// Permission is hereby granted, free of charge, to any person obtaining a copy +// of this software and associated documentation files (the "Software"), to +// deal in the Software without restriction, including without limitation the +// rights to use, copy, modify, merge, publish, distribute, sublicense, and/or +// sell copies of the Software, and to permit persons to whom the Software is +// furnished to do so, subject to the following conditions: +// +// The above copyright notice and this permission notice shall be included in +// all copies or substantial portions of the Software. +// +// THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR +// IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, +// FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE +// AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER +// LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, +// OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN +// THE SOFTWARE. + +import { Component, OnInit } from "@angular/core"; + +import { mapContentFile } from "../../../../demo-files-factory"; + +@Component({ + selector: "nui-risk-score-docs", + templateUrl: "./risk-score-docs.component.html", + standalone: false, +}) +export class RiskScoreDocsComponent implements OnInit { + public riskScoreWidgetFileText = ""; + public riskScoreConfiguratorFileText = ""; + + public async ngOnInit(): Promise { + this.riskScoreWidgetFileText = await import( + "./../../../../../../src/lib/widget-types/risk-score/risk-score-widget" + ).then(mapContentFile); + this.riskScoreConfiguratorFileText = await import( + "./../../../../../../src/lib/widget-types/risk-score/risk-score-configurator" + ).then(mapContentFile); + } +} +\\\`, + "widget-types/risk-score/risk-score-docs.module.ts": \\\`// © 2023 SolarWinds Worldwide, LLC. All rights reserved. +// +// Permission is hereby granted, free of charge, to any person obtaining a copy +// of this software and associated documentation files (the "Software"), to +// deal in the Software without restriction, including without limitation the +// rights to use, copy, modify, merge, publish, distribute, sublicense, and/or +// sell copies of the Software, and to permit persons to whom the Software is +// furnished to do so, subject to the following conditions: +// +// The above copyright notice and this permission notice shall be included in +// all copies or substantial portions of the Software. +// +// THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR +// IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, +// FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE +// AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER +// LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, +// OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN +// THE SOFTWARE. + +import { NgModule } from "@angular/core"; +import { RouterModule, Routes } from "@angular/router"; + +import { + DEMO_PATH_TOKEN, + NuiButtonModule, + NuiDocsModule, + NuiMessageModule, + NuiSwitchModule, +} from "@nova-ui/bits"; +import { NuiDashboardsModule } from "@nova-ui/dashboards"; + +import { RiskScoreDocsComponent } from "./risk-score-docs.component"; +import { RiskScoreWidgetExampleComponent } from "./risk-score-widget-example/risk-score-widget-example.component"; +import { getDemoFiles } from "../../../../demo-files-factory"; + +const routes: Routes = [ + { + path: "", + component: RiskScoreDocsComponent, + data: { + srlc: { + hideIndicator: true, + }, + showThemeSwitcher: true, + }, + }, + { + path: "example", + component: RiskScoreWidgetExampleComponent, + data: { + srlc: { + hideIndicator: true, + }, + }, + }, +]; + +@NgModule({ + imports: [ + RouterModule.forChild(routes), + NuiButtonModule, + NuiDocsModule, + NuiMessageModule, + NuiDashboardsModule, + NuiSwitchModule, + ], + declarations: [RiskScoreDocsComponent, RiskScoreWidgetExampleComponent], + providers: [ + { + provide: DEMO_PATH_TOKEN, + useValue: getDemoFiles("risk-score"), + }, + ], +}) +export default class RiskScoreDocsModule {} +\\\`, + "widget-types/risk-score/risk-score-widget-example/risk-score-widget-example.component.ts": \\\`// © 2023 SolarWinds Worldwide, LLC. All rights reserved. +// +// Permission is hereby granted, free of charge, to any person obtaining a copy +// of this software and associated documentation files (the "Software"), to +// deal in the Software without restriction, including without limitation the +// rights to use, copy, modify, merge, publish, distribute, sublicense, and/or +// sell copies of the Software, and to permit persons to whom the Software is +// furnished to do so, subject to the following conditions: +// +// The above copyright notice and this permission notice shall be included in +// all copies or substantial portions of the Software. +// +// THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR +// IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, +// FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE +// AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER +// LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, +// OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN +// THE SOFTWARE. + +import { HttpClient, HttpErrorResponse } from "@angular/common/http"; +import { Component, Injectable, OnDestroy, OnInit } from "@angular/core"; +import { GridsterConfig, GridsterItem } from "angular-gridster2"; +import { BehaviorSubject } from "rxjs"; +import { finalize } from "rxjs/operators"; + +import { DataSourceService, IFilteringOutputs } from "@nova-ui/bits"; +import { + DATA_SOURCE, + DEFAULT_PIZZAGNA_ROOT, + IDashboard, + IRiskScoreData, + IProviderConfiguration, + IRefresherProperties, + IWidget, + IWidgets, + RiskScoreTileComponent, + NOVA_KPI_DATASOURCE_ADAPTER, + PizzagnaLayer, + ProviderRegistryService, + WellKnownPathKey, + WellKnownProviders, + WidgetTypesService, +} from "@nova-ui/dashboards"; + +/** + * A simple KPI data source to retrieve the average rating of Harry Potter and the Sorcerer's Stone (book) via googleapis + */ +@Injectable() +export class AverageRatingRiskScoreDataSource + extends DataSourceService + implements OnDestroy +{ + // This is the ID we'll use to identify the provider + public static providerId = "AverageRatingRiskScoreDataSource"; + + // Use this subject to communicate the data source's busy state + public busy = new BehaviorSubject(false); + + constructor(private http: HttpClient) { + super(); + } + + // In this example, getFilteredData is invoked every 10 minutes (Take a look at the refresher + // provider definition in the widget configuration below to see how the interval is set) + public async getFilteredData(): Promise { + this.busy.next(true); + return new Promise((resolve) => { + // *** Make a resource request to an external API (if needed) + this.http + .get("https://www.googleapis.com/books/v1/volumes/5MQFrgEACAAJ") + .pipe(finalize(() => this.busy.next(false))) + .subscribe({ + next: (data: any) => { + resolve({ + result: { + value: data.volumeInfo.averageRating, + }, + }); + }, + error: (error: HttpErrorResponse) => { + resolve({ + result: null, + error: { + type: error.status, + }, + }); + }, + }); + }); + } + + public ngOnDestroy(): void { + this.outputsSubject.complete(); + } +} + +/** + * A component that instantiates the dashboard + */ +@Component({ + selector: "risk-score-widget-example", + templateUrl: "./risk-score-widget-example.component.html", + styleUrls: ["./risk-score-widget-example.component.less"], + standalone: false, +}) +export class RiskScoreWidgetExampleComponent implements OnInit { + // This variable will hold all the data needed to define the layout and behavior of the widgets. + // Pass this to the dashboard component's dashboard input in the template. + public dashboard: IDashboard; + + // Angular gridster requires a configuration object even if it's empty. + // Pass this to the dashboard component's gridsterConfig input in the template. + public gridsterConfig: GridsterConfig = {}; + + // Boolean passed as an input to the dashboard. When true, widgets can be moved, resized, removed, or edited + public editMode: boolean = false; + + constructor( + // WidgetTypesService provides the widget's necessary structure information + private widgetTypesService: WidgetTypesService, + + // In general, the ProviderRegistryService is used for making entities available for injection into dynamically loaded components. + private providerRegistry: ProviderRegistryService + ) {} + + public ngOnInit(): void { + // Grabbing the widget's default template which will be needed as a parameter for setNode + const widgetTemplate = this.widgetTypesService.getWidgetType( + "risk-score", + 1 + ); + // Registering our data sources as dropdown options in the widget editor/configurator + // Note: This could also be done in the parent module's constructor so that + // multiple dashboards could have access to the same widget template modification. + this.widgetTypesService.setNode( + // This is the template we grabbed above with getWidgetType + widgetTemplate, + // We are setting the editor/configurator part of the widget template + "configurator", + // This indicates which node you are changing and we want to change + // the data source providers available for selection in the editor. + WellKnownPathKey.DataSourceProviders, + // We are setting the data sources available for selection in the editor + [AverageRatingRiskScoreDataSource.providerId] + ); + + // Registering the data source for injection into the KPI tile. + // Note: Each tile of a KPI widget is assigned its own instance of the data source + this.providerRegistry.setProviders({ + [AverageRatingRiskScoreDataSource.providerId]: { + provide: DATA_SOURCE, + useClass: AverageRatingRiskScoreDataSource, + // Any dependencies that need to be injected into the provider must be listed here + deps: [HttpClient], + }, + }); + + this.initializeDashboard(); + } + + public initializeDashboard(): void { + // We're using a static configuration object for this example, but this is where + // the widget's configuration could potentially be populated from a database + const riskScoreWidget = widgetConfig; + const widgetIndex: IWidgets = { + // Complete the KPI widget with information coming from its type definition + [riskScoreWidget.id]: + this.widgetTypesService.mergeWithWidgetType(riskScoreWidget), + }; + + // Setting the widget dimensions and position (this is for gridster) + const positions: Record = { + [riskScoreWidget.id]: { + cols: 4, + rows: 6, + y: 0, + x: 0, + }, + }; + + // Finally, assigning the variables we created above to the dashboard + this.dashboard = { + positions, + widgets: widgetIndex, + }; + } +} + +const widgetConfig: IWidget = { + id: "riskScoreWidgetId", + type: "risk-score", + pizzagna: { + [PizzagnaLayer.Configuration]: { + [DEFAULT_PIZZAGNA_ROOT]: { + providers: { + [WellKnownProviders.Refresher]: { + properties: { + // Configuring the refresher interval so that our data source is invoked every ten minutes + interval: 60 * 10, + enabled: true, + } as IRefresherProperties, + } as Partial, + }, + }, + header: { + properties: { + title: "Harry Potter and the Sorcerer's Stone", + subtitle: "By J. K. Rowling", + }, + }, + tiles: { + properties: { + nodes: ["riskScore1"], + }, + }, + riskScore1: { + id: "riskScore1", + componentType: RiskScoreTileComponent.lateLoadKey, + properties: { + widgetData: { + minValue: 0, + maxValue: 5, + useStaticLabel: false, + staticLabel: undefined, + label: \\\\\\\`Average Rating\\\\\\\`, + description: \\\\\\\`Harry Potter and the Sorcerer's Stone By J. K. Rowling Average Rating Risk Score\\\\\\\`, + }, + }, + providers: { + [WellKnownProviders.DataSource]: { + // Setting the data source providerId for the tile with id "kpi1" + providerId: AverageRatingRiskScoreDataSource.providerId, + } as IProviderConfiguration, + [WellKnownProviders.Adapter]: { + providerId: NOVA_KPI_DATASOURCE_ADAPTER, + properties: { + componentId: "riskScore1", + propertyPath: "widgetData", + }, + } as IProviderConfiguration, + }, + }, + }, + }, +}; +\\\`, + "widget-types/table/table-docs.component.ts": \\\`// © 2022 SolarWinds Worldwide, LLC. All rights reserved. +// +// Permission is hereby granted, free of charge, to any person obtaining a copy +// of this software and associated documentation files (the "Software"), to +// deal in the Software without restriction, including without limitation the +// rights to use, copy, modify, merge, publish, distribute, sublicense, and/or +// sell copies of the Software, and to permit persons to whom the Software is +// furnished to do so, subject to the following conditions: +// +// The above copyright notice and this permission notice shall be included in +// all copies or substantial portions of the Software. +// +// THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR +// IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, +// FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE +// AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER +// LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, +// OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN +// THE SOFTWARE. + +import { Component, OnInit } from "@angular/core"; + +import { mapContentFile } from "../../../../demo-files-factory"; + +@Component({ + selector: "nui-table-docs", + templateUrl: "./table-docs.component.html", + standalone: false, +}) +export class TableDocsComponent implements OnInit { + public widgetFileText = ""; + public configuratorFileText = ""; + + public async ngOnInit(): Promise { + this.widgetFileText = await import( + "./../../../../../../src/lib/widget-types/table/table-widget" + ).then(mapContentFile); + this.configuratorFileText = await import( + "./../../../../../../src/lib/widget-types/table/table-configurator" + ).then(mapContentFile); + } +} +\\\`, + "widget-types/table/table-docs.module.ts": \\\`// © 2022 SolarWinds Worldwide, LLC. All rights reserved. +// +// Permission is hereby granted, free of charge, to any person obtaining a copy +// of this software and associated documentation files (the "Software"), to +// deal in the Software without restriction, including without limitation the +// rights to use, copy, modify, merge, publish, distribute, sublicense, and/or +// sell copies of the Software, and to permit persons to whom the Software is +// furnished to do so, subject to the following conditions: +// +// The above copyright notice and this permission notice shall be included in +// all copies or substantial portions of the Software. +// +// THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR +// IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, +// FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE +// AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER +// LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, +// OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN +// THE SOFTWARE. + +import { NgModule } from "@angular/core"; +import { RouterModule, Routes } from "@angular/router"; + +import { DEMO_PATH_TOKEN } from "@nova-ui/bits"; +import { + NuiButtonModule, + NuiDocsModule, + NuiMessageModule, + NuiSwitchModule, +} from "@nova-ui/bits"; +import { + NuiDashboardsModule, + TableFormatterRegistryService, +} from "@nova-ui/dashboards"; + +import { TableDocsComponent } from "./table-docs.component"; +import { TablePaginatorDocsComponent } from "./table-paginator-docs.component"; +import { TableSelectableDocsComponent } from "./table-selectable-docs.component"; +import { TableWidgetExampleComponent } from "./table-widget/table-widget-example.component"; +import { TableWidgetInteractiveExampleComponent } from "./table-widget-interactive/table-widget-interactive-example.component"; +import { TableWidgetPaginatorExampleComponent } from "./table-widget-paginator/table-widget-paginator-example.component"; +import { TableWidgetSearchExampleComponent } from "./table-widget-search/table-widget-search-example.component"; +import { TableSearchDocsComponent } from "./table-widget-search-docs.component"; +import { TableWidgetSelectableMultiExampleComponent } from "./table-widget-selectable/table-widget-selectable-multi/table-widget-selectable-multi.example.component"; +import { TableWidgetSelectableRadioExampleComponent } from "./table-widget-selectable/table-widget-selectable-radio/table-widget-selectable-radio.example.component"; +import { TableWidgetSelectableSingleExampleComponent } from "./table-widget-selectable/table-widget-selectable-single/table-widget-selectable-single.example.component"; +import { TableWidgetSelectableExampleComponent } from "./table-widget-selectable/table-widget-selectable.example.component"; +import { DEFAULT_TABLE_FORMATTERS } from "../../../../../../src/lib/widget-types/table/default-table-formatters"; +import { getDemoFiles } from "../../../../demo-files-factory"; + +const routes: Routes = [ + { + path: "", + component: TableDocsComponent, + data: { + srlc: { + hideIndicator: true, + }, + showThemeSwitcher: true, + }, + }, + { + path: "example", + component: TableWidgetExampleComponent, + data: { + srlc: { + hideIndicator: true, + }, + }, + }, + { + path: "table-search", + component: TableSearchDocsComponent, + data: { + srlc: { + hideIndicator: true, + }, + showThemeSwitcher: true, + }, + }, + { + path: "table-paginator", + component: TablePaginatorDocsComponent, + data: { + srlc: { + hideIndicator: true, + }, + showThemeSwitcher: true, + }, + }, + { + path: "table-select", + component: TableSelectableDocsComponent, + data: { + srlc: { + hideIndicator: true, + }, + showThemeSwitcher: true, + }, + }, +]; + +@NgModule({ + imports: [ + RouterModule.forChild(routes), + NuiButtonModule, + NuiDocsModule, + NuiMessageModule, + NuiSwitchModule, + NuiDashboardsModule, + ], + declarations: [ + TableDocsComponent, + TableSearchDocsComponent, + TablePaginatorDocsComponent, + TableWidgetPaginatorExampleComponent, + TableSelectableDocsComponent, + TableWidgetInteractiveExampleComponent, + TableWidgetExampleComponent, + TableWidgetSearchExampleComponent, + TableWidgetSelectableExampleComponent, + TableWidgetSelectableMultiExampleComponent, + TableWidgetSelectableSingleExampleComponent, + TableWidgetSelectableRadioExampleComponent, + ], + providers: [ + { + provide: DEMO_PATH_TOKEN, + useValue: getDemoFiles("table"), + }, + ], +}) +export default class TableDocsModule { + constructor(tableFormattersRegistryService: TableFormatterRegistryService) { + tableFormattersRegistryService.addItems(DEFAULT_TABLE_FORMATTERS); + } +} +\\\`, + "widget-types/table/table-paginator-docs.component.ts": \\\`// © 2022 SolarWinds Worldwide, LLC. All rights reserved. +// +// Permission is hereby granted, free of charge, to any person obtaining a copy +// of this software and associated documentation files (the "Software"), to +// deal in the Software without restriction, including without limitation the +// rights to use, copy, modify, merge, publish, distribute, sublicense, and/or +// sell copies of the Software, and to permit persons to whom the Software is +// furnished to do so, subject to the following conditions: +// +// The above copyright notice and this permission notice shall be included in +// all copies or substantial portions of the Software. +// +// THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR +// IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, +// FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE +// AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER +// LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, +// OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN +// THE SOFTWARE. + +import { Component } from "@angular/core"; + +@Component({ + selector: "nui-table-paginator-docs", + templateUrl: "./table-paginator-docs.component.html", + standalone: false, +}) +export class TablePaginatorDocsComponent { + public tableConfigurationText = \\\\\\\` + "table": { + ... + properties: { + configuration: { + // define paginator configuration here + scrollType: ScrollType.paginator, + paginatorConfiguration: { + pageSize: 10, // Value have to be one of pageSizeSet values + pageSizeSet: [10, 20, 30], + }, + // If not specified, default is set to + // pageSize: 10, + // pageSizeSet: [10, 20, 50], + hasVirtualScroll: false, // Has to be speciefied because of backward compatibility + } as ITableWidgetConfig, + }, + }, + \\\\\\\`; +} +\\\`, + "widget-types/table/table-selectable-docs.component.ts": \\\`// © 2022 SolarWinds Worldwide, LLC. All rights reserved. +// +// Permission is hereby granted, free of charge, to any person obtaining a copy +// of this software and associated documentation files (the "Software"), to +// deal in the Software without restriction, including without limitation the +// rights to use, copy, modify, merge, publish, distribute, sublicense, and/or +// sell copies of the Software, and to permit persons to whom the Software is +// furnished to do so, subject to the following conditions: +// +// The above copyright notice and this permission notice shall be included in +// all copies or substantial portions of the Software. +// +// THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR +// IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, +// FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE +// AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER +// LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, +// OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN +// THE SOFTWARE. + +import { Component } from "@angular/core"; + +@Component({ + selector: "nui-table-selectable-docs", + templateUrl: "./table-selectable-docs.component.html", + standalone: false, +}) +export class TableSelectableDocsComponent { + public tableConfigurationText = \\\\\\\` + "table": { + ... + properties: { + // enabling selection here + selectionConfiguration: { + // whether the selection is enabled or disabled + enabled: true, + // can be Multi | Radio | Single + selectionMode: TableSelectionMode.Multi, + // property that uniquely identifies row in a table + trackByProperty: "id", + // whether clicking on row should select it + clickableRow: true, + }, + }, + }, + \\\\\\\`; + + public eventSubscriptionText = \\\\\\\` +... +constructor(Inject(PIZZAGNA_EVENT_BUS) eventBus: EventBus) { + eventBus + .getStream(SELECTION) + // don't forget to unsubscribe! + .subscribe((selection: ISelection) => ...) +} +... + \\\\\\\`; +} +\\\`, + "widget-types/table/table-widget/table-widget-example.component.ts": \\\`// © 2022 SolarWinds Worldwide, LLC. All rights reserved. +// +// Permission is hereby granted, free of charge, to any person obtaining a copy +// of this software and associated documentation files (the "Software"), to +// deal in the Software without restriction, including without limitation the +// rights to use, copy, modify, merge, publish, distribute, sublicense, and/or +// sell copies of the Software, and to permit persons to whom the Software is +// furnished to do so, subject to the following conditions: +// +// The above copyright notice and this permission notice shall be included in +// all copies or substantial portions of the Software. +// +// THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR +// IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, +// FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE +// AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER +// LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, +// OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN +// THE SOFTWARE. + +import { ChangeDetectorRef, Component, OnInit } from "@angular/core"; +import { GridsterConfig, GridsterItem } from "angular-gridster2"; +import orderBy from "lodash/orderBy"; +import { BehaviorSubject, firstValueFrom, from } from "rxjs"; +import { map, tap } from "rxjs/operators"; + +import { + DataSourceService, + IDataField, + INovaFilteringOutputs, + INovaFilters, + nameof, +} from "@nova-ui/bits"; +import { + DATA_SOURCE, + IDashboard, + ITableWidgetColumnConfig, + IWidget, + IWidgets, + ProviderRegistryService, + WellKnownPathKey, + WellKnownProviders, + WidgetTypesService, +} from "@nova-ui/dashboards"; + +export const BREW_API_URL = "https://api.punkapi.com/v2/beers"; + +export interface IBrewInfo { + id: string; + name: string; + tagline: string; + first_brewed: string; + description: string; + brewers_tips: string; +} + +export interface IBrewDatasourceResponse { + brewInfo: IBrewInfo[]; + total: number; +} + +export class BeerDataSource extends DataSourceService { + public static providerId = "BeerDataSource"; + + private cache: IBrewInfo[] = []; + + public busy = new BehaviorSubject(false); + + public dataFields: Array = [ + { + id: nameof("id"), + label: "No", + dataType: "number", + sortable: true, + }, + // To indicate that a column should not be sortable, set the optional IDataField 'sortable' property to false + { + id: nameof("name"), + label: "Name", + dataType: "string", + sortable: true, + }, + { + id: nameof("tagline"), + label: "Tagline", + dataType: "string", + sortable: true, + }, + { + id: nameof("first_brewed"), + label: "First Brewed", + dataType: "string", + sortable: true, + }, + { + id: nameof("description"), + label: "Description", + dataType: "string", + sortable: false, + }, + { + id: nameof("brewers_tips"), + label: "Brewer's Tips", + dataType: "string", + sortable: false, + }, + ]; + + public async getFilteredData( + filters: INovaFilters + ): Promise { + const start = filters.virtualScroll?.value?.start ?? 0; + const end = filters.virtualScroll?.value?.end ?? 0; + + // Resetting cache on first page request + if (start === 0) { + this.cache = []; + } + + // extract sorter settings to send to the backend + // filters.sorterValue.sortBy; filters.sorterValue.direction + return firstValueFrom( + from(this.fetch(start, end)).pipe( + tap((response: IBrewDatasourceResponse | undefined) => { + if (!response) { + return; + } + this.cache = this.sortData( + this.cache.concat(response.brewInfo), + filters + ); + this.dataSubject.next(this.cache); + }), + map(() => ({ + repeat: { itemsSource: this.cache }, + dataFields: this.dataFields, + })) + ) + ); + } + + public async fetch( + start: number, + end: number + ): Promise { + const delta: number = end - start; + const currentPage: number = end / delta || 0; + const response: object | Array = await ( + await fetch( + \\\\\\\`\\\\\\\${BREW_API_URL}/?page=\\\\\\\${currentPage}&per_page=\\\\\\\${delta}\\\\\\\` + ) + ).json(); + + // Note: In case request fails we should not proceed with mapping + if (!Array.isArray(response)) { + return undefined; + } + + return { + brewInfo: response.map((result: IBrewInfo) => ({ + id: result.id, + name: result.name, + tagline: result.tagline, + first_brewed: result.first_brewed, + description: result.description, + brewers_tips: result.brewers_tips, + })), + total: response.length, + }; + } + + private sortData(data: IBrewInfo[], filters: INovaFilters): IBrewInfo[] { + return orderBy( + data, + filters.sorter?.value?.sortBy, + filters.sorter?.value?.direction as "desc" | "asc" + ); + } +} + +/** + * A component that instantiates the dashboard + */ +@Component({ + selector: "table-widget-example", + templateUrl: "./table-widget-example.component.html", + styleUrls: ["./table-widget-example.component.less"], + standalone: false, +}) +export class TableWidgetExampleComponent implements OnInit { + // This variable will hold all the data needed to define the layout and behavior of the widgets. + // Pass this to the dashboard component's dashboard input in the template. + public dashboard: IDashboard | undefined; + + // Angular gridster requires a configuration object even if it's empty. + // Pass this to the dashboard component's gridsterConfig input in the template. + public gridsterConfig: GridsterConfig = {}; + + // Boolean passed as an input to the dashboard. When true, widgets can be moved, resized, removed, or edited + public editMode: boolean = false; + + constructor( + // WidgetTypesService provides the widget's necessary structure information + private widgetTypesService: WidgetTypesService, + // In general, the ProviderRegistryService is used for making entities available for injection into dynamically loaded components. + private providerRegistry: ProviderRegistryService, + private changeDetectorRef: ChangeDetectorRef + ) {} + + public ngOnInit(): void { + // Grabbing the widget's default template which will be needed as a parameter for setNode + const widgetTemplate = this.widgetTypesService.getWidgetType( + "table", + 1 + ); + + // Registering our data sources as dropdown options in the widget editor/configurator + // Note: This could also be done in the parent module's constructor so that + // multiple dashboards could have access to the same widget template modification. + this.widgetTypesService.setNode( + // This is the template we grabbed above with getWidgetType + widgetTemplate, + // We are setting the editor/configurator part of the widget template + "configurator", + // This indicates which node you are changing and we want to change + // the data source providers available for selection in the editor. + WellKnownPathKey.DataSourceProviders, + // We are setting the data sources available for selection in the editor + [BeerDataSource.providerId] + ); + + // Registering the data source for injection into the widget. + this.providerRegistry.setProviders({ + [BeerDataSource.providerId]: { + provide: DATA_SOURCE, + useClass: BeerDataSource, + // Any dependencies that need to be injected into the provider must be listed here + deps: [], + }, + }); + + this.initializeDashboard(); + } + + /** Used for restoring widgets state */ + public reInitializeDashboard(): void { + // destroys the components and their providers so the dashboard can re init data + this.dashboard = undefined; + this.changeDetectorRef.detectChanges(); + + this.initializeDashboard(); + } + + public initializeDashboard(): void { + // We're using a static configuration object for this example, but this is where + // the widget's configuration could potentially be populated from a database + const tableWidget = widgetConfig; + const widgetIndex: IWidgets = { + // Enhance the widget with information coming from it's type definition + [tableWidget.id]: + this.widgetTypesService.mergeWithWidgetType(tableWidget), + }; + + // Setting the widget dimensions and position (this is for gridster) + const positions: Record = { + [tableWidget.id]: { + cols: 12, + rows: 6, + y: 0, + x: 0, + }, + }; + + // Finally, assigning the variables we created above to the dashboard + this.dashboard = { + positions, + widgets: widgetIndex, + }; + } +} + +const TABLE_COLUMNS: ITableWidgetColumnConfig[] = [ + { + id: "column1", + label: $localize\\\\\\\`Beer Name\\\\\\\`, + isActive: true, + width: 185, + formatter: { + componentType: "RawFormatterComponent", + properties: { + dataFieldIds: { + value: "name", + }, + }, + }, + }, + { + id: "column2", + label: $localize\\\\\\\`Tagline\\\\\\\`, + isActive: true, + width: 250, + formatter: { + componentType: "RawFormatterComponent", + properties: { + dataFieldIds: { + value: "tagline", + }, + }, + }, + }, + { + id: "column3", + label: $localize\\\\\\\`First Brewed\\\\\\\`, + isActive: true, + width: 100, + formatter: { + componentType: "RawFormatterComponent", + properties: { + dataFieldIds: { + value: "first_brewed", + }, + }, + }, + }, + { + id: "column4", + label: $localize\\\\\\\`Description\\\\\\\`, + isActive: true, + formatter: { + componentType: "RawFormatterComponent", + properties: { + dataFieldIds: { + value: "description", + }, + }, + }, + }, +]; + +export const widgetConfig: IWidget = { + id: "tableWidgetId", + type: "table", + pizzagna: { + configuration: { + header: { + properties: { + title: "Stupendous Suds", + subtitle: "Try These Brilliant Brews", + }, + }, + table: { + providers: { + [WellKnownProviders.DataSource]: { + providerId: BeerDataSource.providerId, + }, + }, + properties: { + configuration: { + columns: TABLE_COLUMNS, + sortable: true, + sorterConfiguration: { + descendantSorting: false, + sortBy: "", + }, + hasVirtualScroll: true, + }, + }, + }, + }, + }, +}; +\\\`, + "widget-types/table/table-widget-interactive/table-widget-interactive-example.component.ts": \\\`// © 2022 SolarWinds Worldwide, LLC. All rights reserved. +// +// Permission is hereby granted, free of charge, to any person obtaining a copy +// of this software and associated documentation files (the "Software"), to +// deal in the Software without restriction, including without limitation the +// rights to use, copy, modify, merge, publish, distribute, sublicense, and/or +// sell copies of the Software, and to permit persons to whom the Software is +// furnished to do so, subject to the following conditions: +// +// The above copyright notice and this permission notice shall be included in +// all copies or substantial portions of the Software. +// +// THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR +// IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, +// FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE +// AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER +// LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, +// OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN +// THE SOFTWARE. + +import { ChangeDetectorRef, Component, OnInit } from "@angular/core"; +import { GridsterConfig, GridsterItem } from "angular-gridster2"; +import orderBy from "lodash/orderBy"; +import { BehaviorSubject, firstValueFrom, from } from "rxjs"; +import { map, tap } from "rxjs/operators"; + +import { + DataSourceService, + IDataField, + INovaFilteringOutputs, + INovaFilters, + nameof, +} from "@nova-ui/bits"; +import { + DATA_SOURCE, + DEFAULT_PIZZAGNA_ROOT, + IDashboard, + ITableWidgetColumnConfig, + IWidget, + IWidgets, + NOVA_URL_INTERACTION_HANDLER, + ProviderRegistryService, + WellKnownPathKey, + WellKnownProviders, + WidgetTypesService, +} from "@nova-ui/dashboards"; + +export const BREW_API_URL = "https://api.punkapi.com/v2/beers"; + +export interface IBrewInfo { + id: string; + name: string; + tagline: string; + first_brewed: string; + description: string; + brewers_tips: string; +} + +export interface IBrewDatasourceResponse { + brewInfo: IBrewInfo[]; + total: number; +} + +export class MockBeerDataSource extends DataSourceService { + public static providerId = "MockBeerDataSource"; + + private cache: IBrewInfo[] = []; + + public busy = new BehaviorSubject(false); + + public dataFields: Array = [ + { + id: nameof("id"), + label: "No", + dataType: "number", + sortable: true, + }, + // To indicate that a column should not be sortable, set the optional IDataField 'sortable' property to false + { + id: nameof("name"), + label: "Name", + dataType: "string", + sortable: true, + }, + { + id: nameof("tagline"), + label: "Tagline", + dataType: "string", + sortable: true, + }, + { + id: nameof("first_brewed"), + label: "First Brewed", + dataType: "string", + sortable: true, + }, + { + id: nameof("description"), + label: "Description", + dataType: "string", + sortable: false, + }, + { + id: nameof("brewers_tips"), + label: "Brewer's Tips", + dataType: "string", + sortable: false, + }, + ]; + + public async getFilteredData( + filters: INovaFilters + ): Promise { + const start = filters.virtualScroll?.value?.start ?? 0; + const end = filters.virtualScroll?.value?.end ?? 0; + + // Resetting cache on first page request + if (start === 0) { + this.cache = []; + } + + // extract sorter settings to send to the backend + // filters.sorterValue.sortBy; filters.sorterValue.direction + return firstValueFrom( + from(this.fetch(start, end)).pipe( + tap((response) => { + if (!response) { + return; + } + this.cache = this.sortData( + this.cache.concat(response.brewInfo), + filters + ); + this.dataSubject.next(this.cache); + }), + map(() => ({ + repeat: { itemsSource: this.cache }, + dataFields: this.dataFields, + })) + ) + ); + } + + public async fetch( + start: number, + end: number + ): Promise { + const delta: number = end - start; + const currentPage: number = end / delta || 0; + const response: object | Array = await ( + await fetch( + \\\\\\\`\\\\\\\${BREW_API_URL}/?page=\\\\\\\${currentPage}&per_page=\\\\\\\${delta}\\\\\\\` + ) + ).json(); + console.log( + "📘 table-widget-interactive-example.component: 85# -> response:", + response + ); + + // Note: In case request fails we should not proceed with mapping + if (!Array.isArray(response)) { + return undefined; + } + + return { + brewInfo: response.map((result: IBrewInfo) => ({ + id: result.id, + name: result.name, + tagline: result.tagline, + first_brewed: result.first_brewed, + description: result.description, + brewers_tips: result.brewers_tips, + })), + total: response.length, + }; + } + + private sortData(data: IBrewInfo[], filters: INovaFilters): IBrewInfo[] { + return orderBy( + data, + filters.sorter?.value?.sortBy, + filters.sorter?.value?.direction as "desc" | "asc" + ); + } +} + +/** + * A component that instantiates the dashboard + */ +@Component({ + selector: "table-widget-interactive-example", + templateUrl: "./table-widget-interactive-example.component.html", + styleUrls: ["./table-widget-interactive-example.component.less"], + standalone: false, +}) +export class TableWidgetInteractiveExampleComponent implements OnInit { + // This variable will hold all the data needed to define the layout and behavior of the widgets. + // Pass this to the dashboard component's dashboard input in the template. + public dashboard: IDashboard | undefined; + + // Angular gridster requires a configuration object even if it's empty. + // Pass this to the dashboard component's gridsterConfig input in the template. + public gridsterConfig: GridsterConfig = {}; + + // Boolean passed as an input to the dashboard. When true, widgets can be moved, resized, removed, or edited + public editMode: boolean = false; + + constructor( + // WidgetTypesService provides the widget's necessary structure information + private widgetTypesService: WidgetTypesService, + // In general, the ProviderRegistryService is used for making entities available for injection into dynamically loaded components. + private providerRegistry: ProviderRegistryService, + private changeDetectorRef: ChangeDetectorRef + ) {} + + public ngOnInit(): void { + // Grabbing the widget's default template which will be needed as a parameter for setNode + const widgetTemplate = this.widgetTypesService.getWidgetType( + "table", + 1 + ); + + // Registering our data sources as dropdown options in the widget editor/configurator + // Note: This could also be done in the parent module's constructor so that + // multiple dashboards could have access to the same widget template modification. + this.widgetTypesService.setNode( + // This is the template we grabbed above with getWidgetType + widgetTemplate, + // We are setting the editor/configurator part of the widget template + "configurator", + // This indicates which node you are changing and we want to change + // the data source providers available for selection in the editor. + WellKnownPathKey.DataSourceProviders, + // We are setting the data sources available for selection in the editor + [MockBeerDataSource.providerId] + ); + + // Registering the data source for injection into the widget. + this.providerRegistry.setProviders({ + [MockBeerDataSource.providerId]: { + provide: DATA_SOURCE, + useClass: MockBeerDataSource, + // Any dependencies that need to be injected into the provider must be listed here + deps: [], + }, + }); + + this.initializeDashboard(); + } + + /** Used for restoring widgets state */ + public reInitializeDashboard(): void { + // destroys the components and their providers so the dashboard can re init data + this.dashboard = undefined; + this.changeDetectorRef.detectChanges(); + + this.initializeDashboard(); + } + + public initializeDashboard(): void { + // We're using a static configuration object for this example, but this is where + // the widget's configuration could potentially be populated from a database + const tableWidget = widgetConfig; + const widgetIndex: IWidgets = { + // Enhance the widget with information coming from it's type definition + [tableWidget.id]: + this.widgetTypesService.mergeWithWidgetType(tableWidget), + }; + + // Setting the widget dimensions and position (this is for gridster) + const positions: Record = { + [tableWidget.id]: { + cols: 12, + rows: 6, + y: 0, + x: 0, + }, + }; + + // Finally, assigning the variables we created above to the dashboard + this.dashboard = { + positions, + widgets: widgetIndex, + }; + } +} + +const TABLE_COLUMNS: ITableWidgetColumnConfig[] = [ + { + id: "column1", + label: $localize\\\\\\\`Beer Name\\\\\\\`, + isActive: true, + width: 185, + formatter: { + componentType: "RawFormatterComponent", + properties: { + dataFieldIds: { + value: "name", + }, + }, + }, + }, + { + id: "column2", + label: $localize\\\\\\\`Tagline\\\\\\\`, + isActive: true, + width: 250, + formatter: { + componentType: "RawFormatterComponent", + properties: { + dataFieldIds: { + value: "tagline", + }, + }, + }, + }, + { + id: "column3", + label: $localize\\\\\\\`First Brewed\\\\\\\`, + isActive: true, + width: 100, + formatter: { + componentType: "RawFormatterComponent", + properties: { + dataFieldIds: { + value: "first_brewed", + }, + }, + }, + }, + { + id: "column4", + label: $localize\\\\\\\`Description\\\\\\\`, + isActive: true, + formatter: { + componentType: "RawFormatterComponent", + properties: { + dataFieldIds: { + value: "description", + }, + }, + }, + }, +]; + +export const widgetConfig: IWidget = { + id: "tableWidgetId", + type: "table", + pizzagna: { + configuration: { + [DEFAULT_PIZZAGNA_ROOT]: { + providers: { + [WellKnownProviders.InteractionHandler]: { + // Configuring the UrlInteractionHandler to handle interactions + providerId: NOVA_URL_INTERACTION_HANDLER, + properties: { + // the 'url' property tells the handler what link to use when interaction occurs on the series + url: "\\\\\\\${'https://untappd.com/search?q='+data.name}", + // by default the link is opened in the current window, set 'newWindow' to true to open in a new tab instead + newWindow: true, + }, + }, + }, + }, + header: { + properties: { + title: "Stupendous Suds", + subtitle: "Try These Brilliant Brews", + }, + }, + table: { + providers: { + [WellKnownProviders.DataSource]: { + providerId: MockBeerDataSource.providerId, + }, + }, + properties: { + configuration: { + // set interactions to true on the table + interactive: true, + columns: TABLE_COLUMNS, + sortable: true, + sorterConfiguration: { + descendantSorting: false, + sortBy: "", + }, + hasVirtualScroll: true, + }, + }, + }, + }, + }, +}; +\\\`, + "widget-types/table/table-widget-paginator/table-widget-paginator-example.component.ts": \\\`// © 2022 SolarWinds Worldwide, LLC. All rights reserved. +// +// Permission is hereby granted, free of charge, to any person obtaining a copy +// of this software and associated documentation files (the "Software"), to +// deal in the Software without restriction, including without limitation the +// rights to use, copy, modify, merge, publish, distribute, sublicense, and/or +// sell copies of the Software, and to permit persons to whom the Software is +// furnished to do so, subject to the following conditions: +// +// The above copyright notice and this permission notice shall be included in +// all copies or substantial portions of the Software. +// +// THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR +// IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, +// FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE +// AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER +// LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, +// OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN +// THE SOFTWARE. + +import { HttpClient } from "@angular/common/http"; +import { ChangeDetectorRef, Component, OnInit } from "@angular/core"; +import { GridsterConfig, GridsterItem } from "angular-gridster2"; + +import { LoggerService } from "@nova-ui/bits"; +import { + DATA_SOURCE, + DEFAULT_PIZZAGNA_ROOT, + IDashboard, + IProviderConfiguration, + ITableWidgetConfig, + IWidget, + IWidgets, + NOVA_URL_INTERACTION_HANDLER, + PizzagnaLayer, + ProviderRegistryService, + RawFormatterComponent, + ScrollType, + WellKnownPathKey, + WellKnownProviders, + WidgetTypesService, +} from "@nova-ui/dashboards"; + +import { AcmeTableMockDataSource } from "../../../../prototypes/data/table/acme-table-mock-data-source.service"; + +/** + * A component that instantiates the dashboard + */ +@Component({ + selector: "table-widget-paginator-example", + templateUrl: "./table-widget-paginator-example.component.html", + styleUrls: ["./table-widget-paginator-example.component.less"], + standalone: false, +}) +export class TableWidgetPaginatorExampleComponent implements OnInit { + public dashboard: IDashboard | undefined; + public gridsterConfig: GridsterConfig = {}; + public editMode: boolean = false; + + constructor( + private widgetTypesService: WidgetTypesService, + private providerRegistry: ProviderRegistryService, + private changeDetectorRef: ChangeDetectorRef + ) {} + + public ngOnInit(): void { + const widgetTemplate = this.widgetTypesService.getWidgetType( + "table", + 1 + ); + this.widgetTypesService.setNode( + widgetTemplate, + "configurator", + WellKnownPathKey.DataSourceProviders, + [AcmeTableMockDataSource.providerId] + ); + + this.providerRegistry.setProviders({ + [AcmeTableMockDataSource.providerId]: { + provide: DATA_SOURCE, + useClass: AcmeTableMockDataSource, + deps: [LoggerService, HttpClient], + }, + }); + + this.initializeDashboard(); + } + + /** Used for restoring widgets state */ + public reInitializeDashboard(): void { + // destroys the components and their providers so the dashboard can re init data + this.dashboard = undefined; + this.changeDetectorRef.detectChanges(); + + this.initializeDashboard(); + } + + public initializeDashboard(): void { + const tableWithPaginator = tableWidgetWithPaginator; + const tableWithVirtualScroll = tableWidgetWithVirtualScroll; + + const widgetIndex: IWidgets = { + [tableWithPaginator.id]: + this.widgetTypesService.mergeWithWidgetType(tableWithPaginator), + [tableWithVirtualScroll.id]: + this.widgetTypesService.mergeWithWidgetType( + tableWithVirtualScroll + ), + }; + + const positions: Record = { + [tableWithPaginator.id]: { + cols: 6, + rows: 6, + y: 0, + x: 0, + }, + [tableWithVirtualScroll.id]: { + cols: 6, + rows: 6, + y: 0, + x: 0, + }, + }; + + this.dashboard = { + positions, + widgets: widgetIndex, + }; + } +} + +export const tableWidgetWithPaginator: IWidget = { + id: "widget1", + type: "table", + pizzagna: { + [PizzagnaLayer.Configuration]: { + [DEFAULT_PIZZAGNA_ROOT]: { + providers: { + [WellKnownProviders.InteractionHandler]: { + providerId: NOVA_URL_INTERACTION_HANDLER, + }, + }, + }, + header: { + properties: { + title: "Table Widget with paginator!", + subtitle: "Basic table widget", + collapsible: true, + }, + }, + table: { + providers: { + [WellKnownProviders.DataSource]: { + providerId: AcmeTableMockDataSource.providerId, + } as IProviderConfiguration, + }, + properties: { + configuration: { + interactive: true, + columns: [ + { + id: "column1", + label: "No.", + isActive: true, + formatter: { + componentType: + RawFormatterComponent.lateLoadKey, + properties: { + dataFieldIds: { + value: "position", + }, + }, + }, + }, + { + id: "column2", + label: "Name", + isActive: true, + formatter: { + componentType: + RawFormatterComponent.lateLoadKey, + properties: { + dataFieldIds: { + value: "name", + }, + }, + }, + }, + { + id: "column3", + label: "Status", + isActive: true, + formatter: { + componentType: + RawFormatterComponent.lateLoadKey, + properties: { + dataFieldIds: { + value: "status", + }, + }, + }, + }, + ], + sorterConfiguration: { + descendantSorting: false, + sortBy: "column1", + }, + scrollType: ScrollType.paginator, + paginatorConfiguration: { + pageSize: 5, + pageSizeSet: [5, 10, 20, 30], + }, + hasVirtualScroll: false, + searchConfiguration: { + enabled: true, + }, + } as ITableWidgetConfig, + }, + }, + }, + }, +}; + +export const tableWidgetWithVirtualScroll: IWidget = { + id: "widget2", + type: "table", + pizzagna: { + [PizzagnaLayer.Configuration]: { + [DEFAULT_PIZZAGNA_ROOT]: { + providers: { + [WellKnownProviders.InteractionHandler]: { + providerId: NOVA_URL_INTERACTION_HANDLER, + }, + }, + }, + header: { + properties: { + title: "Table Widget with virtual scroll!", + subtitle: "Basic table widget", + collapsible: true, + }, + }, + table: { + providers: { + [WellKnownProviders.DataSource]: { + providerId: AcmeTableMockDataSource.providerId, + } as IProviderConfiguration, + }, + properties: { + configuration: { + interactive: true, + columns: [ + { + id: "column1", + label: "No.", + isActive: true, + formatter: { + componentType: + RawFormatterComponent.lateLoadKey, + properties: { + dataFieldIds: { + value: "position", + }, + }, + }, + }, + { + id: "column2", + label: "Name", + isActive: true, + formatter: { + componentType: + RawFormatterComponent.lateLoadKey, + properties: { + dataFieldIds: { + value: "name", + }, + }, + }, + }, + { + id: "column3", + label: "Status", + isActive: true, + formatter: { + componentType: + RawFormatterComponent.lateLoadKey, + properties: { + dataFieldIds: { + value: "status", + }, + }, + }, + }, + ], + sorterConfiguration: { + descendantSorting: false, + sortBy: "column1", + }, + hasVirtualScroll: true, + searchConfiguration: { + enabled: true, + }, + } as ITableWidgetConfig, + }, + }, + }, + }, +}; +\\\`, + "widget-types/table/table-widget-search/table-widget-search-example.component.ts": \\\`// © 2022 SolarWinds Worldwide, LLC. All rights reserved. +// +// Permission is hereby granted, free of charge, to any person obtaining a copy +// of this software and associated documentation files (the "Software"), to +// deal in the Software without restriction, including without limitation the +// rights to use, copy, modify, merge, publish, distribute, sublicense, and/or +// sell copies of the Software, and to permit persons to whom the Software is +// furnished to do so, subject to the following conditions: +// +// The above copyright notice and this permission notice shall be included in +// all copies or substantial portions of the Software. +// +// THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR +// IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, +// FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE +// AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER +// LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, +// OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN +// THE SOFTWARE. + +import { HttpClient } from "@angular/common/http"; +import { + ChangeDetectorRef, + Component, + Injectable, + OnInit, +} from "@angular/core"; +import { GridsterConfig, GridsterItem } from "angular-gridster2"; +import isEqual from "lodash/isEqual"; +import isNil from "lodash/isNil"; +import { BehaviorSubject, firstValueFrom, Observable, of, Subject } from "rxjs"; +import { + catchError, + delay, + finalize, + map, + // eslint-disable-next-line import/no-deprecated + switchMap, + tap, +} from "rxjs/operators"; + +import { + DataSourceFeatures, + DataSourceService, + IDataField, + IDataSource, + IDataSourceFeatures, + IDataSourceFeaturesConfiguration, + IDataSourceOutput, + IFilter, + IFilters, + INovaFilteringOutputs, + INovaFilters, + LoggerService, +} from "@nova-ui/bits"; +import { + DATA_SOURCE, + IDashboard, + ITableWidgetConfig, + IWidget, + IWidgets, + ProviderRegistryService, + WellKnownPathKey, + WellKnownProviders, + WidgetTypesService, +} from "@nova-ui/dashboards"; + +import { GBOOKS_API_URL } from "../../../../prototypes/data/table/constants"; + +interface IGBooksApiResponse { + kind: string; + totalItems: number; + items: IGBooksItemModel[]; + [key: string]: any; +} + +interface IGBooksItemModel { + id: string; + volumeInfo: { + title: string; + subtitle: string; + authors: string[]; + [key: string]: any; + }; + accessInfo: { [key: string]: any }; + saleInfo: { [key: string]: any }; +} + +interface IGBooksData { + books: IGBooksVolume[]; + totalItems: number; +} + +interface IGBooksVolume { + title: string; + authors: string; +} + +type searchableColumnType = "title" | "authors"; + +@Injectable() +export class AcmeTableGBooksDataSource + extends DataSourceService + implements IDataSource +{ + public static providerId = "AcmeTableGBooksDataSource"; + public static mockError = false; + + public searchableColumn: searchableColumnType = "title"; + + public page: number = 1; + public busy = new BehaviorSubject(false); + public features: IDataSourceFeaturesConfiguration; + + private cache = Array.from({ length: 0 }); + private previousFilters: INovaFilters; + // DataSource Features declared + private supportedFeatures: IDataSourceFeatures = { + search: { enabled: true }, + pagination: { enabled: true }, + }; + private columnToQueryParamMap: { [k in searchableColumnType]: string } = { + title: "intitle", + authors: "inauthor", + }; + + private applyFilters$ = new Subject(); + + public dataFields: Array = [ + { + id: "title", + label: $localize\\\\\\\`Title\\\\\\\`, + dataType: "string", + sortable: false, + }, + { + id: "authors", + label: $localize\\\\\\\`Authors\\\\\\\`, + dataType: "string", + sortable: false, + }, + ]; + + constructor(private logger: LoggerService, private http: HttpClient) { + super(); + // Using Nova DataSourceFeatures implementation for the features + this.features = new DataSourceFeatures(this.supportedFeatures); + + this.applyFilters$ + // eslint-disable-next-line import/no-deprecated + .pipe(switchMap((filters) => this.getData(filters))) + .subscribe(async (res) => { + this.outputsSubject.next(await this.getFilteredData(res)); + }); + } + + public async getFilteredData( + booksData: IGBooksData + ): Promise> { + return firstValueFrom( + of(booksData).pipe( + tap((response) => { + this.cache = this.cache.concat(response.books); + }), + map((response) => ({ + result: { + repeat: { itemsSource: this.cache }, + paginator: { total: response.totalItems }, + dataFields: this.dataFields, + }, + })) + ) + ); + } + + private getData(filters: INovaFilters): Observable { + if ( + this.isNewSearchTerm(filters.search) && + filters.virtualScroll?.value.start === 0 + ) { + this.cache = []; + } + + return this.http + .get(this.getComposedUrl(filters)) + .pipe( + tap(() => this.busy.next(true)), + delay(300), // mock + map((response) => ({ + books: + response.items?.map((volume) => ({ + title: volume.volumeInfo.title, + authors: + volume.volumeInfo.authors?.join(", ") || "", + })) || [], + totalItems: response.totalItems, + })), + catchError((e) => { + this.logger.error(e); + return of({ + books: [], + totalItems: 0, + }); + }), + finalize(() => { + this.busy.next(false); + this.previousFilters = filters; + }) + ); + } + + private getComposedUrl(filters: INovaFilters) { + const initialUrl = \\\\\\\`\\\\\\\${GBOOKS_API_URL}?q=\\\\\\\`; + const maxResults = \\\\\\\`maxResults=\\\\\\\${ + (filters.virtualScroll?.value.end || 0) - + (filters.virtualScroll?.value.start || 0) + }\\\\\\\`; + + const virtualScrollPart = filters.virtualScroll + ? \\\\\\\`startIndex=\\\\\\\${filters.virtualScroll.value.start}\\\\\\\` + : ""; + + const searchQueryParam = + this.columnToQueryParamMap[this.searchableColumn]; + const searchPart = filters.search + ? \\\\\\\`\\\\\\\${searchQueryParam}:\\\\\\\${filters.search.value}\\\\\\\` + : "_"; // google books api requires some criteria to do the search + + return \\\\\\\`\\\\\\\${initialUrl}\\\\\\\${searchPart}&\\\\\\\${maxResults}&\\\\\\\${virtualScrollPart}&filter=full\\\\\\\`; + } + + private isNewSearchTerm(search: IFilter | undefined) { + return ( + !isNil(search?.value) && + !isEqual(search?.value, this.previousFilters?.search?.value) + ); + } + + // redefine parent method + public async applyFilters(): Promise { + this.applyFilters$.next(this.getFilters()); + } +} + +/** + * A component that instantiates the dashboard + */ +@Component({ + selector: "table-widget-search-example", + templateUrl: "./table-widget-search-example.component.html", + styleUrls: ["./table-widget-search-example.component.less"], + standalone: false, +}) +export class TableWidgetSearchExampleComponent implements OnInit { + public dashboard: IDashboard | undefined; + public gridsterConfig: GridsterConfig = {}; + public editMode: boolean = false; + + constructor( + private widgetTypesService: WidgetTypesService, + private providerRegistry: ProviderRegistryService, + private changeDetectorRef: ChangeDetectorRef + ) {} + + public ngOnInit(): void { + const widgetTemplate = this.widgetTypesService.getWidgetType( + "table", + 1 + ); + this.widgetTypesService.setNode( + widgetTemplate, + "configurator", + WellKnownPathKey.DataSourceProviders, + [AcmeTableGBooksDataSource.providerId] + ); + + this.providerRegistry.setProviders({ + [AcmeTableGBooksDataSource.providerId]: { + provide: DATA_SOURCE, + useClass: AcmeTableGBooksDataSource, + deps: [LoggerService, HttpClient], + }, + }); + + this.initializeDashboard(); + } + + /** Used for restoring widgets state */ + public reInitializeDashboard(): void { + // destroys the components and their providers so the dashboard can re init data + this.dashboard = undefined; + this.changeDetectorRef.detectChanges(); + + this.initializeDashboard(); + } + + public initializeDashboard(): void { + const tableWidget = widgetConfig; + const widgetIndex: IWidgets = { + [tableWidget.id]: + this.widgetTypesService.mergeWithWidgetType(tableWidget), + }; + + const positions: Record = { + [tableWidget.id]: { + cols: 12, + rows: 6, + y: 0, + x: 0, + }, + }; + + this.dashboard = { + positions, + widgets: widgetIndex, + }; + } +} + +export const widgetConfig: IWidget = { + id: "tableWidgetId", + type: "table", + pizzagna: { + configuration: { + header: { + properties: { + title: "Google Books", + }, + }, + table: { + providers: { + [WellKnownProviders.DataSource]: { + providerId: AcmeTableGBooksDataSource.providerId, + }, + }, + properties: { + configuration: { + columns: [ + { + id: "column1", + label: $localize\\\\\\\`Title\\\\\\\`, + isActive: true, + formatter: { + componentType: "RawFormatterComponent", + properties: { + dataFieldIds: { + value: "title", + }, + }, + }, + }, + { + id: "column2", + label: $localize\\\\\\\`Author\\\\\\\`, + isActive: true, + formatter: { + componentType: "RawFormatterComponent", + properties: { + dataFieldIds: { + value: "authors", + }, + }, + }, + }, + ], + sortable: false, + // define search configuration here + searchConfiguration: { + enabled: true, + // following properties below can be configured as well + // searchTerm: "search criteria here", + // searchDebounce: 300, + }, + hasVirtualScroll: true, + } as ITableWidgetConfig, + }, + }, + }, + }, +}; +\\\`, + "widget-types/table/table-widget-search-docs.component.ts": \\\`// © 2022 SolarWinds Worldwide, LLC. All rights reserved. +// +// Permission is hereby granted, free of charge, to any person obtaining a copy +// of this software and associated documentation files (the "Software"), to +// deal in the Software without restriction, including without limitation the +// rights to use, copy, modify, merge, publish, distribute, sublicense, and/or +// sell copies of the Software, and to permit persons to whom the Software is +// furnished to do so, subject to the following conditions: +// +// The above copyright notice and this permission notice shall be included in +// all copies or substantial portions of the Software. +// +// THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR +// IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, +// FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE +// AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER +// LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, +// OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN +// THE SOFTWARE. + +import { Component } from "@angular/core"; + +@Component({ + selector: "nui-table-search-docs", + templateUrl: "./table-widget-search-docs.component.html", + standalone: false, +}) +export class TableSearchDocsComponent { + public featuredDeclaredText = \\\\\\\` + private supportedFeatures: IDataSourceFeatures = { + search: { enabled: true }, + pagination: { enabled: true }, + };\\\\\\\`; + public featuresUsedText = \\\\\\\` + this.features = new DataSourceFeatures(this.supportedFeatures); + \\\\\\\`; + public tableConfigurationText = \\\\\\\` + "table": { + ... + properties: { + configuration: { + // define search configuration here + searchConfiguration: { + enabled: true, + // following optional properties below can be configured as well + // searchTerm: "search criteria here", + // searchDebounce: 300, + }, + } as ITableWidgetConfig, + }, + }, + \\\\\\\`; +} +\\\`, + "widget-types/table/table-widget-selectable/table-widget-selectable-multi/table-widget-selectable-multi.example.component.ts": \\\`// © 2022 SolarWinds Worldwide, LLC. All rights reserved. +// +// Permission is hereby granted, free of charge, to any person obtaining a copy +// of this software and associated documentation files (the "Software"), to +// deal in the Software without restriction, including without limitation the +// rights to use, copy, modify, merge, publish, distribute, sublicense, and/or +// sell copies of the Software, and to permit persons to whom the Software is +// furnished to do so, subject to the following conditions: +// +// The above copyright notice and this permission notice shall be included in +// all copies or substantial portions of the Software. +// +// THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR +// IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, +// FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE +// AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER +// LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, +// OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN +// THE SOFTWARE. + +import { Component } from "@angular/core"; + +import { TableSelectionMode } from "@nova-ui/bits"; +import { TableWidgetSelectionConfig } from "@nova-ui/dashboards"; + +/** + * A component that instantiates the dashboard + */ +@Component({ + selector: "table-widget-selectable-multi-example", + templateUrl: "./table-widget-selectable-multi.example.component.html", + styleUrls: ["./table-widget-selectable-multi.example.component.less"], + standalone: false, +}) +export class TableWidgetSelectableMultiExampleComponent { + public selectionConfiguration: TableWidgetSelectionConfig = { + enabled: true, + selectionMode: TableSelectionMode.Multi, + trackByProperty: "id", + clickableRow: true, + }; +} +\\\`, + "widget-types/table/table-widget-selectable/table-widget-selectable-radio/table-widget-selectable-radio.example.component.ts": \\\`// © 2022 SolarWinds Worldwide, LLC. All rights reserved. +// +// Permission is hereby granted, free of charge, to any person obtaining a copy +// of this software and associated documentation files (the "Software"), to +// deal in the Software without restriction, including without limitation the +// rights to use, copy, modify, merge, publish, distribute, sublicense, and/or +// sell copies of the Software, and to permit persons to whom the Software is +// furnished to do so, subject to the following conditions: +// +// The above copyright notice and this permission notice shall be included in +// all copies or substantial portions of the Software. +// +// THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR +// IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, +// FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE +// AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER +// LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, +// OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN +// THE SOFTWARE. + +import { Component } from "@angular/core"; + +import { TableSelectionMode } from "@nova-ui/bits"; +import { TableWidgetSelectionConfig } from "@nova-ui/dashboards"; + +/** + * A component that instantiates the dashboard + */ +@Component({ + selector: "table-widget-selectable-radio-example", + templateUrl: "./table-widget-selectable-radio.example.component.html", + styleUrls: ["./table-widget-selectable-radio.example.component.less"], + standalone: false, +}) +export class TableWidgetSelectableRadioExampleComponent { + public selectionConfiguration: TableWidgetSelectionConfig = { + enabled: true, + selectionMode: TableSelectionMode.Radio, + trackByProperty: "id", + clickableRow: true, + }; +} +\\\`, + "widget-types/table/table-widget-selectable/table-widget-selectable-single/table-widget-selectable-single.example.component.ts": \\\`// © 2022 SolarWinds Worldwide, LLC. All rights reserved. +// +// Permission is hereby granted, free of charge, to any person obtaining a copy +// of this software and associated documentation files (the "Software"), to +// deal in the Software without restriction, including without limitation the +// rights to use, copy, modify, merge, publish, distribute, sublicense, and/or +// sell copies of the Software, and to permit persons to whom the Software is +// furnished to do so, subject to the following conditions: +// +// The above copyright notice and this permission notice shall be included in +// all copies or substantial portions of the Software. +// +// THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR +// IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, +// FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE +// AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER +// LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, +// OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN +// THE SOFTWARE. + +import { Component } from "@angular/core"; + +import { TableSelectionMode } from "@nova-ui/bits"; +import { TableWidgetSelectionConfig } from "@nova-ui/dashboards"; + +/** + * A component that instantiates the dashboard + */ +@Component({ + selector: "table-widget-selectable-single-example", + templateUrl: "./table-widget-selectable-single.example.component.html", + styleUrls: ["./table-widget-selectable-single.example.component.less"], + standalone: false, +}) +export class TableWidgetSelectableSingleExampleComponent { + public selectionConfiguration: TableWidgetSelectionConfig = { + enabled: true, + selectionMode: TableSelectionMode.Single, + trackByProperty: "id", + }; +} +\\\`, + "widget-types/table/table-widget-selectable/table-widget-selectable.example.component.ts": \\\`// © 2022 SolarWinds Worldwide, LLC. All rights reserved. +// +// Permission is hereby granted, free of charge, to any person obtaining a copy +// of this software and associated documentation files (the "Software"), to +// deal in the Software without restriction, including without limitation the +// rights to use, copy, modify, merge, publish, distribute, sublicense, and/or +// sell copies of the Software, and to permit persons to whom the Software is +// furnished to do so, subject to the following conditions: +// +// The above copyright notice and this permission notice shall be included in +// all copies or substantial portions of the Software. +// +// THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR +// IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, +// FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE +// AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER +// LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, +// OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN +// THE SOFTWARE. + +import { HttpClient } from "@angular/common/http"; +import { ChangeDetectorRef, Component, Input, OnInit } from "@angular/core"; +import { GridsterConfig, GridsterItem } from "angular-gridster2"; + +import { LoggerService, TableSelectionMode } from "@nova-ui/bits"; +import { + DATA_SOURCE, + DEFAULT_PIZZAGNA_ROOT, + IDashboard, + IProviderConfiguration, + ITableWidgetConfig, + IWidget, + IWidgets, + NOVA_URL_INTERACTION_HANDLER, + PizzagnaLayer, + ProviderRegistryService, + RawFormatterComponent, + TableWidgetSelectionConfig, + WellKnownPathKey, + WellKnownProviders, + WidgetTypesService, +} from "@nova-ui/dashboards"; + +import { AcmeTableMockDataSource } from "../../../../prototypes/data/table/acme-table-mock-data-source.service"; + +/** + * A component that instantiates the dashboard + */ +@Component({ + selector: "table-widget-selectable-example", + templateUrl: "./table-widget-selectable.example.component.html", + styleUrls: ["./table-widget-selectable.example.component.less"], + standalone: false, +}) +export class TableWidgetSelectableExampleComponent implements OnInit { + public dashboard: IDashboard | undefined; + public gridsterConfig: GridsterConfig = {}; + public editMode: boolean = false; + + @Input() public selectionConfiguration: TableWidgetSelectionConfig = { + enabled: false, + selectionMode: TableSelectionMode.None, + }; + + constructor( + private widgetTypesService: WidgetTypesService, + private providerRegistry: ProviderRegistryService, + private changeDetectorRef: ChangeDetectorRef + ) {} + + public ngOnInit(): void { + const widgetTemplate = this.widgetTypesService.getWidgetType( + "table", + 1 + ); + this.widgetTypesService.setNode( + widgetTemplate, + "configurator", + WellKnownPathKey.DataSourceProviders, + [AcmeTableMockDataSource.providerId] + ); + + this.providerRegistry.setProviders({ + [AcmeTableMockDataSource.providerId]: { + provide: DATA_SOURCE, + useClass: AcmeTableMockDataSource, + deps: [LoggerService, HttpClient], + }, + }); + + this.initializeDashboard(); + } + + /** Used for restoring widgets state */ + public reInitializeDashboard(): void { + // destroys the components and their providers so the dashboard can re init data + this.dashboard = undefined; + this.changeDetectorRef.detectChanges(); + + this.initializeDashboard(); + } + + public initializeDashboard(): void { + const tableWidget = this.widgetConfig; + const widgetIndex: IWidgets = { + [tableWidget.id]: + this.widgetTypesService.mergeWithWidgetType(tableWidget), + }; + + const positions: Record = { + [tableWidget.id]: { + cols: 12, + rows: 6, + y: 0, + x: 0, + }, + }; + + this.dashboard = { + positions, + widgets: widgetIndex, + }; + } + + private get widgetConfig(): IWidget { + return { + id: "widget1", + type: "table", + pizzagna: { + [PizzagnaLayer.Configuration]: { + [DEFAULT_PIZZAGNA_ROOT]: { + providers: { + [WellKnownProviders.InteractionHandler]: { + providerId: NOVA_URL_INTERACTION_HANDLER, + }, + }, + }, + header: { + properties: { + title: "Table Widget with Selection!", + subtitle: "Basic table widget", + collapsible: true, + }, + }, + table: { + providers: { + [WellKnownProviders.DataSource]: { + providerId: AcmeTableMockDataSource.providerId, + } as IProviderConfiguration, + }, + properties: { + configuration: { + // enabling selection here + selectionConfiguration: + this.selectionConfiguration, + columns: [ + { + id: "column1", + label: "No.", + isActive: true, + formatter: { + componentType: + RawFormatterComponent.lateLoadKey, + properties: { + dataFieldIds: { + value: "position", + }, + }, + }, + }, + { + id: "column2", + label: "Name", + isActive: true, + formatter: { + componentType: + RawFormatterComponent.lateLoadKey, + properties: { + dataFieldIds: { + value: "name", + }, + }, + }, + }, + { + id: "column3", + label: "Status", + isActive: true, + formatter: { + componentType: + RawFormatterComponent.lateLoadKey, + properties: { + dataFieldIds: { + value: "status", + }, + }, + }, + }, + ], + } as ITableWidgetConfig, + }, + }, + }, + }, + }; + } +} +\\\`, + "widget-types/timeseries/timeseries-docs.component.ts": \\\`// © 2022 SolarWinds Worldwide, LLC. All rights reserved. +// +// Permission is hereby granted, free of charge, to any person obtaining a copy +// of this software and associated documentation files (the "Software"), to +// deal in the Software without restriction, including without limitation the +// rights to use, copy, modify, merge, publish, distribute, sublicense, and/or +// sell copies of the Software, and to permit persons to whom the Software is +// furnished to do so, subject to the following conditions: +// +// The above copyright notice and this permission notice shall be included in +// all copies or substantial portions of the Software. +// +// THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR +// IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, +// FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE +// AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER +// LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, +// OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN +// THE SOFTWARE. + +import { Component, OnInit } from "@angular/core"; + +import { mapContentFile } from "../../../../demo-files-factory"; + +@Component({ + selector: "nui-timeseries-docs", + templateUrl: "./timeseries-docs.component.html", + standalone: false, +}) +export class TimeseriesDocsComponent implements OnInit { + public timeseriesWidgetFileText = ""; + public timeseriesConfiguratorFileText = ""; + + async ngOnInit(): Promise { + this.timeseriesWidgetFileText = await import( + "./../../../../../../src/lib/widget-types/timeseries/timeseries-widget" + ).then(mapContentFile); + this.timeseriesConfiguratorFileText = await import( + "./../../../../../../src/lib/widget-types/timeseries/timeseries-configurator" + ).then(mapContentFile); + } +} +\\\`, + "widget-types/timeseries/timeseries-docs.module.ts": \\\`// © 2022 SolarWinds Worldwide, LLC. All rights reserved. +// +// Permission is hereby granted, free of charge, to any person obtaining a copy +// of this software and associated documentation files (the "Software"), to +// deal in the Software without restriction, including without limitation the +// rights to use, copy, modify, merge, publish, distribute, sublicense, and/or +// sell copies of the Software, and to permit persons to whom the Software is +// furnished to do so, subject to the following conditions: +// +// The above copyright notice and this permission notice shall be included in +// all copies or substantial portions of the Software. +// +// THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR +// IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, +// FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE +// AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER +// LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, +// OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN +// THE SOFTWARE. + +import { NgModule } from "@angular/core"; +import { RouterModule, Routes } from "@angular/router"; + +import { DEMO_PATH_TOKEN } from "@nova-ui/bits"; +import { + NuiButtonModule, + NuiDocsModule, + NuiMessageModule, + NuiSwitchModule, +} from "@nova-ui/bits"; +import { NuiDashboardsModule } from "@nova-ui/dashboards"; + +import { TimeseriesDocsComponent } from "./timeseries-docs.component"; +import { TimeseriesWidgetExampleComponent } from "./timeseries-widget-example/timeseries-widget-example.component"; +import { TimeseriesWidgetInteractiveExampleComponent } from "./timeseries-widget-interactive-example/timeseries-widget-interactive-example.component"; +import { TimeseriesWidgetStatusBarExampleComponent } from "./timeseries-widget-status-bar-example/timeseries-widget-status-bar-example.component"; +import { getDemoFiles } from "../../../../demo-files-factory"; + +const routes: Routes = [ + { + path: "", + component: TimeseriesDocsComponent, + data: { + srlc: { + hideIndicator: true, + }, + showThemeSwitcher: true, + }, + }, + { + path: "example", + component: TimeseriesWidgetExampleComponent, + data: { + srlc: { + hideIndicator: true, + }, + }, + }, +]; + +@NgModule({ + imports: [ + RouterModule.forChild(routes), + NuiButtonModule, + NuiDocsModule, + NuiMessageModule, + NuiSwitchModule, + NuiDashboardsModule, + ], + declarations: [ + TimeseriesDocsComponent, + TimeseriesWidgetExampleComponent, + TimeseriesWidgetInteractiveExampleComponent, + TimeseriesWidgetStatusBarExampleComponent, + ], + providers: [ + { + provide: DEMO_PATH_TOKEN, + useValue: getDemoFiles("timeseries"), + }, + ], +}) +export default class TimeseriesDocsModule {} +\\\`, + "widget-types/timeseries/timeseries-widget-example/timeseries-widget-example.component.ts": \\\`// © 2022 SolarWinds Worldwide, LLC. All rights reserved. +// +// Permission is hereby granted, free of charge, to any person obtaining a copy +// of this software and associated documentation files (the "Software"), to +// deal in the Software without restriction, including without limitation the +// rights to use, copy, modify, merge, publish, distribute, sublicense, and/or +// sell copies of the Software, and to permit persons to whom the Software is +// furnished to do so, subject to the following conditions: +// +// The above copyright notice and this permission notice shall be included in +// all copies or substantial portions of the Software. +// +// THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR +// IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, +// FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE +// AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER +// LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, +// OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN +// THE SOFTWARE. + +import { + ChangeDetectorRef, + Component, + Injectable, + OnInit, +} from "@angular/core"; +import { GridsterConfig, GridsterItem } from "angular-gridster2"; +import cloneDeep from "lodash/cloneDeep"; +import keyBy from "lodash/keyBy"; +import moment, { Moment } from "moment/moment"; +import { BehaviorSubject } from "rxjs"; + +import { + DataSourceService, + IDataSource, + INovaFilters, + ITimeframe, +} from "@nova-ui/bits"; +import { + DATA_SOURCE, + DEFAULT_PIZZAGNA_ROOT, + IDashboard, + IDataSourceOutput, + IProviderConfiguration, + ISerializableTimeframe, + ITimeseriesItemConfiguration, + ITimeseriesOutput, + ITimeseriesScaleConfig, + ITimeseriesWidgetConfig, + ITimeseriesWidgetData, + ITimeseriesWidgetSeriesData, + IWidget, + LegendPlacement, + PizzagnaLayer, + ProviderRegistryService, + TimeseriesChartPreset, + TimeseriesScaleType, + WellKnownPathKey, + WellKnownProviders, + WidgetTypesService, +} from "@nova-ui/dashboards"; + +/** + * A simple Timeseries data source implementation + */ +@Injectable() +export class BeerVsReadingMockDataSource + extends DataSourceService + implements IDataSource +{ + public static providerId = "BeerVsReadingMockDataSource"; + + public busy = new BehaviorSubject(false); + + public async getFilteredData( + filters: INovaFilters + ): Promise> { + // In this example we're using some static mock data located at the bottom of this file. In a real-world + // scenario, the data for the chart would likely be retrieved via an asynchronous backend call. + let filteredData = getData(); + + this.busy.next(true); + + // Filtering using the filter registered by the TimeFrameBar + const timeframeFilter = filters.timeframe?.value as ITimeframe; + if (timeframeFilter) { + filteredData = filteredData.map((item: ITimeseriesWidgetData) => ({ + id: item.id, + name: item.name, + description: item.description, + data: item.data.filter( + (seriesData: ITimeseriesWidgetSeriesData) => + filterDates( + seriesData.x, + timeframeFilter.startDatetime, + timeframeFilter.endDatetime + ) + ), + })); + } + + this.busy.next(false); + + return { result: { series: filteredData } }; + } +} + +function filterDates(dateToCheck: Date, startDate: Moment, endDate: Moment) { + const mom = moment(dateToCheck); + return ( + mom.isBetween(startDate, endDate) || + mom.isSame(startDate) || + mom.isSame(endDate) + ); +} + +/** + * A component that instantiates the dashboard + */ +@Component({ + selector: "timeseries-widget-example", + templateUrl: "./timeseries-widget-example.component.html", + styleUrls: ["./timeseries-widget-example.component.less"], + standalone: false, +}) +export class TimeseriesWidgetExampleComponent implements OnInit { + // This variable will hold all the data needed to define the layout and behavior of the widgets. + // Pass this to the dashboard component's dashboard input in the template. + public dashboard: IDashboard | undefined; + + // Angular gridster requires a configuration object even if it's empty. + // Pass this to the dashboard component's gridsterConfig input in the template. + public gridsterConfig: GridsterConfig = {}; + + // Boolean passed as an input to the dashboard. When true, widgets can be moved, resized, removed, or edited + public editMode: boolean = false; + + constructor( + // WidgetTypesService provides the widget's necessary structure information + private widgetTypesService: WidgetTypesService, + + // In general, the ProviderRegistryService is used for making entities available for injection into dynamically loaded components. + private providerRegistry: ProviderRegistryService, + + // Angular's ChangeDetectorRef + private changeDetectorRef: ChangeDetectorRef + ) {} + + public ngOnInit(): void { + // Grabbing the widget's default template which will be needed as a parameter for setNode + const widgetTemplate = this.widgetTypesService.getWidgetType( + "timeseries", + 1 + ); + // Registering our data sources as dropdown options in the widget editor/configurator + // Note: This could also be done in the parent module's constructor so that + // multiple dashboards could have access to the same widget template modification. + this.widgetTypesService.setNode( + // This is the template we grabbed above with getWidgetType + widgetTemplate, + // We are setting the editor/configurator part of the widget template + "configurator", + // This indicates which node you are changing and we want to change + // the data source providers available for selection in the editor. + WellKnownPathKey.DataSourceProviders, + // We are setting the data sources available for selection in the editor + [BeerVsReadingMockDataSource.providerId] + ); + + // Registering the data source for injection into the widget. + this.providerRegistry.setProviders({ + [BeerVsReadingMockDataSource.providerId]: { + provide: DATA_SOURCE, + useClass: BeerVsReadingMockDataSource, + deps: [], + }, + }); + + this.initializeDashboard(); + } + + public initializeDashboard(): void { + // We're using a static configuration object for this example, but this is where + // the widget's configuration could potentially be populated from a database + const widgetsWithStructure = widgetConfigs.map((w) => + this.widgetTypesService.mergeWithWidgetType(w) + ); + const widgetsIndex = keyBy(widgetsWithStructure, (w: IWidget) => w.id); + + // Finally, assigning the variables we created above to the dashboard + this.dashboard = { + positions: cloneDeep(positions), + widgets: widgetsIndex, + }; + } + + /** Used for restoring widgets state */ + public reInitializeDashboard(): void { + // destroys the components and their providers so the dashboard can re init data + this.dashboard = undefined; + this.changeDetectorRef.detectChanges(); + + this.initializeDashboard(); + } +} + +const widgetConfigs: IWidget[] = [ + { + id: "lineWidgetId", + type: "timeseries", + pizzagna: { + [PizzagnaLayer.Configuration]: { + [DEFAULT_PIZZAGNA_ROOT]: { + providers: { + [WellKnownProviders.DataSource]: { + // Setting the initially selected data source providerId + providerId: BeerVsReadingMockDataSource.providerId, + } as IProviderConfiguration, + }, + }, + header: { + properties: { + title: "Line Chart", + subtitle: "Survey of 1000 Solarians", + }, + }, + chart: { + providers: { + [WellKnownProviders.Adapter]: { + properties: { + // Setting the series and corresponding labels to initially display on the chart + series: [ + { + id: "series-1", + label: "Beer Tasting", + selectedSeriesId: "series-1", + }, + { + id: "series-2", + label: "Reading", + selectedSeriesId: "series-2", + }, + ] as ITimeseriesItemConfiguration[], + }, + } as Partial, + }, + properties: { + // Setting the general chart configuration + configuration: { + legendPlacement: LegendPlacement.Right, + enableZoom: true, + leftAxisLabel: "Solarians (%)", + // You can optionally define custom colors for the chart by setting the 'chartColors' configuration property + // "chartColors": [ + // "var(--nui-color-chart-eight)", + // "var(--nui-color-chart-nine)", + // "var(--nui-color-chart-ten)", + // ], + } as ITimeseriesWidgetConfig, + }, + }, + timeframeSelection: { + properties: { + // Setting the initial timeframe selected in the timeframe bar + timeframe: { + selectedPresetId: "last7Days", + } as ISerializableTimeframe, + minDate: moment().subtract(60, "days").format(), + maxDate: moment().format(), + }, + }, + }, + }, + }, + { + id: "stackedAreaWidgetId", + type: "timeseries", + pizzagna: { + [PizzagnaLayer.Configuration]: { + [DEFAULT_PIZZAGNA_ROOT]: { + providers: { + [WellKnownProviders.DataSource]: { + // Setting the initially selected data source providerId + providerId: BeerVsReadingMockDataSource.providerId, + } as IProviderConfiguration, + }, + }, + header: { + properties: { + title: "Stacked Area Chart", + subtitle: "Survey of 1000 Solarians", + }, + }, + chart: { + providers: { + [WellKnownProviders.Adapter]: { + properties: { + // Setting the series and corresponding labels to initially display on the chart + series: [ + { + id: "series-1", + label: "Beer Tasting", + selectedSeriesId: "series-1", + }, + { + id: "series-2", + label: "Reading", + selectedSeriesId: "series-2", + }, + ] as ITimeseriesItemConfiguration[], + }, + } as Partial, + }, + properties: { + // Setting the general chart configuration + configuration: { + legendPlacement: LegendPlacement.Right, + enableZoom: true, + // Setting the preset to stacked area + preset: TimeseriesChartPreset.StackedArea, + leftAxisLabel: "Solarians (%)", + // You can optionally define custom colors for the chart by setting the 'chartColors' configuration property + // "chartColors": [ + // "var(--nui-color-chart-eight)", + // "var(--nui-color-chart-nine)", + // "var(--nui-color-chart-ten)", + // ], + } as ITimeseriesWidgetConfig, + }, + }, + timeframeSelection: { + properties: { + // Setting the initial timeframe selected in the timeframe bar + timeframe: { + selectedPresetId: "last7Days", + } as ISerializableTimeframe, + minDate: moment().subtract(60, "days").format(), + maxDate: moment().format(), + }, + }, + }, + }, + }, + { + id: "stackedPercentageAreaWidgetId", + type: "timeseries", + pizzagna: { + [PizzagnaLayer.Configuration]: { + [DEFAULT_PIZZAGNA_ROOT]: { + providers: { + [WellKnownProviders.DataSource]: { + // Setting the initially selected data source providerId + providerId: BeerVsReadingMockDataSource.providerId, + } as IProviderConfiguration, + }, + }, + header: { + properties: { + title: "Stacked Percentage Area Chart", + subtitle: "Survey of 1000 Solarians", + }, + }, + chart: { + providers: { + [WellKnownProviders.Adapter]: { + properties: { + // Setting the series and corresponding labels to initially display on the chart + series: [ + { + id: "series-1", + label: "Beer Tasting", + selectedSeriesId: "series-1", + }, + { + id: "series-2", + label: "Reading", + selectedSeriesId: "series-2", + }, + ] as ITimeseriesItemConfiguration[], + }, + } as Partial, + }, + properties: { + // Setting the general chart configuration + configuration: { + legendPlacement: LegendPlacement.Right, + enableZoom: true, + // Setting the preset to stacked percentage area + preset: TimeseriesChartPreset.StackedPercentageArea, + leftAxisLabel: "Solarians (%)", + // You can optionally define custom colors for the chart by setting the 'chartColors' configuration property + // "chartColors": [ + // "var(--nui-color-chart-eight)", + // "var(--nui-color-chart-nine)", + // "var(--nui-color-chart-ten)", + // ], + } as ITimeseriesWidgetConfig, + }, + }, + timeframeSelection: { + properties: { + // Setting the initial timeframe selected in the timeframe bar + timeframe: { + selectedPresetId: "last7Days", + } as ISerializableTimeframe, + minDate: moment().subtract(60, "days").format(), + maxDate: moment().format(), + }, + }, + }, + }, + }, + { + id: "stackedBarWidgetId", + type: "timeseries", + pizzagna: { + [PizzagnaLayer.Configuration]: { + [DEFAULT_PIZZAGNA_ROOT]: { + providers: { + [WellKnownProviders.DataSource]: { + // Setting the initially selected data source providerId + providerId: BeerVsReadingMockDataSource.providerId, + } as IProviderConfiguration, + }, + }, + header: { + properties: { + title: "Stacked Bar Chart", + subtitle: "Survey of 1000 Solarians", + }, + }, + chart: { + providers: { + [WellKnownProviders.Adapter]: { + properties: { + // Setting the series and corresponding labels to initially display on the chart + series: [ + { + id: "series-1", + label: "Beer Tasting", + selectedSeriesId: "series-1", + }, + { + id: "series-2", + label: "Reading", + selectedSeriesId: "series-2", + }, + ] as ITimeseriesItemConfiguration[], + }, + } as Partial, + }, + properties: { + configuration: { + legendPlacement: LegendPlacement.Right, + enableZoom: true, + leftAxisLabel: "Solarians (%)", + // Setting the preset to stacked bar + preset: TimeseriesChartPreset.StackedBar, + scales: { + x: { + type: TimeseriesScaleType.TimeInterval, + properties: { + interval: 24 * 60 * 60, + }, + } as ITimeseriesScaleConfig, + }, + } as ITimeseriesWidgetConfig, + }, + }, + timeframeSelection: { + properties: { + // Setting the initial timeframe selected in the timeframe bar + timeframe: { + selectedPresetId: "last7Days", + } as ISerializableTimeframe, + minDate: moment().subtract(60, "days").format(), + maxDate: moment().format(), + }, + }, + }, + }, + }, +]; + +// using startOf("day") so that each band for the bar chart corresponds to a calendar day +const now = moment().startOf("day"); + +export const getData = (): ITimeseriesWidgetData[] => [ + { + id: "series-1", + name: "Beer Tasting", + description: "Havin' some suds", + data: [ + { x: now.clone().subtract(20, "day").toDate(), y: 30 }, + { x: now.clone().subtract(19, "day").toDate(), y: 35 }, + { x: now.clone().subtract(18, "day").toDate(), y: 33 }, + { x: now.clone().subtract(17, "day").toDate(), y: 40 }, + { x: now.clone().subtract(16, "day").toDate(), y: 35 }, + { x: now.clone().subtract(15, "day").toDate(), y: 30 }, + { x: now.clone().subtract(14, "day").toDate(), y: 35 }, + { x: now.clone().subtract(13, "day").toDate(), y: 15 }, + { x: now.clone().subtract(12, "day").toDate(), y: 30 }, + { x: now.clone().subtract(11, "day").toDate(), y: 45 }, + { x: now.clone().subtract(10, "day").toDate(), y: 60 }, + { x: now.clone().subtract(9, "day").toDate(), y: 54 }, + { x: now.clone().subtract(8, "day").toDate(), y: 42 }, + { x: now.clone().subtract(7, "day").toDate(), y: 44 }, + { x: now.clone().subtract(6, "day").toDate(), y: 54 }, + { x: now.clone().subtract(5, "day").toDate(), y: 43 }, + { x: now.clone().subtract(4, "day").toDate(), y: 76 }, + { x: now.clone().subtract(3, "day").toDate(), y: 54 }, + { x: now.clone().subtract(2, "day").toDate(), y: 42 }, + { x: now.clone().subtract(1, "day").toDate(), y: 34 }, + ], + }, + { + id: "series-2", + name: "Reading", + description: "Hittin' the books", + data: [ + { x: now.clone().subtract(20, "day").toDate(), y: 60 }, + { x: now.clone().subtract(19, "day").toDate(), y: 64 }, + { x: now.clone().subtract(18, "day").toDate(), y: 70 }, + { x: now.clone().subtract(17, "day").toDate(), y: 55 }, + { x: now.clone().subtract(16, "day").toDate(), y: 55 }, + { x: now.clone().subtract(15, "day").toDate(), y: 45 }, + { x: now.clone().subtract(14, "day").toDate(), y: 60 }, + { x: now.clone().subtract(13, "day").toDate(), y: 65 }, + { x: now.clone().subtract(12, "day").toDate(), y: 63 }, + { x: now.clone().subtract(11, "day").toDate(), y: 60 }, + { x: now.clone().subtract(10, "day").toDate(), y: 61 }, + { x: now.clone().subtract(9, "day").toDate(), y: 65 }, + { x: now.clone().subtract(8, "day").toDate(), y: 63 }, + { x: now.clone().subtract(7, "day").toDate(), y: 58 }, + { x: now.clone().subtract(6, "day").toDate(), y: 64 }, + { x: now.clone().subtract(5, "day").toDate(), y: 63 }, + { x: now.clone().subtract(4, "day").toDate(), y: 60 }, + { x: now.clone().subtract(3, "day").toDate(), y: 62 }, + { x: now.clone().subtract(2, "day").toDate(), y: 61 }, + { x: now.clone().subtract(1, "day").toDate(), y: 62 }, + ], + }, +]; + +// Setting the widget dimensions and position (this is for gridster) +const positions: Record = { + [widgetConfigs[0].id]: { + cols: 6, + rows: 6, + y: 0, + x: 0, + }, + [widgetConfigs[1].id]: { + cols: 6, + rows: 6, + y: 0, + x: 6, + }, + [widgetConfigs[3].id]: { + cols: 6, + rows: 6, + y: 6, + x: 0, + }, + [widgetConfigs[2].id]: { + cols: 6, + rows: 6, + y: 6, + x: 6, + }, +}; +\\\`, + "widget-types/timeseries/timeseries-widget-interactive-example/timeseries-widget-interactive-example.component.ts": \\\`// © 2022 SolarWinds Worldwide, LLC. All rights reserved. +// +// Permission is hereby granted, free of charge, to any person obtaining a copy +// of this software and associated documentation files (the "Software"), to +// deal in the Software without restriction, including without limitation the +// rights to use, copy, modify, merge, publish, distribute, sublicense, and/or +// sell copies of the Software, and to permit persons to whom the Software is +// furnished to do so, subject to the following conditions: +// +// The above copyright notice and this permission notice shall be included in +// all copies or substantial portions of the Software. +// +// THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR +// IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, +// FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE +// AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER +// LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, +// OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN +// THE SOFTWARE. + +import { + ChangeDetectorRef, + Component, + Injectable, + OnInit, +} from "@angular/core"; +import { GridsterConfig, GridsterItem } from "angular-gridster2"; +import cloneDeep from "lodash/cloneDeep"; +import keyBy from "lodash/keyBy"; +import moment, { Moment } from "moment/moment"; +import { BehaviorSubject } from "rxjs"; + +import { + DataSourceService, + IDataSource, + INovaFilters, + ITimeframe, +} from "@nova-ui/bits"; +import { + DATA_SOURCE, + DEFAULT_PIZZAGNA_ROOT, + IDashboard, + IDataSourceOutput, + IProviderConfiguration, + ISerializableTimeframe, + ITimeseriesItemConfiguration, + ITimeseriesOutput, + ITimeseriesScaleConfig, + ITimeseriesWidgetConfig, + ITimeseriesWidgetData, + ITimeseriesWidgetSeriesData, + IWidget, + NOVA_URL_INTERACTION_HANDLER, + LegendPlacement, + PizzagnaLayer, + ProviderRegistryService, + TimeseriesChartPreset, + TimeseriesScaleType, + WellKnownPathKey, + WellKnownProviders, + WidgetTypesService, +} from "@nova-ui/dashboards"; + +/** + * A simple Timeseries data source implementation + */ +@Injectable() +export class TimeseriesMockDataSource + extends DataSourceService + implements IDataSource +{ + public static providerId = "TimeseriesMockDataSource"; + + public busy = new BehaviorSubject(false); + + public async getFilteredData( + filters: INovaFilters + ): Promise> { + // In this example we're using some static mock data located at the bottom of this file. In a real-world + // scenario, the data for the chart would likely be retrieved via an asynchronous backend call. + let filteredData = getData(); + + this.busy.next(true); + + // Filtering using the filter registered by the TimeFrameBar + const timeframeFilter = filters.timeframe?.value as ITimeframe; + if (timeframeFilter) { + filteredData = filteredData.map((item: ITimeseriesWidgetData) => ({ + id: item.id, + name: item.name, + description: item.description, + // the filtered data should return the provided links if they are set. + link: item?.link, + secondaryLink: item?.secondaryLink, + data: item.data.filter( + (seriesData: ITimeseriesWidgetSeriesData) => + filterDates( + seriesData.x, + timeframeFilter.startDatetime, + timeframeFilter.endDatetime + ) + ), + })); + } + + this.busy.next(false); + + return { result: { series: filteredData } }; + } +} + +function filterDates(dateToCheck: Date, startDate: Moment, endDate: Moment) { + const mom = moment(dateToCheck); + return ( + mom.isBetween(startDate, endDate) || + mom.isSame(startDate) || + mom.isSame(endDate) + ); +} + +/** + * A component that instantiates the dashboard + */ +@Component({ + selector: "timeseries-widget-interactive-example", + templateUrl: "./timeseries-widget-interactive-example.component.html", + styleUrls: ["./timeseries-widget-interactive-example.component.less"], + standalone: false, +}) +export class TimeseriesWidgetInteractiveExampleComponent implements OnInit { + // This variable will hold all the data needed to define the layout and behavior of the widgets. + // Pass this to the dashboard component's dashboard input in the template. + public dashboard: IDashboard | undefined; + + // Angular gridster requires a configuration object even if it's empty. + // Pass this to the dashboard component's gridsterConfig input in the template. + public gridsterConfig: GridsterConfig = {}; + + // Boolean passed as an input to the dashboard. When true, widgets can be moved, resized, removed, or edited + public editMode: boolean = false; + + constructor( + // WidgetTypesService provides the widget's necessary structure information + private widgetTypesService: WidgetTypesService, + + // In general, the ProviderRegistryService is used for making entities available for injection into dynamically loaded components. + private providerRegistry: ProviderRegistryService, + + // Angular's ChangeDetectorRef + private changeDetectorRef: ChangeDetectorRef + ) {} + + public ngOnInit(): void { + // Grabbing the widget's default template which will be needed as a parameter for setNode + const widgetTemplate = this.widgetTypesService.getWidgetType( + "timeseries", + 1 + ); + // Registering our data sources as dropdown options in the widget editor/configurator + // Note: This could also be done in the parent module's constructor so that + // multiple dashboards could have access to the same widget template modification. + this.widgetTypesService.setNode( + // This is the template we grabbed above with getWidgetType + widgetTemplate, + // We are setting the editor/configurator part of the widget template + "configurator", + // This indicates which node you are changing and we want to change + // the data source providers available for selection in the editor. + WellKnownPathKey.DataSourceProviders, + // We are setting the data sources available for selection in the editor + [TimeseriesMockDataSource.providerId] + ); + + // Registering the data source for injection into the widget. + this.providerRegistry.setProviders({ + [TimeseriesMockDataSource.providerId]: { + provide: DATA_SOURCE, + useClass: TimeseriesMockDataSource, + deps: [], + }, + }); + + this.initializeDashboard(); + } + + public initializeDashboard(): void { + // We're using a static configuration object for this example, but this is where + // the widget's configuration could potentially be populated from a database + const widgetsWithStructure = widgetConfigs.map((w) => + this.widgetTypesService.mergeWithWidgetType(w) + ); + const widgetsIndex = keyBy(widgetsWithStructure, (w: IWidget) => w.id); + + // Finally, assigning the variables we created above to the dashboard + this.dashboard = { + positions: cloneDeep(positions), + widgets: widgetsIndex, + }; + } + + /** Used for restoring widgets state */ + public reInitializeDashboard(): void { + // destroys the components and their providers so the dashboard can re init data + this.dashboard = undefined; + this.changeDetectorRef.detectChanges(); + + this.initializeDashboard(); + } +} + +const widgetConfigs: IWidget[] = [ + { + id: "lineWidgetId", + type: "timeseries", + pizzagna: { + [PizzagnaLayer.Configuration]: { + [DEFAULT_PIZZAGNA_ROOT]: { + providers: { + [WellKnownProviders.DataSource]: { + // Setting the initially selected data source providerId + providerId: TimeseriesMockDataSource.providerId, + } as IProviderConfiguration, + [WellKnownProviders.InteractionHandler]: { + // Setting the UrlInteractionHandler as an interactionHandler + providerId: NOVA_URL_INTERACTION_HANDLER, + properties: { + // the 'url' property tells the handler what link to use when interaction occurs on the series + url: "\\\\\\\${data.link || 'https://en.wikipedia.org/wiki/'+data.legendDescriptionPrimary}", + // by default the link is opened in the current window, set 'newWindow' to true to open in a new tab instead + // newWindow: true, + }, + }, + }, + }, + header: { + properties: { + title: "Line Chart", + subtitle: "Basic Timeseries with Interaction", + }, + }, + chart: { + providers: { + [WellKnownProviders.Adapter]: { + properties: { + // Setting the series and corresponding labels to initially display on the chart + series: [ + { + id: "series-1", + label: "Nur-Sultan", + selectedSeriesId: "series-1", + }, + { + id: "series-2", + label: "Brno", + selectedSeriesId: "series-2", + }, + { + id: "series-3", + label: "Lisbon", + selectedSeriesId: "series-3", + }, + { + id: "series-4", + label: "Austin", + selectedSeriesId: "series-4", + }, + ] as ITimeseriesItemConfiguration[], + }, + } as Partial, + }, + properties: { + // Setting the general chart configuration + configuration: { + // setting interaction to 'series' will make all series in the chart interactable + interaction: "series", + legendPlacement: LegendPlacement.Right, + enableZoom: true, + } as ITimeseriesWidgetConfig, + }, + }, + timeframeSelection: { + properties: { + timeframe: { + selectedPresetId: "last7Days", + } as ISerializableTimeframe, + minDate: moment().subtract(60, "days").format(), + maxDate: moment().format(), + }, + }, + }, + }, + }, + { + id: "stackedBarWidgetId", + type: "timeseries", + pizzagna: { + [PizzagnaLayer.Configuration]: { + [DEFAULT_PIZZAGNA_ROOT]: { + providers: { + [WellKnownProviders.DataSource]: { + // Setting the initially selected data source providerId + providerId: TimeseriesMockDataSource.providerId, + } as IProviderConfiguration, + }, + }, + header: { + properties: { + title: "Stacked Bar Chart", + subtitle: + "Basic Timeseries without Interaction Handler", + }, + }, + chart: { + providers: { + [WellKnownProviders.Adapter]: { + properties: { + // Setting the series and corresponding labels to initially display on the chart + series: [ + { + id: "series-1", + label: "Nur-Sultan", + selectedSeriesId: "series-1", + }, + { + id: "series-2", + label: "Brno", + selectedSeriesId: "series-2", + }, + { + id: "series-3", + label: "Lisbon", + selectedSeriesId: "series-3", + }, + { + id: "series-4", + label: "Austin", + selectedSeriesId: "series-4", + }, + ] as ITimeseriesItemConfiguration[], + }, + } as Partial, + }, + properties: { + // Setting the general chart configuration + configuration: { + legendPlacement: LegendPlacement.Right, + enableZoom: true, + // Setting the preset to stacked bar + preset: TimeseriesChartPreset.StackedBar, + scales: { + x: { + type: TimeseriesScaleType.TimeInterval, + properties: { + interval: 24 * 60 * 60, + }, + } as ITimeseriesScaleConfig, + }, + } as ITimeseriesWidgetConfig, + }, + }, + timeframeSelection: { + properties: { + // Setting the initial timeframe selected in the timeframe bar + timeframe: { + selectedPresetId: "last7Days", + } as ISerializableTimeframe, + minDate: moment().subtract(60, "days").format(), + maxDate: moment().format(), + }, + }, + }, + }, + }, +]; + +// using startOf("day") so that each band for the bar chart corresponds to a calendar day +const startOfToday = moment().startOf("day").toDate(); + +export const getData = (): ITimeseriesWidgetData[] => [ + { + id: "series-1", + name: "Nur-Sultan", + description: "'link' only", + link: "https://en.wikipedia.org/wiki/Nur-Sultan", + data: [ + { x: moment(startOfToday).subtract(59, "day").toDate(), y: 35 }, + { x: moment(startOfToday).subtract(58, "day").toDate(), y: 33 }, + { x: moment(startOfToday).subtract(57, "day").toDate(), y: 40 }, + { x: moment(startOfToday).subtract(56, "day").toDate(), y: 35 }, + { x: moment(startOfToday).subtract(55, "day").toDate(), y: 30 }, + { x: moment(startOfToday).subtract(54, "day").toDate(), y: 35 }, + { x: moment(startOfToday).subtract(53, "day").toDate(), y: 15 }, + { x: moment(startOfToday).subtract(52, "day").toDate(), y: 30 }, + { x: moment(startOfToday).subtract(51, "day").toDate(), y: 35 }, + { x: moment(startOfToday).subtract(50, "day").toDate(), y: 34 }, + { x: moment(startOfToday).subtract(49, "day").toDate(), y: 35 }, + { x: moment(startOfToday).subtract(48, "day").toDate(), y: 33 }, + { x: moment(startOfToday).subtract(47, "day").toDate(), y: 40 }, + { x: moment(startOfToday).subtract(46, "day").toDate(), y: 35 }, + { x: moment(startOfToday).subtract(45, "day").toDate(), y: 30 }, + { x: moment(startOfToday).subtract(44, "day").toDate(), y: 35 }, + { x: moment(startOfToday).subtract(43, "day").toDate(), y: 15 }, + { x: moment(startOfToday).subtract(42, "day").toDate(), y: 30 }, + { x: moment(startOfToday).subtract(41, "day").toDate(), y: 35 }, + { x: moment(startOfToday).subtract(40, "day").toDate(), y: 34 }, + { x: moment(startOfToday).subtract(39, "day").toDate(), y: 35 }, + { x: moment(startOfToday).subtract(38, "day").toDate(), y: 33 }, + { x: moment(startOfToday).subtract(37, "day").toDate(), y: 40 }, + { x: moment(startOfToday).subtract(36, "day").toDate(), y: 35 }, + { x: moment(startOfToday).subtract(35, "day").toDate(), y: 30 }, + { x: moment(startOfToday).subtract(34, "day").toDate(), y: 35 }, + { x: moment(startOfToday).subtract(33, "day").toDate(), y: 15 }, + { x: moment(startOfToday).subtract(32, "day").toDate(), y: 30 }, + { x: moment(startOfToday).subtract(31, "day").toDate(), y: 35 }, + { x: moment(startOfToday).subtract(30, "day").toDate(), y: 34 }, + { x: moment(startOfToday).subtract(29, "day").toDate(), y: 35 }, + { x: moment(startOfToday).subtract(28, "day").toDate(), y: 33 }, + { x: moment(startOfToday).subtract(27, "day").toDate(), y: 40 }, + { x: moment(startOfToday).subtract(26, "day").toDate(), y: 35 }, + { x: moment(startOfToday).subtract(25, "day").toDate(), y: 30 }, + { x: moment(startOfToday).subtract(24, "day").toDate(), y: 35 }, + { x: moment(startOfToday).subtract(23, "day").toDate(), y: 15 }, + { x: moment(startOfToday).subtract(22, "day").toDate(), y: 30 }, + { x: moment(startOfToday).subtract(21, "day").toDate(), y: 35 }, + { x: moment(startOfToday).subtract(20, "day").toDate(), y: 34 }, + { x: moment(startOfToday).subtract(19, "day").toDate(), y: 35 }, + { x: moment(startOfToday).subtract(18, "day").toDate(), y: 33 }, + { x: moment(startOfToday).subtract(17, "day").toDate(), y: 40 }, + { x: moment(startOfToday).subtract(16, "day").toDate(), y: 35 }, + { x: moment(startOfToday).subtract(15, "day").toDate(), y: 30 }, + { x: moment(startOfToday).subtract(14, "day").toDate(), y: 35 }, + { x: moment(startOfToday).subtract(13, "day").toDate(), y: 15 }, + { x: moment(startOfToday).subtract(12, "day").toDate(), y: 30 }, + { x: moment(startOfToday).subtract(11, "day").toDate(), y: 35 }, + { x: moment(startOfToday).subtract(10, "day").toDate(), y: 34 }, + { x: moment(startOfToday).subtract(9, "day").toDate(), y: 33 }, + { x: moment(startOfToday).subtract(8, "day").toDate(), y: 35 }, + { x: moment(startOfToday).subtract(7, "day").toDate(), y: 36 }, + { x: moment(startOfToday).subtract(6, "day").toDate(), y: 34 }, + { x: moment(startOfToday).subtract(5, "day").toDate(), y: 33 }, + { x: moment(startOfToday).subtract(4, "day").toDate(), y: 30 }, + { x: moment(startOfToday).subtract(3, "day").toDate(), y: 32 }, + { x: moment(startOfToday).subtract(2, "day").toDate(), y: 31 }, + { x: moment(startOfToday).subtract(1, "day").toDate(), y: 34 }, + { x: moment(startOfToday).toDate(), y: 25 }, + ], + }, + { + id: "series-2", + name: "Brno", + description: "'link' and 'secondaryLink'", + link: "https://en.wikipedia.org/wiki/Brno", + secondaryLink: "https://en.wikipedia.org/wiki/Europe", + data: [ + { x: moment(startOfToday).subtract(59, "day").toDate(), y: 35 }, + { x: moment(startOfToday).subtract(58, "day").toDate(), y: 33 }, + { x: moment(startOfToday).subtract(57, "day").toDate(), y: 40 }, + { x: moment(startOfToday).subtract(56, "day").toDate(), y: 35 }, + { x: moment(startOfToday).subtract(55, "day").toDate(), y: 30 }, + { x: moment(startOfToday).subtract(54, "day").toDate(), y: 35 }, + { x: moment(startOfToday).subtract(53, "day").toDate(), y: 15 }, + { x: moment(startOfToday).subtract(52, "day").toDate(), y: 30 }, + { x: moment(startOfToday).subtract(51, "day").toDate(), y: 35 }, + { x: moment(startOfToday).subtract(50, "day").toDate(), y: 34 }, + { x: moment(startOfToday).subtract(49, "day").toDate(), y: 35 }, + { x: moment(startOfToday).subtract(48, "day").toDate(), y: 33 }, + { x: moment(startOfToday).subtract(47, "day").toDate(), y: 40 }, + { x: moment(startOfToday).subtract(46, "day").toDate(), y: 35 }, + { x: moment(startOfToday).subtract(45, "day").toDate(), y: 30 }, + { x: moment(startOfToday).subtract(44, "day").toDate(), y: 35 }, + { x: moment(startOfToday).subtract(43, "day").toDate(), y: 15 }, + { x: moment(startOfToday).subtract(42, "day").toDate(), y: 30 }, + { x: moment(startOfToday).subtract(41, "day").toDate(), y: 35 }, + { x: moment(startOfToday).subtract(40, "day").toDate(), y: 34 }, + { x: moment(startOfToday).subtract(39, "day").toDate(), y: 35 }, + { x: moment(startOfToday).subtract(38, "day").toDate(), y: 33 }, + { x: moment(startOfToday).subtract(37, "day").toDate(), y: 40 }, + { x: moment(startOfToday).subtract(36, "day").toDate(), y: 35 }, + { x: moment(startOfToday).subtract(35, "day").toDate(), y: 30 }, + { x: moment(startOfToday).subtract(34, "day").toDate(), y: 35 }, + { x: moment(startOfToday).subtract(33, "day").toDate(), y: 15 }, + { x: moment(startOfToday).subtract(32, "day").toDate(), y: 30 }, + { x: moment(startOfToday).subtract(31, "day").toDate(), y: 35 }, + { x: moment(startOfToday).subtract(30, "day").toDate(), y: 34 }, + { x: moment(startOfToday).subtract(29, "day").toDate(), y: 35 }, + { x: moment(startOfToday).subtract(28, "day").toDate(), y: 33 }, + { x: moment(startOfToday).subtract(27, "day").toDate(), y: 40 }, + { x: moment(startOfToday).subtract(26, "day").toDate(), y: 35 }, + { x: moment(startOfToday).subtract(25, "day").toDate(), y: 30 }, + { x: moment(startOfToday).subtract(24, "day").toDate(), y: 35 }, + { x: moment(startOfToday).subtract(23, "day").toDate(), y: 15 }, + { x: moment(startOfToday).subtract(22, "day").toDate(), y: 30 }, + { x: moment(startOfToday).subtract(21, "day").toDate(), y: 35 }, + { x: moment(startOfToday).subtract(20, "day").toDate(), y: 34 }, + { x: moment(startOfToday).subtract(19, "day").toDate(), y: 64 }, + { x: moment(startOfToday).subtract(18, "day").toDate(), y: 70 }, + { x: moment(startOfToday).subtract(17, "day").toDate(), y: 55 }, + { x: moment(startOfToday).subtract(16, "day").toDate(), y: 55 }, + { x: moment(startOfToday).subtract(15, "day").toDate(), y: 45 }, + { x: moment(startOfToday).subtract(14, "day").toDate(), y: 10 }, + { x: moment(startOfToday).subtract(13, "day").toDate(), y: 65 }, + { x: moment(startOfToday).subtract(12, "day").toDate(), y: 35 }, + { x: moment(startOfToday).subtract(11, "day").toDate(), y: 60 }, + { x: moment(startOfToday).subtract(10, "day").toDate(), y: 61 }, + { x: moment(startOfToday).subtract(9, "day").toDate(), y: 65 }, + { x: moment(startOfToday).subtract(8, "day").toDate(), y: 63 }, + { x: moment(startOfToday).subtract(7, "day").toDate(), y: 58 }, + { x: moment(startOfToday).subtract(6, "day").toDate(), y: 64 }, + { x: moment(startOfToday).subtract(5, "day").toDate(), y: 63 }, + { x: moment(startOfToday).subtract(4, "day").toDate(), y: 60 }, + { x: moment(startOfToday).subtract(3, "day").toDate(), y: 62 }, + { x: moment(startOfToday).subtract(2, "day").toDate(), y: 61 }, + { x: moment(startOfToday).subtract(1, "day").toDate(), y: 62 }, + { x: moment(startOfToday).toDate(), y: 55 }, + ], + }, + { + id: "series-3", + name: "Lisbon", + description: "'secondaryLink' only", + secondaryLink: "https://en.wikipedia.org/wiki/Lisbon", + data: [ + { x: moment(startOfToday).subtract(59, "day").toDate(), y: 35 }, + { x: moment(startOfToday).subtract(58, "day").toDate(), y: 33 }, + { x: moment(startOfToday).subtract(57, "day").toDate(), y: 40 }, + { x: moment(startOfToday).subtract(56, "day").toDate(), y: 35 }, + { x: moment(startOfToday).subtract(55, "day").toDate(), y: 30 }, + { x: moment(startOfToday).subtract(54, "day").toDate(), y: 35 }, + { x: moment(startOfToday).subtract(53, "day").toDate(), y: 15 }, + { x: moment(startOfToday).subtract(52, "day").toDate(), y: 30 }, + { x: moment(startOfToday).subtract(51, "day").toDate(), y: 35 }, + { x: moment(startOfToday).subtract(50, "day").toDate(), y: 34 }, + { x: moment(startOfToday).subtract(49, "day").toDate(), y: 35 }, + { x: moment(startOfToday).subtract(48, "day").toDate(), y: 33 }, + { x: moment(startOfToday).subtract(47, "day").toDate(), y: 40 }, + { x: moment(startOfToday).subtract(46, "day").toDate(), y: 35 }, + { x: moment(startOfToday).subtract(45, "day").toDate(), y: 30 }, + { x: moment(startOfToday).subtract(44, "day").toDate(), y: 35 }, + { x: moment(startOfToday).subtract(43, "day").toDate(), y: 15 }, + { x: moment(startOfToday).subtract(42, "day").toDate(), y: 30 }, + { x: moment(startOfToday).subtract(41, "day").toDate(), y: 35 }, + { x: moment(startOfToday).subtract(40, "day").toDate(), y: 34 }, + { x: moment(startOfToday).subtract(39, "day").toDate(), y: 35 }, + { x: moment(startOfToday).subtract(38, "day").toDate(), y: 33 }, + { x: moment(startOfToday).subtract(37, "day").toDate(), y: 40 }, + { x: moment(startOfToday).subtract(36, "day").toDate(), y: 35 }, + { x: moment(startOfToday).subtract(35, "day").toDate(), y: 30 }, + { x: moment(startOfToday).subtract(34, "day").toDate(), y: 35 }, + { x: moment(startOfToday).subtract(33, "day").toDate(), y: 15 }, + { x: moment(startOfToday).subtract(32, "day").toDate(), y: 30 }, + { x: moment(startOfToday).subtract(31, "day").toDate(), y: 35 }, + { x: moment(startOfToday).subtract(30, "day").toDate(), y: 34 }, + { x: moment(startOfToday).subtract(29, "day").toDate(), y: 35 }, + { x: moment(startOfToday).subtract(28, "day").toDate(), y: 33 }, + { x: moment(startOfToday).subtract(27, "day").toDate(), y: 40 }, + { x: moment(startOfToday).subtract(26, "day").toDate(), y: 35 }, + { x: moment(startOfToday).subtract(25, "day").toDate(), y: 30 }, + { x: moment(startOfToday).subtract(24, "day").toDate(), y: 35 }, + { x: moment(startOfToday).subtract(23, "day").toDate(), y: 15 }, + { x: moment(startOfToday).subtract(22, "day").toDate(), y: 30 }, + { x: moment(startOfToday).subtract(21, "day").toDate(), y: 35 }, + { x: moment(startOfToday).subtract(20, "day").toDate(), y: 34 }, + { x: moment(startOfToday).subtract(19, "day").toDate(), y: 80 }, + { x: moment(startOfToday).subtract(18, "day").toDate(), y: 70 }, + { x: moment(startOfToday).subtract(17, "day").toDate(), y: 95 }, + { x: moment(startOfToday).subtract(16, "day").toDate(), y: 90 }, + { x: moment(startOfToday).subtract(15, "day").toDate(), y: 85 }, + { x: moment(startOfToday).subtract(14, "day").toDate(), y: 70 }, + { x: moment(startOfToday).subtract(13, "day").toDate(), y: 75 }, + { x: moment(startOfToday).subtract(12, "day").toDate(), y: 69 }, + { x: moment(startOfToday).subtract(11, "day").toDate(), y: 75 }, + { x: moment(startOfToday).subtract(10, "day").toDate(), y: 81 }, + { x: moment(startOfToday).subtract(9, "day").toDate(), y: 93 }, + { x: moment(startOfToday).subtract(8, "day").toDate(), y: 83 }, + { x: moment(startOfToday).subtract(7, "day").toDate(), y: 70 }, + { x: moment(startOfToday).subtract(6, "day").toDate(), y: 74 }, + { x: moment(startOfToday).subtract(5, "day").toDate(), y: 73 }, + { x: moment(startOfToday).subtract(4, "day").toDate(), y: 68 }, + { x: moment(startOfToday).subtract(3, "day").toDate(), y: 72 }, + { x: moment(startOfToday).subtract(2, "day").toDate(), y: 61 }, + { x: moment(startOfToday).subtract(1, "day").toDate(), y: 69 }, + { x: moment(startOfToday).toDate(), y: 60 }, + ], + }, + { + id: "series-4", + name: "Austin", + description: "No links", + data: [ + { x: moment(startOfToday).subtract(59, "day").toDate(), y: 25 }, + { x: moment(startOfToday).subtract(58, "day").toDate(), y: 43 }, + { x: moment(startOfToday).subtract(57, "day").toDate(), y: 40 }, + { x: moment(startOfToday).subtract(56, "day").toDate(), y: 65 }, + { x: moment(startOfToday).subtract(55, "day").toDate(), y: 30 }, + { x: moment(startOfToday).subtract(54, "day").toDate(), y: 25 }, + { x: moment(startOfToday).subtract(53, "day").toDate(), y: 45 }, + { x: moment(startOfToday).subtract(52, "day").toDate(), y: 30 }, + { x: moment(startOfToday).subtract(51, "day").toDate(), y: 85 }, + { x: moment(startOfToday).subtract(50, "day").toDate(), y: 74 }, + { x: moment(startOfToday).subtract(49, "day").toDate(), y: 55 }, + { x: moment(startOfToday).subtract(48, "day").toDate(), y: 23 }, + { x: moment(startOfToday).subtract(47, "day").toDate(), y: 40 }, + { x: moment(startOfToday).subtract(46, "day").toDate(), y: 15 }, + { x: moment(startOfToday).subtract(45, "day").toDate(), y: 20 }, + { x: moment(startOfToday).subtract(44, "day").toDate(), y: 65 }, + { x: moment(startOfToday).subtract(43, "day").toDate(), y: 25 }, + { x: moment(startOfToday).subtract(42, "day").toDate(), y: 40 }, + { x: moment(startOfToday).subtract(41, "day").toDate(), y: 25 }, + { x: moment(startOfToday).subtract(40, "day").toDate(), y: 54 }, + { x: moment(startOfToday).subtract(39, "day").toDate(), y: 65 }, + { x: moment(startOfToday).subtract(38, "day").toDate(), y: 33 }, + { x: moment(startOfToday).subtract(37, "day").toDate(), y: 50 }, + { x: moment(startOfToday).subtract(36, "day").toDate(), y: 45 }, + { x: moment(startOfToday).subtract(35, "day").toDate(), y: 20 }, + { x: moment(startOfToday).subtract(34, "day").toDate(), y: 25 }, + { x: moment(startOfToday).subtract(33, "day").toDate(), y: 35 }, + { x: moment(startOfToday).subtract(32, "day").toDate(), y: 20 }, + { x: moment(startOfToday).subtract(31, "day").toDate(), y: 35 }, + { x: moment(startOfToday).subtract(30, "day").toDate(), y: 14 }, + { x: moment(startOfToday).subtract(29, "day").toDate(), y: 55 }, + { x: moment(startOfToday).subtract(28, "day").toDate(), y: 23 }, + { x: moment(startOfToday).subtract(27, "day").toDate(), y: 10 }, + { x: moment(startOfToday).subtract(26, "day").toDate(), y: 5 }, + { x: moment(startOfToday).subtract(25, "day").toDate(), y: 20 }, + { x: moment(startOfToday).subtract(24, "day").toDate(), y: 35 }, + { x: moment(startOfToday).subtract(23, "day").toDate(), y: 15 }, + { x: moment(startOfToday).subtract(22, "day").toDate(), y: 30 }, + { x: moment(startOfToday).subtract(21, "day").toDate(), y: 35 }, + { x: moment(startOfToday).subtract(20, "day").toDate(), y: 34 }, + { x: moment(startOfToday).subtract(19, "day").toDate(), y: 50 }, + { x: moment(startOfToday).subtract(18, "day").toDate(), y: 60 }, + { x: moment(startOfToday).subtract(17, "day").toDate(), y: 95 }, + { x: moment(startOfToday).subtract(16, "day").toDate(), y: 80 }, + { x: moment(startOfToday).subtract(15, "day").toDate(), y: 65 }, + { x: moment(startOfToday).subtract(14, "day").toDate(), y: 80 }, + { x: moment(startOfToday).subtract(13, "day").toDate(), y: 85 }, + { x: moment(startOfToday).subtract(12, "day").toDate(), y: 69 }, + { x: moment(startOfToday).subtract(11, "day").toDate(), y: 65 }, + { x: moment(startOfToday).subtract(10, "day").toDate(), y: 71 }, + { x: moment(startOfToday).subtract(9, "day").toDate(), y: 73 }, + { x: moment(startOfToday).subtract(8, "day").toDate(), y: 43 }, + { x: moment(startOfToday).subtract(7, "day").toDate(), y: 70 }, + { x: moment(startOfToday).subtract(6, "day").toDate(), y: 84 }, + { x: moment(startOfToday).subtract(5, "day").toDate(), y: 73 }, + { x: moment(startOfToday).subtract(4, "day").toDate(), y: 38 }, + { x: moment(startOfToday).subtract(3, "day").toDate(), y: 72 }, + { x: moment(startOfToday).subtract(2, "day").toDate(), y: 81 }, + { x: moment(startOfToday).subtract(1, "day").toDate(), y: 59 }, + { x: moment(startOfToday).toDate(), y: 60 }, + ], + }, +]; +// Setting the widget dimensions and position (this is for gridster) +const positions: Record = { + [widgetConfigs[0].id]: { + cols: 6, + rows: 6, + y: 0, + x: 0, + }, + [widgetConfigs[1].id]: { + cols: 6, + rows: 6, + y: 0, + x: 6, + }, +}; +\\\`, + "widget-types/timeseries/timeseries-widget-status-bar-example/timeseries-widget-status-bar-example.component.ts": \\\`// © 2022 SolarWinds Worldwide, LLC. All rights reserved. +// +// Permission is hereby granted, free of charge, to any person obtaining a copy +// of this software and associated documentation files (the "Software"), to +// deal in the Software without restriction, including without limitation the +// rights to use, copy, modify, merge, publish, distribute, sublicense, and/or +// sell copies of the Software, and to permit persons to whom the Software is +// furnished to do so, subject to the following conditions: +// +// The above copyright notice and this permission notice shall be included in +// all copies or substantial portions of the Software. +// +// THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR +// IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, +// FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE +// AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER +// LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, +// OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN +// THE SOFTWARE. + +import { + ChangeDetectorRef, + Component, + Injectable, + OnInit, +} from "@angular/core"; +import { GridsterConfig, GridsterItem } from "angular-gridster2"; +import keyBy from "lodash/keyBy"; +import moment, { Moment } from "moment/moment"; +import { BehaviorSubject } from "rxjs"; + +import { + DataSourceService, + IDataSource, + IDataSourceOutput, + INovaFilters, + ITimeframe, +} from "@nova-ui/bits"; +import { CHART_PALETTE_CS_S_EXTENDED } from "@nova-ui/charts"; +import { + applyStatusEndpoints, + DATA_SOURCE, + DEFAULT_PIZZAGNA_ROOT, + IDashboard, + IProviderConfiguration, + ISerializableTimeframe, + ITimeseriesItemConfiguration, + ITimeseriesOutput, + ITimeseriesScaleConfig, + ITimeseriesWidgetConfig, + ITimeseriesWidgetData, + ITimeseriesWidgetSeriesData, + ITimeseriesWidgetStatusData, + IWidget, + LegendPlacement, + PizzagnaLayer, + ProviderRegistryService, + TimeseriesChartPreset, + TimeseriesScaleType, + WellKnownPathKey, + WellKnownProviders, + WidgetTypesService, +} from "@nova-ui/dashboards"; + +/** + * A simple Timeseries data source implementation with continuous (non-interval-based) output + */ +@Injectable() +export class TimeseriesStatusContinuousDataSource + extends DataSourceService + implements IDataSource> +{ + public static providerId = "TimeseriesStatusContinuousDataSource"; + + public busy = new BehaviorSubject(false); + + public async getFilteredData( + filters: INovaFilters + ): Promise< + IDataSourceOutput> + > { + // In this example we're using some static mock data located at the bottom of this file. In a real-world + // scenario, the data for the chart would likely be retrieved via an asynchronous backend call. + const data = getContinuousData(); + let filteredData = data; + + this.busy.next(true); + + // Filtering using the filter registered by the TimeFrameBar + const timeframeFilter = filters.timeframe?.value as ITimeframe; + if (timeframeFilter) { + filteredData = filteredData.map((item: ITimeseriesWidgetData) => ({ + id: item.id, + name: item.name, + description: item.description, + data: item.data.filter( + (seriesData: ITimeseriesWidgetSeriesData) => + filterDates( + seriesData.x, + timeframeFilter.startDatetime, + timeframeFilter.endDatetime + ) + ), + })); + + // apply endpoints on the filtered status data so that when the status chart is zoomed (filtered), + // each status visualizations is ensured to have valid start and end values + filteredData = applyStatusEndpoints( + timeframeFilter, + filteredData, + data + ); + } + + this.busy.next(false); + return { result: { series: filteredData } }; + } +} + +/** + * A simple Timeseries data source implementation with interval-based output + */ +@Injectable() +export class TimeseriesStatusIntervalDataSource + extends DataSourceService + implements IDataSource> +{ + public static providerId = "TimeseriesStatusIntervalDataSource"; + + public busy = new BehaviorSubject(false); + + public async getFilteredData( + filters: INovaFilters + ): Promise< + IDataSourceOutput> + > { + // In this example we're using some static mock data located at the bottom of this file. In a real-world + // scenario, the data for the chart would likely be retrieved via an asynchronous backend call. + const data = getIntervalData(); + let filteredData = data; + + this.busy.next(true); + + // Filtering using the filter registered by the TimeFrameBar + const timeframeFilter = filters.timeframe?.value as ITimeframe; + if (timeframeFilter) { + filteredData = filteredData.map((item: ITimeseriesWidgetData) => ({ + id: item.id, + name: item.name, + description: item.description, + data: item.data.filter( + (seriesData: ITimeseriesWidgetSeriesData) => + filterDates( + seriesData.x, + timeframeFilter.startDatetime, + timeframeFilter.endDatetime + ) + ), + })); + + // Note: There's no need to apply filter endpoints to the status data in this case since we know it's visualized in regular intervals + } + + this.busy.next(false); + return { result: { series: filteredData } }; + } +} + +function filterDates(dateToCheck: Date, startDate: Moment, endDate: Moment) { + const mom = moment(dateToCheck); + return ( + mom.isBetween(startDate, endDate) || + mom.isSame(startDate) || + mom.isSame(endDate) + ); +} + +/** + * A component that instantiates the dashboard + */ +@Component({ + selector: "timeseries-widget-status-bar-example", + templateUrl: "./timeseries-widget-status-bar-example.component.html", + styleUrls: ["./timeseries-widget-status-bar-example.component.less"], + standalone: false, +}) +export class TimeseriesWidgetStatusBarExampleComponent implements OnInit { + // This variable will hold all the data needed to define the layout and behavior of the widgets. + // Pass this to the dashboard component's dashboard input in the template. + public dashboard: IDashboard | undefined; + + // Angular gridster requires a configuration object even if it's empty. + // Pass this to the dashboard component's gridsterConfig input in the template. + public gridsterConfig: GridsterConfig = {}; + + // Boolean passed as an input to the dashboard. When true, widgets can be moved, resized, removed, or edited + public editMode: boolean = false; + + constructor( + // WidgetTypesService provides the widget's necessary structure information + private widgetTypesService: WidgetTypesService, + + // In general, the ProviderRegistryService is used for making entities available for injection into dynamically loaded components. + private providerRegistry: ProviderRegistryService, + private changeDetectorRef: ChangeDetectorRef + ) {} + + public ngOnInit(): void { + // Grabbing the widget's default template which will be needed as a parameter for setNode + const widgetTemplate = this.widgetTypesService.getWidgetType( + "timeseries", + 1 + ); + // Registering our data sources as dropdown options in the widget editor/configurator + // Note: This could also be done in the parent module's constructor so that + // multiple dashboards could have access to the same widget template modification. + this.widgetTypesService.setNode( + // This is the template we grabbed above with getWidgetType + widgetTemplate, + // We are setting the editor/configurator part of the widget template + "configurator", + // This indicates which node you are changing and we want to change + // the data source providers available for selection in the editor. + WellKnownPathKey.DataSourceProviders, + // We are setting the data sources available for selection in the editor + [ + TimeseriesStatusContinuousDataSource.providerId, + TimeseriesStatusIntervalDataSource.providerId, + ] + ); + + // Registering the data source for injection into the widget. + this.providerRegistry.setProviders({ + [TimeseriesStatusContinuousDataSource.providerId]: { + provide: DATA_SOURCE, + useClass: TimeseriesStatusContinuousDataSource, + deps: [], + }, + [TimeseriesStatusIntervalDataSource.providerId]: { + provide: DATA_SOURCE, + useClass: TimeseriesStatusIntervalDataSource, + deps: [], + }, + }); + + this.initializeDashboard(); + } + + /** Used for restoring widgets state */ + public reInitializeDashboard(): void { + // destroys the components and their providers so the dashboard can re init data + this.dashboard = undefined; + this.changeDetectorRef.detectChanges(); + + this.initializeDashboard(); + } + + public initializeDashboard(): void { + // We're using a static configuration object for this example, but this is where + // the widget's configuration could potentially be populated from a database + const widgetsWithStructure = widgetConfigs.map((w) => + this.widgetTypesService.mergeWithWidgetType(w) + ); + const widgetsIndex = keyBy(widgetsWithStructure, (w: IWidget) => w.id); + + // Finally, assigning the variables we created above to the dashboard + this.dashboard = { + positions, + widgets: widgetsIndex, + }; + } +} + +const widgetConfigs: IWidget[] = [ + { + id: "statusChartWidgetId", + type: "timeseries", + pizzagna: { + [PizzagnaLayer.Configuration]: { + [DEFAULT_PIZZAGNA_ROOT]: { + providers: { + [WellKnownProviders.DataSource]: { + // Setting the initially selected data source providerId + providerId: + TimeseriesStatusContinuousDataSource.providerId, + } as IProviderConfiguration, + }, + }, + header: { + properties: { + title: "Status Bar Chart with Continuous (Non-Interval) Scale", + subtitle: "Basic Timeseries Widget", + }, + }, + chart: { + providers: { + [WellKnownProviders.Adapter]: { + properties: { + // Setting the series and corresponding labels to initially display on the chart + series: [ + { + id: "series-1", + label: "Node Status", + selectedSeriesId: "series-1", + }, + { + id: "series-2", + label: "Node Status", + selectedSeriesId: "series-2", + }, + ] as ITimeseriesItemConfiguration[], + }, + } as Partial, + }, + properties: { + configuration: { + legendPlacement: LegendPlacement.Right, + enableZoom: true, + // Setting the preset to status bar + preset: TimeseriesChartPreset.StatusBar, + } as ITimeseriesWidgetConfig, + }, + }, + timeframeSelection: { + properties: { + // Setting the initial timeframe selected in the timeframe bar + timeframe: { + selectedPresetId: "last7Days", + } as ISerializableTimeframe, + maxDate: moment().format(), + }, + }, + }, + }, + }, + { + id: "statusIntervalChartWidgetId", + type: "timeseries", + pizzagna: { + [PizzagnaLayer.Configuration]: { + [DEFAULT_PIZZAGNA_ROOT]: { + providers: { + [WellKnownProviders.DataSource]: { + // Setting the initially selected data source providerId + providerId: + TimeseriesStatusIntervalDataSource.providerId, + } as IProviderConfiguration, + }, + }, + header: { + properties: { + title: "Status Bar Chart with Interval Scale", + subtitle: "Basic Timeseries Widget", + }, + }, + chart: { + providers: { + [WellKnownProviders.Adapter]: { + properties: { + // Setting the series and corresponding labels to initially display on the chart + series: [ + { + id: "series-1", + label: "Node Status", + selectedSeriesId: "series-1", + }, + { + id: "series-2", + label: "Node Status", + selectedSeriesId: "series-2", + }, + ] as ITimeseriesItemConfiguration[], + }, + } as Partial, + }, + properties: { + configuration: { + legendPlacement: LegendPlacement.Right, + enableZoom: true, + // Setting the preset to status bar + preset: TimeseriesChartPreset.StatusBar, + scales: { + x: { + type: TimeseriesScaleType.TimeInterval, + properties: { + // one-day interval in seconds + interval: 24 * 60 * 60, + }, + } as ITimeseriesScaleConfig, + }, + } as ITimeseriesWidgetConfig, + }, + }, + timeframeSelection: { + properties: { + // Setting the initial timeframe selected in the timeframe bar + timeframe: { + selectedPresetId: "last7Days", + } as ISerializableTimeframe, + maxDate: moment().format(), + }, + }, + }, + }, + }, +]; + +export const startOfToday = (): Moment => moment().startOf("day"); + +export const getContinuousData = + (): ITimeseriesWidgetData[] => { + const series: ITimeseriesWidgetData[] = [ + { + id: "series-1", + name: "Node Status", + description: "lastchance.demo.lab", + data: [ + // the 'x' value is set to the time and 'y' to the status at that given time + { + x: startOfToday().subtract(20, "day").toDate(), + y: Status.Warning, + }, + { + x: startOfToday().subtract(19, "day").toDate(), + y: Status.Down, + }, + { + x: startOfToday().subtract(17, "day").toDate(), + y: Status.Critical, + }, + { + x: startOfToday().subtract(16, "day").toDate(), + y: Status.Warning, + }, + { + x: startOfToday().subtract(15, "day").toDate(), + y: Status.Down, + }, + { + x: startOfToday().subtract(14, "day").toDate(), + y: Status.Critical, + }, + { + x: startOfToday().subtract(12, "day").toDate(), + y: Status.Unknown, + }, + { + x: startOfToday().subtract(10, "day").toDate(), + y: Status.Up, + }, + { + x: startOfToday().subtract(9, "day").toDate(), + y: Status.Critical, + }, + { + x: startOfToday().subtract(6, "day").toDate(), + y: Status.Up, + }, + { + x: startOfToday().subtract(3, "day").toDate(), + y: Status.Warning, + }, + { + x: startOfToday().subtract(2, "day").toDate(), + y: Status.Critical, + }, + { + x: startOfToday().subtract(1, "day").toDate(), + y: Status.Up, + }, + // This data point will be ignored and is only here to provide an endpoint for the previous status. + { x: moment().toDate(), y: Status.Up }, + ], + }, + { + id: "series-2", + name: "Node Status", + description: "newhope.demo.lab", + data: [ + { + x: startOfToday().subtract(19, "day").toDate(), + y: Status.Critical, + }, + { + x: startOfToday().subtract(18, "day").toDate(), + y: Status.Unknown, + }, + { + x: startOfToday().subtract(17, "day").toDate(), + y: Status.Warning, + }, + { + x: startOfToday().subtract(15, "day").toDate(), + y: Status.Down, + }, + { + x: startOfToday().subtract(8, "day").toDate(), + y: Status.Critical, + }, + { + x: startOfToday().subtract(7, "day").toDate(), + y: Status.Down, + }, + { + x: startOfToday().subtract(6, "day").toDate(), + y: Status.Up, + }, + { + x: startOfToday().subtract(5, "day").toDate(), + y: Status.Critical, + }, + { + x: startOfToday().subtract(4, "day").toDate(), + y: Status.Up, + }, + { + x: startOfToday().subtract(3, "day").toDate(), + y: Status.Warning, + }, + { + x: startOfToday().subtract(2, "day").toDate(), + y: Status.Up, + }, + { + x: startOfToday().subtract(1, "day").toDate(), + y: Status.Down, + }, + // This data point will be ignored and is only here to provide an endpoint for the previous status. + { x: moment().toDate(), y: Status.Down }, + ], + }, + ]; + + for (const s of series) { + // here are we setting the color and icon associated to the status for each data point + s.data = s.data.map((d: any, i: number) => ({ + ...d, + color: statusColors[d.y as Status], + // The thickness of the line is dependant on the status. If the status equals 'Up' then 'thick' is set to false. + thick: d.y !== Status.Up, + icon: "status_" + d.y, + })); + } + + return series; + }; + +// Note that the output of this function is spaced evenly at one-day intervals +export const getIntervalData = + (): ITimeseriesWidgetData[] => { + const series: ITimeseriesWidgetData[] = [ + { + id: "series-1", + name: "Node Status", + description: "lastchance.demo.lab", + data: [ + // the 'x' value is set to the time and 'y' to the status at that given time + { + x: startOfToday().subtract(20, "day").toDate(), + y: Status.Up, + }, + { + x: startOfToday().subtract(19, "day").toDate(), + y: Status.Critical, + }, + { + x: startOfToday().subtract(18, "day").toDate(), + y: Status.Warning, + }, + { + x: startOfToday().subtract(17, "day").toDate(), + y: Status.Down, + }, + { + x: startOfToday().subtract(16, "day").toDate(), + y: Status.Critical, + }, + { + x: startOfToday().subtract(15, "day").toDate(), + y: Status.Down, + }, + { + x: startOfToday().subtract(14, "day").toDate(), + y: Status.Up, + }, + { + x: startOfToday().subtract(13, "day").toDate(), + y: Status.Warning, + }, + { + x: startOfToday().subtract(12, "day").toDate(), + y: Status.Up, + }, + { + x: startOfToday().subtract(11, "day").toDate(), + y: Status.Critical, + }, + { + x: startOfToday().subtract(10, "day").toDate(), + y: Status.Up, + }, + { + x: startOfToday().subtract(9, "day").toDate(), + y: Status.Down, + }, + { + x: startOfToday().subtract(8, "day").toDate(), + y: Status.Critical, + }, + { + x: startOfToday().subtract(7, "day").toDate(), + y: Status.Warning, + }, + { + x: startOfToday().subtract(6, "day").toDate(), + y: Status.Down, + }, + { + x: startOfToday().subtract(5, "day").toDate(), + y: Status.Critical, + }, + { + x: startOfToday().subtract(4, "day").toDate(), + y: Status.Down, + }, + { + x: startOfToday().subtract(3, "day").toDate(), + y: Status.Up, + }, + { + x: startOfToday().subtract(2, "day").toDate(), + y: Status.Warning, + }, + { + x: startOfToday().subtract(1, "day").toDate(), + y: Status.Critical, + }, + { x: startOfToday().toDate(), y: Status.Up }, + ], + }, + { + id: "series-2", + name: "Node Status", + description: "newhope.demo.lab", + data: [ + { + x: startOfToday().subtract(20, "day").toDate(), + y: Status.Up, + }, + { + x: startOfToday().subtract(19, "day").toDate(), + y: Status.Up, + }, + { + x: startOfToday().subtract(18, "day").toDate(), + y: Status.Down, + }, + { + x: startOfToday().subtract(17, "day").toDate(), + y: Status.Critical, + }, + { + x: startOfToday().subtract(16, "day").toDate(), + y: Status.Down, + }, + { + x: startOfToday().subtract(15, "day").toDate(), + y: Status.Up, + }, + { + x: startOfToday().subtract(14, "day").toDate(), + y: Status.Critical, + }, + { + x: startOfToday().subtract(13, "day").toDate(), + y: Status.Up, + }, + { + x: startOfToday().subtract(12, "day").toDate(), + y: Status.Critical, + }, + { + x: startOfToday().subtract(11, "day").toDate(), + y: Status.Warning, + }, + { + x: startOfToday().subtract(10, "day").toDate(), + y: Status.Up, + }, + { + x: startOfToday().subtract(9, "day").toDate(), + y: Status.Down, + }, + { + x: startOfToday().subtract(8, "day").toDate(), + y: Status.Up, + }, + { + x: startOfToday().subtract(7, "day").toDate(), + y: Status.Down, + }, + { + x: startOfToday().subtract(6, "day").toDate(), + y: Status.Critical, + }, + { + x: startOfToday().subtract(5, "day").toDate(), + y: Status.Down, + }, + { + x: startOfToday().subtract(4, "day").toDate(), + y: Status.Up, + }, + { + x: startOfToday().subtract(3, "day").toDate(), + y: Status.Critical, + }, + { + x: startOfToday().subtract(2, "day").toDate(), + y: Status.Up, + }, + { + x: startOfToday().subtract(1, "day").toDate(), + y: Status.Warning, + }, + { x: startOfToday().toDate(), y: Status.Critical }, + ], + }, + ]; + + for (const s of series) { + // here are we setting the color and icon associated to the status for each data point + s.data = s.data.map((d: any, i: number) => ({ + ...d, + color: statusColors[d.y as Status], + // The thickness of the line is dependant on the status. If the status equals 'Up' then 'thick' is set to false. + thick: d.y !== Status.Up, + icon: "status_" + d.y, + })); + } + + return series; + }; + +// An enumeration of statuses +enum Status { + Unknown = "unknown", + Up = "up", + Warning = "warning", + Down = "down", + Critical = "critical", +} + +// This is the map used for setting the color of each status bar +const statusColors: Record = { + [Status.Unknown]: CHART_PALETTE_CS_S_EXTENDED[6], + [Status.Up]: CHART_PALETTE_CS_S_EXTENDED[8], + [Status.Warning]: CHART_PALETTE_CS_S_EXTENDED[4], + [Status.Down]: CHART_PALETTE_CS_S_EXTENDED[0], + [Status.Critical]: CHART_PALETTE_CS_S_EXTENDED[2], +}; + +// Setting the widget dimensions and position (this is for gridster) +const positions: Record = { + [widgetConfigs[0].id]: { + cols: 12, + rows: 4, + y: 0, + x: 0, + }, + [widgetConfigs[1].id]: { + cols: 12, + rows: 4, + y: 4, + x: 0, + }, +}; +\\\`, + "widget-types/view-components/kpi-tile-view-basic/kpi-tile-view-basic-example.component.ts": \\\`// © 2022 SolarWinds Worldwide, LLC. All rights reserved. +// +// Permission is hereby granted, free of charge, to any person obtaining a copy +// of this software and associated documentation files (the "Software"), to +// deal in the Software without restriction, including without limitation the +// rights to use, copy, modify, merge, publish, distribute, sublicense, and/or +// sell copies of the Software, and to permit persons to whom the Software is +// furnished to do so, subject to the following conditions: +// +// The above copyright notice and this permission notice shall be included in +// all copies or substantial portions of the Software. +// +// THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR +// IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, +// FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE +// AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER +// LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, +// OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN +// THE SOFTWARE. + +import { Component } from "@angular/core"; +import { FormControl } from "@angular/forms"; + +type KpiTileState = "normal" | "loading" | "empty"; + +/** + * KPI Tile View - Playground example. + * Switch between all visual states (normal / loading / empty) and toggle + * interactivity to explore every variant the standalone tile supports. + */ +@Component({ + selector: "kpi-tile-view-basic-example", + templateUrl: "./kpi-tile-view-basic-example.component.html", + standalone: false, +}) +export class KpiTileViewBasicExampleComponent { + public readonly stateControl = new FormControl("normal", { + nonNullable: true, + }); + public interactive = false; + public lastClicked = ""; + + public readonly stateOptions: KpiTileState[] = ["normal", "loading", "empty"]; + + public onTileClick(label: string): void { + this.lastClicked = label; + } +} +\\\`, + "widget-types/view-components/kpi-tile-view-interactive/kpi-tile-view-interactive-example.component.ts": \\\`// © 2022 SolarWinds Worldwide, LLC. All rights reserved. +// +// Permission is hereby granted, free of charge, to any person obtaining a copy +// of this software and associated documentation files (the "Software"), to +// deal in the Software without restriction, including without limitation the +// rights to use, copy, modify, merge, publish, distribute, sublicense, and/or +// sell copies of the Software, and to permit persons to whom the Software is +// furnished to do so, subject to the following conditions: +// +// The above copyright notice and this permission notice shall be included in +// all copies or substantial portions of the Software. +// +// THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR +// IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, +// FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE +// AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER +// LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, +// OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN +// THE SOFTWARE. + +import { Component, TemplateRef, ViewChild } from "@angular/core"; + +/** + * Interactive KPI Tile View example with custom value formatting + * and click event handling. + */ +@Component({ + selector: "kpi-tile-view-interactive-example", + templateUrl: "./kpi-tile-view-interactive-example.component.html", + standalone: false, +}) +export class KpiTileViewInteractiveExampleComponent { + public currentValue = 1_247; + public lastClickedTile = ""; + + @ViewChild("customValueTpl", { static: true }) + public customValueTpl: TemplateRef; + + public onTileClick(): void { + this.lastClickedTile = "Active Sessions"; + } + + public onUptimeClick(): void { + this.lastClickedTile = "Uptime"; + } +} +\\\`, + "widget-types/view-components/proportional-chart-view-playground/proportional-chart-view-playground-example.component.ts": \\\`// © 2022 SolarWinds Worldwide, LLC. All rights reserved. +// +// Permission is hereby granted, free of charge, to any person obtaining a copy +// of this software and associated documentation files (the "Software"), to +// deal in the Software without restriction, including without limitation the +// rights to use, copy, modify, merge, publish, distribute, sublicense, and/or +// sell copies of the Software, and to permit persons to whom the Software is +// furnished to do so, subject to the following conditions: +// +// The above copyright notice and this permission notice shall be included in +// all copies or substantial portions of the Software. +// +// THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR +// IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, +// FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE +// AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER +// LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, +// OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN +// THE SOFTWARE. + +import { Component } from "@angular/core"; +import { FormControl } from "@angular/forms"; + +import { IProportionalDataItem } from "@nova-ui/dashboards"; + +type ProportionalChartType = "donut" | "pie" | "verticalBar" | "horizontalBar"; +type LegendPlacement = "right" | "bottom" | "none"; + +/** + * Proportional Chart View - Playground example. + * Lets you switch between all supported chart types and legend placements + * to see every visual variant the standalone view component provides. + */ +@Component({ + selector: "proportional-chart-view-playground-example", + templateUrl: "./proportional-chart-view-playground-example.component.html", + standalone: false, +}) +export class ProportionalChartViewPlaygroundExampleComponent { + public readonly chartTypeControl = new FormControl( + "donut", + { nonNullable: true } + ); + public readonly legendPlacementControl = new FormControl( + "right", + { nonNullable: true } + ); + + public readonly chartTypeOptions: ProportionalChartType[] = [ + "donut", + "pie", + "verticalBar", + "horizontalBar", + ]; + public readonly legendPlacementOptions: LegendPlacement[] = [ + "right", + "bottom", + "none", + ]; + + public colors: Record = { + down: "#dc3545", + up: "#2cc079", + warning: "#f3a002", + unknown: "#707070", + }; + + public chartData: Array = [ + { id: "up", name: "Up", value: 78 }, + { id: "down", name: "Down", value: 8 }, + { id: "warning", name: "Warning", value: 12 }, + { id: "unknown", name: "Unknown", value: 2 }, + ]; + + public totalOf(data: IProportionalDataItem[] | undefined): number { + return (data ?? []).reduce((sum, d) => sum + (d?.value ?? 0), 0); + } +} +\\\`, + "widget-types/view-components/view-components-docs.component.ts": \\\`// © 2022 SolarWinds Worldwide, LLC. All rights reserved. +// +// Permission is hereby granted, free of charge, to any person obtaining a copy +// of this software and associated documentation files (the "Software"), to +// deal in the Software without restriction, including without limitation the +// rights to use, copy, modify, merge, publish, distribute, sublicense, and/or +// sell copies of the Software, and to permit persons to whom the Software is +// furnished to do so, subject to the following conditions: +// +// The above copyright notice and this permission notice shall be included in +// all copies or substantial portions of the Software. +// +// THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR +// IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, +// FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE +// AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER +// LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, +// OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN +// THE SOFTWARE. + +import { Component } from "@angular/core"; + +@Component({ + selector: "nui-view-components-docs", + templateUrl: "./view-components-docs.component.html", + standalone: false, +}) +export class ViewComponentsDocsComponent { + public readonly installationSnippet = \\\\\\\`import { NuiDashboardViewsModule } from "@nova-ui/dashboards"; + +@NgModule({ + imports: [NuiDashboardViewsModule], +}) +export class MyFeatureModule {}\\\\\\\`; + + public readonly proportionalDataItemSnippet = \\\\\\\`interface IProportionalDataItem { + id: string; // Unique segment identifier + name: string; // Display name in legend + value: number; // Numeric value determining segment size + color?: string; // Optional CSS color (hex or token) + icon?: string; // Optional icon name for legend + link?: string; // Optional drill-down URL +}\\\\\\\`; +} +\\\`, + "widget-types/view-components/view-components-docs.module.ts": \\\`// © 2022 SolarWinds Worldwide, LLC. All rights reserved. +// +// Permission is hereby granted, free of charge, to any person obtaining a copy +// of this software and associated documentation files (the "Software"), to +// deal in the Software without restriction, including without limitation the +// rights to use, copy, modify, merge, publish, distribute, sublicense, and/or +// sell copies of the Software, and to permit persons to whom the Software is +// furnished to do so, subject to the following conditions: +// +// The above copyright notice and this permission notice shall be included in +// all copies or substantial portions of the Software. +// +// THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR +// IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, +// FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE +// AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER +// LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, +// OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN +// THE SOFTWARE. + +import { CommonModule } from "@angular/common"; +import { NgModule } from "@angular/core"; +import { ReactiveFormsModule } from "@angular/forms"; +import { RouterModule, Routes } from "@angular/router"; + +import { + NuiDocsModule, + NuiIconModule, + NuiMessageModule, + NuiFormFieldModule, + NuiSelectV2Module, + NuiSwitchModule, + DEMO_PATH_TOKEN, +} from "@nova-ui/bits"; +import { NuiDashboardViewsModule } from "@nova-ui/dashboards"; + +import { getDemoFiles } from "../../../../demo-files-factory"; +import { KpiTileViewBasicExampleComponent } from "./kpi-tile-view-basic/kpi-tile-view-basic-example.component"; +import { KpiTileViewInteractiveExampleComponent } from "./kpi-tile-view-interactive/kpi-tile-view-interactive-example.component"; +import { ProportionalChartViewPlaygroundExampleComponent } from "./proportional-chart-view-playground/proportional-chart-view-playground-example.component"; +import { ViewComponentsDocsComponent } from "./view-components-docs.component"; + +const routes: Routes = [ + { + path: "", + component: ViewComponentsDocsComponent, + data: { + srlc: { + hideIndicator: true, + }, + showThemeSwitcher: true, + }, + }, + { + path: "kpi-tile-view-basic", + component: KpiTileViewBasicExampleComponent, + data: { + srlc: { + hideIndicator: true, + }, + }, + }, + { + path: "proportional-chart-view-playground", + component: ProportionalChartViewPlaygroundExampleComponent, + data: { + srlc: { + hideIndicator: true, + }, + }, + }, +]; + +@NgModule({ + imports: [ + CommonModule, + ReactiveFormsModule, + RouterModule.forChild(routes), + NuiDocsModule, + NuiMessageModule, + NuiIconModule, + NuiFormFieldModule, + NuiSelectV2Module, + NuiSwitchModule, + NuiDashboardViewsModule, + ], + declarations: [ + ViewComponentsDocsComponent, + KpiTileViewBasicExampleComponent, + KpiTileViewInteractiveExampleComponent, + ProportionalChartViewPlaygroundExampleComponent, + ], + providers: [ + { + provide: DEMO_PATH_TOKEN, + useValue: getDemoFiles("view-components"), + }, + ], +}) +export default class ViewComponentsDocsModule {} +\\\`, + "widget-types/widget-types.module.ts": \\\`// © 2022 SolarWinds Worldwide, LLC. All rights reserved. +// +// Permission is hereby granted, free of charge, to any person obtaining a copy +// of this software and associated documentation files (the "Software"), to +// deal in the Software without restriction, including without limitation the +// rights to use, copy, modify, merge, publish, distribute, sublicense, and/or +// sell copies of the Software, and to permit persons to whom the Software is +// furnished to do so, subject to the following conditions: +// +// The above copyright notice and this permission notice shall be included in +// all copies or substantial portions of the Software. +// +// THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR +// IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, +// FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE +// AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER +// LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, +// OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN +// THE SOFTWARE. + +import { NgModule, Type } from "@angular/core"; +import { RouterModule, Routes } from "@angular/router"; + +import { NuiDocsModule } from "@nova-ui/bits"; +import { + ConfiguratorHeadingService, + NuiDashboardsModule, +} from "@nova-ui/dashboards"; + +export enum WidgetTypesRoute { + kpi = "kpi", + riskScore = "risk-score", + timeseries = "timeseries", + table = "table", + proportional = "proportional", + embedded = "embedded", + drilldown = "drilldown", + viewComponents = "view-components", +} + +const routes: Routes = [ + { + path: WidgetTypesRoute.kpi, + loadChildren: async () => + import("./kpi/kpi-docs.module") as object as Promise>, + data: { + srlc: { + hideIndicator: true, + }, + }, + }, + { + path: WidgetTypesRoute.riskScore, + loadChildren: async () => + import("./risk-score/risk-score-docs.module") as object as Promise< + Type + >, + data: { + srlc: { + hideIndicator: true, + }, + }, + }, + { + path: WidgetTypesRoute.timeseries, + loadChildren: async () => + import("./timeseries/timeseries-docs.module") as object as Promise< + Type + >, + data: { + srlc: { + hideIndicator: true, + }, + }, + }, + { + path: WidgetTypesRoute.table, + loadChildren: async () => + import("./table/table-docs.module") as object as Promise>, + data: { + srlc: { + hideIndicator: true, + }, + }, + }, + { + path: WidgetTypesRoute.proportional, + loadChildren: async () => + import( + "./proportional/proportional-docs.module" + ) as object as Promise>, + data: { + srlc: { + hideIndicator: true, + }, + }, + }, + { + path: WidgetTypesRoute.embedded, + loadChildren: async () => + import( + "./embedded-content/embedded-content-docs.module" + ) as object as Promise>, + data: { + srlc: { + hideIndicator: true, + }, + }, + }, + { + path: WidgetTypesRoute.drilldown, + loadChildren: async () => + import( + "./drilldown/drilldown-widget-docs.module" + ) as object as Promise>, + data: { + srlc: { + hideIndicator: true, + }, + }, + }, + { + path: WidgetTypesRoute.viewComponents, + loadChildren: async () => + import( + "./view-components/view-components-docs.module" + ) as object as Promise>, + data: { + srlc: { + hideIndicator: true, + }, + }, + }, +]; + +@NgModule({ + imports: [ + RouterModule.forChild(routes), + NuiDocsModule, + NuiDashboardsModule, + ], + providers: [ConfiguratorHeadingService], +}) +export default class WidgetTypesModule {} +\\\`, +}; +\`, + "overview/hero/dashboard/hero-dashboard.component.ts": \`// © 2022 SolarWinds Worldwide, LLC. All rights reserved. +// +// Permission is hereby granted, free of charge, to any person obtaining a copy +// of this software and associated documentation files (the "Software"), to +// deal in the Software without restriction, including without limitation the +// rights to use, copy, modify, merge, publish, distribute, sublicense, and/or +// sell copies of the Software, and to permit persons to whom the Software is +// furnished to do so, subject to the following conditions: +// +// The above copyright notice and this permission notice shall be included in +// all copies or substantial portions of the Software. +// +// THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR +// IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, +// FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE +// AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER +// LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, +// OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN +// THE SOFTWARE. + +import { HttpClient } from "@angular/common/http"; +import { + ChangeDetectionStrategy, + Component, + OnInit, + ViewEncapsulation, +} from "@angular/core"; +import keyBy from "lodash/keyBy"; + +import { LoggerService, ThemeSwitchService } from "@nova-ui/bits"; +import { + DATA_SOURCE, + IDashboard, + IWidget, + ProviderRegistryService, + WidgetTypesService, +} from "@nova-ui/dashboards"; + +import { positions, widgets } from "./widget-configs"; +import { + HarryPotterAverageRatingDataSource, + HarryPotterRatingsCountDataSource, +} from "../data/kpi-datasources"; +import { + BeerReviewCountsByCityMockDataSource, + BeerReviewCountsByCityMockDataSource2, +} from "../data/proportional-datasources"; +import { BeerDataSource } from "../data/table/beer-data-source"; +import { RandomUserDataSource } from "../data/table/random-user-data-source"; +import { + BeerVsReadingMockDataSource, + LoungingVsFrisbeeGolfMockDataSource, +} from "../data/timeseries-data-sources"; + +/** + * A component that instantiates the dashboard + */ +@Component({ + selector: "hero-dashboard", + templateUrl: "./hero-dashboard.component.html", + styleUrls: ["./hero-dashboard.component.less"], + encapsulation: ViewEncapsulation.Emulated, + changeDetection: ChangeDetectionStrategy.Default, + standalone: false, +}) +export class HeroDashboardComponent implements OnInit { + public dashboard: IDashboard = { + positions: {}, + widgets: {}, + }; + + public gridsterConfig = {}; + public editMode = false; + + constructor( + private providerRegistry: ProviderRegistryService, + public themeSwitcherService: ThemeSwitchService, + private widgetTypesService: WidgetTypesService + ) { + this.providerRegistry.setProviders({ + [HarryPotterAverageRatingDataSource.providerId]: { + provide: DATA_SOURCE, + useClass: HarryPotterAverageRatingDataSource, + deps: [HttpClient], + }, + [HarryPotterRatingsCountDataSource.providerId]: { + provide: DATA_SOURCE, + useClass: HarryPotterRatingsCountDataSource, + deps: [HttpClient], + }, + [BeerReviewCountsByCityMockDataSource.providerId]: { + provide: DATA_SOURCE, + useClass: BeerReviewCountsByCityMockDataSource, + deps: [], + }, + [BeerReviewCountsByCityMockDataSource2.providerId]: { + provide: DATA_SOURCE, + useClass: BeerReviewCountsByCityMockDataSource2, + deps: [], + }, + [RandomUserDataSource.providerId]: { + provide: DATA_SOURCE, + useClass: RandomUserDataSource, + deps: [LoggerService], + }, + [BeerDataSource.providerId]: { + provide: DATA_SOURCE, + useClass: BeerDataSource, + deps: [LoggerService], + }, + [BeerVsReadingMockDataSource.providerId]: { + provide: DATA_SOURCE, + useClass: BeerVsReadingMockDataSource, + deps: [], + }, + [LoungingVsFrisbeeGolfMockDataSource.providerId]: { + provide: DATA_SOURCE, + useClass: LoungingVsFrisbeeGolfMockDataSource, + deps: [], + }, + }); + } + + public ngOnInit(): void { + const widgetsWithStructure = widgets.map((w) => ({ + ...w, + pizzagna: { + ...this.widgetTypesService.getWidgetType(w.type, w.version) + .widget, + ...w.pizzagna, + }, + })); + const widgetsIndex = keyBy(widgetsWithStructure, (w: IWidget) => w.id); + + this.dashboard = { + positions: positions, + widgets: widgetsIndex, + }; + } +} +\`, + "overview/hero/dashboard/widget-configs.ts": \`// © 2022 SolarWinds Worldwide, LLC. All rights reserved. +// +// Permission is hereby granted, free of charge, to any person obtaining a copy +// of this software and associated documentation files (the "Software"), to +// deal in the Software without restriction, including without limitation the +// rights to use, copy, modify, merge, publish, distribute, sublicense, and/or +// sell copies of the Software, and to permit persons to whom the Software is +// furnished to do so, subject to the following conditions: +// +// The above copyright notice and this permission notice shall be included in +// all copies or substantial portions of the Software. +// +// THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR +// IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, +// FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE +// AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER +// LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, +// OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN +// THE SOFTWARE. + +import { GridsterItem } from "angular-gridster2"; + +import { IWidget } from "@nova-ui/dashboards"; + +import { kpiConfig } from "../widget-configs/kpi"; +import { proportionalConfig } from "../widget-configs/proportional"; +import { tableConfig } from "../widget-configs/table"; +import { timeseriesConfig } from "../widget-configs/timeseries"; + +export const positions: Record = { + [tableConfig.id]: { + cols: 7, + rows: 7, + y: 0, + x: 0, + }, + [proportionalConfig.id]: { + cols: 5, + rows: 7, + y: 0, + x: 7, + }, + [kpiConfig.id]: { + cols: 6, + rows: 7, + y: 7, + x: 0, + }, + [timeseriesConfig.id]: { + cols: 6, + rows: 7, + y: 7, + x: 6, + }, +}; + +export const widgets: IWidget[] = [ + { + ...tableConfig, + }, + { + ...proportionalConfig, + }, + { + ...kpiConfig, + }, + { + ...timeseriesConfig, + }, +]; +\`, + "overview/hero/data/kpi-datasources.ts": \`// © 2022 SolarWinds Worldwide, LLC. All rights reserved. +// +// Permission is hereby granted, free of charge, to any person obtaining a copy +// of this software and associated documentation files (the "Software"), to +// deal in the Software without restriction, including without limitation the +// rights to use, copy, modify, merge, publish, distribute, sublicense, and/or +// sell copies of the Software, and to permit persons to whom the Software is +// furnished to do so, subject to the following conditions: +// +// The above copyright notice and this permission notice shall be included in +// all copies or substantial portions of the Software. +// +// THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR +// IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, +// FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE +// AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER +// LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, +// OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN +// THE SOFTWARE. + +import { HttpClient, HttpErrorResponse } from "@angular/common/http"; +import { Injectable, OnDestroy } from "@angular/core"; +import { BehaviorSubject } from "rxjs"; +import { finalize } from "rxjs/operators"; + +import { DataSourceService, IFilteringOutputs } from "@nova-ui/bits"; +import { IKpiData } from "@nova-ui/dashboards"; + +import { GOOGLE_BOOKS_URL } from "./table/constants"; + +@Injectable() +export class HarryPotterAverageRatingDataSource + extends DataSourceService + implements OnDestroy +{ + public static providerId = "HarryPotterAverageRatingDataSource"; + + public busy = new BehaviorSubject(false); + + constructor(private http: HttpClient) { + super(); + } + + public async getFilteredData(): Promise { + this.busy.next(true); + return new Promise((resolve) => { + // *** Make a resource request to an external API (if needed) + this.http + .get(\\\`\\\${GOOGLE_BOOKS_URL}/5MQFrgEACAAJ\\\`) + .pipe(finalize(() => this.busy.next(false))) + .subscribe({ + next: (data: any) => { + resolve({ + result: { + value: data.volumeInfo.averageRating, + }, + }); + }, + error: (error: HttpErrorResponse) => { + resolve({ + result: null, + error: { + type: error.status, + }, + }); + }, + }); + }); + } + + public ngOnDestroy(): void { + this.outputsSubject.complete(); + } +} + +@Injectable() +export class HarryPotterRatingsCountDataSource + extends DataSourceService + implements OnDestroy +{ + public static providerId = "HarryPotterRatingsCountDataSource"; + + public busy = new BehaviorSubject(false); + + constructor(private http: HttpClient) { + super(); + } + + public async getFilteredData(): Promise { + this.busy.next(true); + return new Promise((resolve) => { + this.http + .get(\\\`\\\${GOOGLE_BOOKS_URL}/5MQFrgEACAAJ\\\`) + .pipe(finalize(() => this.busy.next(false))) + .subscribe({ + next: (data: any) => { + resolve({ + result: { + value: data.volumeInfo.ratingsCount, + }, + }); + }, + error: (error: HttpErrorResponse) => { + resolve({ + result: null, + error: { + type: error.status, + }, + }); + }, + }); + }); + } + + public ngOnDestroy(): void { + this.outputsSubject.complete(); + } +} +\`, + "overview/hero/data/proportional-datasources.ts": \`// © 2022 SolarWinds Worldwide, LLC. All rights reserved. +// +// Permission is hereby granted, free of charge, to any person obtaining a copy +// of this software and associated documentation files (the "Software"), to +// deal in the Software without restriction, including without limitation the +// rights to use, copy, modify, merge, publish, distribute, sublicense, and/or +// sell copies of the Software, and to permit persons to whom the Software is +// furnished to do so, subject to the following conditions: +// +// The above copyright notice and this permission notice shall be included in +// all copies or substantial portions of the Software. +// +// THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR +// IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, +// FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE +// AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER +// LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, +// OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN +// THE SOFTWARE. + +import { Injectable, OnDestroy } from "@angular/core"; +import { BehaviorSubject } from "rxjs"; + +import { + DataSourceService, + IDataSource, + IFilteringOutputs, +} from "@nova-ui/bits"; + +import { + getMockBeerReviewCountsByCity, + getMockBeerReviewCountsByCity2, + IProportionalWidgetData, +} from "./widget-data"; + +@Injectable() +export class BeerReviewCountsByCityMockDataSource + extends DataSourceService + implements IDataSource, OnDestroy +{ + // This is the ID we'll use to identify the provider + public static providerId = "BeerReviewCountsByCityMockDataSource"; + public busy = new BehaviorSubject(false); + + public async getFilteredData(): Promise { + this.busy.next(true); + return new Promise((resolve) => { + setTimeout(() => { + this.outputsSubject.next({ + result: getMockBeerReviewCountsByCity(), + }); + this.busy.next(false); + }, 300); + }); + } + + public ngOnDestroy(): void { + this.outputsSubject.complete(); + } +} + +@Injectable() +export class BeerReviewCountsByCityMockDataSource2 + extends DataSourceService + implements IDataSource, OnDestroy +{ + // This is the ID we'll use to identify the provider + public static providerId = "BeerReviewCountsByCityMockDataSource2"; + public busy = new BehaviorSubject(false); + + public async getFilteredData(): Promise { + this.busy.next(true); + return new Promise((resolve) => { + setTimeout(() => { + this.outputsSubject.next({ + result: getMockBeerReviewCountsByCity2(), + }); + this.busy.next(false); + }, 1500); + }); + } + + public ngOnDestroy(): void { + this.outputsSubject.complete(); + } +} +\`, + "overview/hero/data/table/beer-data-source.ts": \`// © 2022 SolarWinds Worldwide, LLC. All rights reserved. +// +// Permission is hereby granted, free of charge, to any person obtaining a copy +// of this software and associated documentation files (the "Software"), to +// deal in the Software without restriction, including without limitation the +// rights to use, copy, modify, merge, publish, distribute, sublicense, and/or +// sell copies of the Software, and to permit persons to whom the Software is +// furnished to do so, subject to the following conditions: +// +// The above copyright notice and this permission notice shall be included in +// all copies or substantial portions of the Software. +// +// THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR +// IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, +// FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE +// AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER +// LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, +// OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN +// THE SOFTWARE. + +import { ListRange } from "@angular/cdk/collections"; +import { Injectable } from "@angular/core"; +import isEqual from "lodash/isEqual"; +import orderBy from "lodash/orderBy"; +import { BehaviorSubject } from "rxjs"; + +import { + DataSourceService, + IDataField, + INovaFilteringOutputs, + INovaFilters, + ISorterFilter, + LoggerService, +} from "@nova-ui/bits"; + +import { IBrewDatasourceResponse, IBrewInfo } from "../types"; +import { BREW_API_URL } from "./constants"; + +@Injectable() +export class BeerDataSource extends DataSourceService { + public static providerId = "BeerDataSource"; + + private cache = Array.from({ length: 0 }); + private lastSortValue?: ISorterFilter; + private lastVirtualScroll?: ListRange; + private totalItems: number = 325; + + public page: number = 1; + public busy = new BehaviorSubject(false); + + public dataFields: Array = [ + { id: "id", label: "No", dataType: "number" }, + { id: "name", label: "Name", dataType: "string" }, + { id: "tagline", label: "Tagline", dataType: "string" }, + { id: "first_brewed", label: "First Brewed", dataType: "string" }, + { id: "description", label: "Description", dataType: "string" }, + { id: "brewers_tips", label: "Brewer's Tips", dataType: "string" }, + ]; + + constructor(private logger: LoggerService) { + super(); + } + + public async getFilteredData( + filters: INovaFilters + ): Promise { + const start = filters.virtualScroll?.value?.start ?? 0; + const end = filters.virtualScroll?.value?.end ?? 0; + const delta = end - start; + + // This condition handles sorting. We want to sort columns without fetching another chunk of data. + // Since the data is being fetched when scrolled, we compare virtual scroll indexes here in the condition as well. + if (filters.sorter?.value) { + if ( + !isEqual(this.lastSortValue, filters.sorter.value) && + isEqual(this.lastVirtualScroll, filters.virtualScroll?.value) + ) { + const totalPages = Math.ceil( + delta ? this.totalItems / delta : 1 + ); + const itemsPerPage: number = Math.max( + delta < 80 ? delta : 80, + 1 + ); + let response: Array | null = null; + let map: IBrewDatasourceResponse; + + if (filters.sorter?.value?.direction === "desc") { + this.cache = []; + for (let i = 0; i < this.page; ++i) { + response = await ( + await fetch( + \\\`\\\${BREW_API_URL}/?page=\\\${ + totalPages - i || 1 + }&per_page=\\\${itemsPerPage}\\\` + ) + ).json(); + + // since the last page contains only 5 items we need to fetch another page to give virtual scroll enough space to work + if (response && response.length < itemsPerPage) { + this.page++; + } + map = { + brewInfo: response?.map((result: IBrewInfo) => ({ + id: result.id, + name: result.name, + tagline: result.tagline, + first_brewed: result.first_brewed, + description: result.description, + brewers_tips: result.brewers_tips, + })), + total: response?.length, + } as IBrewDatasourceResponse; + this.cache = + totalPages - i !== 0 + ? this.cache.concat(map.brewInfo) + : this.cache; + } + } + + if (filters.sorter?.value?.direction === "asc") { + this.cache = []; + for (let i = 0; i < this.page; i++) { + response = await ( + await fetch( + \\\`\\\${BREW_API_URL}/?page=\\\${ + i + 1 + }&per_page=\\\${itemsPerPage}\\\` + ) + ).json(); + map = { + brewInfo: response?.map((result: IBrewInfo) => ({ + id: result.id, + name: result.name, + tagline: result.tagline, + first_brewed: result.first_brewed, + description: result.description, + brewers_tips: result.brewers_tips, + })), + total: response?.length, + } as IBrewDatasourceResponse; + this.cache = this.cache.concat(map.brewInfo); + } + } + + this.lastSortValue = filters.sorter?.value; + this.lastVirtualScroll = filters.virtualScroll?.value; + + return { + repeat: { itemsSource: this.sortData(this.cache, filters) }, + paginator: { total: this.totalItems }, + dataFields: this.dataFields, + }; + } + } + + this.busy.next(true); + return new Promise((resolve) => { + setTimeout(() => { + this.getData(start, end, filters).then( + (response: INovaFilteringOutputs) => { + if (!response) { + return; + } + + this.cache = this.cache.concat(response.brewInfo); + + this.dataSubject.next(this.cache); + resolve({ + repeat: { + itemsSource: this.sortData(this.cache, filters), + }, + paginator: { total: this.totalItems }, + dataFields: this.dataFields, + }); + + this.lastSortValue = filters.sorter?.value; + this.lastVirtualScroll = filters.virtualScroll?.value; + this.busy.next(false); + } + ); + }, 500); + }); + } + + public async getData( + start: number = 0, + end: number = 20, + filters: INovaFilters + ): Promise { + const delta = end - start; + const totalPages = Math.ceil(delta ? this.totalItems / delta : 1); + let response: Array | null = null; + // The api.punk.com is able to return only 80 items per page + const itemsPerPage: number = Math.max(delta < 80 ? delta : 80, 1); + + if (filters.sorter?.value?.direction === "asc") { + response = await ( + await fetch( + \\\`\\\${BREW_API_URL}/?page=\\\${this.page}&per_page=\\\${itemsPerPage}\\\` + ) + ).json(); + } + + if (filters.sorter?.value?.direction === "desc") { + response = await ( + await fetch( + \\\`\\\${BREW_API_URL}/?page=\\\${ + totalPages - this.page + }&per_page=\\\${itemsPerPage}\\\` + ) + ).json(); + } + + if (!filters.sorter) { + response = await ( + await fetch( + \\\`\\\${BREW_API_URL}/?page=\\\${this.page}&per_page=\\\${itemsPerPage}\\\` + ) + ).json(); + } + return { + brewInfo: response?.map((result: IBrewInfo, i: number) => ({ + id: result.id, + name: result.name, + tagline: result.tagline, + first_brewed: result.first_brewed, + description: result.description, + brewers_tips: result.brewers_tips, + })), + total: response?.length, + } as IBrewDatasourceResponse; + } + + private sortData(data: IBrewInfo[], filters: INovaFilters) { + return orderBy( + data, + filters.sorter?.value?.sortBy, + filters.sorter?.value?.direction as "desc" | "asc" + ); + } +} +\`, + "overview/hero/data/table/constants.ts": \`// © 2022 SolarWinds Worldwide, LLC. All rights reserved. +// +// Permission is hereby granted, free of charge, to any person obtaining a copy +// of this software and associated documentation files (the "Software"), to +// deal in the Software without restriction, including without limitation the +// rights to use, copy, modify, merge, publish, distribute, sublicense, and/or +// sell copies of the Software, and to permit persons to whom the Software is +// furnished to do so, subject to the following conditions: +// +// The above copyright notice and this permission notice shall be included in +// all copies or substantial portions of the Software. +// +// THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR +// IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, +// FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE +// AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER +// LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, +// OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN +// THE SOFTWARE. + +export const corsProxy = "https://cors-anywhere.herokuapp.com"; +export const RANDOMUSER_API_URL = "https://randomuser.me"; +export const BREW_API_URL = "https://api.punkapi.com/v2/beers"; +export const GOOGLE_BOOKS_URL = "https://www.googleapis.com/books/v1/volumes"; +export const apiRoute = "api/1.3"; +export const responseError = \\\`Error responding from server. Please visit \\\${RANDOMUSER_API_URL} and \\\${corsProxy} to see if they're available\\\`; +\`, + "overview/hero/data/table/random-user-data-source.ts": \`// © 2022 SolarWinds Worldwide, LLC. All rights reserved. +// +// Permission is hereby granted, free of charge, to any person obtaining a copy +// of this software and associated documentation files (the "Software"), to +// deal in the Software without restriction, including without limitation the +// rights to use, copy, modify, merge, publish, distribute, sublicense, and/or +// sell copies of the Software, and to permit persons to whom the Software is +// furnished to do so, subject to the following conditions: +// +// The above copyright notice and this permission notice shall be included in +// all copies or substantial portions of the Software. +// +// THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR +// IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, +// FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE +// AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER +// LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, +// OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN +// THE SOFTWARE. + +import { ListRange } from "@angular/cdk/collections"; +import { Injectable } from "@angular/core"; +import isEqual from "lodash/isEqual"; +import orderBy from "lodash/orderBy"; +import { BehaviorSubject } from "rxjs"; + +import { + DataSourceService, + IDataField, + INovaFilteringOutputs, + INovaFilters, + ISorterFilter, + LoggerService, +} from "@nova-ui/bits"; + +import { + IRandomUserResponse, + IRandomUserResults, + IRandomUserTableModel, + UsersQueryResponse, +} from "../types"; +import { + apiRoute, + corsProxy, + RANDOMUSER_API_URL, + responseError, +} from "./constants"; + +@Injectable() +export class RandomUserDataSource extends DataSourceService { + public static providerId = "RandomUserDataSource"; + + private readonly seed = "sw"; + + private cache = Array.from({ length: 0 }); + private lastSortValue?: ISorterFilter; + private lastVirtualScroll?: ListRange; + + public page: number = 1; + public busy = new BehaviorSubject(false); + + public dataFields: Array = [ + { id: "no", label: $localize\\\`No\\\`, dataType: "number" }, + { id: "nameTitle", label: $localize\\\`Title\\\`, dataType: "string" }, + { id: "nameFirst", label: $localize\\\`First\\\`, dataType: "string" }, + { id: "nameLast", label: $localize\\\`Last\\\`, dataType: "string" }, + { id: "gender", label: $localize\\\`Gender\\\`, dataType: "string" }, + { id: "country", label: $localize\\\`Country\\\`, dataType: "string" }, + { id: "city", label: $localize\\\`City\\\`, dataType: "string" }, + { id: "postcode", label: $localize\\\`Postcode\\\`, dataType: "number" }, + { id: "email", label: $localize\\\`E-Mail\\\`, dataType: "string" }, + { id: "cell", label: $localize\\\`Cell\\\`, dataType: "string" }, + ]; + + constructor(private logger: LoggerService) { + super(); + } + + public async getFilteredData( + filters: INovaFilters + ): Promise { + // This condition handles sorting. We want to sort columns without fetching another chunk of data. + // Since the data is being fetched when scrolled, we compare virtual scroll indexes here in the condition as well. + if (filters.sorter?.value) { + if ( + !isEqual(this.lastSortValue, filters.sorter.value) && + isEqual(this.lastVirtualScroll, filters.virtualScroll?.value) + ) { + this.lastSortValue = filters.sorter?.value; + this.lastVirtualScroll = filters.virtualScroll?.value; + + return { + repeat: { itemsSource: this.sortData(this.cache, filters) }, + paginator: { total: 200 }, + dataFields: this.dataFields, + }; + } + } + this.busy.next(true); + + const virtualScrollFilter = + filters.virtualScroll && filters.virtualScroll.value; + const start = virtualScrollFilter + ? filters.virtualScroll?.value.start + : 0; + const end = virtualScrollFilter ? filters.virtualScroll?.value.end : 0; + + // We're returning Promise with setTimeout here to make the response from the server longer, as the API being used sends responses + // almost immediately. We need it longer to be able the show the spinner component on data load + return new Promise((resolve) => { + setTimeout(() => { + this.getData(start, end).then( + (response: INovaFilteringOutputs | undefined) => { + if (!response) { + return; + } + + this.cache = this.cache.concat(response.users); + + this.dataSubject.next(this.cache); + resolve({ + repeat: { + itemsSource: this.sortData(this.cache, filters), + }, + // This API can return thousands of results, however doesn't return the max number of results, + // so we set the max number of result manually here. + paginator: { total: 200 }, + dataFields: this.dataFields, + }); + + this.lastSortValue = filters.sorter?.value; + this.lastVirtualScroll = filters.virtualScroll?.value; + this.busy.next(false); + } + ); + }, 300); + }); + } + + public async getData( + start: number = 0, + end: number = 20 + ): Promise { + let response: IRandomUserResponse | null = null; + try { + response = await ( + await fetch( + \\\`\\\${corsProxy}/\\\${RANDOMUSER_API_URL}/\\\${apiRoute}/?page=\\\${ + this.page + }&results=\\\${end - start}&seed=\\\${this.seed}\\\` + ) + ).json(); + return { + users: response?.results.map( + (result: IRandomUserResults, i: number) => ({ + no: this.cache.length + i + 1, + nameTitle: result.name.title, + nameFirst: result.name.first, + nameLast: result.name.last, + gender: result.gender, + country: result.location.country, + city: result.location.city, + postcode: result.location.postcode, + email: result.email, + cell: result.cell, + }) + ), + total: response?.results.length, + start: start, + } as UsersQueryResponse; + } catch (e) { + this.logger.error(responseError); + } + } + + private sortData(data: IRandomUserTableModel[], filters: INovaFilters) { + return orderBy( + data, + filters.sorter?.value?.sortBy, + filters.sorter?.value?.direction as "desc" | "asc" + ); + } +} +\`, + "overview/hero/data/table/types.ts": \`// © 2022 SolarWinds Worldwide, LLC. All rights reserved. +// +// Permission is hereby granted, free of charge, to any person obtaining a copy +// of this software and associated documentation files (the "Software"), to +// deal in the Software without restriction, including without limitation the +// rights to use, copy, modify, merge, publish, distribute, sublicense, and/or +// sell copies of the Software, and to permit persons to whom the Software is +// furnished to do so, subject to the following conditions: +// +// The above copyright notice and this permission notice shall be included in +// all copies or substantial portions of the Software. +// +// THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR +// IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, +// FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE +// AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER +// LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, +// OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN +// THE SOFTWARE. + +import { IDataField, INovaFilteringOutputs } from "@nova-ui/bits"; +export interface BasicTableModel { + position: number; + name: string; + features: any; + status: string; + checks: any; + "cpu-load": number; + firstUrl: string; + firstUrlLabel: string; + secondUrl: string; + secondUrlLabel: string; +} + +export interface ITableDataSourceOutput extends INovaFilteringOutputs { + dataFields: IDataField[]; +} +\`, + "overview/hero/data/timeseries-data-sources.ts": \`// © 2022 SolarWinds Worldwide, LLC. All rights reserved. +// +// Permission is hereby granted, free of charge, to any person obtaining a copy +// of this software and associated documentation files (the "Software"), to +// deal in the Software without restriction, including without limitation the +// rights to use, copy, modify, merge, publish, distribute, sublicense, and/or +// sell copies of the Software, and to permit persons to whom the Software is +// furnished to do so, subject to the following conditions: +// +// The above copyright notice and this permission notice shall be included in +// all copies or substantial portions of the Software. +// +// THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR +// IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, +// FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE +// AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER +// LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, +// OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN +// THE SOFTWARE. + +import { Injectable } from "@angular/core"; +import { Moment } from "moment/moment"; +import { BehaviorSubject } from "rxjs"; + +import { DataSourceService, IDataSource, INovaFilters } from "@nova-ui/bits"; +import { + ITimeseriesOutput, + ITimeseriesWidgetData, + ITimeseriesWidgetSeriesData, +} from "@nova-ui/dashboards"; + +import { + BEER_VS_READING_DATA, + LOUNGING_VS_ULTIMATE_FRISBEE_DATA, +} from "./widget-data"; + +@Injectable() +export class BeerVsReadingMockDataSource + extends DataSourceService + implements IDataSource +{ + public static providerId = "BeerVsReadingMockDataSource"; + + public busy = new BehaviorSubject(false); + + constructor() { + super(); + } + + public async getFilteredData( + filters: INovaFilters + ): Promise { + this.busy.next(true); + const result = await delay( + { series: getData(filters, BEER_VS_READING_DATA) }, + 1000 + ); + this.busy.next(false); + return result; + } +} + +@Injectable() +export class LoungingVsFrisbeeGolfMockDataSource + extends DataSourceService + implements IDataSource +{ + public static providerId = "LoungingVsFrisbeeGolfMockDataSource"; + + public busy = new BehaviorSubject(false); + + constructor() { + super(); + } + + public async getFilteredData( + filters: INovaFilters + ): Promise { + this.busy.next(true); + const result = await delay( + { series: getData(filters, LOUNGING_VS_ULTIMATE_FRISBEE_DATA) }, + 1000 + ); + this.busy.next(false); + return result; + } +} + +function getData( + filters: INovaFilters, + data: ITimeseriesWidgetData[] +): ITimeseriesWidgetData[] { + const timeframeFilter = filters.timeframe; + let filteredData = data; + // TIME FRAME PICKER FILTERING + if (timeframeFilter) { + filteredData = filteredData.map((item: ITimeseriesWidgetData) => ({ + id: item.id, + name: item.name, + description: item.description, + data: item.data.filter((seriesData: ITimeseriesWidgetSeriesData) => + filterDates( + seriesData.x, + timeframeFilter.value.startDatetime, + timeframeFilter.value.endDatetime + ) + ), + })); + } + + return filteredData; +} + +function filterDates(dateToCheck: Moment, startDate: Moment, endDate: Moment) { + return ( + dateToCheck.isBetween(startDate, endDate) || + dateToCheck.isSame(startDate) || + dateToCheck.isSame(endDate) + ); +} + +async function delay( + value: ITimeseriesOutput, + ms: number +): Promise { + return new Promise((resolve) => setTimeout(() => resolve(value), ms)); +} +\`, + "overview/hero/data/types.ts": \`// © 2022 SolarWinds Worldwide, LLC. All rights reserved. +// +// Permission is hereby granted, free of charge, to any person obtaining a copy +// of this software and associated documentation files (the "Software"), to +// deal in the Software without restriction, including without limitation the +// rights to use, copy, modify, merge, publish, distribute, sublicense, and/or +// sell copies of the Software, and to permit persons to whom the Software is +// furnished to do so, subject to the following conditions: +// +// The above copyright notice and this permission notice shall be included in +// all copies or substantial portions of the Software. +// +// THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR +// IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, +// FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE +// AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER +// LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, +// OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN +// THE SOFTWARE. + +export interface UsersQueryResponse { + users: IRandomUserTableModel[]; + total: number; + start: number; +} + +export interface IRandomUserResponse { + info: Array; + results: Array; +} + +export interface IRandomUserInfo { + page: number; + results: number; + seed: string; + version: string; +} + +export interface IRandomUserResults { + cell: string; + dob: { + age: number; + date: string; + }; + email: string; + gender: string; + id: any; + location: IRandomUserLocation; + login: { + md5: string; + password: string; + salt: string; + sha1: string; + sha256: string; + username: string; + uuid: string; + }; + name: { + title: string; + first: string; + last: string; + }; + nat: string; + phone: string; + picture: { + large: string; + medium: string; + thumbnail: string; + }; + registered: { + date: string; + age: number; + }; +} + +export interface IRandomUserTableModel { + no: number; + nameTitle: string; + nameFirst: string; + nameLast: string; + gender: string; + country: string; + city: string; + postcode: number; + email: string; + cell: string; +} + +export interface IRandomUserLocation { + city: string; + coordinates: { latitude: string; longitude: string }; + country: string; + postcode: number; + state: string; + street: { number: number; name: string }; + timezone: any; +} + +export interface IBrewInfo { + id: number; + name: string; + tagline: string; + first_brewed: string; + description: string; + brewers_tips: string; +} + +export interface IBrewDatasourceResponse { + brewInfo: IBrewInfo[]; + total: number; +} +\`, + "overview/hero/data/widget-data.ts": \`// © 2022 SolarWinds Worldwide, LLC. All rights reserved. +// +// Permission is hereby granted, free of charge, to any person obtaining a copy +// of this software and associated documentation files (the "Software"), to +// deal in the Software without restriction, including without limitation the +// rights to use, copy, modify, merge, publish, distribute, sublicense, and/or +// sell copies of the Software, and to permit persons to whom the Software is +// furnished to do so, subject to the following conditions: +// +// The above copyright notice and this permission notice shall be included in +// all copies or substantial portions of the Software. +// +// THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR +// IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, +// FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE +// AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER +// LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, +// OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN +// THE SOFTWARE. + +import moment from "moment/moment"; + +import { ITimeseriesWidgetData } from "@nova-ui/dashboards"; + +import { BasicTableModel } from "./table/types"; + +export interface IProportionalWidgetData { + id: string; + name: string; + data: number[]; + link: string; + value: string; +} + +export function getMockBeerReviewCountsByCity(): IProportionalWidgetData[] { + return [ + { + id: "Brno", + name: "Brno", + data: [Math.round(Math.random() * 100000)], + link: "https://en.wikipedia.org/wiki/Brno", + value: "Brno", + }, + { + id: "kyiv", + name: "Kyiv", + data: [Math.round(Math.random() * 100000)], + link: "https://en.wikipedia.org/wiki/Kyiv", + value: "Kyiv", + }, + { + id: "austin", + name: "Austin", + data: [Math.round(Math.random() * 100000)], + link: "https://en.wikipedia.org/wiki/Austin", + value: "Austin", + }, + { + id: "lisbon", + name: "Lisbon", + data: [Math.round(Math.random() * 100000)], + link: "https://en.wikipedia.org/wiki/Lisbon", + value: "Lisbon", + }, + { + id: "sydney", + name: "Sydney", + data: [Math.round(Math.random() * 100000)], + link: "https://en.wikipedia.org/wiki/Sydney", + value: "Sydney", + }, + { + id: "nur-sultan", + name: "Nur-Sultan", + data: [Math.round(Math.random() * 100000)], + link: "https://en.wikipedia.org/wiki/Nur-Sultan", + value: "Nur-Sultan", + }, + ].sort((a, b) => a.data[0] - b.data[0]); +} + +export function getMockBeerReviewCountsByCity2(): IProportionalWidgetData[] { + return [ + { + id: "london", + name: "London", + data: [Math.round(Math.random() * 100000)], + link: "https://en.wikipedia.org/wiki/London", + value: "London", + }, + { + id: "paris", + name: "Paris", + data: [Math.round(Math.random() * 100000)], + link: "https://en.wikipedia.org/wiki/Paris", + value: "Paris", + }, + { + id: "rio", + name: "Rio", + data: [Math.round(Math.random() * 100000)], + link: "https://en.wikipedia.org/wiki/Rio_de_Janeiro", + value: "Rio", + }, + ].sort((a, b) => a.data[0] - b.data[0]); +} + +export const BEER_VS_READING_DATA: ITimeseriesWidgetData[] = [ + { + id: "series-1", + name: "Beer Tasting", + description: "Havin' some suds", + data: [ + { x: moment().subtract(10, "day"), y: 30 }, + { x: moment().subtract(9, "day"), y: 35 }, + { x: moment().subtract(8, "day"), y: 33 }, + { x: moment().subtract(7, "day"), y: 40 }, + { x: moment().subtract(6, "day"), y: 35 }, + { x: moment().subtract(5, "day"), y: 30 }, + { x: moment().subtract(4, "day"), y: 35 }, + { x: moment().subtract(3, "day"), y: 15 }, + { x: moment().subtract(2, "day"), y: 30 }, + { x: moment().subtract(1, "day"), y: 35 }, + { x: moment().subtract(24, "hour"), y: 34 }, + { x: moment().subtract(15, "hour"), y: 33 }, + { x: moment().subtract(10, "hour"), y: 35 }, + { x: moment().subtract(5, "hour"), y: 36 }, + { x: moment().subtract(1, "hour"), y: 34 }, + { x: moment().subtract(50, "minute"), y: 33 }, + { x: moment().subtract(40, "minute"), y: 30 }, + { x: moment().subtract(30, "minute"), y: 32 }, + { x: moment().subtract(20, "minute"), y: 31 }, + { x: moment().subtract(10, "minute"), y: 34 }, + ], + }, + { + id: "series-2", + name: "Reading", + description: "Hittin' the books", + data: [ + { x: moment().subtract(10, "day"), y: 60 }, + { x: moment().subtract(9, "day"), y: 64 }, + { x: moment().subtract(8, "day"), y: 70 }, + { x: moment().subtract(7, "day"), y: 55 }, + { x: moment().subtract(6, "day"), y: 55 }, + { x: moment().subtract(5, "day"), y: 45 }, + { x: moment().subtract(4, "day"), y: 10 }, + { x: moment().subtract(3, "day"), y: 65 }, + { x: moment().subtract(2, "day"), y: 35 }, + { x: moment().subtract(1, "day"), y: 60 }, + { x: moment().subtract(24, "hour"), y: 61 }, + { x: moment().subtract(15, "hour"), y: 65 }, + { x: moment().subtract(10, "hour"), y: 63 }, + { x: moment().subtract(5, "hour"), y: 58 }, + { x: moment().subtract(1, "hour"), y: 64 }, + { x: moment().subtract(50, "minute"), y: 63 }, + { x: moment().subtract(40, "minute"), y: 60 }, + { x: moment().subtract(30, "minute"), y: 62 }, + { x: moment().subtract(20, "minute"), y: 61 }, + { x: moment().subtract(10, "minute"), y: 62 }, + ], + }, +]; + +export const LOUNGING_VS_ULTIMATE_FRISBEE_DATA: ITimeseriesWidgetData[] = [ + { + id: "series-a", + name: "Lounging", + description: "Shootin' the Breeze", + data: [ + { x: moment().subtract(10, "day"), y: 10 }, + { x: moment().subtract(9, "day"), y: 15 }, + { x: moment().subtract(8, "day"), y: 13 }, + { x: moment().subtract(7, "day"), y: 20 }, + { x: moment().subtract(6, "day"), y: 15 }, + { x: moment().subtract(5, "day"), y: 10 }, + { x: moment().subtract(4, "day"), y: 15 }, + { x: moment().subtract(3, "day"), y: 5 }, + { x: moment().subtract(2, "day"), y: 10 }, + { x: moment().subtract(1, "day"), y: 15 }, + { x: moment().subtract(24, "hour"), y: 14 }, + { x: moment().subtract(15, "hour"), y: 13 }, + { x: moment().subtract(10, "hour"), y: 15 }, + { x: moment().subtract(5, "hour"), y: 16 }, + { x: moment().subtract(1, "hour"), y: 14 }, + { x: moment().subtract(50, "minute"), y: 13 }, + { x: moment().subtract(40, "minute"), y: 10 }, + { x: moment().subtract(30, "minute"), y: 12 }, + { x: moment().subtract(20, "minute"), y: 11 }, + { x: moment().subtract(10, "minute"), y: 14 }, + ], + }, + { + id: "series-b", + name: "Frisbee Golfing", + description: "Golfin' with a disc", + data: [ + { x: moment().subtract(10, "day"), y: 80 }, + { x: moment().subtract(9, "day"), y: 84 }, + { x: moment().subtract(8, "day"), y: 80 }, + { x: moment().subtract(7, "day"), y: 75 }, + { x: moment().subtract(6, "day"), y: 95 }, + { x: moment().subtract(5, "day"), y: 85 }, + { x: moment().subtract(4, "day"), y: 80 }, + { x: moment().subtract(3, "day"), y: 85 }, + { x: moment().subtract(2, "day"), y: 85 }, + { x: moment().subtract(1, "day"), y: 80 }, + { x: moment().subtract(24, "hour"), y: 81 }, + { x: moment().subtract(15, "hour"), y: 85 }, + { x: moment().subtract(10, "hour"), y: 83 }, + { x: moment().subtract(5, "hour"), y: 88 }, + { x: moment().subtract(1, "hour"), y: 84 }, + { x: moment().subtract(50, "minute"), y: 83 }, + { x: moment().subtract(40, "minute"), y: 80 }, + { x: moment().subtract(30, "minute"), y: 82 }, + { x: moment().subtract(20, "minute"), y: 81 }, + { x: moment().subtract(10, "minute"), y: 82 }, + ], + }, +]; + +export const TABLE_DATA: BasicTableModel[] = [ + { + position: 1, + name: "FOCUS-SVR-02258", + features: ["remote-access-vpn-tunnel", "patch-manager01"], + status: "Active", + checks: { + icon: "status_up", + num: 25, + }, + "cpu-load": 86, + firstUrl: "https://en.wikipedia.org/wiki/Brno", + firstUrlLabel: "Brno", + secondUrl: "https://en.wikipedia.org/wiki/VMware_Workstation", + secondUrlLabel: "Workstation", + }, + { + position: 2, + name: "FOCUS-SVR-03312", + features: ["tools", "database", "orion-ape-backup"], + status: "Active", + checks: { + icon: "status_critical", + num: 25, + }, + "cpu-load": 47, + firstUrl: "https://en.wikipedia.org/wiki/Brno", + firstUrlLabel: "Brno", + secondUrl: "https://en.wikipedia.org/wiki/VMware_Workstation", + secondUrlLabel: "Workstation", + }, + { + position: 3, + name: "FOCUS-SVR-02258", + features: [ + "remote-access-vpn-tunnel", + "database", + "orion-ape-backup", + "patch-manager01", + ], + status: "Active", + checks: { + icon: "status_down", + num: 25, + }, + "cpu-load": 53, + firstUrl: "https://en.wikipedia.org/wiki/Kyiv", + firstUrlLabel: "Kyiv", + secondUrl: "https://en.wikipedia.org/wiki/VMware_Workstation", + secondUrlLabel: "Workstation", + }, + { + position: 4, + name: "Man-LT-JYJ4AD5", + features: [ + "remote-access-vpn-tunnel", + "tools", + "database", + "orion-ape-backup", + ], + status: "Active", + checks: { + icon: "status_up", + num: 25, + }, + "cpu-load": 32, + firstUrl: "https://en.wikipedia.org/wiki/Kyiv", + firstUrlLabel: "Kyiv", + secondUrl: "https://en.wikipedia.org/wiki/VirtualBox", + secondUrlLabel: "VirtualBox", + }, + { + position: 5, + name: "Man-LT-JYJ425", + features: [ + "remote-access-vpn-tunnel", + "tools", + "database", + "orion-ape-backup", + ], + status: "Active", + checks: { + icon: "status_up", + num: 25, + }, + "cpu-load": 22, + firstUrl: "https://en.wikipedia.org/wiki/Kyiv", + firstUrlLabel: "Kyiv", + secondUrl: "https://en.wikipedia.org/wiki/VirtualBox", + secondUrlLabel: "VirtualBox", + }, + { + position: 6, + name: "Man-LT-JYJ4333", + features: [ + "remote-access-vpn-tunnel", + "tools", + "database", + "orion-ape-backup", + ], + status: "Active", + checks: { + icon: "status_up", + num: 25, + }, + "cpu-load": 12, + firstUrl: "https://en.wikipedia.org/wiki/Kyiv", + firstUrlLabel: "Kyiv", + secondUrl: "https://en.wikipedia.org/wiki/VirtualBox", + secondUrlLabel: "VirtualBox", + }, + { + position: 7, + name: "FOCUS-SVR-02258", + features: ["remote-access-vpn-tunnel", "patch-manager01"], + status: "Active", + checks: { + icon: "status_up", + num: 25, + }, + "cpu-load": 86, + firstUrl: "https://en.wikipedia.org/wiki/Austin", + firstUrlLabel: "Austin", + secondUrl: "https://en.wikipedia.org/wiki/VMware_Workstation", + secondUrlLabel: "Workstation", + }, + { + position: 8, + name: "Man-LT-JYJ4AD5", + features: [ + "remote-access-vpn-tunnel", + "tools", + "database", + "orion-ape-backup", + "patch-manager01", + ], + status: "Active", + checks: { + icon: "status_inactive", + num: 25, + }, + "cpu-load": 35, + firstUrl: "https://en.wikipedia.org/wiki/Austin", + firstUrlLabel: "Austin", + secondUrl: "https://en.wikipedia.org/wiki/VMware_Workstation", + secondUrlLabel: "Workstation", + }, + { + position: 9, + name: "Man-LT-JYJ4AD5", + features: [ + "remote-access-vpn-tunnel", + "tools", + "database", + "patch-manager01", + ], + status: "Active", + checks: { + icon: "status_up", + num: 25, + }, + "cpu-load": 32, + firstUrl: "https://en.wikipedia.org/wiki/Brno", + firstUrlLabel: "Brno", + secondUrl: "https://en.wikipedia.org/wiki/VMware_Workstation", + secondUrlLabel: "Workstation", + }, + { + position: 10, + name: "Man-LT-JYJ4AD5", + features: [ + "remote-access-vpn-tunnel", + "database", + "orion-ape-backup", + "patch-manager01", + ], + status: "Active", + checks: { + icon: "status_up", + num: 25, + }, + "cpu-load": 64, + firstUrl: "https://en.wikipedia.org/wiki/Kyiv", + firstUrlLabel: "Kyiv", + secondUrl: "https://en.wikipedia.org/wiki/VMware_Workstation", + secondUrlLabel: "Workstation", + }, + { + position: 11, + name: "Man-LT-111", + features: [], + status: "Active", + checks: { + icon: "status_external", + num: 25, + }, + "cpu-load": 55, + firstUrl: "https://en.wikipedia.org/wiki/Brno", + firstUrlLabel: "Brno", + secondUrl: "https://en.wikipedia.org/wiki/VMware_Workstation", + secondUrlLabel: "Workstation", + }, + { + position: 12, + name: "Man-LT-2222", + features: [ + "remote-access-vpn-tunnel", + "tools", + "database", + "orion-ape-backup", + "patch-manager01", + ], + status: "Active", + checks: { + icon: "status_inactive", + num: 25, + }, + "cpu-load": 34, + firstUrl: "https://en.wikipedia.org/wiki/Brno", + firstUrlLabel: "Brno", + secondUrl: "https://en.wikipedia.org/wiki/VMware_Workstation", + secondUrlLabel: "Workstation", + }, + { + position: 13, + name: "Man-LT-333333", + features: [ + "remote-access-vpn-tunnel", + "tools", + "database", + "patch-manager01", + ], + status: "Active", + checks: { + icon: "status_up", + num: 25, + }, + "cpu-load": 56, + firstUrl: "https://en.wikipedia.org/wiki/Kyiv", + firstUrlLabel: "Kyiv", + secondUrl: "https://en.wikipedia.org/wiki/VMware_Workstation", + secondUrlLabel: "Workstation", + }, + { + position: 14, + name: "Man-LT-444444", + features: [ + "remote-access-vpn-tunnel", + "database", + "orion-ape-backup", + "patch-manager01", + ], + status: "Active", + checks: { + icon: "status_up", + num: 25, + }, + "cpu-load": 26, + firstUrl: "https://en.wikipedia.org/wiki/Kyiv", + firstUrlLabel: "Kyiv", + secondUrl: "https://en.wikipedia.org/wiki/VMware_Workstation", + secondUrlLabel: "Workstation", + }, + { + position: 15, + name: "Man-LT-555555", + features: [ + "remote-access-vpn-tunnel", + "database", + "orion-ape-backup", + "patch-manager01", + ], + status: "Active", + checks: { + icon: "status_up", + num: 25, + }, + "cpu-load": 76, + firstUrl: "https://en.wikipedia.org/wiki/Austin", + firstUrlLabel: "Austin", + secondUrl: "https://en.wikipedia.org/wiki/VMware_Workstation", + secondUrlLabel: "Workstation", + }, + { + position: 16, + name: "FOCUS-SVR-02258", + features: ["remote-access-vpn-tunnel", "patch-manager01"], + status: "Active", + checks: { + icon: "status_up", + num: 25, + }, + "cpu-load": 86, + firstUrl: "https://en.wikipedia.org/wiki/Brno", + firstUrlLabel: "Brno", + secondUrl: "https://en.wikipedia.org/wiki/VMware_Workstation", + secondUrlLabel: "Workstation", + }, + { + position: 17, + name: "FOCUS-SVR-03312", + features: ["tools", "database", "orion-ape-backup"], + status: "Active", + checks: { + icon: "status_critical", + num: 25, + }, + "cpu-load": 47, + firstUrl: "https://en.wikipedia.org/wiki/Brno", + firstUrlLabel: "Brno", + secondUrl: "https://en.wikipedia.org/wiki/VMware_Workstation", + secondUrlLabel: "Workstation", + }, + { + position: 18, + name: "FOCUS-SVR-02258", + features: [ + "remote-access-vpn-tunnel", + "database", + "orion-ape-backup", + "patch-manager01", + ], + status: "Active", + checks: { + icon: "status_down", + num: 25, + }, + "cpu-load": 53, + firstUrl: "https://en.wikipedia.org/wiki/Kyiv", + firstUrlLabel: "Kyiv", + secondUrl: "https://en.wikipedia.org/wiki/VMware_Workstation", + secondUrlLabel: "Workstation", + }, + { + position: 19, + name: "Man-LT-JYJ4AD5", + features: [ + "remote-access-vpn-tunnel", + "tools", + "database", + "orion-ape-backup", + ], + status: "Active", + checks: { + icon: "status_up", + num: 25, + }, + "cpu-load": 32, + firstUrl: "https://en.wikipedia.org/wiki/Kyiv", + firstUrlLabel: "Kyiv", + secondUrl: "https://en.wikipedia.org/wiki/VirtualBox", + secondUrlLabel: "VirtualBox", + }, + { + position: 20, + name: "Man-LT-JYJ425", + features: [ + "remote-access-vpn-tunnel", + "tools", + "database", + "orion-ape-backup", + ], + status: "Active", + checks: { + icon: "status_up", + num: 25, + }, + "cpu-load": 22, + firstUrl: "https://en.wikipedia.org/wiki/Kyiv", + firstUrlLabel: "Kyiv", + secondUrl: "https://en.wikipedia.org/wiki/VirtualBox", + secondUrlLabel: "VirtualBox", + }, + { + position: 21, + name: "Man-LT-JYJ4333", + features: [ + "remote-access-vpn-tunnel", + "tools", + "database", + "orion-ape-backup", + ], + status: "Active", + checks: { + icon: "status_up", + num: 25, + }, + "cpu-load": 12, + firstUrl: "https://en.wikipedia.org/wiki/Kyiv", + firstUrlLabel: "Kyiv", + secondUrl: "https://en.wikipedia.org/wiki/VirtualBox", + secondUrlLabel: "VirtualBox", + }, + { + position: 22, + name: "FOCUS-SVR-02258", + features: ["remote-access-vpn-tunnel", "patch-manager01"], + status: "Active", + checks: { + icon: "status_up", + num: 25, + }, + "cpu-load": 86, + firstUrl: "https://en.wikipedia.org/wiki/Austin", + firstUrlLabel: "Austin", + secondUrl: "https://en.wikipedia.org/wiki/VMware_Workstation", + secondUrlLabel: "Workstation", + }, + { + position: 23, + name: "Man-LT-JYJ4AD5", + features: [ + "remote-access-vpn-tunnel", + "tools", + "database", + "orion-ape-backup", + "patch-manager01", + ], + status: "Active", + checks: { + icon: "status_inactive", + num: 25, + }, + "cpu-load": 35, + firstUrl: "https://en.wikipedia.org/wiki/Austin", + firstUrlLabel: "Austin", + secondUrl: "https://en.wikipedia.org/wiki/VMware_Workstation", + secondUrlLabel: "Workstation", + }, + { + position: 24, + name: "Man-LT-JYJ4AD5", + features: [ + "remote-access-vpn-tunnel", + "tools", + "database", + "patch-manager01", + ], + status: "Active", + checks: { + icon: "status_up", + num: 25, + }, + "cpu-load": 32, + firstUrl: "https://en.wikipedia.org/wiki/Brno", + firstUrlLabel: "Brno", + secondUrl: "https://en.wikipedia.org/wiki/VMware_Workstation", + secondUrlLabel: "Workstation", + }, + { + position: 25, + name: "Man-LT-JYJ4AD5", + features: [ + "remote-access-vpn-tunnel", + "database", + "orion-ape-backup", + "patch-manager01", + ], + status: "Active", + checks: { + icon: "status_up", + num: 25, + }, + "cpu-load": 64, + firstUrl: "https://en.wikipedia.org/wiki/Kyiv", + firstUrlLabel: "Kyiv", + secondUrl: "https://en.wikipedia.org/wiki/VMware_Workstation", + secondUrlLabel: "Workstation", + }, + { + position: 26, + name: "Man-LT-111", + features: [], + status: "Active", + checks: { + icon: "status_external", + num: 25, + }, + "cpu-load": 55, + firstUrl: "https://en.wikipedia.org/wiki/Brno", + firstUrlLabel: "Brno", + secondUrl: "https://en.wikipedia.org/wiki/VMware_Workstation", + secondUrlLabel: "Workstation", + }, + { + position: 27, + name: "Man-LT-2222", + features: [ + "remote-access-vpn-tunnel", + "tools", + "database", + "orion-ape-backup", + "patch-manager01", + ], + status: "Active", + checks: { + icon: "status_inactive", + num: 25, + }, + "cpu-load": 34, + firstUrl: "https://en.wikipedia.org/wiki/Brno", + firstUrlLabel: "Brno", + secondUrl: "https://en.wikipedia.org/wiki/VMware_Workstation", + secondUrlLabel: "Workstation", + }, + { + position: 28, + name: "Man-LT-333333", + features: [ + "remote-access-vpn-tunnel", + "tools", + "database", + "patch-manager01", + ], + status: "Active", + checks: { + icon: "status_up", + num: 25, + }, + "cpu-load": 56, + firstUrl: "https://en.wikipedia.org/wiki/Kyiv", + firstUrlLabel: "Kyiv", + secondUrl: "https://en.wikipedia.org/wiki/VMware_Workstation", + secondUrlLabel: "Workstation", + }, + { + position: 29, + name: "Man-LT-444444", + features: [ + "remote-access-vpn-tunnel", + "database", + "orion-ape-backup", + "patch-manager01", + ], + status: "Active", + checks: { + icon: "status_up", + num: 25, + }, + "cpu-load": 26, + firstUrl: "https://en.wikipedia.org/wiki/Kyiv", + firstUrlLabel: "Kyiv", + secondUrl: "https://en.wikipedia.org/wiki/VMware_Workstation", + secondUrlLabel: "Workstation", + }, + { + position: 30, + name: "Man-LT-555555", + features: [ + "remote-access-vpn-tunnel", + "database", + "orion-ape-backup", + "patch-manager01", + ], + status: "Active", + checks: { + icon: "status_up", + num: 25, + }, + "cpu-load": 76, + firstUrl: "https://en.wikipedia.org/wiki/Austin", + firstUrlLabel: "Austin", + secondUrl: "https://en.wikipedia.org/wiki/VMware_Workstation", + secondUrlLabel: "Workstation", + }, + { + position: 31, + name: "FOCUS-SVR-02258", + features: ["remote-access-vpn-tunnel", "patch-manager01"], + status: "Active", + checks: { + icon: "status_up", + num: 25, + }, + "cpu-load": 86, + firstUrl: "https://en.wikipedia.org/wiki/Brno", + firstUrlLabel: "Brno", + secondUrl: "https://en.wikipedia.org/wiki/VMware_Workstation", + secondUrlLabel: "Workstation", + }, + { + position: 32, + name: "FOCUS-SVR-03312", + features: ["tools", "database", "orion-ape-backup"], + status: "Active", + checks: { + icon: "status_critical", + num: 25, + }, + "cpu-load": 47, + firstUrl: "https://en.wikipedia.org/wiki/Brno", + firstUrlLabel: "Brno", + secondUrl: "https://en.wikipedia.org/wiki/VMware_Workstation", + secondUrlLabel: "Workstation", + }, + { + position: 33, + name: "FOCUS-SVR-02258", + features: [ + "remote-access-vpn-tunnel", + "database", + "orion-ape-backup", + "patch-manager01", + ], + status: "Active", + checks: { + icon: "status_down", + num: 25, + }, + "cpu-load": 53, + firstUrl: "https://en.wikipedia.org/wiki/Kyiv", + firstUrlLabel: "Kyiv", + secondUrl: "https://en.wikipedia.org/wiki/VMware_Workstation", + secondUrlLabel: "Workstation", + }, + { + position: 34, + name: "Man-LT-JYJ4AD5", + features: [ + "remote-access-vpn-tunnel", + "tools", + "database", + "orion-ape-backup", + ], + status: "Active", + checks: { + icon: "status_up", + num: 25, + }, + "cpu-load": 32, + firstUrl: "https://en.wikipedia.org/wiki/Kyiv", + firstUrlLabel: "Kyiv", + secondUrl: "https://en.wikipedia.org/wiki/VirtualBox", + secondUrlLabel: "VirtualBox", + }, + { + position: 35, + name: "Man-LT-JYJ425", + features: [ + "remote-access-vpn-tunnel", + "tools", + "database", + "orion-ape-backup", + ], + status: "Active", + checks: { + icon: "status_up", + num: 25, + }, + "cpu-load": 22, + firstUrl: "https://en.wikipedia.org/wiki/Kyiv", + firstUrlLabel: "Kyiv", + secondUrl: "https://en.wikipedia.org/wiki/VirtualBox", + secondUrlLabel: "VirtualBox", + }, + { + position: 36, + name: "Man-LT-JYJ4333", + features: [ + "remote-access-vpn-tunnel", + "tools", + "database", + "orion-ape-backup", + ], + status: "Active", + checks: { + icon: "status_up", + num: 25, + }, + "cpu-load": 12, + firstUrl: "https://en.wikipedia.org/wiki/Kyiv", + firstUrlLabel: "Kyiv", + secondUrl: "https://en.wikipedia.org/wiki/VirtualBox", + secondUrlLabel: "VirtualBox", + }, + { + position: 37, + name: "FOCUS-SVR-02258", + features: ["remote-access-vpn-tunnel", "patch-manager01"], + status: "Active", + checks: { + icon: "status_up", + num: 25, + }, + "cpu-load": 86, + firstUrl: "https://en.wikipedia.org/wiki/Austin", + firstUrlLabel: "Austin", + secondUrl: "https://en.wikipedia.org/wiki/VMware_Workstation", + secondUrlLabel: "Workstation", + }, + { + position: 38, + name: "Man-LT-JYJ4AD5", + features: [ + "remote-access-vpn-tunnel", + "tools", + "database", + "orion-ape-backup", + "patch-manager01", + ], + status: "Active", + checks: { + icon: "status_inactive", + num: 25, + }, + "cpu-load": 35, + firstUrl: "https://en.wikipedia.org/wiki/Austin", + firstUrlLabel: "Austin", + secondUrl: "https://en.wikipedia.org/wiki/VMware_Workstation", + secondUrlLabel: "Workstation", + }, + { + position: 39, + name: "Man-LT-JYJ4AD5", + features: [ + "remote-access-vpn-tunnel", + "tools", + "database", + "patch-manager01", + ], + status: "Active", + checks: { + icon: "status_up", + num: 25, + }, + "cpu-load": 32, + firstUrl: "https://en.wikipedia.org/wiki/Brno", + firstUrlLabel: "Brno", + secondUrl: "https://en.wikipedia.org/wiki/VMware_Workstation", + secondUrlLabel: "Workstation", + }, + { + position: 40, + name: "Man-LT-JYJ4AD5", + features: [ + "remote-access-vpn-tunnel", + "database", + "orion-ape-backup", + "patch-manager01", + ], + status: "Active", + checks: { + icon: "status_up", + num: 25, + }, + "cpu-load": 64, + firstUrl: "https://en.wikipedia.org/wiki/Kyiv", + firstUrlLabel: "Kyiv", + secondUrl: "https://en.wikipedia.org/wiki/VMware_Workstation", + secondUrlLabel: "Workstation", + }, + { + position: 41, + name: "Man-LT-111", + features: [], + status: "Active", + checks: { + icon: "status_external", + num: 25, + }, + "cpu-load": 55, + firstUrl: "https://en.wikipedia.org/wiki/Brno", + firstUrlLabel: "Brno", + secondUrl: "https://en.wikipedia.org/wiki/VMware_Workstation", + secondUrlLabel: "Workstation", + }, + { + position: 42, + name: "Man-LT-2222", + features: [ + "remote-access-vpn-tunnel", + "tools", + "database", + "orion-ape-backup", + "patch-manager01", + ], + status: "Active", + checks: { + icon: "status_inactive", + num: 25, + }, + "cpu-load": 34, + firstUrl: "https://en.wikipedia.org/wiki/Brno", + firstUrlLabel: "Brno", + secondUrl: "https://en.wikipedia.org/wiki/VMware_Workstation", + secondUrlLabel: "Workstation", + }, + { + position: 43, + name: "Man-LT-333333", + features: [ + "remote-access-vpn-tunnel", + "tools", + "database", + "patch-manager01", + ], + status: "Active", + checks: { + icon: "status_up", + num: 25, + }, + "cpu-load": 56, + firstUrl: "https://en.wikipedia.org/wiki/Kyiv", + firstUrlLabel: "Kyiv", + secondUrl: "https://en.wikipedia.org/wiki/VMware_Workstation", + secondUrlLabel: "Workstation", + }, + { + position: 44, + name: "Man-LT-444444", + features: [ + "remote-access-vpn-tunnel", + "database", + "orion-ape-backup", + "patch-manager01", + ], + status: "Active", + checks: { + icon: "status_up", + num: 25, + }, + "cpu-load": 26, + firstUrl: "https://en.wikipedia.org/wiki/Kyiv", + firstUrlLabel: "Kyiv", + secondUrl: "https://en.wikipedia.org/wiki/VMware_Workstation", + secondUrlLabel: "Workstation", + }, + { + position: 45, + name: "Man-LT-555555", + features: [ + "remote-access-vpn-tunnel", + "database", + "orion-ape-backup", + "patch-manager01", + ], + status: "Active", + checks: { + icon: "status_up", + num: 25, + }, + "cpu-load": 76, + firstUrl: "https://en.wikipedia.org/wiki/Austin", + firstUrlLabel: "Austin", + secondUrl: "https://en.wikipedia.org/wiki/VMware_Workstation", + secondUrlLabel: "Workstation", + }, +]; +\`, + "overview/hero/widget-configs/kpi.ts": \`// © 2022 SolarWinds Worldwide, LLC. All rights reserved. +// +// Permission is hereby granted, free of charge, to any person obtaining a copy +// of this software and associated documentation files (the "Software"), to +// deal in the Software without restriction, including without limitation the +// rights to use, copy, modify, merge, publish, distribute, sublicense, and/or +// sell copies of the Software, and to permit persons to whom the Software is +// furnished to do so, subject to the following conditions: +// +// The above copyright notice and this permission notice shall be included in +// all copies or substantial portions of the Software. +// +// THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR +// IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, +// FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE +// AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER +// LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, +// OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN +// THE SOFTWARE. + +import { + DEFAULT_PIZZAGNA_ROOT, + IProviderConfiguration, + IRefresherProperties, + IWidget, + KpiComponent, + NOVA_KPI_DATASOURCE_ADAPTER, + PizzagnaLayer, + WellKnownProviders, +} from "@nova-ui/dashboards"; + +import { + HarryPotterAverageRatingDataSource, + HarryPotterRatingsCountDataSource, +} from "../data/kpi-datasources"; + +export const kpiConfig: IWidget = { + id: "kpiWidgetId", + type: "kpi", + pizzagna: { + [PizzagnaLayer.Configuration]: { + [DEFAULT_PIZZAGNA_ROOT]: { + providers: { + [WellKnownProviders.Refresher]: { + properties: { + // Configuring the refresher interval so that our data source is invoked every ten minutes + interval: 60 * 10, + enabled: true, + } as IRefresherProperties, + } as Partial, + }, + }, + header: { + properties: { + title: "Harry Potter and the Sorcerer's Stone", + subtitle: "By J. K. Rowling", + }, + }, + tiles: { + properties: { + nodes: ["kpi1", "kpi2"], + }, + }, + kpi1: { + id: "kpi1", + componentType: KpiComponent.lateLoadKey, + properties: { + widgetData: { + label: "Average Rating", + backgroundColor: "var(--nui-color-chart-three)", + units: "out of 5 Stars", + }, + }, + providers: { + [WellKnownProviders.DataSource]: { + // Setting the data source providerId for the tile with id "kpi1" + providerId: + HarryPotterAverageRatingDataSource.providerId, + } as IProviderConfiguration, + [WellKnownProviders.Adapter]: { + providerId: NOVA_KPI_DATASOURCE_ADAPTER, + properties: { + componentId: "kpi1", + propertyPath: "widgetData", + }, + } as IProviderConfiguration, + }, + }, + kpi2: { + id: "kpi2", + componentType: KpiComponent.lateLoadKey, + properties: { + widgetData: { + label: "Reader Feedback", + units: "Ratings", + }, + }, + providers: { + [WellKnownProviders.DataSource]: { + // Setting the data source providerId for the tile with id "kpi" + providerId: + HarryPotterRatingsCountDataSource.providerId, + } as IProviderConfiguration, + [WellKnownProviders.Adapter]: { + providerId: NOVA_KPI_DATASOURCE_ADAPTER, + properties: { + componentId: "kpi2", + propertyPath: "widgetData", + }, + } as IProviderConfiguration, + }, + }, + }, + }, +}; +\`, + "overview/hero/widget-configs/proportional.ts": \`// © 2022 SolarWinds Worldwide, LLC. All rights reserved. +// +// Permission is hereby granted, free of charge, to any person obtaining a copy +// of this software and associated documentation files (the "Software"), to +// deal in the Software without restriction, including without limitation the +// rights to use, copy, modify, merge, publish, distribute, sublicense, and/or +// sell copies of the Software, and to permit persons to whom the Software is +// furnished to do so, subject to the following conditions: +// +// The above copyright notice and this permission notice shall be included in +// all copies or substantial portions of the Software. +// +// THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR +// IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, +// FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE +// AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER +// LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, +// OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN +// THE SOFTWARE. + +import { + DEFAULT_PIZZAGNA_ROOT, + IProportionalWidgetChartOptions, + IProviderConfiguration, + IWidget, + LegendPlacement, + PizzagnaLayer, + ProportionalWidgetChartTypes, + WellKnownProviders, +} from "@nova-ui/dashboards"; + +import { BeerReviewCountsByCityMockDataSource } from "../data/proportional-datasources"; + +export const proportionalConfig: IWidget = { + id: "proportionalWidgetId", + type: "proportional", + pizzagna: { + [PizzagnaLayer.Configuration]: { + [DEFAULT_PIZZAGNA_ROOT]: { + providers: { + [WellKnownProviders.Refresher]: { + properties: { + interval: 0, + }, + }, + }, + }, + header: { + properties: { + title: "Beer Review Tally by City", + subtitle: "These People Love Beer", + }, + }, + chart: { + providers: { + [WellKnownProviders.DataSource]: { + providerId: + BeerReviewCountsByCityMockDataSource.providerId, + } as IProviderConfiguration, + }, + properties: { + configuration: { + chartOptions: { + type: ProportionalWidgetChartTypes.DonutChart, + legendPlacement: LegendPlacement.Right, + } as IProportionalWidgetChartOptions, + }, + }, + }, + }, + }, +}; +\`, + "overview/hero/widget-configs/risk-score.ts": \`// © 2023 SolarWinds Worldwide, LLC. All rights reserved. +// +// Permission is hereby granted, free of charge, to any person obtaining a copy +// of this software and associated documentation files (the "Software"), to +// deal in the Software without restriction, including without limitation the +// rights to use, copy, modify, merge, publish, distribute, sublicense, and/or +// sell copies of the Software, and to permit persons to whom the Software is +// furnished to do so, subject to the following conditions: +// +// The above copyright notice and this permission notice shall be included in +// all copies or substantial portions of the Software. +// +// THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR +// IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, +// FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE +// AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER +// LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, +// OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN +// THE SOFTWARE. + +import { + DEFAULT_PIZZAGNA_ROOT, + IProviderConfiguration, + IRefresherProperties, + IWidget, + RiskScoreTileComponent, + NOVA_KPI_DATASOURCE_ADAPTER, + PizzagnaLayer, + WellKnownProviders, +} from "@nova-ui/dashboards"; + +import { HarryPotterAverageRatingDataSource } from "../data/kpi-datasources"; + +export const riskScoreConfig: IWidget = { + id: "riskScoreWidgetId", + type: "risk-score", + pizzagna: { + [PizzagnaLayer.Configuration]: { + [DEFAULT_PIZZAGNA_ROOT]: { + providers: { + [WellKnownProviders.Refresher]: { + properties: { + // Configuring the refresher interval so that our data source is invoked every ten minutes + interval: 60 * 10, + enabled: true, + } as IRefresherProperties, + } as Partial, + }, + }, + header: { + properties: { + title: "Harry Potter and the Sorcerer's Stone", + subtitle: "By J. K. Rowling", + }, + }, + tiles: { + properties: { + nodes: ["riskScore1"], + }, + }, + riskScore1: { + id: "riskScore1", + componentType: RiskScoreTileComponent.lateLoadKey, + properties: { + widgetData: { + minValue: 0, + maxValue: 5, + useStaticLabel: false, + staticLabel: undefined, + label: \\\`Average Rating\\\`, + description: \\\`Harry Potter and the Sorcerer's Stone By J. K. Rowling Average Rating Risk Score\\\`, + }, + }, + providers: { + [WellKnownProviders.DataSource]: { + // Setting the data source providerId for the tile with id "riskScore1" + providerId: + HarryPotterAverageRatingDataSource.providerId, + } as IProviderConfiguration, + [WellKnownProviders.Adapter]: { + providerId: NOVA_KPI_DATASOURCE_ADAPTER, + properties: { + componentId: "riskScore1", + propertyPath: "widgetData", + }, + } as IProviderConfiguration, + }, + }, + }, + }, +}; +\`, + "overview/hero/widget-configs/table.ts": \`// © 2022 SolarWinds Worldwide, LLC. All rights reserved. +// +// Permission is hereby granted, free of charge, to any person obtaining a copy +// of this software and associated documentation files (the "Software"), to +// deal in the Software without restriction, including without limitation the +// rights to use, copy, modify, merge, publish, distribute, sublicense, and/or +// sell copies of the Software, and to permit persons to whom the Software is +// furnished to do so, subject to the following conditions: +// +// The above copyright notice and this permission notice shall be included in +// all copies or substantial portions of the Software. +// +// THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR +// IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, +// FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE +// AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER +// LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, +// OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN +// THE SOFTWARE. + +import { + ITableWidgetColumnConfig, + ITableWidgetSorterConfig, + IWidget, + PizzagnaLayer, + RawFormatterComponent, + WellKnownProviders, +} from "@nova-ui/dashboards"; + +import { BeerDataSource } from "../data/table/beer-data-source"; + +export const tableConfig: IWidget = { + id: "tableWidgetId", + type: "table", + pizzagna: { + [PizzagnaLayer.Configuration]: { + header: { + properties: { + title: "Stupendous Suds", + subtitle: "Try These Brilliant Brews", + }, + }, + table: { + providers: { + [WellKnownProviders.DataSource]: { + providerId: BeerDataSource.providerId, + }, + }, + properties: { + configuration: { + columns: [ + { + id: "column1", + label: "Beer Name", + isActive: true, + width: 185, + formatter: { + componentType: + RawFormatterComponent.lateLoadKey, + properties: { + dataFieldIds: { + value: "name", + }, + }, + }, + }, + { + id: "column2", + label: "Tagline", + isActive: true, + width: 250, + formatter: { + componentType: + RawFormatterComponent.lateLoadKey, + properties: { + dataFieldIds: { + value: "tagline", + }, + }, + }, + }, + { + id: "column3", + label: "First Brewed", + isActive: true, + formatter: { + componentType: + RawFormatterComponent.lateLoadKey, + properties: { + dataFieldIds: { + value: "first_brewed", + }, + }, + }, + }, + { + id: "column4", + label: "Description", + isActive: true, + width: 275, + formatter: { + componentType: + RawFormatterComponent.lateLoadKey, + properties: { + dataFieldIds: { + value: "description", + }, + }, + }, + }, + ] as ITableWidgetColumnConfig[], + sorterConfiguration: { + descendantSorting: false, + sortBy: "", + } as ITableWidgetSorterConfig, + hasVirtualScroll: true, + }, + }, + }, + }, + }, +}; +\`, + "overview/hero/widget-configs/timeseries.ts": \`// © 2022 SolarWinds Worldwide, LLC. All rights reserved. +// +// Permission is hereby granted, free of charge, to any person obtaining a copy +// of this software and associated documentation files (the "Software"), to +// deal in the Software without restriction, including without limitation the +// rights to use, copy, modify, merge, publish, distribute, sublicense, and/or +// sell copies of the Software, and to permit persons to whom the Software is +// furnished to do so, subject to the following conditions: +// +// The above copyright notice and this permission notice shall be included in +// all copies or substantial portions of the Software. +// +// THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR +// IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, +// FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE +// AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER +// LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, +// OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN +// THE SOFTWARE. + +import moment from "moment/moment"; + +import { + DEFAULT_PIZZAGNA_ROOT, + IProviderConfiguration, + ISerializableTimeframe, + ITimeseriesItemConfiguration, + IWidget, + LegendPlacement, + WellKnownProviders, +} from "@nova-ui/dashboards"; + +import { BeerVsReadingMockDataSource } from "../data/timeseries-data-sources"; + +export const timeseriesConfig: IWidget = { + id: "timeseriesWidgetId", + type: "timeseries", + pizzagna: { + configuration: { + [DEFAULT_PIZZAGNA_ROOT]: { + providers: { + [WellKnownProviders.DataSource]: { + providerId: BeerVsReadingMockDataSource.providerId, + } as IProviderConfiguration, + }, + }, + header: { + properties: { + title: "Primary Leisure Activity Over Time", + subtitle: "Survey of 1000 Solarians", + }, + }, + chart: { + providers: { + [WellKnownProviders.Adapter]: { + properties: { + series: [ + { + id: "series-1", + label: "Beer Tasting", + selectedSeriesId: "series-1", + }, + { + id: "series-2", + label: "Reading", + selectedSeriesId: "series-2", + }, + ] as ITimeseriesItemConfiguration[], + }, + } as Partial, + }, + properties: { + configuration: { + legendPlacement: LegendPlacement.Right, + enableZoom: true, + leftAxisLabel: "Solarians (%)", + }, + }, + }, + timeframeSelection: { + properties: { + timeframe: { + selectedPresetId: "last7Days", + } as ISerializableTimeframe, + minDate: moment().subtract(10, "days").format(), + maxDate: moment().format(), + }, + }, + }, + }, +}; +\`, + "overview/overview-docs.component.ts": \`// © 2022 SolarWinds Worldwide, LLC. All rights reserved. +// +// Permission is hereby granted, free of charge, to any person obtaining a copy +// of this software and associated documentation files (the "Software"), to +// deal in the Software without restriction, including without limitation the +// rights to use, copy, modify, merge, publish, distribute, sublicense, and/or +// sell copies of the Software, and to permit persons to whom the Software is +// furnished to do so, subject to the following conditions: +// +// The above copyright notice and this permission notice shall be included in +// all copies or substantial portions of the Software. +// +// THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR +// IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, +// FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE +// AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER +// LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, +// OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN +// THE SOFTWARE. + +import { Component } from "@angular/core"; + +@Component({ + selector: "nui-dashboard-overview-docs", + templateUrl: "./overview-docs.component.html", + standalone: false, +}) +export class OverviewDocsComponent {} +\`, + "overview/overview.module.ts": \`// © 2022 SolarWinds Worldwide, LLC. All rights reserved. +// +// Permission is hereby granted, free of charge, to any person obtaining a copy +// of this software and associated documentation files (the "Software"), to +// deal in the Software without restriction, including without limitation the +// rights to use, copy, modify, merge, publish, distribute, sublicense, and/or +// sell copies of the Software, and to permit persons to whom the Software is +// furnished to do so, subject to the following conditions: +// +// The above copyright notice and this permission notice shall be included in +// all copies or substantial portions of the Software. +// +// THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR +// IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, +// FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE +// AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER +// LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, +// OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN +// THE SOFTWARE. + +import { CommonModule } from "@angular/common"; +import { NgModule } from "@angular/core"; +import { RouterModule } from "@angular/router"; + +import { + NuiBusyModule, + NuiButtonModule, + NuiDocsModule, + NuiIconModule, + NuiMessageModule, + NuiSwitchModule, +} from "@nova-ui/bits"; +import { + ConfiguratorHeadingService, + IFormatterDefinition, + LinkFormatterComponent, + NuiDashboardsModule, + WellKnownPathKey, + WidgetTypesService, +} from "@nova-ui/dashboards"; + +import { HeroDashboardComponent } from "./hero/dashboard/hero-dashboard.component"; +import { + HarryPotterAverageRatingDataSource, + HarryPotterRatingsCountDataSource, +} from "./hero/data/kpi-datasources"; +import { + BeerReviewCountsByCityMockDataSource, + BeerReviewCountsByCityMockDataSource2, +} from "./hero/data/proportional-datasources"; +import { BeerDataSource } from "./hero/data/table/beer-data-source"; +import { RandomUserDataSource } from "./hero/data/table/random-user-data-source"; +import { + BeerVsReadingMockDataSource, + LoungingVsFrisbeeGolfMockDataSource, +} from "./hero/data/timeseries-data-sources"; +import { OverviewDocsComponent } from "./overview-docs.component"; + +const routes = [ + { + path: "", + component: OverviewDocsComponent, + data: { + srlc: { + hideIndicator: true, + }, + showThemeSwitcher: true, + }, + }, + { + path: "hero", + component: HeroDashboardComponent, + data: { + srlc: { + hideIndicator: true, + }, + }, + }, +]; + +@NgModule({ + imports: [ + CommonModule, + NuiDashboardsModule, + NuiBusyModule, + NuiButtonModule, + NuiDocsModule, + NuiMessageModule, + NuiSwitchModule, + NuiIconModule, + RouterModule.forChild(routes), + ], + declarations: [OverviewDocsComponent, HeroDashboardComponent], + providers: [ConfiguratorHeadingService], +}) +export default class OverviewModule { + constructor(private widgetTypesService: WidgetTypesService) { + this.setupDataSourceProviders(); + this.setupProportionalLegendFormatters(); + } + + private setupDataSourceProviders() { + this.setDataSourceProviders("table", [ + RandomUserDataSource.providerId, + BeerDataSource.providerId, + ]); + this.setDataSourceProviders("kpi", [ + HarryPotterAverageRatingDataSource.providerId, + HarryPotterRatingsCountDataSource.providerId, + ]); + this.setDataSourceProviders("risk-score", [ + HarryPotterAverageRatingDataSource.providerId, + HarryPotterRatingsCountDataSource.providerId, + ]); + this.setDataSourceProviders("proportional", [ + BeerReviewCountsByCityMockDataSource.providerId, + BeerReviewCountsByCityMockDataSource2.providerId, + ]); + this.setDataSourceProviders("timeseries", [ + BeerVsReadingMockDataSource.providerId, + LoungingVsFrisbeeGolfMockDataSource.providerId, + ]); + } + + private setDataSourceProviders(type: string, providers: string[]) { + const widgetTemplate = this.widgetTypesService.getWidgetType(type, 1); + this.widgetTypesService.setNode( + widgetTemplate, + "configurator", + WellKnownPathKey.DataSourceProviders, + providers + ); + } + + private setupProportionalLegendFormatters() { + const formatters: IFormatterDefinition[] = [ + { + componentType: LinkFormatterComponent.lateLoadKey, + label: $localize\\\`Link\\\`, + dataTypes: { + value: "label", + link: "link", + }, + }, + ]; + + const widgetTemplate = this.widgetTypesService.getWidgetType( + "proportional", + 1 + ); + this.widgetTypesService.setNode( + widgetTemplate, + "configurator", + WellKnownPathKey.Formatters, + formatters + ); + } +} +\`, + "tutorials/customization/configurator-section/custom-configurator-section/custom-configurator-section.example.component.ts": \`// © 2022 SolarWinds Worldwide, LLC. All rights reserved. +// +// Permission is hereby granted, free of charge, to any person obtaining a copy +// of this software and associated documentation files (the "Software"), to +// deal in the Software without restriction, including without limitation the +// rights to use, copy, modify, merge, publish, distribute, sublicense, and/or +// sell copies of the Software, and to permit persons to whom the Software is +// furnished to do so, subject to the following conditions: +// +// The above copyright notice and this permission notice shall be included in +// all copies or substantial portions of the Software. +// +// THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR +// IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, +// FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE +// AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER +// LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, +// OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN +// THE SOFTWARE. + +import { HttpClient, HttpErrorResponse } from "@angular/common/http"; +import { + ChangeDetectionStrategy, + ChangeDetectorRef, + Component, + EventEmitter, + Injectable, + Input, + OnChanges, + OnDestroy, + OnInit, + Output, + SimpleChanges, +} from "@angular/core"; +import { FormBuilder, FormGroup, Validators } from "@angular/forms"; +import { GridsterConfig, GridsterItem } from "angular-gridster2"; +// eslint-disable-next-line import/no-deprecated +import { BehaviorSubject, combineLatest, Observable } from "rxjs"; +// eslint-disable-next-line import/no-deprecated +import { finalize, map, startWith } from "rxjs/operators"; + +import { DataSourceService, IFilteringOutputs } from "@nova-ui/bits"; +import { + ComponentRegistryService, + DATA_SOURCE, + DEFAULT_PIZZAGNA_ROOT, + IDashboard, + IHasChangeDetector, + IHasForm, + IKpiData, + IProviderConfiguration, + IRefresherProperties, + IWidget, + IWidgets, + KpiComponent, + NOVA_KPI_DATASOURCE_ADAPTER, + PizzagnaLayer, + ProviderRegistryService, + WellKnownPathKey, + WellKnownProviders, + WidgetTypesService, +} from "@nova-ui/dashboards"; + +/** + * A custom version of the KpiDescriptionConfigurationComponent provided by the dashboards framework. + * --- + * For this example, the existing background color selection functionality has been replaced by custom + * template content. + */ +@Component({ + selector: "custom-kpi-description-configuration", + template: \\\` + + +
+
+ + + +
+ + +
+
+ Custom Content +
+
+ The default version of this configurator section + displays a background color selector here. +
+
+ + +
+ + + +
+
+
+ \\\`, + styleUrls: ["./custom-configurator-section.example.component.less"], + changeDetection: ChangeDetectionStrategy.OnPush, + standalone: false, +}) +// Remember to declare this class in the parent module +export class CustomKpiDescriptionConfigurationComponent + implements OnInit, OnChanges, IHasChangeDetector, IHasForm +{ + // Ensure that the lateLoadKey value matches class name + public static lateLoadKey = "CustomKpiDescriptionConfigurationComponent"; + + @Input() componentId: string; + @Input() configurableUnits: boolean; + + @Input() label: string = ""; + @Input() units: string = ""; + + @Output() formReady = new EventEmitter(); + + public form: FormGroup; + public subtitle$: Observable; + + constructor( + public changeDetector: ChangeDetectorRef, + private formBuilder: FormBuilder + ) {} + + public ngOnInit(): void { + this.form = this.formBuilder.group({ + label: [this.label, [Validators.required]], + }); + + if (this.configurableUnits) { + this.form.addControl("units", this.formBuilder.control(this.units)); + } + + const label = this.form.get("label"); + // eslint-disable-next-line import/no-deprecated + const labelValue = label?.valueChanges.pipe(startWith(label?.value)); + + // eslint-disable-next-line import/no-deprecated + this.subtitle$ = combineLatest([ + labelValue?.pipe(map((t) => t || $localize\\\`no label\\\`)), + ]).pipe(map((labels) => labels.join(", "))); + + this.formReady.emit(this.form); + } + + public ngOnChanges(changes: SimpleChanges): void { + if (changes.label) { + this.form.patchValue({ label: changes.label.currentValue }); + } + if (changes.units) { + this.form.patchValue({ units: changes.units.currentValue }); + } + } +} + +/** + * A simple KPI data source to retrieve the average rating of Harry Potter and the Sorcerer's Stone (book) via googleapis + */ +@Injectable() +export class AverageRatingKpiDataSource + extends DataSourceService + implements OnDestroy +{ + // This is the ID we'll use to identify the provider + public static providerId = "AverageRatingKpiDataSource"; + + // Use this subject to communicate the data source's busy state + public busy = new BehaviorSubject(false); + + constructor(private http: HttpClient) { + super(); + } + + // In this example, getFilteredData is invoked every 10 minutes (Take a look at the refresher + // provider definition in the widget configuration below to see how the interval is set) + public async getFilteredData(): Promise { + this.busy.next(true); + return new Promise((resolve) => { + // *** Make a resource request to an external API (if needed) + this.http + .get("https://www.googleapis.com/books/v1/volumes/5MQFrgEACAAJ") + .pipe(finalize(() => this.busy.next(false))) + .subscribe({ + next: (data: any) => { + resolve({ + result: { + value: data.volumeInfo.averageRating, + }, + }); + }, + error: (error: HttpErrorResponse) => { + resolve({ + result: null, + error: { + type: error.status, + }, + }); + }, + }); + }); + } + + public ngOnDestroy(): void { + this.outputsSubject.complete(); + } +} + +/** + * A simple KPI data source to retrieve the ratings count of Harry Potter and the Sorcerer's Stone (book) via googleapis + */ +@Injectable() +export class RatingsCountKpiDataSource + extends DataSourceService + implements OnDestroy +{ + public static providerId = "RatingsCountKpiDataSource"; + + // Use this subject to communicate the data source's busy state + public busy = new BehaviorSubject(false); + + constructor(private http: HttpClient) { + super(); + } + + public async getFilteredData(): Promise { + this.busy.next(true); + return new Promise((resolve) => { + this.http + .get("https://www.googleapis.com/books/v1/volumes/5MQFrgEACAAJ") + .pipe(finalize(() => this.busy.next(false))) + .subscribe({ + next: (data: any) => { + resolve({ + result: { + value: data.volumeInfo.ratingsCount, + }, + }); + }, + error: (error: HttpErrorResponse) => { + resolve({ + result: null, + error: { + type: error.status, + }, + }); + }, + }); + }); + } + + public ngOnDestroy(): void { + this.outputsSubject.complete(); + } +} + +/** + * A component that instantiates the dashboard + */ +@Component({ + selector: "custom-configurator-section-example", + templateUrl: "./custom-configurator-section.example.component.html", + styleUrls: ["./custom-configurator-section.example.component.less"], + standalone: false, +}) +export class CustomConfiguratorSectionExampleComponent implements OnInit { + // This variable will hold all the data needed to define the layout and behavior of the widgets. + // Pass this to the dashboard component's dashboard input in the template. + public dashboard: IDashboard | undefined; + + // Angular gridster requires a configuration object even if it's empty. + // Pass this to the dashboard component's gridsterConfig input in the template. + public gridsterConfig: GridsterConfig = {}; + + // Boolean which dashboard takes in as an input if its true it allows you to move widgets around. + public editMode: boolean = false; + + constructor( + // WidgetTypesService provides the widget's necessary structure information + private widgetTypesService: WidgetTypesService, + + // In general, the ProviderRegistryService is used for making entities available for injection into dynamically loaded components. + private providerRegistry: ProviderRegistryService, + + // Inject the ComponentRegistryService to make our custom component available for late loading by the dashboards framework + private componentRegistry: ComponentRegistryService, + + private changeDetectorRef: ChangeDetectorRef + ) {} + + public ngOnInit(): void { + // Grab the widget's default template which will be needed as a parameter for setNode. + const widgetTemplate = this.widgetTypesService.getWidgetType("kpi", 1); + + // Replace the default KPI description configuration component with our custom one. + // Note: This could also be done in the parent module's constructor to give + // multiple dashboards access to the same custom configurator section. + this.widgetTypesService.setNode( + widgetTemplate, + "configurator", + WellKnownPathKey.TileDescriptionConfigComponentType, + CustomKpiDescriptionConfigurationComponent.lateLoadKey + ); + + // Register the custom configurator section with the component registry to make it available + // for late loading by the dashboards framework. + this.componentRegistry.registerByLateLoadKey( + CustomKpiDescriptionConfigurationComponent + ); + + // Register our data sources as dropdown options in the widget editor/configurator + // Note: This could also be done in the parent module's constructor so that + // multiple dashboards could have access to the same widget template modification. + this.widgetTypesService.setNode( + // This is the template we grabbed above with getWidgetType + widgetTemplate, + // We are setting the editor/configurator part of the widget template + "configurator", + // This indicates which node you are changing and we want to change + // the data source providers available for selection in the editor. + WellKnownPathKey.DataSourceProviders, + // We are setting the data sources available for selection in the editor + [ + AverageRatingKpiDataSource.providerId, + RatingsCountKpiDataSource.providerId, + ] + ); + + // Register the data sources available for injection into the KPI tiles. + // Note: Each tile of a KPI widget is assigned its own instance of a data source + this.providerRegistry.setProviders({ + [AverageRatingKpiDataSource.providerId]: { + provide: DATA_SOURCE, + useClass: AverageRatingKpiDataSource, + // Any dependencies that need to be injected into the provider must be listed here + deps: [HttpClient], + }, + [RatingsCountKpiDataSource.providerId]: { + provide: DATA_SOURCE, + useClass: RatingsCountKpiDataSource, + deps: [HttpClient], + }, + }); + + this.initializeDashboard(); + } + + public initializeDashboard(): void { + // We're using a static configuration object for this example (see widgetConfig at the bottom of the file), + // but this is where the widget's configuration could potentially be populated from a database + const kpiWidget = widgetConfig; + const widgetIndex: IWidgets = { + // Complete the KPI widget with information coming from its type definition + [kpiWidget.id]: + this.widgetTypesService.mergeWithWidgetType(kpiWidget), + }; + + // Setting the widget dimensions and position (this is for gridster) + const positions: Record = { + [kpiWidget.id]: { + cols: 4, + rows: 6, + y: 0, + x: 0, + }, + }; + + // Finally, assigning the variables we created above to the dashboard + this.dashboard = { + positions, + widgets: widgetIndex, + }; + } + + /** Used for restoring widgets state */ + public reInitializeDashboard(): void { + // destroys the components and their providers so the dashboard can re init data + this.dashboard = undefined; + this.changeDetectorRef.detectChanges(); + + this.initializeDashboard(); + } +} + +const widgetConfig: IWidget = { + id: "widget1", + type: "kpi", + pizzagna: { + [PizzagnaLayer.Configuration]: { + [DEFAULT_PIZZAGNA_ROOT]: { + providers: { + [WellKnownProviders.Refresher]: { + properties: { + // Configuring the refresher interval so that our data source is invoked every ten minutes + interval: 60 * 10, + enabled: true, + } as IRefresherProperties, + } as Partial, + }, + }, + header: { + properties: { + title: "Harry Potter and the Sorcerer's Stone", + subtitle: "By J. K. Rowling", + }, + }, + tiles: { + properties: { + nodes: ["kpi1"], + }, + }, + kpi1: { + id: "kpi1", + componentType: KpiComponent.lateLoadKey, + properties: { + widgetData: { + units: "out of 5 Stars", + label: "Average Rating", + }, + }, + providers: { + [WellKnownProviders.DataSource]: { + // Setting the data source providerId for the tile with id "kpi1" + providerId: AverageRatingKpiDataSource.providerId, + } as IProviderConfiguration, + [WellKnownProviders.Adapter]: { + providerId: NOVA_KPI_DATASOURCE_ADAPTER, + properties: { + componentId: "kpi1", + propertyPath: "widgetData", + }, + } as IProviderConfiguration, + }, + }, + }, + }, +}; +\`, + "tutorials/customization/configurator-section/custom-configurator-section-docs.component.ts": \`// © 2022 SolarWinds Worldwide, LLC. All rights reserved. +// +// Permission is hereby granted, free of charge, to any person obtaining a copy +// of this software and associated documentation files (the "Software"), to +// deal in the Software without restriction, including without limitation the +// rights to use, copy, modify, merge, publish, distribute, sublicense, and/or +// sell copies of the Software, and to permit persons to whom the Software is +// furnished to do so, subject to the following conditions: +// +// The above copyright notice and this permission notice shall be included in +// all copies or substantial portions of the Software. +// +// THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR +// IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, +// FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE +// AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER +// LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, +// OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN +// THE SOFTWARE. + +import { Component } from "@angular/core"; + +@Component({ + selector: "custom-configurator-section-docs", + templateUrl: "./custom-configurator-section-docs.component.html", + standalone: false, +}) +export class CustomConfiguratorSectionDocsComponent {} +\`, + "tutorials/customization/configurator-section/custom-configurator-section.module.ts": \`// © 2022 SolarWinds Worldwide, LLC. All rights reserved. +// +// Permission is hereby granted, free of charge, to any person obtaining a copy +// of this software and associated documentation files (the "Software"), to +// deal in the Software without restriction, including without limitation the +// rights to use, copy, modify, merge, publish, distribute, sublicense, and/or +// sell copies of the Software, and to permit persons to whom the Software is +// furnished to do so, subject to the following conditions: +// +// The above copyright notice and this permission notice shall be included in +// all copies or substantial portions of the Software. +// +// THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR +// IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, +// FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE +// AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER +// LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, +// OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN +// THE SOFTWARE. + +import { CommonModule } from "@angular/common"; +import { HttpClientModule } from "@angular/common/http"; +import { NgModule } from "@angular/core"; +import { ReactiveFormsModule } from "@angular/forms"; +import { RouterModule } from "@angular/router"; + +import { + NuiButtonModule, + NuiDocsModule, + NuiFormFieldModule, + NuiIconModule, + NuiMessageModule, + NuiSwitchModule, + NuiTextboxModule, + DEMO_PATH_TOKEN, +} from "@nova-ui/bits"; +import { + NuiDashboardConfiguratorModule, + NuiDashboardsModule, +} from "@nova-ui/dashboards"; + +import { + CustomConfiguratorSectionExampleComponent, + CustomKpiDescriptionConfigurationComponent, +} from "./custom-configurator-section/custom-configurator-section.example.component"; +import { CustomConfiguratorSectionDocsComponent } from "./custom-configurator-section-docs.component"; +import { getDemoFiles } from "../../../../../demo-files-factory"; + +const routes = [ + { + path: "", + component: CustomConfiguratorSectionDocsComponent, + data: { + srlc: { + hideIndicator: true, + }, + showThemeSwitcher: true, + }, + }, + { + path: "example", + component: CustomConfiguratorSectionExampleComponent, + data: { + srlc: { + hideIndicator: true, + }, + }, + }, +]; + +@NgModule({ + imports: [ + CommonModule, + ReactiveFormsModule, + HttpClientModule, + NuiDashboardsModule, + NuiDashboardConfiguratorModule, + NuiDocsModule, + NuiFormFieldModule, + NuiIconModule, + NuiMessageModule, + NuiSwitchModule, + NuiTextboxModule, + NuiButtonModule, + RouterModule.forChild(routes), + ], + declarations: [ + CustomConfiguratorSectionDocsComponent, + CustomKpiDescriptionConfigurationComponent, + CustomConfiguratorSectionExampleComponent, + ], + providers: [ + { + provide: DEMO_PATH_TOKEN, + useValue: getDemoFiles("configurator-section"), + }, + ], +}) +export default class CustomConfiguratorSectionModule {} +\`, + "tutorials/customization/customization.module.ts": \`// © 2022 SolarWinds Worldwide, LLC. All rights reserved. +// +// Permission is hereby granted, free of charge, to any person obtaining a copy +// of this software and associated documentation files (the "Software"), to +// deal in the Software without restriction, including without limitation the +// rights to use, copy, modify, merge, publish, distribute, sublicense, and/or +// sell copies of the Software, and to permit persons to whom the Software is +// furnished to do so, subject to the following conditions: +// +// The above copyright notice and this permission notice shall be included in +// all copies or substantial portions of the Software. +// +// THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR +// IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, +// FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE +// AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER +// LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, +// OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN +// THE SOFTWARE. + +import { NgModule, Type } from "@angular/core"; +import { RouterModule, Routes } from "@angular/router"; + +import { ConfiguratorHeadingService } from "@nova-ui/dashboards"; + +enum CustomizationModuleRoute { + ConfiguratorSection = "configurator-section", + Widget = "widget", + Formatter = "formatter", + DataSourceConfigurator = "data-source-configurator", +} + +const routes: Routes = [ + { + path: CustomizationModuleRoute.ConfiguratorSection, + loadChildren: async () => + import( + "./configurator-section/custom-configurator-section.module" + ) as object as Promise>, + }, + { + path: CustomizationModuleRoute.Widget, + loadChildren: async () => + import("./widget/custom-widget.module") as object as Promise< + Type + >, + }, + { + path: CustomizationModuleRoute.Formatter, + loadChildren: async () => + import("./formatter/custom-formatter.module") as object as Promise< + Type + >, + }, + { + path: CustomizationModuleRoute.DataSourceConfigurator, + loadChildren: async () => + import( + "./data-source-configurator/custom-data-source-configurator.module" + ) as object as Promise>, + }, +]; + +@NgModule({ + imports: [RouterModule.forChild(routes)], + providers: [ConfiguratorHeadingService], +}) +export default class CustomizationModule {} +\`, + "tutorials/customization/data-source-configurator/custom-data-source-configurator-docs.component.ts": \`// © 2022 SolarWinds Worldwide, LLC. All rights reserved. +// +// Permission is hereby granted, free of charge, to any person obtaining a copy +// of this software and associated documentation files (the "Software"), to +// deal in the Software without restriction, including without limitation the +// rights to use, copy, modify, merge, publish, distribute, sublicense, and/or +// sell copies of the Software, and to permit persons to whom the Software is +// furnished to do so, subject to the following conditions: +// +// The above copyright notice and this permission notice shall be included in +// all copies or substantial portions of the Software. +// +// THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR +// IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, +// FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE +// AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER +// LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, +// OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN +// THE SOFTWARE. + +import { Component } from "@angular/core"; + +@Component({ + selector: "nui-custom-data-source-configurator-docs", + templateUrl: "./custom-data-source-configurator-docs.component.html", + standalone: false, +}) +export class CustomDataSourceConfiguratorDocComponent {} +\`, + "tutorials/customization/data-source-configurator/custom-data-source-configurator.module.ts": \`// © 2022 SolarWinds Worldwide, LLC. All rights reserved. +// +// Permission is hereby granted, free of charge, to any person obtaining a copy +// of this software and associated documentation files (the "Software"), to +// deal in the Software without restriction, including without limitation the +// rights to use, copy, modify, merge, publish, distribute, sublicense, and/or +// sell copies of the Software, and to permit persons to whom the Software is +// furnished to do so, subject to the following conditions: +// +// The above copyright notice and this permission notice shall be included in +// all copies or substantial portions of the Software. +// +// THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR +// IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, +// FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE +// AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER +// LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, +// OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN +// THE SOFTWARE. + +import { NgModule } from "@angular/core"; +import { ReactiveFormsModule } from "@angular/forms"; +import { RouterModule, Routes } from "@angular/router"; + +// eslint-disable-next-line max-len +import { + NuiButtonModule, + NuiDocsModule, + NuiFormFieldModule, + NuiIconModule, + NuiMessageModule, + NuiSelectV2Module, + NuiSwitchModule, + NuiTextboxModule, + NuiValidationMessageModule, + DEMO_PATH_TOKEN, +} from "@nova-ui/bits"; +import { + NuiDashboardConfiguratorModule, + NuiDashboardsModule, +} from "@nova-ui/dashboards"; + +import { CustomDataSourceConfiguratorDocComponent } from "./custom-data-source-configurator-docs.component"; +import { + CustomDataSourceConfiguratorExampleComponent, + HarryPotterDataSourceConfiguratorComponent, +} from "./example/custom-data-source-configurator-example.component"; +import { getDemoFiles } from "../../../../../demo-files-factory"; + +const routes: Routes = [ + { + path: "", + component: CustomDataSourceConfiguratorDocComponent, + data: { + srlc: { + hideIndicator: true, + }, + showThemeSwitcher: true, + }, + }, +]; + +@NgModule({ + imports: [ + RouterModule.forChild(routes), + NuiDocsModule, + NuiButtonModule, + NuiMessageModule, + NuiDashboardConfiguratorModule, + NuiDashboardsModule, + NuiFormFieldModule, + NuiTextboxModule, + NuiSwitchModule, + NuiSelectV2Module, + NuiValidationMessageModule, + NuiIconModule, + ReactiveFormsModule, + ], + declarations: [ + CustomDataSourceConfiguratorDocComponent, + CustomDataSourceConfiguratorExampleComponent, + HarryPotterDataSourceConfiguratorComponent, + ], + providers: [ + { + provide: DEMO_PATH_TOKEN, + useValue: getDemoFiles("data-source-configurator"), + }, + ], +}) +export default class CustomDataSourceConfiguratorModuleRoute {} +\`, + "tutorials/customization/data-source-configurator/example/custom-data-source-configurator-example.component.ts": \`// © 2022 SolarWinds Worldwide, LLC. All rights reserved. +// +// Permission is hereby granted, free of charge, to any person obtaining a copy +// of this software and associated documentation files (the "Software"), to +// deal in the Software without restriction, including without limitation the +// rights to use, copy, modify, merge, publish, distribute, sublicense, and/or +// sell copies of the Software, and to permit persons to whom the Software is +// furnished to do so, subject to the following conditions: +// +// The above copyright notice and this permission notice shall be included in +// all copies or substantial portions of the Software. +// +// THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR +// IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, +// FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE +// AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER +// LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, +// OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN +// THE SOFTWARE. + +import { HttpClient, HttpErrorResponse } from "@angular/common/http"; +import { + ChangeDetectorRef, + Component, + Inject, + Injectable, + Injector, + OnDestroy, + OnInit, +} from "@angular/core"; +import { FormBuilder, Validators } from "@angular/forms"; +import { GridsterConfig, GridsterItem } from "angular-gridster2"; +import { BehaviorSubject } from "rxjs"; +import { finalize } from "rxjs/operators"; + +import { + DataSourceService, + EventBus, + IEvent, + IFilteringOutputs, + LoggerService, +} from "@nova-ui/bits"; +import { + ComponentRegistryService, + ConfiguratorHeadingService, + DataSourceConfigurationV2Component, + DATA_SOURCE, + DEFAULT_PIZZAGNA_ROOT, + IConfigurable, + IDashboard, + IKpiData, + IProperties, + IProviderConfiguration, + IRefresherProperties, + IWidget, + IWidgets, + KpiComponent, + NOVA_KPI_DATASOURCE_ADAPTER, + PizzagnaLayer, + PIZZAGNA_EVENT_BUS, + ProviderRegistryService, + WellKnownPathKey, + WellKnownProviders, + WidgetTypesService, +} from "@nova-ui/dashboards"; + +/** + * This component will serve as the data source accordion in the configurator. + */ +@Component({ + selector: "harry-potter-data-source-configurator", + styleUrls: ["./custom-data-source-configurator-example.component.less"], + template: \\\` + +
+ +
+ Data Source +
+ Harry Potter Books +
+
+
+
+ + + + {{ book.title }} + + + +
+
+ + + + {{ metric.label }} + + + +
+
+ \\\`, + standalone: false, +}) +@Injectable() +export class HarryPotterDataSourceConfiguratorComponent + extends DataSourceConfigurationV2Component + implements OnInit +{ + // This lateLoadKey allows the component to be able to be registered by the componentRegistry + public static lateLoadKey = "HarryPotterDataSourceConfiguratorComponent"; + + // Array of books that will populate the book select + public books = [ + { + id: "5MQFrgEACAAJ", + title: $localize\\\`Harry Potter and the Sorcerer's Stone\\\`, + }, + { + id: "5iTebBW-w7QC", + title: $localize\\\`Harry Potter and the Chamber of Secrets\\\`, + }, + ]; + + // Array of metrics that will populate the metric select + public metrics = [ + { + id: "averageRating", + label: $localize\\\`Average Rating\\\`, + }, + { + id: "ratingsCount", + label: $localize\\\`Ratings Count\\\`, + }, + ]; + + // These need to be injected because DataSourceConfigurationV2Component uses them + constructor( + changeDetector: ChangeDetectorRef, + configuratorHeading: ConfiguratorHeadingService, + formBuilder: FormBuilder, + providerRegistryService: ProviderRegistryService, + @Inject(PIZZAGNA_EVENT_BUS) eventBus: EventBus, + injector: Injector, + logger: LoggerService + ) { + super( + changeDetector, + configuratorHeading, + formBuilder, + providerRegistryService, + eventBus, + injector, + logger + ); + } + + // Overriding 'ngOnInit' to add custom controls to the 'properties' form group + public ngOnInit(): void { + super.ngOnInit(); + + // Overriding the 'properties' control on the form to create a form group that accommodates our custom properties + this.form.setControl( + "properties", + this.formBuilder.group({ + bookId: [this.properties?.bookId ?? "", Validators.required], + metric: [this.properties?.metric ?? "", Validators.required], + }) + ); + // The default data source control has a required validator we're removing that validator here since we aren't using it. + this.form.setControl("dataSource", this.formBuilder.control(null)); + // Here we set the providerId to our only data source so when a new tile gets created it will default to it. + this.form.get("providerId")?.setValue(AcmeKpiDataSource.providerId); + // Here we subscribe to the form and if there are any changes we invoke the data source + this.form.valueChanges.subscribe((value) => { + if (!value.providerId) { + return; + } + this.invokeDataSource(value); + }); + } +} + +/** + * A simple KPI data source to retrieve the average rating of Harry Potter and the Sorcerer's Stone (book) via googleapis + */ +@Injectable() +export class AcmeKpiDataSource + extends DataSourceService + implements OnDestroy, IConfigurable +{ + // This is the ID we'll use to identify the provider + public static providerId = "AcmeKpiDataSource"; + + // Use this subject to communicate the data source's busy state + public busy = new BehaviorSubject(false); + + public properties: IProperties; + + constructor(private http: HttpClient) { + super(); + } + + // This function MUST be implemented in order to receive property updates from our configurator + public updateConfiguration(properties: IProperties): void { + // Saving the properties because we will need it for this data source. + this.properties = properties; + } + + // In this example, getFilteredData is invoked every 10 minutes (Take a look at the refresher + // provider definition in the widget configuration below to see how the interval is set) + public async getFilteredData(): Promise { + // For loading indicator to show + this.busy.next(true); + return new Promise((resolve) => { + // *** Make a resource request to an external API (if needed) + this.http + .get( + \\\`https://www.googleapis.com/books/v1/volumes/\\\${this.properties?.bookId}\\\` + ) + // For loading indicator to be hidden + .pipe(finalize(() => this.busy.next(false))) + .subscribe({ + next: (data: any) => { + resolve({ + result: { + value: data.volumeInfo[this.properties?.metric], + }, + }); + }, + error: (error: HttpErrorResponse) => { + resolve({ + result: null, + error: { + type: error.status, + }, + }); + }, + }); + }); + } + + public ngOnDestroy(): void { + this.outputsSubject.complete(); + } +} + +/** + * A component that instantiates the dashboard + */ +@Component({ + selector: "custom-data-source-configurator-example", + templateUrl: "./custom-data-source-configurator-example.component.html", + styleUrls: ["./custom-data-source-configurator-example.component.less"], + standalone: false, +}) +export class CustomDataSourceConfiguratorExampleComponent implements OnInit { + // This variable will hold all the data needed to define the layout and behavior of the widgets. + // Pass this to the dashboard component's dashboard input in the template. + public dashboard: IDashboard; + + // Angular gridster requires a configuration object even if it's empty. + // Pass this to the dashboard component's gridsterConfig input in the template. + public gridsterConfig: GridsterConfig = {}; + + // Boolean which dashboard takes in as an input if its true it allows you to move widgets around. + public editMode: boolean = false; + + constructor( + // WidgetTypesService provides the widget's necessary structure information + private widgetTypesService: WidgetTypesService, + + // In general, the ProviderRegistryService is used for making entities available for injection into dynamically loaded components. + private providerRegistry: ProviderRegistryService, + + // Inject the ComponentRegistryService to make our custom component available for late loading by the dashboards framework + private componentRegistry: ComponentRegistryService + ) {} + + public ngOnInit(): void { + // Registering the new data source configurator so it can be used. + this.componentRegistry.registerByLateLoadKey( + HarryPotterDataSourceConfiguratorComponent + ); + // Registering the data source for injection into the KPI tile. + // Note: Each tile of a KPI widget is assigned its own instance of the data source + this.providerRegistry.setProviders({ + [AcmeKpiDataSource.providerId]: { + provide: DATA_SOURCE, + useClass: AcmeKpiDataSource, + // Any dependencies that need to be injected into the provider must be listed here + deps: [HttpClient], + }, + }); + + const kpiWidgetTemplate = this.widgetTypesService.getWidgetType( + "kpi", + 1 + ); + + this.widgetTypesService.setNode( + // This is the template we grabbed above with getWidgetType + kpiWidgetTemplate, + // We are setting the editor/configurator part of the widget template + "configurator", + // This is the path to go to the data source config component type. + WellKnownPathKey.DataSourceConfigComponentType, + // We are changing it to use the component we just created above instead of the default. + HarryPotterDataSourceConfiguratorComponent.lateLoadKey + ); + + // We're using a static configuration object for this example, but this is where + // the widget's configuration could potentially be populated from a database + const kpiWidget = widgetConfig; + const widgetIndex: IWidgets = { + // Complete the KPI widget with information coming from its type definition + [kpiWidget.id]: + this.widgetTypesService.mergeWithWidgetType(kpiWidget), + }; + + // Setting the widget dimensions and position (this is for gridster) + const positions: Record = { + [kpiWidget.id]: { + cols: 4, + rows: 6, + y: 0, + x: 0, + }, + }; + + // Finally, assigning the variables we created above to the dashboard + this.dashboard = { + positions, + widgets: widgetIndex, + }; + } +} + +const widgetConfig: IWidget = { + id: "widget1", + type: "kpi", + pizzagna: { + [PizzagnaLayer.Configuration]: { + [DEFAULT_PIZZAGNA_ROOT]: { + providers: { + [WellKnownProviders.Refresher]: { + properties: { + // Configuring the refresher interval so that our data source is invoked every ten minutes + interval: 60 * 10, + enabled: true, + } as IRefresherProperties, + } as Partial, + }, + }, + header: { + properties: { + title: "Harry Potter and the Sorcerer's Stone", + subtitle: "By J. K. Rowling", + }, + }, + tiles: { + properties: { + nodes: ["kpi1"], + }, + }, + kpi1: { + id: "kpi1", + componentType: KpiComponent.lateLoadKey, + properties: { + widgetData: { + units: "out of 5 Stars", + label: "Average Rating", + }, + }, + providers: { + [WellKnownProviders.DataSource]: { + // Setting the data source providerId for the tile with id "kpi1" + providerId: AcmeKpiDataSource.providerId, + properties: { + bookId: "5MQFrgEACAAJ", + metric: "averageRating", + }, + } as IProviderConfiguration, + [WellKnownProviders.Adapter]: { + providerId: NOVA_KPI_DATASOURCE_ADAPTER, + properties: { + componentId: "kpi1", + propertyPath: "widgetData", + }, + } as IProviderConfiguration, + }, + }, + }, + }, +}; +\`, + "tutorials/customization/formatter/custom-formatter.module.ts": \`// © 2022 SolarWinds Worldwide, LLC. All rights reserved. +// +// Permission is hereby granted, free of charge, to any person obtaining a copy +// of this software and associated documentation files (the "Software"), to +// deal in the Software without restriction, including without limitation the +// rights to use, copy, modify, merge, publish, distribute, sublicense, and/or +// sell copies of the Software, and to permit persons to whom the Software is +// furnished to do so, subject to the following conditions: +// +// The above copyright notice and this permission notice shall be included in +// all copies or substantial portions of the Software. +// +// THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR +// IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, +// FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE +// AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER +// LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, +// OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN +// THE SOFTWARE. + +import { NgModule } from "@angular/core"; +import { ReactiveFormsModule } from "@angular/forms"; +import { RouterModule, Routes } from "@angular/router"; + +// eslint-disable-next-line max-len +import { + NuiButtonModule, + NuiDocsModule, + NuiFormFieldModule, + NuiIconModule, + NuiMessageModule, + NuiSelectV2Module, + NuiSwitchModule, + NuiTextboxModule, + NuiValidationMessageModule, + DEMO_PATH_TOKEN, +} from "@nova-ui/bits"; +import { NuiDashboardsModule } from "@nova-ui/dashboards"; + +import { CustomDonutContentFormatterDocComponent } from "./donut-content-formatter-example/custom-donut-content-formatter-docs.component"; +import { + CustomDonutContentFormatterComponent, + CustomDonutContentFormatterConfiguratorComponent, + CustomDonutContentFormatterExampleComponent, +} from "./donut-content-formatter-example/custom-donut-content-formatter-example.component"; +import { CustomFormatterDocComponent } from "./formatter-example/custom-formatter-docs.component"; +import { + CustomFormatterComponent, + CustomFormatterConfiguratorComponent, + CustomFormatterExampleComponent, +} from "./formatter-example/custom-formatter-example.component"; +import { getDemoFiles } from "../../../../../demo-files-factory"; + +const routes: Routes = [ + { + path: "table-formatter", + component: CustomFormatterDocComponent, + data: { + srlc: { + hideIndicator: true, + }, + showThemeSwitcher: true, + }, + }, + { + path: "donut-content-formatter", + component: CustomDonutContentFormatterDocComponent, + data: { + srlc: { + hideIndicator: true, + }, + showThemeSwitcher: true, + }, + }, +]; + +@NgModule({ + imports: [ + RouterModule.forChild(routes), + NuiDocsModule, + NuiButtonModule, + NuiMessageModule, + NuiDashboardsModule, + NuiFormFieldModule, + NuiTextboxModule, + NuiSwitchModule, + NuiSelectV2Module, + NuiValidationMessageModule, + NuiIconModule, + ReactiveFormsModule, + ], + declarations: [ + CustomDonutContentFormatterComponent, + CustomDonutContentFormatterExampleComponent, + CustomDonutContentFormatterConfiguratorComponent, + CustomDonutContentFormatterDocComponent, + CustomFormatterDocComponent, + CustomFormatterExampleComponent, + CustomFormatterConfiguratorComponent, + CustomFormatterComponent, + ], + providers: [ + { + provide: DEMO_PATH_TOKEN, + useValue: getDemoFiles("formatter"), + }, + ], +}) +export default class CustomFormatterModuleRoute {} +\`, + "tutorials/customization/formatter/donut-content-formatter-example/custom-donut-content-formatter-docs.component.ts": \`// © 2022 SolarWinds Worldwide, LLC. All rights reserved. +// +// Permission is hereby granted, free of charge, to any person obtaining a copy +// of this software and associated documentation files (the "Software"), to +// deal in the Software without restriction, including without limitation the +// rights to use, copy, modify, merge, publish, distribute, sublicense, and/or +// sell copies of the Software, and to permit persons to whom the Software is +// furnished to do so, subject to the following conditions: +// +// The above copyright notice and this permission notice shall be included in +// all copies or substantial portions of the Software. +// +// THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR +// IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, +// FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE +// AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER +// LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, +// OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN +// THE SOFTWARE. + +import { Component } from "@angular/core"; + +@Component({ + selector: "nui-custom-donut-content-formatter-docs", + templateUrl: "./custom-donut-content-formatter-docs.component.html", + standalone: false, +}) +export class CustomDonutContentFormatterDocComponent {} +\`, + "tutorials/customization/formatter/donut-content-formatter-example/custom-donut-content-formatter-example.component.ts": \`// © 2022 SolarWinds Worldwide, LLC. All rights reserved. +// +// Permission is hereby granted, free of charge, to any person obtaining a copy +// of this software and associated documentation files (the "Software"), to +// deal in the Software without restriction, including without limitation the +// rights to use, copy, modify, merge, publish, distribute, sublicense, and/or +// sell copies of the Software, and to permit persons to whom the Software is +// furnished to do so, subject to the following conditions: +// +// The above copyright notice and this permission notice shall be included in +// all copies or substantial portions of the Software. +// +// THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR +// IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, +// FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE +// AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER +// LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, +// OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN +// THE SOFTWARE. + +import { HttpClient } from "@angular/common/http"; +import { + ChangeDetectorRef, + Component, + Injectable, + Input, + OnChanges, + OnDestroy, + OnInit, + SimpleChanges, +} from "@angular/core"; +import { FormBuilder, FormGroup, Validators } from "@angular/forms"; +import { GridsterConfig, GridsterItem } from "angular-gridster2"; +import { Subject } from "rxjs"; +import { takeUntil, tap } from "rxjs/operators"; + +import { + DataSourceService, + IconService, + IDataSource, + IFilteringOutputs, + LoggerService, +} from "@nova-ui/bits"; +import { + ChartAssist, + IAccessors, + IChartAssistEvent, + IChartAssistSeries, +} from "@nova-ui/charts"; +import { + ComponentRegistryService, + ConfiguratorHeadingService, + DATA_SOURCE, + DonutChartFormatterConfiguratorComponent, + DonutContentPercentageConfigurationComponent, + DonutContentPercentageFormatterComponent, + DonutContentSumFormatterComponent, + IDashboard, + IFormatterDefinition, + IHasChangeDetector, + IProperties, + IProportionalWidgetChartOptions, + IProportionalWidgetConfig, + IProviderConfiguration, + IWidget, + IWidgets, + LegendPlacement, + PizzagnaLayer, + ProportionalWidgetChartTypes, + ProviderRegistryService, + WellKnownPathKey, + WellKnownProviders, + WidgetTypesService, +} from "@nova-ui/dashboards"; + +export enum Units { + Days = "Day(s)", + Weeks = "Week(s)", + Hours = "Hour(s)", +} + +@Component({ + selector: "custom-donut-content-formatter", + host: { class: "d-flex flex-column align-items-center" }, + template: \\\` +
+ + {{ chartMetric || properties?.currentMetric || data[0].id }} +
+
+ {{ chartContent }} +
+
+ {{ units }} +
+
\\\`, + styleUrls: ["./custom-donut-content-formatter-example.component.less"], + standalone: false, +}) +export class CustomDonutContentFormatterComponent + implements IHasChangeDetector, OnInit, OnChanges +{ + public static lateLoadKey = "CustomDonutContentFormatterComponent"; + + // Used to emphasize the chart series when user interacts either with the chart legend, or chart segments. + public emphasizedSeriesData: IChartAssistSeries | undefined; + + // Current raw value of the metric to display + public currentMetricData: number; + + // Metric value rendered inside the template, when user selects a metric, and gets automatically recalculated depending on selected units + public chartContent: number; + + // Metric value rendered inside the template, when user interacts with either chart legend, or chart segments + public chartMetric: number; + + // Units which user can select from the configuration + public units: Units = Units.Days; + + private readonly destroy$ = new Subject(); + + constructor(public changeDetector: ChangeDetectorRef) {} + + // The data we receive from the chart, including metrics names and their values + @Input() data: IChartAssistSeries[]; + + // We use this chart assist instance to subscribe to the events triggered when an interaction with the chart occurs + @Input() chartAssist: ChartAssist; + + // These are the current properties from pizzagna. Used to use data set at the configuration layer + @Input() properties: IProperties; + + public ngOnChanges(changes: SimpleChanges): void { + if (changes.properties || !this.properties) { + // If current metric is not in the list of metrics any more we fall back to the very first one from the list we get from the datasource + this.currentMetricData = + this.data.find( + (item) => item.id === this.properties?.currentMetric + )?.data[0] || this.data[0].data[0]; + + // We either take the selected value, or fall back to the preselected default one + this.units = this.properties?.units || this.units; + } + + this.setContentValue(); + } + + public ngOnInit(): void { + // Here 'chartAssistSubject' is the entity that emits events every time user interacts with either chart legend, or chart segments. + // Subscribing to properly react on these kind of events + this.chartAssist.chartAssistSubject + .pipe( + tap( + (data: IChartAssistEvent) => + (this.emphasizedSeriesData = this.data.find( + (item) => item.id === data.payload.seriesId + )) + ), + tap(() => this.setContentValue()), + tap(() => this.setMetricValue()), + takeUntil(this.destroy$) + ) + .subscribe(); + } + + public getConvertedData(emphData: number): number { + // Recalculating data depending on the units user selected from the configuration view + switch (this.units) { + case Units.Weeks: + return this.emphasizedSeriesData + ? this.convertToWeeks(emphData) + : this.convertToWeeks(this.currentMetricData); + + case Units.Hours: + return this.emphasizedSeriesData + ? this.convertToHours(emphData) + : this.convertToHours(this.currentMetricData); + + default: + return this.emphasizedSeriesData + ? emphData + : this.currentMetricData; + } + } + + public setContentValue(): void { + this.chartContent = this.getConvertedData( + this.emphasizedSeriesData?.data[0] + ); + } + + public setMetricValue(): void { + this.chartMetric = this.emphasizedSeriesData + ? this.data.find( + (item) => + this.getConvertedData(item.data[0]) === + this.getConvertedData(this.emphasizedSeriesData?.data[0]) + )?.id + : // if metric was not initially selected we fall back to the very first one + this.properties?.currentMetric || this.data[0].id; + } + + private convertToWeeks(days: number | undefined): number { + return days ? Number((days / 7).toFixed(2)) : 0; + } + + private convertToHours(days: number | undefined): number { + return days ? Number((days * 24).toFixed(2)) : 0; + } +} + +@Component({ + selector: "custom-donut-content-formatter-configurator", + styleUrls: ["./custom-donut-content-formatter-example.component.less"], + template: \\\` +
+
+ + + + {{ itemValue?.name }} + + + + This field is required + + +
+
+ + + + {{ itemValue }} + + + + This field is required + + +
+
+ \\\`, + standalone: false, +}) +export class CustomDonutContentFormatterConfiguratorComponent + extends DonutChartFormatterConfiguratorComponent + implements OnChanges, OnInit, IHasChangeDetector +{ + public static lateLoadKey = "CustomFormatterConfiguratorComponent"; + + constructor( + changeDetector: ChangeDetectorRef, + formBuilder: FormBuilder, + logger: LoggerService, + public iconService: IconService, + public configuratorHeading: ConfiguratorHeadingService + ) { + super(changeDetector, formBuilder, logger); + } + + public availableUnits: Units[] = [Units.Days, Units.Hours, Units.Weeks]; + + protected addCustomFormControls(form: FormGroup): void { + form.addControl( + "units", + this.formBuilder.control(Units.Days, Validators.required) + ); + } +} + +/** + * A component that instantiates the dashboard + */ +@Component({ + selector: "custom-donut-content-formatter-example", + templateUrl: "./custom-donut-content-formatter-example.component.html", + styleUrls: ["./custom-donut-content-formatter-example.component.less"], + standalone: false, +}) +export class CustomDonutContentFormatterExampleComponent implements OnInit { + public editMode: boolean = false; + // This variable will hold all the data needed to define the layout and behavior of the widgets. + // Pass this to the dashboard component's dashboard input in the template. + public dashboard: IDashboard | undefined; + + // Angular gridster requires a configuration object even if it's empty. + // Pass this to the dashboard component's gridsterConfig input in the template. + public gridsterConfig: GridsterConfig = {}; + + constructor( + // WidgetTypesService provides the widget's necessary structure information + private widgetTypesService: WidgetTypesService, + // In general, the ProviderRegistryService is used for making entities available for injection into dynamically loaded components. + private providerRegistry: ProviderRegistryService, + // Inject the ComponentRegistryService to make our custom component available for late loading by the dashboards framework + private componentRegistry: ComponentRegistryService, + private changeDetectorRef: ChangeDetectorRef + ) { + // Register the custom configurator component with the component registry to make it available + // for late loading by the dashboard framework. + this.componentRegistry.registerByLateLoadKey( + CustomDonutContentFormatterConfiguratorComponent + ); + // Register the custom formatter component with the component registry to make it available + // for late loading by the dashboard framework. + this.componentRegistry.registerByLateLoadKey( + CustomDonutContentFormatterComponent + ); + + // Grab the widget's default template which will be needed as a parameter for setNode below. + const proportional = this.widgetTypesService.getWidgetType( + "proportional", + 1 + ); + + const donutFormatters: IFormatterDefinition[] = [ + { + componentType: DonutContentSumFormatterComponent.lateLoadKey, + label: $localize\\\`Sum\\\`, + } as IFormatterDefinition, + { + componentType: + DonutContentPercentageFormatterComponent.lateLoadKey, + label: $localize\\\`Percentage\\\`, + configurationComponent: + DonutContentPercentageConfigurationComponent.lateLoadKey, + } as IFormatterDefinition, + { + componentType: CustomDonutContentFormatterComponent.lateLoadKey, + label: $localize\\\`Custom\\\`, + // This is a custom configurator that will pop up below the formatter once it gets selected + configurationComponent: + CustomDonutContentFormatterConfiguratorComponent.lateLoadKey, + } as IFormatterDefinition, + ]; + + this.widgetTypesService.setNode( + // This is the template we grabbed above with getWidgetType + proportional, + // We are setting the editor/configurator part of the widget template + "configurator", + // This indicates which node you are changing and we want to change the formatters available for selection in the editor. + WellKnownPathKey.Formatters, + // We are setting the available formatters with the array we created above. + donutFormatters + ); + + // This sets the donut chart's datasource to have the StatusesExampleDatasource so the drop down is filled similar to the line above. + this.widgetTypesService.setNode( + proportional, + "configurator", + WellKnownPathKey.DataSourceProviders, + [StatusesExampleDatasource.providerId] + ); + + // Registering the data source for injection into the widget. + this.providerRegistry.setProviders({ + [StatusesExampleDatasource.providerId]: { + provide: DATA_SOURCE, + useClass: StatusesExampleDatasource, + // Any dependencies that need to be injected into the provider must be listed here + deps: [], + }, + }); + } + + public ngOnInit(): void { + this.initializeDashboard(); + } + + /** Used for restoring widgets state */ + public reInitializeDashboard(): void { + // destroys the components and their providers so the dashboard can re init data + this.dashboard = undefined; + this.changeDetectorRef.detectChanges(); + + this.initializeDashboard(); + } + + public initializeDashboard(): void { + // We're using a static configuration object for this example, but this is where + // the widget's configuration could potentially be populated from a database + const proportionalWidget = widgetConfig; + const widgetIndex: IWidgets = { + // Enhance the widget with information coming from it's type definition + [proportionalWidget.id]: + this.widgetTypesService.mergeWithWidgetType(proportionalWidget), + }; + + // Setting the widget dimensions and position (this is for gridster) + const positions: Record = { + [proportionalWidget.id]: { + cols: 12, + rows: 6, + y: 0, + x: 0, + }, + }; + + // Finally, assigning the variables we created above to the dashboard + this.dashboard = { + positions, + widgets: widgetIndex, + }; + } +} + +export interface IStatusesWidgetData { + id: string; + name: string; + data: number[]; +} + +export const randomStatusesWidgetData: IStatusesWidgetData[] = [ + { + id: "Down", + name: "Down", + data: [Math.round(Math.random() * 100)], + }, + { + id: "Critical", + name: "Critical", + data: [Math.round(Math.random() * 100)], + }, + { + id: "Warning", + name: "Warning", + data: [Math.round(Math.random() * 100)], + }, + { + id: "Unknown", + name: "Unknown", + data: [Math.round(Math.random() * 100)], + }, + { + id: "Up", + name: "Up", + data: [Math.round(Math.random() * 100)], + }, + { + id: "Unmanaged", + name: "Unmanaged", + data: [Math.round(Math.random() * 100)], + }, +]; + +@Injectable() +export class StatusesExampleDatasource + extends DataSourceService + implements IDataSource, OnDestroy +{ + public static providerId = "StatusesExampleDatasource"; + + public busy = new Subject(); + + constructor(private http: HttpClient) { + super(); + } + + public async getFilteredData(): Promise { + this.busy.next(true); + + return new Promise((resolve) => { + setTimeout(() => { + resolve({ + result: randomStatusesWidgetData, + }); + this.busy.next(false); + }, 1000); + }); + } + + public ngOnDestroy(): void { + this.outputsSubject.complete(); + } +} + +export const widgetConfig: IWidget = { + id: "proportionalWidgetId", + type: "proportional", + pizzagna: { + [PizzagnaLayer.Configuration]: { + header: { + properties: { + title: "Proportional Widget!", + subtitle: "Proportional widget with legend formatters", + }, + }, + chart: { + providers: { + [WellKnownProviders.DataSource]: { + providerId: StatusesExampleDatasource.providerId, + } as IProviderConfiguration, + }, + properties: { + configuration: { + interactive: true, + chartOptions: { + type: ProportionalWidgetChartTypes.DonutChart, + legendPlacement: LegendPlacement.Right, + contentFormatter: { + componentType: + CustomDonutContentFormatterComponent.lateLoadKey, + properties: { + // here you can set the default value for the metric you receive. If not set the first one from the list will be taken + currentMetric: "Down", + // here you set the default value for your custom controls. If not set the first one from the list will be taken + units: Units.Weeks, + }, + }, + } as IProportionalWidgetChartOptions, + chartColors: [ + "var(--nui-color-chart-eight)", + "var(--nui-color-chart-nine)", + "var(--nui-color-chart-ten)", + ], + } as IProportionalWidgetConfig, + }, + }, + }, + }, +}; +\`, + "tutorials/customization/formatter/formatter-example/custom-formatter-docs.component.ts": \`// © 2022 SolarWinds Worldwide, LLC. All rights reserved. +// +// Permission is hereby granted, free of charge, to any person obtaining a copy +// of this software and associated documentation files (the "Software"), to +// deal in the Software without restriction, including without limitation the +// rights to use, copy, modify, merge, publish, distribute, sublicense, and/or +// sell copies of the Software, and to permit persons to whom the Software is +// furnished to do so, subject to the following conditions: +// +// The above copyright notice and this permission notice shall be included in +// all copies or substantial portions of the Software. +// +// THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR +// IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, +// FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE +// AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER +// LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, +// OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN +// THE SOFTWARE. + +import { Component } from "@angular/core"; + +@Component({ + selector: "nui-custom-formatter-docs", + templateUrl: "./custom-formatter-docs.component.html", + standalone: false, +}) +export class CustomFormatterDocComponent {} +\`, + "tutorials/customization/formatter/formatter-example/custom-formatter-example.component.ts": \`// © 2022 SolarWinds Worldwide, LLC. All rights reserved. +// +// Permission is hereby granted, free of charge, to any person obtaining a copy +// of this software and associated documentation files (the "Software"), to +// deal in the Software without restriction, including without limitation the +// rights to use, copy, modify, merge, publish, distribute, sublicense, and/or +// sell copies of the Software, and to permit persons to whom the Software is +// furnished to do so, subject to the following conditions: +// +// The above copyright notice and this permission notice shall be included in +// all copies or substantial portions of the Software. +// +// THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR +// IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, +// FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE +// AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER +// LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, +// OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN +// THE SOFTWARE. + +import { ListRange } from "@angular/cdk/collections"; +import { ChangeDetectorRef, Component, Input, OnInit } from "@angular/core"; +import { FormBuilder, FormGroup, Validators } from "@angular/forms"; +import { GridsterConfig, GridsterItem } from "angular-gridster2"; +import isEqual from "lodash/isEqual"; +import orderBy from "lodash/orderBy"; +import { BehaviorSubject } from "rxjs"; + +import { + DataSourceService, + IconService, + IDataField, + INovaFilteringOutputs, + INovaFilters, + ISorterFilter, + LoggerService, +} from "@nova-ui/bits"; +import { + ComponentRegistryService, + ConfiguratorHeadingService, + DATA_SOURCE, + FormatterConfiguratorComponent, + IDashboard, + IDataSourceOutput, + IFormatterDefinition, + IHasChangeDetector, + ITableWidgetColumnConfig, + ITableWidgetSorterConfig, + IWidget, + IWidgets, + PizzagnaLayer, + ProviderRegistryService, + RawFormatterComponent, + TableFormatterRegistryService, + WellKnownPathKey, + WellKnownProviders, + WidgetTypesService, +} from "@nova-ui/dashboards"; + +export const BREW_API_URL = "https://api.punkapi.com/v2/beers"; + +@Component({ + selector: "custom-formatter", + host: { class: "d-flex" }, + template: \\\` +
+
+ +
+
+ {{ data.value }} +
+
+ \\\`, + styleUrls: ["./custom-formatter-example.component.less"], + standalone: false, +}) +export class CustomFormatterComponent implements IHasChangeDetector { + public static lateLoadKey = "CustomFormatterComponent"; + + constructor(public changeDetector: ChangeDetectorRef) {} + + @Input() public data: any; + @Input() public icon: string; + @Input() public threshold: string; + + public isAboveThreshold(): boolean { + return parseFloat(this.threshold) <= this.data.value; + } +} + +@Component({ + selector: "custom-formatter-configurator", + styleUrls: ["./custom-formatter-example.component.less"], + template: \\\` +
+
+ + + + {{ item.label }} + + + + This field is required + + +
+
+ + + + + + + + This field is required + + +
+
+ + + + + This field is required + + +
+
+ +
+
+ +
+ + +
+ + + Select Item + +
+ \\\`, + standalone: false, +}) +export class CustomFormatterConfiguratorComponent + extends FormatterConfiguratorComponent + implements OnInit, IHasChangeDetector +{ + public static lateLoadKey = "CustomFormatterConfiguratorComponent"; + + constructor( + changeDetector: ChangeDetectorRef, + configuratorHeading: ConfiguratorHeadingService, + formBuilder: FormBuilder, + logger: LoggerService, + public iconService: IconService + ) { + super(changeDetector, configuratorHeading, formBuilder, logger); + } + + public formatterFormGroup: FormGroup; + // This array is where the icon names will be stored + public options: string[] = []; + + public ngOnInit(): void { + for (const icon of this.iconService.icons) { + if (icon.category === "severity") { + this.options.push(icon.name); + } + } + } + + protected addCustomFormControls(form: FormGroup): void { + form.addControl( + "icon", + this.formBuilder.control("", Validators.required) + ); + form.addControl( + "threshold", + this.formBuilder.control(null, Validators.required) + ); + } +} + +/** + * A component that instantiates the dashboard + */ +@Component({ + selector: "custom-formatter-example", + templateUrl: "./custom-formatter-example.component.html", + styleUrls: ["./custom-formatter-example.component.less"], + standalone: false, +}) +export class CustomFormatterExampleComponent implements OnInit { + public editMode: boolean = false; + // This variable will hold all the data needed to define the layout and behavior of the widgets. + // Pass this to the dashboard component's dashboard input in the template. + public dashboard: IDashboard | undefined; + + // Angular gridster requires a configuration object even if it's empty. + // Pass this to the dashboard component's gridsterConfig input in the template. + public gridsterConfig: GridsterConfig = {}; + + constructor( + // WidgetTypesService provides the widget's necessary structure information + private widgetTypesService: WidgetTypesService, + // In general, the ProviderRegistryService is used for making entities available for injection into dynamically loaded components. + private providerRegistry: ProviderRegistryService, + // Inject the ComponentRegistryService to make our custom component available for late loading by the dashboards framework + private componentRegistry: ComponentRegistryService, + private tableFormatterRegistryService: TableFormatterRegistryService, + private changeDetectorRef: ChangeDetectorRef + ) { + // Register the custom configurator component with the component registry to make it available + // for late loading by the dashboard framework. + this.componentRegistry.registerByLateLoadKey( + CustomFormatterConfiguratorComponent + ); + // Register the custom formatter component with the component registry to make it available + // for late loading by the dashboard framework. + this.componentRegistry.registerByLateLoadKey(CustomFormatterComponent); + + // Grab the widget's default template which will be needed as a parameter for setNode below. + const table = this.widgetTypesService.getWidgetType("table", 1); + + const tableFormatters: IFormatterDefinition[] = [ + { + // This will be the component that will format the data + componentType: RawFormatterComponent.lateLoadKey, + // This is the label for what the formatter is selected in the drop down + label: $localize\\\`:table formatter|:No formatter\\\`, + // This says what datatype the formatter supports. If the value node is null, it accepts any data type. + dataTypes: { + // @ts-ignore: Ignoring compiler error to keep the same flow + value: null, + }, + }, + { + componentType: CustomFormatterComponent.lateLoadKey, + label: $localize\\\`:table formatter|:Custom formatter\\\`, + // This is a custom configurator that will pop up below the formatter once it gets selected + configurationComponent: + CustomFormatterConfiguratorComponent.lateLoadKey, + // This says what data types the formatter supports. + // In this case, it supports abv values only. + // If you look below in the table data source you'll see where we define our column's data types. + dataTypes: { + value: ["abv"], + }, + }, + ]; + + // Registering the formatters + this.tableFormatterRegistryService.addItems(tableFormatters); + + // This sets the table's datasource to have the BeerDataSource so the drop down is filled similar to the line above. + this.widgetTypesService.setNode( + table, + "configurator", + WellKnownPathKey.DataSourceProviders, + [BeerDataSource.providerId] + ); + + // Registering the data source for injection into the widget. + this.providerRegistry.setProviders({ + [BeerDataSource.providerId]: { + provide: DATA_SOURCE, + useClass: BeerDataSource, + // Any dependencies that need to be injected into the provider must be listed here + deps: [], + }, + }); + } + + public ngOnInit(): void { + this.initializeDashboard(); + } + + /** Used for restoring widgets state */ + public reInitializeDashboard(): void { + // destroys the components and their providers so the dashboard can re init data + this.dashboard = undefined; + this.changeDetectorRef.detectChanges(); + + this.initializeDashboard(); + } + + public initializeDashboard(): void { + // We're using a static configuration object for this example, but this is where + // the widget's configuration could potentially be populated from a database + const tableWidget = widgetConfig; + const widgetIndex: IWidgets = { + // Enhance the widget with information coming from it's type definition + [tableWidget.id]: + this.widgetTypesService.mergeWithWidgetType(tableWidget), + }; + + // Setting the widget dimensions and position (this is for gridster) + const positions: Record = { + [tableWidget.id]: { + cols: 12, + rows: 6, + y: 0, + x: 0, + }, + }; + + // Finally, assigning the variables we created above to the dashboard + this.dashboard = { + positions, + widgets: widgetIndex, + }; + } +} + +export interface IBrewInfo { + id: number; + name: string; + tagline: string; + first_brewed: string; + description: string; + brewers_tips: string; + abv: number; +} + +export interface IBrewDatasourceResponse { + brewInfo: IBrewInfo[]; + total: number; +} + +export class BeerDataSource extends DataSourceService { + public static providerId = "BeerDataSource"; + + private cache = Array.from({ length: 0 }); + private lastSortValue?: ISorterFilter; + private lastVirtualScroll?: ListRange; + // For simplicity, the totalItems value is hard-coded here, but in a real-world scenario the value would likely be retrieved via an async backend call + private totalItems: number = 325; + + public page: number = 1; + public busy = new BehaviorSubject(false); + + public dataFields: Array = [ + { id: "id", label: "No", dataType: "number" }, + { id: "name", label: "Name", dataType: "string" }, + { id: "tagline", label: "Tagline", dataType: "string" }, + { id: "first_brewed", label: "First Brewed", dataType: "string" }, + { id: "description", label: "Description", dataType: "string" }, + { id: "brewers_tips", label: "Brewer's Tips", dataType: "string" }, + // We are giving this field a custom data type of 'abv' so the dropdown in the custom formatter configurator can use it to filter out other data types + { id: "abv", label: "Alcohol By Volume", dataType: "abv" }, + ]; + + constructor(private logger: LoggerService) { + super(); + } + + public async getFilteredData( + filters: INovaFilters + ): Promise> { + const start = filters.virtualScroll?.value?.start ?? 0; + const end = filters.virtualScroll?.value?.end ?? 0; + const delta = end - start; + + // Note: We should start with a clean cache every time first page is requested + if (start === 0) { + this.cache = []; + } + + // This condition handles sorting. We want to sort columns without fetching another chunk of data. + // Since the data is being fetched when scrolled, we compare virtual scroll indexes here in the condition as well. + if (filters.sorter?.value) { + if ( + !isEqual(this.lastSortValue, filters.sorter.value) && + filters.virtualScroll?.value.start === 0 && + !!this.lastVirtualScroll + ) { + const totalPages = Math.ceil( + delta ? this.totalItems / delta : 1 + ); + const itemsPerPage: number = Math.max( + delta < 80 ? delta : 80, + 1 + ); + let response: Array | null = null; + let map: IBrewDatasourceResponse; + + if (filters.sorter?.value?.direction === "desc") { + this.cache = []; + for (let i = 0; i < this.page; ++i) { + response = await ( + await fetch( + \\\`\\\${BREW_API_URL}/?page=\\\${ + totalPages - i || 1 + }&per_page=\\\${itemsPerPage}\\\` + ) + ).json(); + + // since the last page contains only 5 items we need to fetch another page to give virtual scroll enough space to work + if (response && response.length < itemsPerPage) { + this.page++; + } + map = { + brewInfo: response?.map((result: IBrewInfo) => ({ + id: result.id, + name: result.name, + tagline: result.tagline, + first_brewed: result.first_brewed, + description: result.description, + brewers_tips: result.brewers_tips, + })), + total: response?.length, + } as IBrewDatasourceResponse; + this.cache = + totalPages - i !== 0 + ? this.cache.concat(map.brewInfo) + : this.cache; + } + } + + if (filters.sorter?.value?.direction === "asc") { + this.cache = []; + for (let i = 0; i < this.page; i++) { + response = await ( + await fetch( + \\\`\\\${BREW_API_URL}/?page=\\\${ + i + 1 + }&per_page=\\\${itemsPerPage}\\\` + ) + ).json(); + map = { + brewInfo: response?.map((result: IBrewInfo) => ({ + id: result.id, + name: result.name, + tagline: result.tagline, + first_brewed: result.first_brewed, + description: result.description, + brewers_tips: result.brewers_tips, + })), + total: response?.length, + } as IBrewDatasourceResponse; + this.cache = this.cache.concat(map.brewInfo); + } + } + + this.lastSortValue = filters.sorter?.value; + this.lastVirtualScroll = filters.virtualScroll?.value; + + return { + result: { + repeat: { + itemsSource: this.sortData(this.cache, filters), + }, + paginator: { total: this.totalItems }, + dataFields: this.dataFields, + }, + }; + } + } + + this.busy.next(true); + return new Promise((resolve) => { + setTimeout(() => { + this.getData(start, end, filters).then( + (response: INovaFilteringOutputs) => { + if (!response) { + return; + } + + this.cache = this.cache.concat(response.brewInfo); + + this.dataSubject.next(this.cache); + resolve({ + result: { + repeat: { + itemsSource: this.sortData( + this.cache, + filters + ), + }, + paginator: { total: this.totalItems }, + dataFields: this.dataFields, + }, + }); + + this.lastSortValue = filters.sorter?.value; + this.lastVirtualScroll = filters.virtualScroll?.value; + this.busy.next(false); + } + ); + }, 500); + }); + } + + public async getData( + start: number = 0, + end: number = 20, + filters: INovaFilters + ): Promise { + const delta = end - start; + const totalPages = Math.ceil(delta ? this.totalItems / delta : 1); + let response: Array | null = null; + // The api.punk.com is able to return only 80 items per page + const itemsPerPage: number = Math.max(delta < 80 ? delta : 80, 1); + + if (filters.sorter?.value?.direction === "asc") { + response = await ( + await fetch( + \\\`\\\${BREW_API_URL}/?page=\\\${this.page}&per_page=\\\${itemsPerPage}\\\` + ) + ).json(); + } + + if (filters.sorter?.value?.direction === "desc") { + response = await ( + await fetch( + \\\`\\\${BREW_API_URL}/?page=\\\${ + totalPages - this.page + }&per_page=\\\${itemsPerPage}\\\` + ) + ).json(); + } + + if (!filters.sorter) { + response = await ( + await fetch( + \\\`\\\${BREW_API_URL}/?page=\\\${this.page}&per_page=\\\${itemsPerPage}\\\` + ) + ).json(); + } + return { + brewInfo: response?.map((result: IBrewInfo, i: number) => ({ + id: result.id, + abv: result.abv, + name: result.name, + tagline: result.tagline, + first_brewed: result.first_brewed, + description: result.description, + brewers_tips: result.brewers_tips, + })), + total: response?.length, + } as IBrewDatasourceResponse; + } + + private sortData(data: IBrewInfo[], filters: INovaFilters) { + return orderBy( + data, + filters.sorter?.value?.sortBy, + filters.sorter?.value?.direction as "desc" | "asc" + ); + } +} + +export const widgetConfig: IWidget = { + id: "tableWidgetId", + type: "table", + pizzagna: { + [PizzagnaLayer.Configuration]: { + header: { + properties: { + title: "Stupendous Suds", + subtitle: "Try These Brilliant Brews", + }, + }, + table: { + providers: { + [WellKnownProviders.DataSource]: { + providerId: BeerDataSource.providerId, + }, + }, + properties: { + configuration: { + columns: [ + { + id: "column1", + label: "Beer Name", + isActive: true, + width: 185, + formatter: { + componentType: + RawFormatterComponent.lateLoadKey, + properties: { + dataFieldIds: { + value: "name", + }, + }, + }, + }, + { + id: "column2", + label: "Tagline", + isActive: true, + width: 250, + formatter: { + componentType: + RawFormatterComponent.lateLoadKey, + properties: { + dataFieldIds: { + value: "tagline", + }, + }, + }, + }, + { + id: "column3", + label: "Alcohol By Volume", + isActive: true, + width: 150, + formatter: { + componentType: + CustomFormatterComponent.lateLoadKey, + properties: { + dataFieldIds: { + value: "abv", + }, + icon: "severity_error", + threshold: "5", + }, + }, + }, + { + id: "column4", + label: "Description", + isActive: true, + formatter: { + componentType: + RawFormatterComponent.lateLoadKey, + properties: { + dataFieldIds: { + value: "description", + }, + }, + }, + }, + ] as ITableWidgetColumnConfig[], + sorterConfiguration: { + descendantSorting: false, + sortBy: "", + } as ITableWidgetSorterConfig, + hasVirtualScroll: true, + }, + }, + }, + }, + }, +}; +\`, + "tutorials/customization/widget/custom-widget-docs.component.ts": \`// © 2022 SolarWinds Worldwide, LLC. All rights reserved. +// +// Permission is hereby granted, free of charge, to any person obtaining a copy +// of this software and associated documentation files (the "Software"), to +// deal in the Software without restriction, including without limitation the +// rights to use, copy, modify, merge, publish, distribute, sublicense, and/or +// sell copies of the Software, and to permit persons to whom the Software is +// furnished to do so, subject to the following conditions: +// +// The above copyright notice and this permission notice shall be included in +// all copies or substantial portions of the Software. +// +// THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR +// IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, +// FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE +// AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER +// LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, +// OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN +// THE SOFTWARE. + +import { Component } from "@angular/core"; + +@Component({ + selector: "custom-widget-docs", + templateUrl: "./custom-widget-docs.component.html", + standalone: false, +}) +export class CustomWidgetDocsComponent {} +\`, + "tutorials/customization/widget/custom-widget.component.ts": \`// © 2022 SolarWinds Worldwide, LLC. All rights reserved. +// +// Permission is hereby granted, free of charge, to any person obtaining a copy +// of this software and associated documentation files (the "Software"), to +// deal in the Software without restriction, including without limitation the +// rights to use, copy, modify, merge, publish, distribute, sublicense, and/or +// sell copies of the Software, and to permit persons to whom the Software is +// furnished to do so, subject to the following conditions: +// +// The above copyright notice and this permission notice shall be included in +// all copies or substantial portions of the Software. +// +// THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR +// IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, +// FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE +// AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER +// LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, +// OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN +// THE SOFTWARE. + +import { + ChangeDetectionStrategy, + ChangeDetectorRef, + Component, + EventEmitter, + HostBinding, + Input, + OnChanges, + OnInit, + Output, + SimpleChanges, +} from "@angular/core"; +import { FormBuilder, FormGroup, Validators } from "@angular/forms"; +import { GridsterConfig, GridsterItem } from "angular-gridster2"; + +import { IMenuItem } from "@nova-ui/bits"; +import { + ComponentRegistryService, + ConfiguratorHeadingService, + DEFAULT_PIZZAGNA_ROOT, + EVENT_PROXY, + FormStackComponent, + IConverterFormPartsProperties, + IDashboard, + IHasChangeDetector, + IHasForm, + IProviderConfiguration, + IWidget, + IWidgets, + IWidgetTypeDefinition, + NOVA_GENERIC_CONVERTER, + NOVA_TITLE_AND_DESCRIPTION_CONVERTER, + PizzagnaLayer, + refresher, + StackComponent, + TitleAndDescriptionConfigurationComponent, + WellKnownPathKey, + WellKnownProviders, + widgetBodyContentNodes, + WidgetConfiguratorSectionComponent, + WidgetTypesService, + WIDGET_BODY, + WIDGET_HEADER, + WIDGET_LOADING, +} from "@nova-ui/dashboards"; + +// The custom widget type name we'll use +const CUSTOM_WIDGET_TYPENAME = "example-custom-widget"; +// The path key we'll use for image selection in the configurator definition +const IMAGE_SELECTION_CONFIGURATOR_PATH_KEY = "imageSelection"; + +@Component({ + selector: "custom-widget-body", + // A simple template for our custom widget + template: \\\`\\\`, + styleUrls: ["./custom-widget.component.less"], + changeDetection: ChangeDetectionStrategy.OnPush, + standalone: false, +}) +// Remember to declare this class in the parent module +export class CustomWidgetBodyContentComponent implements IHasChangeDetector { + // Ensure that the lateLoadKey value matches class name + public static lateLoadKey = "CustomWidgetBodyContentComponent"; + + // Optionally, providing an input for styling of the host element + @Input() @HostBinding("class") public elementClass = ""; + + // We'll map this input with the configurator form using the NOVA_GENERIC_CONVERTER. + // See the customWidget definition at the bottom of the file. + @Input() public imageSource: string; + + // Injecting the ChangeDetectorRef to implement IHasChangeDetector. + // This allows the dashboard framework to reliably propagate component property changes to the DOM. + constructor(public changeDetector: ChangeDetectorRef) {} +} + +/** + * A custom configurator section component for selecting the image source for the custom widget + */ +@Component({ + selector: "custom-configurator-section", + template: \\\` + + + + +
+ + +
+ Image Selection +
+ {{ imageDisplayValue }} +
+
+
+
+ + + + + {{ item.title }} + + + +
+
+ \\\`, + styleUrls: ["./custom-widget.component.less"], + changeDetection: ChangeDetectionStrategy.OnPush, + standalone: false, +}) +// Remember to declare this class in the parent module +export class CustomConfiguratorSectionComponent + implements OnInit, OnChanges, IHasChangeDetector, IHasForm +{ + // Ensure that the lateLoadKey value matches the class name + public static lateLoadKey = "CustomConfiguratorSectionComponent"; + + /** + * This input serves as the itemsSource a user can select an image from. + */ + @Input() imageItems: IMenuItem[] = []; + /** + * This property holds the currently selected image source string. + */ + @Input() imageSource: string; + + /** + * An output for emitting formReady to allow the immediate parent formGroup component to register us as a form control + * in the larger form. In this case, the immediate parent would be the WidgetConfiguratorSectionComponent as specified + * in the customWidget configurator definition at the bottom of this file. + */ + @Output() formReady = new EventEmitter(); + + public form: FormGroup; + public imageDisplayValue: string; + + constructor( + public changeDetector: ChangeDetectorRef, + private formBuilder: FormBuilder, + public configuratorHeading: ConfiguratorHeadingService + ) {} + + public ngOnInit(): void { + // Initializing the form + this.form = this.formBuilder.group({ + // Note: When using the NOVA_GENERIC_CONVERTER, the form control name, in this case 'imageSource', must match the input name on + // this component as well as that of the corresponding property on the custom widget body component. + imageSource: [{}, [Validators.required]], + }); + + // Emitting the formReady as described above. + this.formReady.emit(this.form); + } + + public ngOnChanges(changes: SimpleChanges): void { + if (changes.imageSource && !changes.imageSource.isFirstChange()) { + const previousValue: string = changes.imageSource.previousValue; + if (previousValue !== this.imageSource) { + // Setting the display value according to the current imageSource value + this.imageDisplayValue = this.imageItems.find( + (item: IMenuItem) => item.url === this.imageSource + )?.title; + + // Updating the form when the imageSource input gets updated + this.form.get("imageSource")?.setValue(this.imageSource); + } + } + } + + public onChanged(newValue: string): void { + // Keeping the display value updated as the user changes the dropdown selection + this.imageDisplayValue = this.imageItems.find( + (item: IMenuItem) => item.url === newValue + )?.title; + } +} + +/** + * The component that instantiates the dashboard + */ +@Component({ + selector: "custom-widget", + templateUrl: "./custom-widget.component.html", + styleUrls: ["./custom-widget.component.less"], + standalone: false, +}) +export class CustomWidgetComponent implements OnInit { + // This variable will hold all the data needed to define the layout and behavior of the widgets. + // Pass this to the dashboard component's dashboard input in the template. + public dashboard: IDashboard | undefined; + + // Angular gridster requires a configuration object even if it's empty. + // Pass this to the dashboard component's gridsterConfig input in the template. + public gridsterConfig: GridsterConfig = {}; + + // Boolean which dashboard takes in as an input if its true it allows you to move widgets around. + public editMode: boolean = false; + + constructor( + // WidgetTypesService provides the widget's necessary structure information + private widgetTypesService: WidgetTypesService, + + // Inject the ComponentRegistryService to make our custom component available for late loading by the dashboards framework + private componentRegistry: ComponentRegistryService, + private changeDetectorRef: ChangeDetectorRef + ) {} + + public ngOnInit(): void { + // Register the custom widget type and custom components + // Note: This could also be done in the parent module's constructor so that + // multiple dashboards could have access to the same registrations. + this.prepareNovaDashboards(); + + // Register some image items as dropdown options in the widget editor/configurator + // Note: This could also be done in the parent module's constructor so that + // multiple dashboards could have access to the same dropdown options. + this.registerImageOptions(); + + // Initialize our current instance of a dashboard with an instance of our custom widget + this.initializeDashboard(); + } + + /** Used for restoring widgets state */ + public reInitializeDashboard(): void { + // destroys the components and their providers so the dashboard can re init data + this.dashboard = undefined; + this.changeDetectorRef.detectChanges(); + + this.initializeDashboard(); + } + + public initializeDashboard(): void { + // We're using a static configuration object for this example (see widgetConfig at the bottom of the file), + // but this is where the widget's configuration could potentially be populated from a database + const widget = widgetConfig; + + // Create an index of widgets complete with structure and configuration to assign to the dashboard + const widgets: IWidgets = { + // Complete the custom widget with structure information coming from its type definition + [widget.id]: this.widgetTypesService.mergeWithWidgetType(widget), + }; + + // Setting the widget dimensions and position (this is for gridster) + const positions: Record = { + [widget.id]: { + cols: 4, + rows: 11, + y: 0, + x: 0, + }, + }; + + // Finally, assigning the variables we created above to the dashboard + this.dashboard = { positions, widgets }; + } + + private prepareNovaDashboards() { + // Register the custom widget type + this.widgetTypesService.registerWidgetType( + CUSTOM_WIDGET_TYPENAME, + 1, + customWidget + ); + + // Register the custom widget body component with the component registry to make it available + // for late loading by the dashboard framework. + this.componentRegistry.registerByLateLoadKey( + CustomWidgetBodyContentComponent + ); + + // Register the custom configurator section with the component registry to make it available + // for late loading by the dashboard framework. + this.componentRegistry.registerByLateLoadKey( + CustomConfiguratorSectionComponent + ); + } + + private registerImageOptions() { + // Grab the widget's default template which will be needed as a parameter for setNode below. + const widgetTemplate = this.widgetTypesService.getWidgetType( + CUSTOM_WIDGET_TYPENAME, + 1 + ); + + // Register some image items as dropdown options in the widget editor/configurator + this.widgetTypesService.setNode( + // This is the template we grabbed above with getWidgetType + widgetTemplate, + // We are setting the editor/configurator part of the widget template + "configurator", + // This indicates which node you are changing and we want to change the image items available for selection in the editor. + // For reference, see the 'paths' property of the custom widget's IWidgetTypeDefinition at the bottom of this file. + IMAGE_SELECTION_CONFIGURATOR_PATH_KEY, + // We are setting the image items available for selection in the editor. 'imageItems' is defined + // at the bottom of this file. + imageItems + ); + } +} + +/*************************************************************************************************** + * This is the type definition of our custom widget + ***************************************************************************************************/ +const customWidget: IWidgetTypeDefinition = { + /*************************************************************************************************** + * Paths to important settings in this type definition + ***************************************************************************************************/ + paths: { + widget: { + [WellKnownPathKey.Root]: DEFAULT_PIZZAGNA_ROOT, + }, + configurator: { + [WellKnownPathKey.Root]: DEFAULT_PIZZAGNA_ROOT, + // for the custom configuration component, this is the path for the list of image items available for selection + [IMAGE_SELECTION_CONFIGURATOR_PATH_KEY]: + "imageSelection.properties.imageItems", + }, + }, + /*************************************************************************************************** + * Widget section describes the structural part of the custom widget + ***************************************************************************************************/ + widget: { + [PizzagnaLayer.Structure]: { + [DEFAULT_PIZZAGNA_ROOT]: { + id: DEFAULT_PIZZAGNA_ROOT, + // base layout of the widget - all components referenced herein will be stacked in a column + componentType: StackComponent.lateLoadKey, + providers: { + // When enabled, this provider emits the REFRESH event on the pizzagna event bus every X seconds + [WellKnownProviders.Refresher]: refresher(), + // event proxy manages the transmission of events between widget and dashboard such as the WIDGET_EDIT and WIDGET_REMOVE events + [WellKnownProviders.EventProxy]: EVENT_PROXY, + }, + properties: { + // these values reference child components in the widget structure defined below + nodes: ["header", "loading", "body"], + }, + }, + // standard widget header + header: WIDGET_HEADER, + // this is the loading bar below the header + loading: WIDGET_LOADING, + // the body node + body: WIDGET_BODY, + + // retrieving the definitions for the body content nodes. the argument corresponds to the main content node key + ...widgetBodyContentNodes("mainContent"), + + // the component that supplies the content of our custom widget + mainContent: { + id: "mainContent", + componentType: CustomWidgetBodyContentComponent.lateLoadKey, + properties: { + elementClass: "d-flex w-100 justify-content-center", + }, + }, + }, + [PizzagnaLayer.Configuration]: { + [DEFAULT_PIZZAGNA_ROOT]: { + id: DEFAULT_PIZZAGNA_ROOT, + providers: { + // default refresher configuration + [WellKnownProviders.Refresher]: refresher(false, 60), + }, + }, + // default header configuration + header: { + properties: { + title: $localize\\\`Empty Custom Widget\\\`, + }, + }, + }, + }, + /*************************************************************************************************** + * Configurator section describes the form that's used to configure the widget + ***************************************************************************************************/ + configurator: { + [PizzagnaLayer.Structure]: { + [DEFAULT_PIZZAGNA_ROOT]: { + id: DEFAULT_PIZZAGNA_ROOT, + // base layout of the configurator - all form components referenced herein will be stacked in a column + componentType: FormStackComponent.lateLoadKey, + properties: { + elementClass: + "flex-grow-1 overflow-auto nui-scroll-shadows", + // these values reference child components laid out in this form (defined below) + nodes: ["presentation", "customConfig"], + }, + }, + // /presentation + presentation: { + id: "presentation", + componentType: WidgetConfiguratorSectionComponent.lateLoadKey, + properties: { + headerText: $localize\\\`Presentation\\\`, + nodes: ["titleAndDescription"], + }, + }, + // /presentation/titleAndDescription + titleAndDescription: { + id: "titleAndDescription", + componentType: + TitleAndDescriptionConfigurationComponent.lateLoadKey, + providers: { + converter: { + providerId: NOVA_TITLE_AND_DESCRIPTION_CONVERTER, + } as IProviderConfiguration, + }, + }, + // /customConfig + customConfig: { + id: "customConfig", + componentType: WidgetConfiguratorSectionComponent.lateLoadKey, + properties: { + headerText: $localize\\\`Custom Widget Configuration\\\`, + nodes: ["imageSelection"], + }, + }, + // /customConfig/imageSelection + imageSelection: { + id: "imageSelection", + // Here's where we set the configurator to use our custom configurator section + componentType: CustomConfiguratorSectionComponent.lateLoadKey, + properties: { + // This corresponds to the 'imageItems' input on the custom configurator section component + // which defines the list of image items to pick from. The empty value shown here is overridden + // in the 'registerImageOptions' method above. + imageItems: [] as IMenuItem[], + }, + providers: { + // Using the generic converter to map the selected image source between the widget and the form + [WellKnownProviders.Converter]: { + providerId: NOVA_GENERIC_CONVERTER, + properties: { + formParts: [ + { + // Setting up the generic converter to update the 'imageSource' property of the custom widget 'mainContent' component + previewPath: "mainContent.properties", + // Note: To use the NOVA_GENERIC_CONVERTER, the linked properties must have the same name between the configurator + // section component and the widget 'mainContent' component. Additionally, the property name must match the formControl + // name used in the configurator section component. In this case, the common name among all three is 'imageSource'. + keys: ["imageSource"], + }, + ] as IConverterFormPartsProperties[], + }, + } as IProviderConfiguration, + }, + }, + }, + }, +}; + +// For this example, we're using static items for the image selection dropdown. In a more realistic scenario, +// the items available for selection might come from a backend database. +const imageItems = [ + { + title: "Harry Potter Book Cover", + url: "https://imgc.allpostersimages.com/img/print/u-g-F8PQ9I0.jpg?w=550&h=550&p=0", + }, + { + title: "Harry Potter Movie Poster", + url: "https://images-na.ssl-images-amazon.com/images/I/81gpmMdKOHL._AC_SY741_.jpg", + }, +] as IMenuItem[]; + +// We're using a static configuration object for this example. In a more realistic scenario, +// a widget's configuration would likely be stored in a database. +const widgetConfig: IWidget = { + id: "widget1", + // This custom type is registered in the 'prepareNovaDashboards' method above. + type: CUSTOM_WIDGET_TYPENAME, + pizzagna: { + [PizzagnaLayer.Configuration]: { + header: { + // Setting the initial property values for the WidgetHeaderComponent + properties: { + title: "Harry Potter and the Sorcerer's Stone", + subtitle: "By J. K. Rowling", + }, + }, + mainContent: { + properties: { + // Setting the initial value for the 'imageSource' property on our custom widget body + imageSource: imageItems[0].url, + }, + }, + }, + }, +}; +\`, + "tutorials/customization/widget/custom-widget.module.ts": \`// © 2022 SolarWinds Worldwide, LLC. All rights reserved. +// +// Permission is hereby granted, free of charge, to any person obtaining a copy +// of this software and associated documentation files (the "Software"), to +// deal in the Software without restriction, including without limitation the +// rights to use, copy, modify, merge, publish, distribute, sublicense, and/or +// sell copies of the Software, and to permit persons to whom the Software is +// furnished to do so, subject to the following conditions: +// +// The above copyright notice and this permission notice shall be included in +// all copies or substantial portions of the Software. +// +// THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR +// IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, +// FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE +// AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER +// LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, +// OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN +// THE SOFTWARE. + +import { CommonModule } from "@angular/common"; +import { HttpClientModule } from "@angular/common/http"; +import { NgModule } from "@angular/core"; +import { ReactiveFormsModule } from "@angular/forms"; +import { RouterModule } from "@angular/router"; + +import { + NuiButtonModule, + NuiDocsModule, + NuiFormFieldModule, + NuiIconModule, + NuiImageModule, + NuiMessageModule, + NuiSelectV2Module, + NuiSwitchModule, + DEMO_PATH_TOKEN, +} from "@nova-ui/bits"; +import { + NuiDashboardConfiguratorModule, + NuiDashboardsModule, +} from "@nova-ui/dashboards"; + +import { CustomWidgetDocsComponent } from "./custom-widget-docs.component"; +import { + CustomConfiguratorSectionComponent, + CustomWidgetBodyContentComponent, + CustomWidgetComponent, +} from "./custom-widget.component"; +import { getDemoFiles } from "../../../../../demo-files-factory"; + +const routes = [ + { + path: "", + component: CustomWidgetDocsComponent, + data: { + srlc: { + hideIndicator: true, + }, + showThemeSwitcher: true, + }, + }, + { + path: "example", + component: CustomWidgetComponent, + data: { + srlc: { + hideIndicator: true, + }, + }, + }, +]; + +@NgModule({ + imports: [ + CommonModule, + ReactiveFormsModule, + HttpClientModule, + NuiDashboardsModule, + NuiDashboardConfiguratorModule, + NuiDocsModule, + NuiFormFieldModule, + NuiIconModule, + NuiImageModule, + NuiMessageModule, + NuiSelectV2Module, + NuiSwitchModule, + NuiButtonModule, + RouterModule.forChild(routes), + ], + declarations: [ + CustomWidgetDocsComponent, + CustomConfiguratorSectionComponent, + CustomWidgetBodyContentComponent, + CustomWidgetComponent, + ], + providers: [ + { + provide: DEMO_PATH_TOKEN, + useValue: getDemoFiles("widget"), + }, + ], +}) +export default class CustomWidgetModule {} +\`, + "tutorials/data-source-setup/data-source-setup-docs.component.ts": \`// © 2022 SolarWinds Worldwide, LLC. All rights reserved. +// +// Permission is hereby granted, free of charge, to any person obtaining a copy +// of this software and associated documentation files (the "Software"), to +// deal in the Software without restriction, including without limitation the +// rights to use, copy, modify, merge, publish, distribute, sublicense, and/or +// sell copies of the Software, and to permit persons to whom the Software is +// furnished to do so, subject to the following conditions: +// +// The above copyright notice and this permission notice shall be included in +// all copies or substantial portions of the Software. +// +// THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR +// IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, +// FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE +// AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER +// LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, +// OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN +// THE SOFTWARE. + +import { Component } from "@angular/core"; + +@Component({ + selector: "nui-dashboard-data-source-docs", + templateUrl: "./data-source-setup-docs.component.html", + standalone: false, +}) +export class DataSourceDocsComponent {} +\`, + "tutorials/data-source-setup/data-source-setup.component.ts": \`// © 2022 SolarWinds Worldwide, LLC. All rights reserved. +// +// Permission is hereby granted, free of charge, to any person obtaining a copy +// of this software and associated documentation files (the "Software"), to +// deal in the Software without restriction, including without limitation the +// rights to use, copy, modify, merge, publish, distribute, sublicense, and/or +// sell copies of the Software, and to permit persons to whom the Software is +// furnished to do so, subject to the following conditions: +// +// The above copyright notice and this permission notice shall be included in +// all copies or substantial portions of the Software. +// +// THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR +// IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, +// FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE +// AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER +// LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, +// OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN +// THE SOFTWARE. + +import { HttpClient, HttpErrorResponse } from "@angular/common/http"; +import { Component, Injectable, OnDestroy, OnInit } from "@angular/core"; +import { GridsterConfig, GridsterItem } from "angular-gridster2"; +import { BehaviorSubject } from "rxjs"; +import { finalize } from "rxjs/operators"; + +import { DataSourceService, IFilteringOutputs } from "@nova-ui/bits"; +import { + DATA_SOURCE, + DEFAULT_PIZZAGNA_ROOT, + IDashboard, + IKpiData, + IProviderConfiguration, + IRefresherProperties, + IWidget, + IWidgets, + KpiComponent, + NOVA_KPI_DATASOURCE_ADAPTER, + PizzagnaLayer, + ProviderRegistryService, + WellKnownProviders, + WidgetTypesService, +} from "@nova-ui/dashboards"; + +/** + * A simple KPI data source to retrieve the average rating of Harry Potter and the Sorcerer's Stone (book) via googleapis + */ +@Injectable() +export class AverageRatingKpiDataSource + extends DataSourceService + implements OnDestroy +{ + // This is the ID we'll use to identify the provider + public static providerId = "AverageRatingKpiDataSource"; + + // Use this subject to communicate the data source's busy state + public busy = new BehaviorSubject(false); + + constructor(private http: HttpClient) { + super(); + } + + // In this example, getFilteredData is invoked every 10 minutes (Take a look at the refresher + // provider definition in the widget configuration below to see how the interval is set) + public async getFilteredData(): Promise { + this.busy.next(true); + return new Promise((resolve) => { + // *** Make a resource request to an external API (if needed) + this.http + .get("https://www.googleapis.com/books/v1/volumes/5MQFrgEACAAJ") + .pipe(finalize(() => this.busy.next(false))) + .subscribe({ + next: (data: any) => { + resolve({ + result: { + value: data.volumeInfo.averageRating, + }, + }); + }, + error: (error: HttpErrorResponse) => { + resolve({ + result: null, + error: { + type: error.status, + }, + }); + }, + }); + }); + } + + public ngOnDestroy(): void { + this.outputsSubject.complete(); + } +} + +/** + * A component that instantiates the dashboard + */ +@Component({ + selector: "data-source-setup", + templateUrl: "./data-source-setup.component.html", + styleUrls: ["./data-source-setup.component.less"], + standalone: false, +}) +export class DataSourceSetupComponent implements OnInit { + // This variable will hold all the data needed to define the layout and behavior of the widgets. + // Pass this to the dashboard component's dashboard input in the template. + public dashboard: IDashboard; + + // Angular gridster requires a configuration object even if it's empty. + // Pass this to the dashboard component's gridsterConfig input in the template. + public gridsterConfig: GridsterConfig = {}; + + constructor( + // WidgetTypesService provides the widget's necessary structure information + private widgetTypesService: WidgetTypesService, + + // In general, the ProviderRegistryService is used for making entities available for injection into dynamically loaded components. + private providerRegistry: ProviderRegistryService + ) {} + + public ngOnInit(): void { + // Registering the data source for injection into the KPI tile. + // Note: Each tile of a KPI widget is assigned its own instance of the data source + this.providerRegistry.setProviders({ + [AverageRatingKpiDataSource.providerId]: { + provide: DATA_SOURCE, + useClass: AverageRatingKpiDataSource, + // Any dependencies that need to be injected into the provider must be listed here + deps: [HttpClient], + }, + }); + // We're using a static configuration object for this example, but this is where + // the widget's configuration could potentially be populated from a database + const kpiWidget = widgetConfig; + const widgetIndex: IWidgets = { + // Complete the KPI widget with information coming from its type definition + [kpiWidget.id]: + this.widgetTypesService.mergeWithWidgetType(kpiWidget), + }; + + // Setting the widget dimensions and position (this is for gridster) + const positions: Record = { + [kpiWidget.id]: { + cols: 4, + rows: 6, + y: 0, + x: 0, + }, + }; + + // Finally, assigning the variables we created above to the dashboard + this.dashboard = { + positions, + widgets: widgetIndex, + }; + } +} + +const widgetConfig: IWidget = { + id: "widget1", + type: "kpi", + pizzagna: { + [PizzagnaLayer.Configuration]: { + [DEFAULT_PIZZAGNA_ROOT]: { + providers: { + [WellKnownProviders.Refresher]: { + properties: { + // Configuring the refresher interval so that our data source is invoked every ten minutes + interval: 60 * 10, + enabled: true, + } as IRefresherProperties, + } as Partial, + }, + }, + header: { + properties: { + title: "Harry Potter and the Sorcerer's Stone", + subtitle: "By J. K. Rowling", + }, + }, + tiles: { + properties: { + nodes: ["kpi1"], + }, + }, + kpi1: { + id: "kpi1", + componentType: KpiComponent.lateLoadKey, + properties: { + widgetData: { + units: "out of 5 Stars", + label: "Average Rating", + }, + }, + providers: { + [WellKnownProviders.DataSource]: { + // Setting the data source providerId for the tile with id "kpi1" + providerId: AverageRatingKpiDataSource.providerId, + } as IProviderConfiguration, + [WellKnownProviders.Adapter]: { + providerId: NOVA_KPI_DATASOURCE_ADAPTER, + properties: { + componentId: "kpi1", + propertyPath: "widgetData", + }, + } as IProviderConfiguration, + }, + }, + }, + }, +}; +\`, + "tutorials/data-source-setup/data-source-setup.module.ts": \`// © 2022 SolarWinds Worldwide, LLC. All rights reserved. +// +// Permission is hereby granted, free of charge, to any person obtaining a copy +// of this software and associated documentation files (the "Software"), to +// deal in the Software without restriction, including without limitation the +// rights to use, copy, modify, merge, publish, distribute, sublicense, and/or +// sell copies of the Software, and to permit persons to whom the Software is +// furnished to do so, subject to the following conditions: +// +// The above copyright notice and this permission notice shall be included in +// all copies or substantial portions of the Software. +// +// THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR +// IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, +// FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE +// AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER +// LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, +// OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN +// THE SOFTWARE. + +import { CommonModule } from "@angular/common"; +import { HttpClientModule } from "@angular/common/http"; +import { NgModule } from "@angular/core"; +import { RouterModule } from "@angular/router"; + +import { + NuiDocsModule, + NuiMessageModule, + DEMO_PATH_TOKEN, +} from "@nova-ui/bits"; +import { NuiDashboardsModule } from "@nova-ui/dashboards"; + +import { DataSourceDocsComponent } from "./data-source-setup-docs.component"; +import { DataSourceSetupComponent } from "./data-source-setup.component"; +import { getDemoFiles } from "../../../../demo-files-factory"; + +const routes = [ + { + path: "", + component: DataSourceDocsComponent, + data: { + srlc: { + hideIndicator: true, + }, + showThemeSwitcher: true, + }, + }, + { + path: "example", + component: DataSourceSetupComponent, + data: { + srlc: { + hideIndicator: true, + }, + }, + }, +]; + +@NgModule({ + imports: [ + CommonModule, + HttpClientModule, + NuiDashboardsModule, + NuiDocsModule, + NuiMessageModule, + RouterModule.forChild(routes), + ], + declarations: [DataSourceDocsComponent, DataSourceSetupComponent], + providers: [ + { + provide: DEMO_PATH_TOKEN, + useValue: getDemoFiles("data-source-setup"), + }, + ], +}) +export default class DataSourceSetupModule {} +\`, + "tutorials/dynamic-header-links/dynamic-header-links-docs.component.ts": \`// © 2022 SolarWinds Worldwide, LLC. All rights reserved. +// +// Permission is hereby granted, free of charge, to any person obtaining a copy +// of this software and associated documentation files (the "Software"), to +// deal in the Software without restriction, including without limitation the +// rights to use, copy, modify, merge, publish, distribute, sublicense, and/or +// sell copies of the Software, and to permit persons to whom the Software is +// furnished to do so, subject to the following conditions: +// +// The above copyright notice and this permission notice shall be included in +// all copies or substantial portions of the Software. +// +// THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR +// IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, +// FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE +// AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER +// LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, +// OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN +// THE SOFTWARE. + +import { Component } from "@angular/core"; + +@Component({ + selector: "nui-dynamic-header-links-docs", + templateUrl: "./dynamic-header-links-docs.component.html", + standalone: false, +}) +export class DynamicHeaderLinksDocsComponent {} +\`, + "tutorials/dynamic-header-links/dynamic-header-links-docs.module.ts": \`// © 2022 SolarWinds Worldwide, LLC. All rights reserved. +// +// Permission is hereby granted, free of charge, to any person obtaining a copy +// of this software and associated documentation files (the "Software"), to +// deal in the Software without restriction, including without limitation the +// rights to use, copy, modify, merge, publish, distribute, sublicense, and/or +// sell copies of the Software, and to permit persons to whom the Software is +// furnished to do so, subject to the following conditions: +// +// The above copyright notice and this permission notice shall be included in +// all copies or substantial portions of the Software. +// +// THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR +// IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, +// FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE +// AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER +// LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, +// OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN +// THE SOFTWARE. + +import { NgModule } from "@angular/core"; +import { RouterModule } from "@angular/router"; + +import { DynamicHeaderLinksDocsComponent } from "./dynamic-header-links-docs.component"; + +const routes = [ + { + path: "", + component: DynamicHeaderLinksDocsComponent, + data: { + srlc: { + hideIndicator: true, + }, + showThemeSwitcher: true, + }, + }, +]; + +@NgModule({ + imports: [RouterModule.forChild(routes)], + declarations: [DynamicHeaderLinksDocsComponent], +}) +export default class DynamicHeaderLinksDocsModule {} +\`, + "tutorials/hello-dashboards/hello-dashboards-docs.component.ts": \`// © 2022 SolarWinds Worldwide, LLC. All rights reserved. +// +// Permission is hereby granted, free of charge, to any person obtaining a copy +// of this software and associated documentation files (the "Software"), to +// deal in the Software without restriction, including without limitation the +// rights to use, copy, modify, merge, publish, distribute, sublicense, and/or +// sell copies of the Software, and to permit persons to whom the Software is +// furnished to do so, subject to the following conditions: +// +// The above copyright notice and this permission notice shall be included in +// all copies or substantial portions of the Software. +// +// THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR +// IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, +// FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE +// AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER +// LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, +// OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN +// THE SOFTWARE. + +import { Component } from "@angular/core"; + +@Component({ + selector: "nui-dashboard-hello-dashboards-docs", + templateUrl: "./hello-dashboards-docs.component.html", + standalone: false, +}) +export class HelloDashboardsDocsComponent {} +\`, + "tutorials/hello-dashboards/hello-dashboards-example/hello-dashboards-example.component.ts": \`// © 2022 SolarWinds Worldwide, LLC. All rights reserved. +// +// Permission is hereby granted, free of charge, to any person obtaining a copy +// of this software and associated documentation files (the "Software"), to +// deal in the Software without restriction, including without limitation the +// rights to use, copy, modify, merge, publish, distribute, sublicense, and/or +// sell copies of the Software, and to permit persons to whom the Software is +// furnished to do so, subject to the following conditions: +// +// The above copyright notice and this permission notice shall be included in +// all copies or substantial portions of the Software. +// +// THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR +// IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, +// FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE +// AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER +// LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, +// OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN +// THE SOFTWARE. + +import { Component, OnInit } from "@angular/core"; +import { GridsterConfig, GridsterItem } from "angular-gridster2"; + +import { + IDashboard, + IWidget, + IWidgets, + KpiComponent, + PizzagnaLayer, + WidgetTypesService, +} from "@nova-ui/dashboards"; + +/** + * A component that instantiates the dashboard + */ +@Component({ + selector: "hello-dashboards-example", + templateUrl: "./hello-dashboards-example.component.html", + styleUrls: ["./hello-dashboards-example.component.less"], + standalone: false, +}) +export class HelloDashboardsExampleComponent implements OnInit { + // This variable will have all the data needed to render the widgets widgets. + // Pass this to the dashboard component's dashboard input. + public dashboard: IDashboard; + // Angular gridster requires a configuration object even if its empty. + // Pass this to the dashboard component's gridsterConfig input. + public gridsterConfig: GridsterConfig = {}; + + // WidgetTypesService provides the widget's necessary structure information + constructor(private widgetTypesService: WidgetTypesService) {} + + public ngOnInit(): void { + // Here we are hard-coding the widget config for this example, but this is where you + // could potentially populate the widget's configuration from a database + const kpiWidget = widgetConfig; + const widgetIndex: IWidgets = { + // Complete the KPI widget with information coming from its type definition + [kpiWidget.id]: + this.widgetTypesService.mergeWithWidgetType(kpiWidget), + }; + // Setting widget position and dimensions (this is for gridster) + const positions: Record = { + [kpiWidget.id]: { + cols: 4, + rows: 6, + y: 0, + x: 0, + }, + }; + // Finally, assigning the variables we created above to the dashboard + this.dashboard = { + positions, + widgets: widgetIndex, + }; + } +} + +// In a real-world scenario, this configuration would typically be fetched from a database or at least live in another file +const widgetConfig: IWidget = { + id: "widget1", + type: "kpi", + pizzagna: { + [PizzagnaLayer.Configuration]: { + header: { + properties: { + title: "Hello, KPI Widget!", + subtitle: "A Venue for Meaningful Values", + }, + }, + tiles: { + properties: { + nodes: ["kpi1"], + }, + }, + kpi1: { + id: "kpi1", + componentType: KpiComponent.lateLoadKey, + properties: { + widgetData: { + id: "totalStorage", + value: 1, + label: "Total storage", + units: "TB", + }, + }, + }, + }, + }, +}; +\`, + "tutorials/hello-dashboards/hello-dashboards.module.ts": \`// © 2022 SolarWinds Worldwide, LLC. All rights reserved. +// +// Permission is hereby granted, free of charge, to any person obtaining a copy +// of this software and associated documentation files (the "Software"), to +// deal in the Software without restriction, including without limitation the +// rights to use, copy, modify, merge, publish, distribute, sublicense, and/or +// sell copies of the Software, and to permit persons to whom the Software is +// furnished to do so, subject to the following conditions: +// +// The above copyright notice and this permission notice shall be included in +// all copies or substantial portions of the Software. +// +// THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR +// IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, +// FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE +// AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER +// LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, +// OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN +// THE SOFTWARE. + +import { CommonModule } from "@angular/common"; +import { NgModule } from "@angular/core"; +import { RouterModule } from "@angular/router"; + +import { + NuiDocsModule, + NuiMessageModule, + DEMO_PATH_TOKEN, +} from "@nova-ui/bits"; +import { NuiDashboardsModule } from "@nova-ui/dashboards"; + +import { HelloDashboardsDocsComponent } from "./hello-dashboards-docs.component"; +import { HelloDashboardsExampleComponent } from "./hello-dashboards-example/hello-dashboards-example.component"; +import { getDemoFiles } from "../../../../demo-files-factory"; + +const routes = [ + { + path: "", + component: HelloDashboardsDocsComponent, + data: { + srlc: { + hideIndicator: true, + }, + showThemeSwitcher: true, + }, + }, + { + path: "example", + component: HelloDashboardsExampleComponent, + data: { + srlc: { + hideIndicator: true, + }, + }, + }, +]; + +@NgModule({ + imports: [ + CommonModule, + NuiDashboardsModule, + NuiDocsModule, + NuiMessageModule, + RouterModule.forChild(routes), + ], + declarations: [ + HelloDashboardsDocsComponent, + HelloDashboardsExampleComponent, + ], + providers: [ + { + provide: DEMO_PATH_TOKEN, + useValue: getDemoFiles("hello-dashboards"), + }, + ], +}) +export default class HelloDashboardsModule {} +\`, + "tutorials/persistence-handler-setup/persistence-handler-setup-docs.component.ts": \`// © 2022 SolarWinds Worldwide, LLC. All rights reserved. +// +// Permission is hereby granted, free of charge, to any person obtaining a copy +// of this software and associated documentation files (the "Software"), to +// deal in the Software without restriction, including without limitation the +// rights to use, copy, modify, merge, publish, distribute, sublicense, and/or +// sell copies of the Software, and to permit persons to whom the Software is +// furnished to do so, subject to the following conditions: +// +// The above copyright notice and this permission notice shall be included in +// all copies or substantial portions of the Software. +// +// THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR +// IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, +// FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE +// AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER +// LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, +// OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN +// THE SOFTWARE. + +import { Component } from "@angular/core"; + +@Component({ + selector: "nui-dashboard-persistence-handler-setup-docs", + templateUrl: "./persistence-handler-setup-docs.component.html", + standalone: false, +}) +export class PersistenceHandlerSetupDocsComponent {} +\`, + "tutorials/persistence-handler-setup/persistence-handler-setup.component.ts": \`// © 2022 SolarWinds Worldwide, LLC. All rights reserved. +// +// Permission is hereby granted, free of charge, to any person obtaining a copy +// of this software and associated documentation files (the "Software"), to +// deal in the Software without restriction, including without limitation the +// rights to use, copy, modify, merge, publish, distribute, sublicense, and/or +// sell copies of the Software, and to permit persons to whom the Software is +// furnished to do so, subject to the following conditions: +// +// The above copyright notice and this permission notice shall be included in +// all copies or substantial portions of the Software. +// +// THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR +// IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, +// FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE +// AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER +// LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, +// OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN +// THE SOFTWARE. + +import { HttpClient, HttpErrorResponse } from "@angular/common/http"; +import { + ChangeDetectorRef, + Component, + Injectable, + OnDestroy, + OnInit, +} from "@angular/core"; +import { GridsterConfig, GridsterItem } from "angular-gridster2"; +import { BehaviorSubject, Observable, Subject } from "rxjs"; +import { finalize } from "rxjs/operators"; + +import { + DataSourceService, + IFilteringOutputs, + ToastService, + uuid, +} from "@nova-ui/bits"; +import { + DATA_SOURCE, + DEFAULT_PIZZAGNA_ROOT, + IDashboard, + IDashboardPersistenceHandler, + IKpiData, + IProviderConfiguration, + IRefresherProperties, + IWidget, + IWidgets, + KpiComponent, + NOVA_KPI_DATASOURCE_ADAPTER, + PizzagnaLayer, + ProviderRegistryService, + WellKnownPathKey, + WellKnownProviders, + WidgetTypesService, +} from "@nova-ui/dashboards"; + +/** + * A simple persistence handler that is tied to the widget editor directive + */ +@Injectable() +// The realizer of IDashboardPersistenceHandler may implement a trySubmit and/or a tryRemove method. +export class PersistenceHandler implements IDashboardPersistenceHandler { + // This variable is just to show how to handle error handling. + private persistenceSucceeded: boolean = true; + + // The example uses the toast service to demonstrate the + // invocation of each of the persistence handler callbacks + constructor(private toastService: ToastService) { + // toastService options to let it sit on the page for 2 seconds. + this.toastService.setConfig({ + timeOut: 2000, + }); + } + + // This method will be invoked anytime the widget editor form gets submitted. + public trySubmit = (widget: IWidget): Observable => { + // Since we are working asynchronously, we'll return a subject. So, after the submit attempt + // succeeds or fails, we can let the subscriber know the result. + const subject = new Subject(); + + if (!widget.id) { + // Creates an id if the widget has no id. + // (This step will make more sense in the context of the widget cloning tutorial + // in which we handle the persistence of a newly created widget.) + widget.id = uuid(); + } + + // For this example, we're using a setTimeout to mock an asynchronous persistence request to a backend + setTimeout(() => { + if (this.persistenceSucceeded) { + // Passes along the new widget after one second. + subject.next(widget); + // Toast on the page on success. + this.toastService.success({ + title: $localize\\\`Submit succeeded.\\\`, + }); + } else { + const errorText = $localize\\\`Submit failed.\\\`; + // Toast on the page on failure. + this.toastService.error({ title: errorText }); + // Makes the subject say there is an error. + subject.error(errorText); + } + // Completes the subject so whoever subscribes to it knows its finished. + subject.complete(); + }, 1000); + + // Returns the subject as an observable. + return subject.asObservable(); + }; + + // This method will be invoked anytime there's a widget removal attempt. + public tryRemove = (widgetId: string): Observable => { + const subject = new Subject(); + + setTimeout(() => { + if (this.persistenceSucceeded) { + // Pass through the id of the widget that was removed. + subject.next(widgetId); + this.toastService.success({ + title: $localize\\\`Removal succeeded.\\\`, + }); + } else { + const errorText = $localize\\\`Removal failed.\\\`; + this.toastService.error({ title: errorText }); + subject.error(errorText); + } + subject.complete(); + }, 1000); + + return subject.asObservable(); + }; +} + +/** + * A component that instantiates the dashboard + */ +@Component({ + selector: "persistence-handler-setup", + templateUrl: "./persistence-handler-setup.component.html", + styleUrls: ["./persistence-handler-setup.component.less"], + // Here we provide our persistence handler at the component level; this can also be done in the module. + providers: [PersistenceHandler], + standalone: false, +}) +export class PersistenceHandlerSetupComponent implements OnInit { + // This variable will hold all the data needed to define the layout and behavior of the widgets. + // Pass this to the dashboard component's dashboard input in the template. + public dashboard: IDashboard | undefined; + + // Angular gridster requires a configuration object even if it's empty. + // Pass this to the dashboard component's gridsterConfig input in the template. + public gridsterConfig: GridsterConfig = {}; + + // Boolean which dashboard takes in as an input if its true it allows you to move widgets around. + public editMode: boolean = false; + + constructor( + // WidgetTypesService provides the widget's necessary structure information + private widgetTypesService: WidgetTypesService, + + // In general, the ProviderRegistryService is used for making entities available for injection into dynamically loaded components. + private providerRegistry: ProviderRegistryService, + + // We are injecting the PersistenceHandler we created and assigning it to a property we use in the template. + public persistenceHandler: PersistenceHandler, + private changeDetectorRef: ChangeDetectorRef + ) {} + + public ngOnInit(): void { + // Grabbing the widget's default template which will be needed as a parameter for setNode + const widgetTemplate = this.widgetTypesService.getWidgetType("kpi", 1); + // Registering our data sources as dropdown options in the widget editor/configurator + // Note: This could also be done in the parent module's constructor so that + // multiple dashboards could have access to the same widget template modification. + this.widgetTypesService.setNode( + // This is the template we grabbed above with getWidgetType + widgetTemplate, + // We are setting the editor/configurator part of the widget template + "configurator", + // This indicates which node you are changing and we want to change + // the data source providers available for selection in the editor. + WellKnownPathKey.DataSourceProviders, + // We are setting the data sources available for selection in the editor + [ + AverageRatingKpiDataSource.providerId, + RatingsCountKpiDataSource.providerId, + ] + ); + + // Registering the data sources available for injection into the KPI tiles. + // Note: Each tile of a KPI widget is assigned its own instance of a data source + this.providerRegistry.setProviders({ + [AverageRatingKpiDataSource.providerId]: { + provide: DATA_SOURCE, + useClass: AverageRatingKpiDataSource, + // Any dependencies that need to be injected into the provider must be listed here + deps: [HttpClient], + }, + [RatingsCountKpiDataSource.providerId]: { + provide: DATA_SOURCE, + useClass: RatingsCountKpiDataSource, + deps: [HttpClient], + }, + }); + + this.initializeDashboard(); + } + + /** Used for restoring widgets state */ + public reInitializeDashboard(): void { + // destroys the components and their providers so the dashboard can re init data + this.dashboard = undefined; + this.changeDetectorRef.detectChanges(); + + this.initializeDashboard(); + } + + public initializeDashboard(): void { + // We're using a static configuration object for this example (see widgetConfig at the bottom of the file), + // but this is where the widget's configuration could potentially be populated from a database + const kpiWidget = widgetConfig; + const widgetIndex: IWidgets = { + // Complete the KPI widget with information coming from its type definition + [kpiWidget.id]: + this.widgetTypesService.mergeWithWidgetType(kpiWidget), + }; + + // Setting the widget dimensions and position (this is for gridster) + const positions: Record = { + [kpiWidget.id]: { + cols: 4, + rows: 6, + y: 0, + x: 0, + }, + }; + + // Finally, assigning the variables we created above to the dashboard + this.dashboard = { + positions, + widgets: widgetIndex, + }; + } +} + +/** + * A simple KPI data source to retrieve the average rating of Harry Potter and the Sorcerer's Stone (book) via googleapis + */ +@Injectable() +export class AverageRatingKpiDataSource + extends DataSourceService + implements OnDestroy +{ + // This is the ID we'll use to identify the provider + public static providerId = "AverageRatingKpiDataSource"; + + // Use this subject to communicate the data source's busy state + public busy = new BehaviorSubject(false); + + constructor(private http: HttpClient) { + super(); + } + + // In this example, getFilteredData is invoked every 10 minutes (Take a look at the refresher + // provider definition in the widget configuration below to see how the interval is set) + public async getFilteredData(): Promise { + this.busy.next(true); + return new Promise((resolve) => { + // *** Make a resource request to an external API (if needed) + this.http + .get("https://www.googleapis.com/books/v1/volumes/5MQFrgEACAAJ") + .pipe(finalize(() => this.busy.next(false))) + .subscribe({ + next: (data: any) => { + resolve({ + result: { + value: data.volumeInfo.averageRating, + }, + }); + }, + error: (error: HttpErrorResponse) => { + resolve({ + result: null, + error: { + type: error.status, + }, + }); + }, + }); + }); + } + + public ngOnDestroy(): void { + this.outputsSubject.complete(); + } +} + +/** + * A simple KPI data source to retrieve the ratings count of Harry Potter and the Sorcerer's Stone (book) via googleapis + */ +@Injectable() +export class RatingsCountKpiDataSource + extends DataSourceService + implements OnDestroy +{ + public static providerId = "RatingsCountKpiDataSource"; + + // Use this subject to communicate the data source's busy state + public busy = new BehaviorSubject(false); + + constructor(private http: HttpClient) { + super(); + } + + public async getFilteredData(): Promise { + this.busy.next(true); + return new Promise((resolve) => { + this.http + .get("https://www.googleapis.com/books/v1/volumes/5MQFrgEACAAJ") + .pipe(finalize(() => this.busy.next(false))) + .subscribe({ + next: (data: any) => { + resolve({ + result: { + value: data.volumeInfo.ratingsCount, + }, + }); + }, + error: (error: HttpErrorResponse) => { + resolve({ + result: null, + error: { + type: error.status, + }, + }); + }, + }); + }); + } + + public ngOnDestroy(): void { + this.outputsSubject.complete(); + } +} + +const widgetConfig: IWidget = { + id: "widget1", + type: "kpi", + pizzagna: { + [PizzagnaLayer.Configuration]: { + [DEFAULT_PIZZAGNA_ROOT]: { + providers: { + [WellKnownProviders.Refresher]: { + properties: { + // Configuring the refresher interval so that our data source is invoked every ten minutes + interval: 60 * 10, + enabled: true, + } as IRefresherProperties, + } as Partial, + }, + }, + header: { + properties: { + title: "Harry Potter and the Sorcerer's Stone", + subtitle: "By J. K. Rowling", + }, + }, + tiles: { + properties: { + nodes: ["kpi1"], + }, + }, + kpi1: { + id: "kpi1", + componentType: KpiComponent.lateLoadKey, + properties: { + widgetData: { + units: "out of 5 Stars", + label: "Average Rating", + }, + }, + providers: { + [WellKnownProviders.DataSource]: { + // Setting the data source providerId for the tile with id "kpi1" + providerId: AverageRatingKpiDataSource.providerId, + } as IProviderConfiguration, + [WellKnownProviders.Adapter]: { + providerId: NOVA_KPI_DATASOURCE_ADAPTER, + properties: { + componentId: "kpi1", + propertyPath: "widgetData", + }, + } as IProviderConfiguration, + }, + }, + }, + }, +}; +\`, + "tutorials/persistence-handler-setup/persistence-handler-setup.module.ts": \`// © 2022 SolarWinds Worldwide, LLC. All rights reserved. +// +// Permission is hereby granted, free of charge, to any person obtaining a copy +// of this software and associated documentation files (the "Software"), to +// deal in the Software without restriction, including without limitation the +// rights to use, copy, modify, merge, publish, distribute, sublicense, and/or +// sell copies of the Software, and to permit persons to whom the Software is +// furnished to do so, subject to the following conditions: +// +// The above copyright notice and this permission notice shall be included in +// all copies or substantial portions of the Software. +// +// THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR +// IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, +// FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE +// AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER +// LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, +// OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN +// THE SOFTWARE. + +import { CommonModule } from "@angular/common"; +import { HttpClientModule } from "@angular/common/http"; +import { NgModule } from "@angular/core"; +import { RouterModule } from "@angular/router"; + +import { + NuiButtonModule, + NuiDocsModule, + NuiMessageModule, + NuiSwitchModule, + NuiToastModule, + DEMO_PATH_TOKEN, +} from "@nova-ui/bits"; +import { NuiDashboardsModule } from "@nova-ui/dashboards"; + +import { PersistenceHandlerSetupDocsComponent } from "./persistence-handler-setup-docs.component"; +import { PersistenceHandlerSetupComponent } from "./persistence-handler-setup.component"; +import { getDemoFiles } from "../../../../demo-files-factory"; + +const routes = [ + { + path: "", + component: PersistenceHandlerSetupDocsComponent, + data: { + srlc: { + hideIndicator: true, + }, + showThemeSwitcher: true, + }, + }, + { + path: "example", + component: PersistenceHandlerSetupComponent, + data: { + srlc: { + hideIndicator: true, + }, + }, + }, +]; + +@NgModule({ + imports: [ + CommonModule, + HttpClientModule, + NuiDashboardsModule, + NuiDocsModule, + NuiMessageModule, + NuiSwitchModule, + NuiToastModule, + NuiButtonModule, + RouterModule.forChild(routes), + ], + declarations: [ + PersistenceHandlerSetupDocsComponent, + PersistenceHandlerSetupComponent, + ], + providers: [ + { + provide: DEMO_PATH_TOKEN, + useValue: getDemoFiles("persistence-handler-setup"), + }, + ], +}) +export default class PersistenceHandlerSetupModule {} +\`, + "tutorials/tutorials.module.ts": \`// © 2022 SolarWinds Worldwide, LLC. All rights reserved. +// +// Permission is hereby granted, free of charge, to any person obtaining a copy +// of this software and associated documentation files (the "Software"), to +// deal in the Software without restriction, including without limitation the +// rights to use, copy, modify, merge, publish, distribute, sublicense, and/or +// sell copies of the Software, and to permit persons to whom the Software is +// furnished to do so, subject to the following conditions: +// +// The above copyright notice and this permission notice shall be included in +// all copies or substantial portions of the Software. +// +// THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR +// IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, +// FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE +// AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER +// LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, +// OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN +// THE SOFTWARE. + +import { NgModule, Type } from "@angular/core"; +import { RouterModule, Routes } from "@angular/router"; + +import { ConfiguratorHeadingService } from "@nova-ui/dashboards"; + +export enum TutorialsModuleRoute { + HelloDashboards = "hello-dashboards", + DataSource = "data-source-setup", + WidgetEditor = "widget-editor-setup", + SubmitHandler = "persistence-handler-setup", + WidgetCreation = "widget-creation", + Customization = "customization", + WidgetErrorHandling = "widget-error-handling", + DynamicHeaderLinks = "dynamic-header-links", +} + +const routes: Routes = [ + { + path: TutorialsModuleRoute.HelloDashboards, + loadChildren: async () => + import( + "./hello-dashboards/hello-dashboards.module" + ) as object as Promise>, + }, + { + path: TutorialsModuleRoute.DataSource, + loadChildren: async () => + import( + "./data-source-setup/data-source-setup.module" + ) as object as Promise>, + }, + { + path: TutorialsModuleRoute.WidgetEditor, + loadChildren: async () => + import( + "./widget-editor-setup/widget-editor-setup.module" + ) as object as Promise>, + }, + { + path: TutorialsModuleRoute.SubmitHandler, + loadChildren: async () => + import( + "./persistence-handler-setup/persistence-handler-setup.module" + ) as object as Promise>, + }, + { + path: TutorialsModuleRoute.WidgetCreation, + loadChildren: async () => + import( + "./widget-creation/widget-creation.module" + ) as object as Promise>, + }, + { + path: TutorialsModuleRoute.Customization, + loadChildren: async () => + import("./customization/customization.module") as object as Promise< + Type + >, + }, + { + path: TutorialsModuleRoute.WidgetErrorHandling, + loadChildren: async () => + import( + "./widget-error-handling/widget-error-handling.module" + ) as object as Promise>, + }, + { + path: TutorialsModuleRoute.DynamicHeaderLinks, + loadChildren: async () => + import( + "./dynamic-header-links/dynamic-header-links-docs.module" + ) as object as Promise>, + }, +]; + +@NgModule({ + imports: [RouterModule.forChild(routes)], + providers: [ConfiguratorHeadingService], +}) +export default class TutorialsModule {} +\`, + "tutorials/widget-creation/widget-creation-docs.component.ts": \`// © 2022 SolarWinds Worldwide, LLC. All rights reserved. +// +// Permission is hereby granted, free of charge, to any person obtaining a copy +// of this software and associated documentation files (the "Software"), to +// deal in the Software without restriction, including without limitation the +// rights to use, copy, modify, merge, publish, distribute, sublicense, and/or +// sell copies of the Software, and to permit persons to whom the Software is +// furnished to do so, subject to the following conditions: +// +// The above copyright notice and this permission notice shall be included in +// all copies or substantial portions of the Software. +// +// THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR +// IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, +// FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE +// AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER +// LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, +// OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN +// THE SOFTWARE. + +import { Component } from "@angular/core"; + +@Component({ + selector: "nui-dashboard-widget-creation-docs", + templateUrl: "./widget-creation-docs.component.html", + standalone: false, +}) +export class WidgetCreationDocsComponent {} +\`, + "tutorials/widget-creation/widget-creation.component.ts": \`// © 2022 SolarWinds Worldwide, LLC. All rights reserved. +// +// Permission is hereby granted, free of charge, to any person obtaining a copy +// of this software and associated documentation files (the "Software"), to +// deal in the Software without restriction, including without limitation the +// rights to use, copy, modify, merge, publish, distribute, sublicense, and/or +// sell copies of the Software, and to permit persons to whom the Software is +// furnished to do so, subject to the following conditions: +// +// The above copyright notice and this permission notice shall be included in +// all copies or substantial portions of the Software. +// +// THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR +// IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, +// FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE +// AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER +// LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, +// OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN +// THE SOFTWARE. + +import { HttpClient, HttpErrorResponse } from "@angular/common/http"; +import { + Component, + EventEmitter, + Injectable, + OnDestroy, + OnInit, + Output, + ViewChild, +} from "@angular/core"; +import { GridsterConfig, GridsterItem } from "angular-gridster2"; +import { BehaviorSubject, Observable, Subject } from "rxjs"; +import { finalize, take, takeUntil } from "rxjs/operators"; + +import { + DataSourceService, + IFilteringOutputs, + ToastService, + uuid, +} from "@nova-ui/bits"; +import { + DashboardComponent, + DATA_SOURCE, + DEFAULT_PIZZAGNA_ROOT, + IDashboard, + IDashboardPersistenceHandler, + IDataSourceOutput, + IKpiData, + IProviderConfiguration, + IRefresherProperties, + IWidget, + IWidgets, + IWidgetSelector, + IWidgetTemplateSelector, + KpiComponent, + NOVA_KPI_DATASOURCE_ADAPTER, + PizzagnaLayer, + ProviderRegistryService, + WellKnownPathKey, + WellKnownProviders, + WidgetClonerService, + WidgetTypesService, +} from "@nova-ui/dashboards"; + +// Interface of a widget item +interface IWidgetItem { + name: string; + widget: IWidget; +} + +// This component acts as the first step, or page, in the wizard where the user selects a wizard type to create. +// It's recommended to have this component in a different file. For this tutorial, it's included in the same +// file for simplicity. +@Component({ + selector: "widget-template-selection", + styleUrls: ["./widget-creation.component.less"], + template: \\\` +
+ + +
+ + +
+
{{ item.name }}
+
+
+ \\\`, + standalone: false, +}) +export class WidgetTemplateSelectionComponent + implements IWidgetTemplateSelector, OnInit +{ + // This output will notify the wizard that a widget has been selected. + @Output() public widgetSelected = new EventEmitter(); + + public widgetItems: IWidgetItem[] = []; + public widgetSelection: IWidgetItem[]; + + constructor(private widgetTypesService: WidgetTypesService) {} + + public ngOnInit(): void { + // Here we combine the widget structure from the WidgetTypesService with the corresponding widget + // configuration to create an array of widget objects for the itemSource on the repeat component. + this.widgetItems = [ + { + name: "Fully Configured KPI Widget", + widget: this.widgetTypesService.mergeWithWidgetType( + fullKpiWidgetConfig + ), + }, + { + name: "Unconfigured Proportional Widget", + // Note that 'partialPropWidgetConfig' sets 'metadata.needsConfiguration' to true. + // When this widget is selected in the wizard, the 'Create Widget' button will be hidden + // to guide the user to the second step where they can complete the configuration. + widget: this.widgetTypesService.mergeWithWidgetType( + partialPropWidgetConfig + ), + }, + ]; + + // You can optionally auto-select a widget by doing the following + // this.onSelect([this.widgetItems[0]]); + } + + public onSelect(selectedItems: any[]): void { + // We emit the selected widget to communicate the selection to the configurator + this.widgetSelected.emit(selectedItems[0].widget); + this.widgetSelection = selectedItems; + } +} + +/** + * A simple persistence handler that is tied to the widget editor directive + */ +@Injectable() +// The realizer of IDashboardPersistenceHandler may implement a trySubmit and/or a tryRemove method. +export class PersistenceHandler implements IDashboardPersistenceHandler { + // This variable is just to show how to handle error handling. + private persistenceSucceeded: boolean = true; + + // The example uses the toast service to demonstrate the + // invocation of each of the persistence handler callbacks + constructor(private toastService: ToastService) { + // toastService options to let it sit on the page for 2 seconds. + this.toastService.setConfig({ + timeOut: 2000, + }); + } + + // This method will be invoked anytime the widget editor form gets submitted. + public trySubmit = (widget: IWidget): Observable => { + // Since we are working asynchronously, we'll return a subject. So, after the submit attempt + // succeeds or fails, we can let the subscriber know the result. + const subject = new Subject(); + + if (!widget.id) { + // Creates an id if the widget has no id. + // (This step will make more sense in the context of the widget cloning tutorial + // in which we handle the persistence of a newly created widget.) + widget.id = uuid(); + } + + // For this example, we're using a setTimeout to mock an asynchronous persistence request to a backend + setTimeout(() => { + if (this.persistenceSucceeded) { + // Passes along the new widget after one second. + subject.next(widget); + // Toast on the page on success. + this.toastService.success({ + title: $localize\\\`Submit succeeded.\\\`, + }); + } else { + const errorText = $localize\\\`Submit failed.\\\`; + // Toast on the page on failure. + this.toastService.error({ title: errorText }); + // Makes the subject say there is an error. + subject.error(errorText); + } + // Completes the subject so whoever subscribes to it knows its finished. + subject.complete(); + }, 1000); + + // Returns the subject as an observable. + return subject.asObservable(); + }; + + // This method will be invoked anytime there's a widget removal attempt. + public tryRemove = (widgetId: string): Observable => { + const subject = new Subject(); + + setTimeout(() => { + if (this.persistenceSucceeded) { + // Pass through the id of the widget that was removed. + subject.next(widgetId); + this.toastService.success({ + title: $localize\\\`Removal success\\\`, + }); + } else { + const errorText = $localize\\\`Removal failed.\\\`; + this.toastService.error({ title: errorText }); + subject.error(errorText); + } + subject.complete(); + }, 1000); + + return subject.asObservable(); + }; +} + +/** + * A component that instantiates the dashboard + */ +@Component({ + selector: "widget-creation", + templateUrl: "./widget-creation.component.html", + styleUrls: ["./widget-creation.component.less"], + // Here we provide our persistence handler at the component level; this can also be done in the module. + providers: [PersistenceHandler], + standalone: false, +}) +export class WidgetCreationComponent implements OnInit { + // The WidgetClonerService will need this for updating the dashboard + @ViewChild(DashboardComponent, { static: true }) + dashboardComponent: DashboardComponent; + // This variable will hold all the data needed to define the layout and behavior of the widgets. + // Pass this to the dashboard component's dashboard input in the template. + public dashboard: IDashboard; + + // Angular gridster requires a configuration object even if it's empty. + // Pass this to the dashboard component's gridsterConfig input in the template. + public gridsterConfig: GridsterConfig = { + // These values will be used to set the initial widget dimensions on creation. + // If not set, they each default to 6. + defaultItemCols: 3, + defaultItemRows: 5, + }; + + // Boolean the dashboard takes in as an input; if it's set to true + // the dashboard allows you to resize widgets and move them around. + public editMode: boolean = false; + + // Subject used for auto-unsubscribing from subscriptions on component destruction + private readonly destroy$ = new Subject(); + + constructor( + // WidgetTypesService provides the widget's necessary structure information + private widgetTypesService: WidgetTypesService, + + // In general, the ProviderRegistryService is used for making entities available for injection into dynamically loaded components. + private providerRegistry: ProviderRegistryService, + + // Injecting the PersistenceHandler we created and assigning it to a property we use in the template. + public persistenceHandler: PersistenceHandler, + + // Injecting the cloner service which is needed for opening up the cloner wizard. + private widgetClonerService: WidgetClonerService + ) {} + + public ngOnInit(): void { + // Grabbing the widget's default template which will be needed as a parameter for setNode + const kpiTemplate = this.widgetTypesService.getWidgetType("kpi", 1); + const proportionalTemplate = this.widgetTypesService.getWidgetType( + "proportional", + 1 + ); + + // Registering our data sources as dropdown options in the widget editor/configurator + // Note: This could also be done in the parent module's constructor so that + // multiple dashboards could have access to the same widget template modification. + this.widgetTypesService.setNode( + // This is the template we grabbed above with getWidgetType + proportionalTemplate, + // Setting the editor/configurator part of the widget template + "configurator", + // This indicates which node you are changing and we want to change + // the data source providers available for selection in the editor. + WellKnownPathKey.DataSourceProviders, + // Setting the data sources available for selection in the editor + [RandomCitiesProportionalDataSource.providerId] + ); + + // Same as above, but for the KPI data sources + this.widgetTypesService.setNode( + kpiTemplate, + "configurator", + WellKnownPathKey.DataSourceProviders, + [ + AverageRatingKpiDataSource.providerId, + RatingsCountKpiDataSource.providerId, + ] + ); + + // Registering the data sources available for injection into the KPI tiles and proportional widget. + // Note: Each tile of a KPI widget is assigned its own instance of a data source. + this.providerRegistry.setProviders({ + [AverageRatingKpiDataSource.providerId]: { + provide: DATA_SOURCE, + useClass: AverageRatingKpiDataSource, + // Any dependencies that need to be injected into the provider must be listed here + deps: [HttpClient], + }, + [RatingsCountKpiDataSource.providerId]: { + provide: DATA_SOURCE, + useClass: RatingsCountKpiDataSource, + deps: [HttpClient], + }, + [RandomCitiesProportionalDataSource.providerId]: { + provide: DATA_SOURCE, + useClass: RandomCitiesProportionalDataSource, + deps: [], + }, + }); + + this.initializeDashboard(); + } + + public onCreateWidget(): void { + const widgetSelector: IWidgetSelector = { + // Template ref of the dashboard component. + dashboardComponent: this.dashboardComponent, + // A trySubmit function; in this case, we use the trySubmit from the PersistenceHandler created in the previous tutorial. + trySubmit: this.persistenceHandler.trySubmit, + // WidgetTemplateSelectionComponent will act as step one of the wizard to allow the user to select which widget will be cloned. + widgetSelectionComponentType: WidgetTemplateSelectionComponent, + }; + this.widgetClonerService + .open(widgetSelector) + .pipe( + // Auto-unsubscribe after one emission or on component destruction + take(1), + takeUntil(this.destroy$) + ) + .subscribe(); + } + + public initializeDashboard(): void { + // We're using a static configuration object for this example (see widgetConfig at the bottom of the file), + // but this is where the widget's configuration could potentially be populated from a database + const kpiWidget = fullKpiWidgetConfig; + const widgetIndex: IWidgets = { + // Complete the KPI widget with information coming from its type definition + [kpiWidget.id]: + this.widgetTypesService.mergeWithWidgetType(kpiWidget), + }; + + // Setting the widget dimensions and position (this is for gridster) + // Note: If no position is given for a widget the 'defaultItemCols' and 'defaultItemRows' properties + // from the gridsterConfig will be used for the dimensions + const positions: Record = { + [kpiWidget.id]: { + cols: 3, + rows: 5, + y: 0, + x: 0, + }, + }; + + // Finally, assigning the variables we created above to the dashboard + this.dashboard = { + positions, + widgets: widgetIndex, + }; + } +} + +// Interface for each data point in a proportional widget. +interface IProportionalWidgetData { + id: string; + name: string; + data: number[]; + icon: string; + link: string; + value: string; +} + +@Injectable() +export class RandomCitiesProportionalDataSource implements OnDestroy { + public static providerId = "RandomCitiesProportionalDataSource"; + + public outputsSubject = new Subject< + IDataSourceOutput + >(); + + // Every time applyFilters gets ran we are changing the data source. + public applyFilters(): void { + setTimeout(() => { + this.outputsSubject.next({ + result: this.getRandomProportionalWidgetData(), + }); + }, 1000); + } + + public ngOnDestroy(): void { + this.outputsSubject.complete(); + } + + private getRandomProportionalWidgetData(): IProportionalWidgetData[] { + return [ + { + id: "Down", + name: "Down", + data: [Math.round(Math.random() * 100)], + icon: "status_down", + link: "https://en.wikipedia.org/wiki/Brno", + value: "Brno", + }, + { + id: "Critical", + name: "Critical", + data: [Math.round(Math.random() * 100)], + icon: "status_critical", + link: "https://en.wikipedia.org/wiki/Kyiv", + value: "Kyiv", + }, + { + id: "Warning", + name: "Warning", + data: [Math.round(Math.random() * 100)], + icon: "status_warning", + link: "https://en.wikipedia.org/wiki/Austin", + value: "Austin", + }, + { + id: "Unknown", + name: "Unknown", + data: [Math.round(Math.random() * 100)], + icon: "status_unknown", + link: "https://en.wikipedia.org/wiki/Lisbon", + value: "Lisbon", + }, + { + id: "Up", + name: "Up", + data: [Math.round(Math.random() * 100)], + icon: "status_up", + link: "https://en.wikipedia.org/wiki/Sydney", + value: "Sydney", + }, + { + id: "Unmanaged", + name: "Unmanaged", + data: [Math.round(Math.random() * 100)], + icon: "status_unmanaged", + link: "https://en.wikipedia.org/wiki/Nur-Sultan", + value: "Nur-Sultan", + }, + ]; + } +} + +/** + * A simple KPI data source to retrieve the average rating of Harry Potter and the Sorcerer's Stone (book) via googleapis + */ +@Injectable() +export class AverageRatingKpiDataSource + extends DataSourceService + implements OnDestroy +{ + // This is the ID we'll use to identify the provider + public static providerId = "AverageRatingKpiDataSource"; + + // Use this subject to communicate the data source's busy state + public busy = new BehaviorSubject(false); + + constructor(private http: HttpClient) { + super(); + } + + // In this example, getFilteredData is invoked every 10 minutes (Take a look at the refresher + // provider definition in the widget configuration below to see how the interval is set) + public async getFilteredData(): Promise { + this.busy.next(true); + return new Promise((resolve) => { + // *** Make a resource request to an external API (if needed) + this.http + .get("https://www.googleapis.com/books/v1/volumes/5MQFrgEACAAJ") + .pipe(finalize(() => this.busy.next(false))) + .subscribe({ + next: (data: any) => { + resolve({ + result: { + value: data.volumeInfo.averageRating, + }, + }); + }, + error: (error: HttpErrorResponse) => { + resolve({ + result: null, + error: { + type: error.status, + }, + }); + }, + }); + }); + } + + public ngOnDestroy(): void { + this.outputsSubject.complete(); + } +} + +/** + * A simple KPI data source to retrieve the ratings count of Harry Potter and the Sorcerer's Stone (book) via googleapis + */ +@Injectable() +export class RatingsCountKpiDataSource + extends DataSourceService + implements OnDestroy +{ + public static providerId = "RatingsCountKpiDataSource"; + + // Use this subject to communicate the data source's busy state + public busy = new BehaviorSubject(false); + + constructor(private http: HttpClient) { + super(); + } + + public async getFilteredData(): Promise { + this.busy.next(true); + return new Promise((resolve) => { + this.http + .get("https://www.googleapis.com/books/v1/volumes/5MQFrgEACAAJ") + .pipe(finalize(() => this.busy.next(false))) + .subscribe({ + next: (data: any) => { + resolve({ + result: { + value: data.volumeInfo.ratingsCount, + }, + }); + }, + error: (error: HttpErrorResponse) => { + resolve({ + result: null, + error: { + type: error.status, + }, + }); + }, + }); + }); + } + + public ngOnDestroy(): void { + this.outputsSubject.complete(); + } +} + +const fullKpiWidgetConfig: IWidget = { + id: "widget1", + type: "kpi", + pizzagna: { + [PizzagnaLayer.Configuration]: { + [DEFAULT_PIZZAGNA_ROOT]: { + providers: { + [WellKnownProviders.Refresher]: { + properties: { + // Configuring the refresher interval so that our data source is invoked every ten minutes + interval: 60 * 10, + enabled: true, + } as IRefresherProperties, + } as Partial, + }, + }, + header: { + properties: { + title: "Harry Potter and the Sorcerer's Stone", + subtitle: "By J. K. Rowling", + }, + }, + tiles: { + properties: { + nodes: ["kpi1"], + }, + }, + kpi1: { + id: "kpi1", + componentType: KpiComponent.lateLoadKey, + properties: { + widgetData: { + units: \\\`out of 5 Stars\\\`, + label: \\\`Average Rating\\\`, + }, + }, + providers: { + [WellKnownProviders.DataSource]: { + // Setting the data source providerId for the tile with id "kpi1" + providerId: AverageRatingKpiDataSource.providerId, + } as IProviderConfiguration, + [WellKnownProviders.Adapter]: { + providerId: NOVA_KPI_DATASOURCE_ADAPTER, + properties: { + componentId: "kpi1", + propertyPath: "widgetData", + }, + } as IProviderConfiguration, + }, + }, + }, + }, +}; + +const partialPropWidgetConfig: IWidget = { + id: "widget2", + type: "proportional", + metadata: { + // Set 'needsConfiguration' to true if the widget needs further configuration before it can be + // placed on the dashboard. The "Create Widget" button will be hidden in the wizard when this + // widget is selected. + needsConfiguration: true, + }, + pizzagna: { + [PizzagnaLayer.Configuration]: { + header: { + properties: { + title: "*New Proportional Widget*", + }, + }, + }, + }, +}; +\`, + "tutorials/widget-creation/widget-creation.module.ts": \`// © 2022 SolarWinds Worldwide, LLC. All rights reserved. +// +// Permission is hereby granted, free of charge, to any person obtaining a copy +// of this software and associated documentation files (the "Software"), to +// deal in the Software without restriction, including without limitation the +// rights to use, copy, modify, merge, publish, distribute, sublicense, and/or +// sell copies of the Software, and to permit persons to whom the Software is +// furnished to do so, subject to the following conditions: +// +// The above copyright notice and this permission notice shall be included in +// all copies or substantial portions of the Software. +// +// THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR +// IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, +// FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE +// AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER +// LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, +// OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN +// THE SOFTWARE. + +import { CommonModule } from "@angular/common"; +import { HttpClientModule } from "@angular/common/http"; +import { NgModule } from "@angular/core"; +import { RouterModule } from "@angular/router"; + +import { + NuiButtonModule, + NuiDocsModule, + NuiImageModule, + NuiMessageModule, + NuiRepeatModule, + NuiSwitchModule, + NuiToastModule, + DEMO_PATH_TOKEN, +} from "@nova-ui/bits"; +import { NuiDashboardsModule } from "@nova-ui/dashboards"; + +import { WidgetCreationDocsComponent } from "./widget-creation-docs.component"; +import { + WidgetCreationComponent, + WidgetTemplateSelectionComponent, +} from "./widget-creation.component"; +import { getDemoFiles } from "../../../../demo-files-factory"; + +const routes = [ + { + path: "", + component: WidgetCreationDocsComponent, + data: { + srlc: { + hideIndicator: true, + }, + showThemeSwitcher: true, + }, + }, + { + path: "example", + component: WidgetCreationComponent, + data: { + srlc: { + hideIndicator: true, + }, + }, + }, +]; + +@NgModule({ + imports: [ + CommonModule, + HttpClientModule, + NuiDashboardsModule, + NuiDocsModule, + NuiMessageModule, + NuiSwitchModule, + NuiToastModule, + NuiButtonModule, + NuiRepeatModule, + NuiImageModule, + RouterModule.forChild(routes), + ], + declarations: [ + WidgetCreationDocsComponent, + WidgetCreationComponent, + WidgetTemplateSelectionComponent, + ], + providers: [ + { + provide: DEMO_PATH_TOKEN, + useValue: getDemoFiles("widget-creation"), + }, + ], +}) +export default class WidgetCreationModule {} +\`, + "tutorials/widget-editor-setup/widget-editor-setup-docs.component.ts": \`// © 2022 SolarWinds Worldwide, LLC. All rights reserved. +// +// Permission is hereby granted, free of charge, to any person obtaining a copy +// of this software and associated documentation files (the "Software"), to +// deal in the Software without restriction, including without limitation the +// rights to use, copy, modify, merge, publish, distribute, sublicense, and/or +// sell copies of the Software, and to permit persons to whom the Software is +// furnished to do so, subject to the following conditions: +// +// The above copyright notice and this permission notice shall be included in +// all copies or substantial portions of the Software. +// +// THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR +// IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, +// FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE +// AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER +// LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, +// OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN +// THE SOFTWARE. + +import { Component } from "@angular/core"; + +@Component({ + selector: "nui-dashboard-widget-editor-docs", + templateUrl: "./widget-editor-setup-docs.component.html", + standalone: false, +}) +export class WidgetEditorDocsComponent {} +\`, + "tutorials/widget-editor-setup/widget-editor-setup.component.ts": \`// © 2022 SolarWinds Worldwide, LLC. All rights reserved. +// +// Permission is hereby granted, free of charge, to any person obtaining a copy +// of this software and associated documentation files (the "Software"), to +// deal in the Software without restriction, including without limitation the +// rights to use, copy, modify, merge, publish, distribute, sublicense, and/or +// sell copies of the Software, and to permit persons to whom the Software is +// furnished to do so, subject to the following conditions: +// +// The above copyright notice and this permission notice shall be included in +// all copies or substantial portions of the Software. +// +// THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR +// IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, +// FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE +// AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER +// LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, +// OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN +// THE SOFTWARE. + +import { HttpClient, HttpErrorResponse } from "@angular/common/http"; +import { + ChangeDetectorRef, + Component, + Injectable, + OnDestroy, + OnInit, +} from "@angular/core"; +import { GridsterConfig, GridsterItem } from "angular-gridster2"; +import { BehaviorSubject } from "rxjs"; +import { finalize } from "rxjs/operators"; + +import { DataSourceService, IFilteringOutputs } from "@nova-ui/bits"; +import { + DATA_SOURCE, + DEFAULT_PIZZAGNA_ROOT, + IDashboard, + IKpiData, + IProviderConfiguration, + IRefresherProperties, + IWidget, + IWidgets, + KpiComponent, + NOVA_KPI_DATASOURCE_ADAPTER, + PizzagnaLayer, + ProviderRegistryService, + WellKnownPathKey, + WellKnownProviders, + WidgetTypesService, +} from "@nova-ui/dashboards"; + +/** + * A simple KPI data source to retrieve the average rating of Harry Potter and the Sorcerer's Stone (book) via googleapis + */ +@Injectable() +export class AverageRatingKpiDataSource + extends DataSourceService + implements OnDestroy +{ + // This is the ID we'll use to identify the provider + public static providerId = "AverageRatingKpiDataSource"; + + // Use this subject to communicate the data source's busy state + public busy = new BehaviorSubject(false); + + constructor(private http: HttpClient) { + super(); + } + + // In this example, getFilteredData is invoked every 10 minutes (Take a look at the refresher + // provider definition in the widget configuration below to see how the interval is set) + public async getFilteredData(): Promise { + this.busy.next(true); + return new Promise((resolve) => { + // *** Make a resource request to an external API (if needed) + this.http + .get("https://www.googleapis.com/books/v1/volumes/5MQFrgEACAAJ") + .pipe(finalize(() => this.busy.next(false))) + .subscribe({ + next: (data: any) => { + resolve({ + result: { + value: data.volumeInfo.averageRating, + }, + }); + }, + error: (error: HttpErrorResponse) => { + resolve({ + result: null, + error: { + type: error.status, + }, + }); + }, + }); + }); + } + + public ngOnDestroy(): void { + this.outputsSubject.complete(); + } +} + +/** + * A simple KPI data source to retrieve the ratings count of Harry Potter and the Sorcerer's Stone (book) via googleapis + */ +@Injectable() +export class RatingsCountKpiDataSource + extends DataSourceService + implements OnDestroy +{ + public static providerId = "RatingsCountKpiDataSource"; + + // Use this subject to communicate the data source's busy state + public busy = new BehaviorSubject(false); + + constructor(private http: HttpClient) { + super(); + } + + public async getFilteredData(): Promise { + this.busy.next(true); + return new Promise((resolve) => { + this.http + .get("https://www.googleapis.com/books/v1/volumes/5MQFrgEACAAJ") + .pipe(finalize(() => this.busy.next(false))) + .subscribe({ + next: (data: any) => { + resolve({ + result: { + value: data.volumeInfo.ratingsCount, + }, + }); + }, + error: (error: HttpErrorResponse) => { + resolve({ + result: null, + error: { + type: error.status, + }, + }); + }, + }); + }); + } + + public ngOnDestroy(): void { + this.outputsSubject.complete(); + } +} + +/** + * A component that instantiates the dashboard + */ +@Component({ + selector: "widget-editor-setup", + templateUrl: "./widget-editor-setup.component.html", + styleUrls: ["./widget-editor-setup.component.less"], + standalone: false, +}) +export class WidgetEditorSetupComponent implements OnInit { + // This variable will hold all the data needed to define the layout and behavior of the widgets. + // Pass this to the dashboard component's dashboard input in the template. + public dashboard: IDashboard | undefined; + + // Angular gridster requires a configuration object even if it's empty. + // Pass this to the dashboard component's gridsterConfig input in the template. + public gridsterConfig: GridsterConfig = {}; + + // Boolean which dashboard takes in as an input if its true it allows you to move widgets around. + public editMode: boolean = false; + + constructor( + // WidgetTypesService provides the widget's necessary structure information + private widgetTypesService: WidgetTypesService, + + // In general, the ProviderRegistryService is used for making entities available for injection into dynamically loaded components. + private providerRegistry: ProviderRegistryService, + private changeDetectorRef: ChangeDetectorRef + ) {} + + public ngOnInit(): void { + // Grabbing the widget's default template which will be needed as a parameter for setNode + const widgetTemplate = this.widgetTypesService.getWidgetType("kpi", 1); + // Registering our data sources as dropdown options in the widget editor/configurator + // Note: This could also be done in the parent module's constructor so that + // multiple dashboards could have access to the same widget template modification. + this.widgetTypesService.setNode( + // This is the template we grabbed above with getWidgetType + widgetTemplate, + // We are setting the editor/configurator part of the widget template + "configurator", + // This indicates which node you are changing and we want to change + // the data source providers available for selection in the editor. + WellKnownPathKey.DataSourceProviders, + // We are setting the data sources available for selection in the editor + [ + AverageRatingKpiDataSource.providerId, + RatingsCountKpiDataSource.providerId, + ] + ); + + // Registering the data sources available for injection into the KPI tiles. + // Note: Each tile of a KPI widget is assigned its own instance of a data source + this.providerRegistry.setProviders({ + [AverageRatingKpiDataSource.providerId]: { + provide: DATA_SOURCE, + useClass: AverageRatingKpiDataSource, + // Any dependencies that need to be injected into the provider must be listed here + deps: [HttpClient], + }, + [RatingsCountKpiDataSource.providerId]: { + provide: DATA_SOURCE, + useClass: RatingsCountKpiDataSource, + deps: [HttpClient], + }, + }); + + this.initializeDashboard(); + } + + /** Used for restoring widgets state */ + public reInitializeDashboard(): void { + // destroys the components and their providers so the dashboard can re init data + this.dashboard = undefined; + this.changeDetectorRef.detectChanges(); + + this.initializeDashboard(); + } + + public initializeDashboard(): void { + // We're using a static configuration object for this example (see widgetConfig at the bottom of the file), + // but this is where the widget's configuration could potentially be populated from a database + const kpiWidget = widgetConfig; + const widgetIndex: IWidgets = { + // Complete the KPI widget with information coming from its type definition + [kpiWidget.id]: + this.widgetTypesService.mergeWithWidgetType(kpiWidget), + }; + + // Setting the widget dimensions and position (this is for gridster) + const positions: Record = { + [kpiWidget.id]: { + cols: 4, + rows: 6, + y: 0, + x: 0, + }, + }; + + // Finally, assigning the variables we created above to the dashboard + this.dashboard = { + positions, + widgets: widgetIndex, + }; + } +} + +const widgetConfig: IWidget = { + id: "widget1", + type: "kpi", + pizzagna: { + [PizzagnaLayer.Configuration]: { + [DEFAULT_PIZZAGNA_ROOT]: { + providers: { + [WellKnownProviders.Refresher]: { + properties: { + // Configuring the refresher interval so that our data source is invoked every ten minutes + interval: 60 * 10, + enabled: true, + } as IRefresherProperties, + } as Partial, + }, + }, + header: { + properties: { + title: "Harry Potter and the Sorcerer's Stone", + subtitle: "By J. K. Rowling", + }, + }, + tiles: { + properties: { + nodes: ["kpi1"], + }, + }, + kpi1: { + id: "kpi1", + componentType: KpiComponent.lateLoadKey, + properties: { + widgetData: { + units: "out of 5 Stars", + label: "Average Rating", + }, + }, + providers: { + [WellKnownProviders.DataSource]: { + // Setting the data source providerId for the tile with id "kpi1" + providerId: AverageRatingKpiDataSource.providerId, + } as IProviderConfiguration, + [WellKnownProviders.Adapter]: { + providerId: NOVA_KPI_DATASOURCE_ADAPTER, + properties: { + componentId: "kpi1", + propertyPath: "widgetData", + }, + } as IProviderConfiguration, + }, + }, + }, + }, +}; +\`, + "tutorials/widget-editor-setup/widget-editor-setup.module.ts": \`// © 2022 SolarWinds Worldwide, LLC. All rights reserved. +// +// Permission is hereby granted, free of charge, to any person obtaining a copy +// of this software and associated documentation files (the "Software"), to +// deal in the Software without restriction, including without limitation the +// rights to use, copy, modify, merge, publish, distribute, sublicense, and/or +// sell copies of the Software, and to permit persons to whom the Software is +// furnished to do so, subject to the following conditions: +// +// The above copyright notice and this permission notice shall be included in +// all copies or substantial portions of the Software. +// +// THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR +// IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, +// FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE +// AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER +// LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, +// OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN +// THE SOFTWARE. + +import { CommonModule } from "@angular/common"; +import { HttpClientModule } from "@angular/common/http"; +import { NgModule } from "@angular/core"; +import { RouterModule } from "@angular/router"; + +import { + NuiButtonModule, + NuiDocsModule, + NuiMessageModule, + NuiSwitchModule, + DEMO_PATH_TOKEN, +} from "@nova-ui/bits"; +import { NuiDashboardsModule } from "@nova-ui/dashboards"; + +import { WidgetEditorDocsComponent } from "./widget-editor-setup-docs.component"; +import { WidgetEditorSetupComponent } from "./widget-editor-setup.component"; +import { getDemoFiles } from "../../../../demo-files-factory"; + +const routes = [ + { + path: "", + component: WidgetEditorDocsComponent, + data: { + srlc: { + hideIndicator: true, + }, + showThemeSwitcher: true, + }, + }, + { + path: "example", + component: WidgetEditorSetupComponent, + data: { + srlc: { + hideIndicator: true, + }, + }, + }, +]; + +@NgModule({ + imports: [ + CommonModule, + HttpClientModule, + NuiDashboardsModule, + NuiDocsModule, + NuiMessageModule, + NuiSwitchModule, + NuiButtonModule, + RouterModule.forChild(routes), + ], + declarations: [WidgetEditorDocsComponent, WidgetEditorSetupComponent], + providers: [ + { + provide: DEMO_PATH_TOKEN, + useValue: getDemoFiles("widget-editor-setup"), + }, + ], +}) +export default class WidgetEditorSetupModule {} +\`, + "tutorials/widget-error-handling/widget-error-handling-docs.component.ts": \`// © 2022 SolarWinds Worldwide, LLC. All rights reserved. +// +// Permission is hereby granted, free of charge, to any person obtaining a copy +// of this software and associated documentation files (the "Software"), to +// deal in the Software without restriction, including without limitation the +// rights to use, copy, modify, merge, publish, distribute, sublicense, and/or +// sell copies of the Software, and to permit persons to whom the Software is +// furnished to do so, subject to the following conditions: +// +// The above copyright notice and this permission notice shall be included in +// all copies or substantial portions of the Software. +// +// THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR +// IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, +// FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE +// AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER +// LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, +// OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN +// THE SOFTWARE. + +import { Component } from "@angular/core"; + +@Component({ + selector: "nui-widget-error-handling-docs", + templateUrl: "./widget-error-handling-docs.component.html", + standalone: false, +}) +export class WidgetErrorHandlingDocsComponent { + public fallbackAdapter = \\\` +@Injectable() +export class StatusContentFallbackAdapter implements OnDestroy, IHasComponent { + + protected readonly destroy$ = new Subject(); + protected componentId: string; + + constructor(@Inject(PIZZAGNA_EVENT_BUS) protected eventBus: EventBus, + protected pizzagnaService: PizzagnaService) { + this.eventBus.getStream(DATA_SOURCE_OUTPUT) + .pipe(takeUntil(this.destroy$)).subscribe((event: IEvent>) => { + this.handleDataSourceOutput(event); + }); + } + + public ngOnDestroy(): void { + this.destroy$.next(); + this.destroy$.complete(); + } + + public setComponent(component: any, componentId: string) { + this.componentId = componentId; + } + + protected handleDataSourceOutput(event: IEvent>) { + this.pizzagnaService.setProperty({ + componentId: this.componentId, + propertyPath: ["fallbackKey"], + pizzagnaKey: PizzagnaLayer.Data, + }, typeof event.payload?.error?.type !== "undefined" ? event.payload?.error?.type.toString() : undefined); + } +}\\\`; + public errorsMap = \\\` +export const ERROR_FALLBACK_MAP: Record = { + [HttpStatusCode.Unknown]: ErrorNodeKey.ErrorUnknown, + [HttpStatusCode.Forbidden]: ErrorNodeKey.ErrorForbidden, + [HttpStatusCode.NotFound]: ErrorNodeKey.ErrorNotFound, +}; +\\\`; + public errorNodes = \\\` +export const ERROR_NODES: Record = { + [ErrorNodeKey.ErrorUnknown]: { + id: ErrorNodeKey.ErrorUnknown, + componentType: WidgetErrorComponent.lateLoadKey, + properties: { + image: "no-data-to-show", + title: $localize\\\\\\\`Whoops, something went wrong\\\\\\\`, + description: $localize\\\\\\\`There was an unexpected error.\\\\\\\`, + } as IWidgetErrorDisplayProperties, + }, + [ErrorNodeKey.ErrorForbidden]: { + id: ErrorNodeKey.ErrorForbidden, + componentType: WidgetErrorComponent.lateLoadKey, + properties: { + image: "no-data-to-show", + title: $localize\\\\\\\`403 - Forbidden\\\\\\\`, + description: $localize\\\\\\\`The requested action was forbidden.\\\\\\\`, + } as IWidgetErrorDisplayProperties, + }, + [ErrorNodeKey.ErrorNotFound]: { + id: ErrorNodeKey.ErrorNotFound, + componentType: WidgetErrorComponent.lateLoadKey, + properties: { + image: "no-data-to-show", + title: $localize\\\\\\\`404 - Not Found\\\\\\\`, + description: $localize\\\\\\\`The requested resource could not be found.\\\\\\\`, + } as IWidgetErrorDisplayProperties, + }, +};\\\`; + public widgetBodyContentNodesSignature = \\\` +/** + * Retrieves an index of the basic widget body content nodes including fallback nodes + * + * @param mainContentNodeKey The key corresponding to the main body content node + * @param fallbackAdapterId The id for the adapter responsible for activating fallback content in case of an error + * @param fallbackMap A map of node keys to fallback content definitions + * @param fallbackNodes An index of fallback content definitions + * + * @returns An index of component configurations + */ +export function widgetBodyContentNodes( + mainContentNodeKey: string, + fallbackAdapterId = NOVA_STATUS_CONTENT_FALLBACK_ADAPTER, + fallbackMap: Record = ERROR_FALLBACK_MAP, + fallbackNodes: Record = ERROR_NODES +): Record { ... } +\\\`; +} +\`, + "tutorials/widget-error-handling/widget-error-handling.component.ts": \`// © 2022 SolarWinds Worldwide, LLC. All rights reserved. +// +// Permission is hereby granted, free of charge, to any person obtaining a copy +// of this software and associated documentation files (the "Software"), to +// deal in the Software without restriction, including without limitation the +// rights to use, copy, modify, merge, publish, distribute, sublicense, and/or +// sell copies of the Software, and to permit persons to whom the Software is +// furnished to do so, subject to the following conditions: +// +// The above copyright notice and this permission notice shall be included in +// all copies or substantial portions of the Software. +// +// THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR +// IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, +// FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE +// AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER +// LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, +// OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN +// THE SOFTWARE. + +import { HttpClient, HttpErrorResponse } from "@angular/common/http"; +import { + ChangeDetectorRef, + Component, + Injectable, + OnDestroy, + OnInit, +} from "@angular/core"; +import { GridsterConfig, GridsterItem } from "angular-gridster2"; +import { BehaviorSubject } from "rxjs"; +import { finalize } from "rxjs/operators"; + +import { DataSourceService, IFilteringOutputs } from "@nova-ui/bits"; +import { + DATA_SOURCE, + DEFAULT_PIZZAGNA_ROOT, + HttpStatusCode, + IDashboard, + IKpiData, + IProviderConfiguration, + IRefresherProperties, + IWidget, + IWidgets, + KpiComponent, + NOVA_KPI_DATASOURCE_ADAPTER, + PizzagnaLayer, + ProviderRegistryService, + WellKnownPathKey, + WellKnownProviders, + WidgetTypesService, +} from "@nova-ui/dashboards"; + +/** + * A simple KPI data source to retrieve the average rating of Harry Potter and the Sorcerer's Stone (book) via googleapis + */ +@Injectable() +export class AverageRatingKpiDataSource + extends DataSourceService + implements OnDestroy +{ + // This is the ID we'll use to identify the provider + public static providerId = "AverageRatingKpiDataSource"; + + // Use this subject to communicate the data source's busy state + public busy = new BehaviorSubject(false); + + constructor(private http: HttpClient) { + super(); + } + + // In this example, getFilteredData is invoked every 10 minutes (Take a look at the refresher + // provider definition in the widget configuration below to see how the interval is set) + public async getFilteredData(): Promise { + this.busy.next(true); + return new Promise((resolve) => { + // *** Make a resource request to an external API (if needed) + this.http + .get("https://www.googleapis.com/books/v1/volumes/5MQFrgEACAAJ") + .pipe(finalize(() => this.busy.next(false))) + .subscribe({ + next: (data: any) => { + resolve({ + result: { + value: data.volumeInfo.averageRating, + }, + }); + }, + error: (error: HttpErrorResponse) => { + resolve({ + result: null, + error: { + type: error.status, + }, + }); + }, + }); + }); + } + + public ngOnDestroy(): void { + this.outputsSubject.complete(); + } +} + +/** + * A simple KPI data source to retrieve the average rating of Harry Potter and the Sorcerer's Stone (book) via googleapis + */ +@Injectable() +export class ErrorUnknownDataSource + extends DataSourceService + implements OnDestroy +{ + // This is the ID we'll use to identify the provider + public static providerId = "ErrorUnknownDataSource"; + + // Use this subject to communicate the data source's busy state + public busy = new BehaviorSubject(false); + + // In this example, getFilteredData is invoked every 10 minutes (Take a look at the refresher + // provider definition in the widget configuration below to see how the interval is set) + public async getFilteredData(): Promise { + this.busy.next(true); + const mockError = { + result: null, + error: { type: HttpStatusCode.Unknown }, + }; + this.busy.next(false); + return mockError; + } + + public ngOnDestroy(): void { + this.outputsSubject.complete(); + } +} + +/** + * A simple KPI data source to retrieve the ratings count of Harry Potter and the Sorcerer's Stone (book) via googleapis + */ +@Injectable() +export class ErrorForbiddenDataSource + extends DataSourceService + implements OnDestroy +{ + public static providerId = "ErrorForbiddenDataSource"; + + // Use this subject to communicate the data source's busy state + public busy = new BehaviorSubject(false); + + constructor(private http: HttpClient) { + super(); + } + + public async getFilteredData(): Promise { + this.busy.next(true); + // generate a 403 + return new Promise((resolve) => { + this.http + .get( + "http://www.mocky.io/v2/5ecc724a3200000f0023614a?mocky-delay=4000ms" + ) + .pipe(finalize(() => this.busy.next(false))) + .subscribe({ + error: (error: HttpErrorResponse) => { + resolve({ + result: null, + error: { + type: error.status, + }, + }); + }, + }); + }); + } + + public ngOnDestroy(): void { + this.outputsSubject.complete(); + } +} + +/** + * A simple KPI data source to retrieve the ratings count of Harry Potter and the Sorcerer's Stone (book) via googleapis + */ +@Injectable() +export class ErrorNotFoundDataSource + extends DataSourceService + implements OnDestroy +{ + public static providerId = "ErrorNotFoundDataSource"; + + // Use this subject to communicate the data source's busy state + public busy = new BehaviorSubject(false); + + constructor(private http: HttpClient) { + super(); + } + + public async getFilteredData(): Promise { + this.busy.next(true); + // generate a 404 + return new Promise((resolve) => { + this.http + .get( + "http://www.mocky.io/v2/5ec6bfd93200007800d75100?mocky-delay=1000ms" + ) + .pipe(finalize(() => this.busy.next(false))) + .subscribe({ + error: (error: HttpErrorResponse) => { + resolve({ + result: null, + error: { + type: error.status, + }, + }); + }, + }); + }); + } + + public ngOnDestroy(): void { + this.outputsSubject.complete(); + } +} + +/** + * A component that instantiates the dashboard + */ +@Component({ + selector: "widget-error-handling", + templateUrl: "./widget-error-handling.component.html", + styleUrls: ["./widget-error-handling.component.less"], + standalone: false, +}) +export class WidgetErrorHandlingComponent implements OnInit { + // This variable will hold all the data needed to define the layout and behavior of the widgets. + // Pass this to the dashboard component's dashboard input in the template. + public dashboard: IDashboard | undefined; + + // Angular gridster requires a configuration object even if it's empty. + // Pass this to the dashboard component's gridsterConfig input in the template. + public gridsterConfig: GridsterConfig = {}; + + // Boolean which dashboard takes in as an input if its true it allows you to move widgets around. + public editMode: boolean = false; + + constructor( + // WidgetTypesService provides the widget's necessary structure information + private widgetTypesService: WidgetTypesService, + + // In general, the ProviderRegistryService is used for making entities available for injection into dynamically loaded components. + private providerRegistry: ProviderRegistryService, + private changeDetectorRef: ChangeDetectorRef + ) {} + + public ngOnInit(): void { + // Grab the widget's default template which will be needed as a parameter for setNode. + const widgetTemplate = this.widgetTypesService.getWidgetType("kpi", 1); + // Register our data sources as dropdown options in the widget editor/configurator + // Note: This could also be done in the parent module's constructor so that + // multiple dashboards could have access to the same widget template modification. + this.widgetTypesService.setNode( + // This is the template we grabbed above with getWidgetType + widgetTemplate, + // We are setting the editor/configurator part of the widget template + "configurator", + // This indicates which node you are changing and we want to change + // the data source providers available for selection in the editor. + WellKnownPathKey.DataSourceProviders, + // We are setting the data sources available for selection in the editor + [ + ErrorUnknownDataSource.providerId, + ErrorForbiddenDataSource.providerId, + ErrorNotFoundDataSource.providerId, + AverageRatingKpiDataSource.providerId, + ] + ); + + // Register the data sources available for injection into the KPI tiles. + // Note: Each tile of a KPI widget is assigned its own instance of a data source + this.providerRegistry.setProviders({ + [ErrorUnknownDataSource.providerId]: { + provide: DATA_SOURCE, + useClass: ErrorUnknownDataSource, + // Any dependencies that need to be injected into the provider must be listed here + deps: [HttpClient], + }, + [ErrorForbiddenDataSource.providerId]: { + provide: DATA_SOURCE, + useClass: ErrorForbiddenDataSource, + deps: [HttpClient], + }, + [ErrorNotFoundDataSource.providerId]: { + provide: DATA_SOURCE, + useClass: ErrorNotFoundDataSource, + deps: [HttpClient], + }, + [AverageRatingKpiDataSource.providerId]: { + provide: DATA_SOURCE, + useClass: AverageRatingKpiDataSource, + // Any dependencies that need to be injected into the provider must be listed here + deps: [HttpClient], + }, + }); + + this.initializeDashboard(); + } + + /** Used for restoring widgets state */ + public reInitializeDashboard(): void { + // destroys the components and their providers so the dashboard can re init data + this.dashboard = undefined; + this.changeDetectorRef.detectChanges(); + + this.initializeDashboard(); + } + + public initializeDashboard(): void { + // We're using a static configuration object for this example (see widgetConfig at the bottom of the file), + // but this is where the widget's configuration could potentially be populated from a database + const kpiWidget = widgetConfig; + const widgetIndex: IWidgets = { + // Complete the KPI widget with information coming from its type definition + [kpiWidget.id]: + this.widgetTypesService.mergeWithWidgetType(kpiWidget), + }; + + // Setting the widget dimensions and position (this is for gridster) + const positions: Record = { + [kpiWidget.id]: { + cols: 4, + rows: 6, + y: 0, + x: 0, + }, + }; + + // Finally, assigning the variables we created above to the dashboard + this.dashboard = { + positions, + widgets: widgetIndex, + }; + } +} + +const widgetConfig: IWidget = { + id: "widget1", + type: "kpi", + pizzagna: { + [PizzagnaLayer.Configuration]: { + [DEFAULT_PIZZAGNA_ROOT]: { + providers: { + [WellKnownProviders.Refresher]: { + properties: { + // Configuring the refresher interval so that our data source is invoked every ten minutes + interval: 60 * 10, + enabled: true, + } as IRefresherProperties, + } as Partial, + }, + }, + header: { + properties: { + title: "Harry Potter and the Sorcerer's Stone", + subtitle: "By J. K. Rowling", + }, + }, + tiles: { + properties: { + nodes: ["kpi1"], + }, + }, + kpi1: { + id: "kpi1", + componentType: KpiComponent.lateLoadKey, + properties: { + widgetData: { + units: "out of 5 Stars", + label: "Average Rating", + }, + }, + providers: { + [WellKnownProviders.DataSource]: { + // Setting the data source providerId for the tile with id "kpi1" + providerId: ErrorUnknownDataSource.providerId, + } as IProviderConfiguration, + [WellKnownProviders.Adapter]: { + providerId: NOVA_KPI_DATASOURCE_ADAPTER, + properties: { + componentId: "kpi1", + propertyPath: "widgetData", + }, + } as IProviderConfiguration, + }, + }, + }, + }, +}; +\`, + "tutorials/widget-error-handling/widget-error-handling.module.ts": \`// © 2022 SolarWinds Worldwide, LLC. All rights reserved. +// +// Permission is hereby granted, free of charge, to any person obtaining a copy +// of this software and associated documentation files (the "Software"), to +// deal in the Software without restriction, including without limitation the +// rights to use, copy, modify, merge, publish, distribute, sublicense, and/or +// sell copies of the Software, and to permit persons to whom the Software is +// furnished to do so, subject to the following conditions: +// +// The above copyright notice and this permission notice shall be included in +// all copies or substantial portions of the Software. +// +// THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR +// IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, +// FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE +// AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER +// LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, +// OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN +// THE SOFTWARE. + +import { CommonModule } from "@angular/common"; +import { HttpClientModule } from "@angular/common/http"; +import { NgModule } from "@angular/core"; +import { ReactiveFormsModule } from "@angular/forms"; +import { RouterModule } from "@angular/router"; + +import { + NuiButtonModule, + NuiDocsModule, + NuiFormFieldModule, + NuiIconModule, + NuiMessageModule, + NuiSwitchModule, + NuiTextboxModule, + DEMO_PATH_TOKEN, +} from "@nova-ui/bits"; +import { + NuiDashboardConfiguratorModule, + NuiDashboardsModule, +} from "@nova-ui/dashboards"; + +import { WidgetErrorHandlingDocsComponent } from "./widget-error-handling-docs.component"; +import { WidgetErrorHandlingComponent } from "./widget-error-handling.component"; +import { getDemoFiles } from "../../../../demo-files-factory"; + +const routes = [ + { + path: "", + component: WidgetErrorHandlingDocsComponent, + data: { + srlc: { + hideIndicator: true, + }, + showThemeSwitcher: true, + }, + }, + { + path: "example", + component: WidgetErrorHandlingComponent, + data: { + srlc: { + hideIndicator: true, + }, + }, + }, +]; + +@NgModule({ + imports: [ + CommonModule, + ReactiveFormsModule, + HttpClientModule, + NuiButtonModule, + NuiDashboardsModule, + NuiDashboardConfiguratorModule, + NuiDocsModule, + NuiFormFieldModule, + NuiIconModule, + NuiMessageModule, + NuiIconModule, + NuiTextboxModule, + NuiIconModule, + NuiSwitchModule, + RouterModule.forChild(routes), + ], + declarations: [ + WidgetErrorHandlingDocsComponent, + WidgetErrorHandlingComponent, + ], + providers: [ + { + provide: DEMO_PATH_TOKEN, + useValue: getDemoFiles("widget-error-handling"), + }, + ], +}) +export default class WidgetErrorHandlingModule {} +\`, + "types.ts": \`// © 2022 SolarWinds Worldwide, LLC. All rights reserved. +// +// Permission is hereby granted, free of charge, to any person obtaining a copy +// of this software and associated documentation files (the "Software"), to +// deal in the Software without restriction, including without limitation the +// rights to use, copy, modify, merge, publish, distribute, sublicense, and/or +// sell copies of the Software, and to permit persons to whom the Software is +// furnished to do so, subject to the following conditions: +// +// The above copyright notice and this permission notice shall be included in +// all copies or substantial portions of the Software. +// +// THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR +// IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, +// FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE +// AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER +// LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, +// OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN +// THE SOFTWARE. + +export enum APOLLO_API_NAMESPACE { + COUNTRIES = "countries", +} +\`, + "widget-types/drilldown/drilldown-multi-request-widget/drilldown-multi-request-widget-example.component.ts": \`// © 2022 SolarWinds Worldwide, LLC. All rights reserved. +// +// Permission is hereby granted, free of charge, to any person obtaining a copy +// of this software and associated documentation files (the "Software"), to +// deal in the Software without restriction, including without limitation the +// rights to use, copy, modify, merge, publish, distribute, sublicense, and/or +// sell copies of the Software, and to permit persons to whom the Software is +// furnished to do so, subject to the following conditions: +// +// The above copyright notice and this permission notice shall be included in +// all copies or substantial portions of the Software. +// +// THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR +// IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, +// FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE +// AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER +// LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, +// OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN +// THE SOFTWARE. + +import { HttpClient } from "@angular/common/http"; +import { + ChangeDetectorRef, + Component, + Injectable, + OnDestroy, + OnInit, +} from "@angular/core"; +import { GridsterConfig, GridsterItem } from "angular-gridster2"; +import { Apollo, gql } from "apollo-angular"; +import { BehaviorSubject, Observable, of, Subject } from "rxjs"; +// eslint-disable-next-line import/no-deprecated +import { finalize, map, switchMap, tap } from "rxjs/operators"; + +import { + DataSourceService, + IconStatus, + IDataField, + IFilters, + INovaFilters, +} from "@nova-ui/bits"; +import { + DATA_SOURCE, + DEFAULT_PIZZAGNA_ROOT, + IDashboard, + IDrilldownComponentsConfiguration, + IListWidgetConfiguration, + IProviderConfiguration, + IWidget, + IWidgets, + ListGroupItemComponent, + ListLeafItemComponent, + NOVA_DRILLDOWN_DATASOURCE_ADAPTER, + PizzagnaLayer, + ProviderRegistryService, + WellKnownPathKey, + WellKnownProviders, + WidgetTypesService, +} from "@nova-ui/dashboards"; + +import { APOLLO_API_NAMESPACE } from "../../../types"; + +/** + * A simple KPI data source to retrieve the average rating of Harry Potter and the Sorcerer's Stone (book) via googleapis + */ +@Injectable() +export class DrilldownDataSource + extends DataSourceService + implements OnDestroy +{ + // This is the ID we'll use to identify the provider + public static providerId = "DrilldownDataSource"; + + // Use this subject to communicate the data source's busy state + public busy = new BehaviorSubject(false); + + public dataFields: Partial[] = [ + { id: "Region", label: "Region name" }, + { id: "Subregion", label: "Subregion name" }, + ]; + + private drillState: string[] = []; + private groupBy: string[]; + private cache: any; + private lastDrillState: string[] = []; + private leafGroup: string = "country"; + private applyFilters$ = new Subject(); + + constructor(private http: HttpClient, private apollo: Apollo) { + super(); + + // TODO: remove Partial in vNext after marking dataType field as optional - NUI-5838 + ( + this.dataFieldsConfig.dataFields$ as BehaviorSubject< + Partial[] + > + ).next(this.dataFields); + + this.applyFilters$ + // eslint-disable-next-line import/no-deprecated + .pipe(switchMap((filters) => this.getData(filters))) + .subscribe(async (res) => { + this.outputsSubject.next(await this.getFilteredData(res)); + }); + } + + private groupedDataHistory: any[] = []; + + // In this example, getFilteredData is invoked every 10 minutes (Take a look at the refresher + // provider definition in the widget configuration below to see how the interval is set) + public async getFilteredData(data: any): Promise { + return of(data) + .pipe( + map((entries) => { + if (this.isDrillDown()) { + const activeDrillLvl = this.drillState.length; + const group = this.groupBy[activeDrillLvl]; + const lastGroupedValue = + this.getTransformedDataForGroup( + entries, + group, + getLast(this.drillState) + ); + + this.groupedDataHistory.push(lastGroupedValue); + + return lastGroupedValue; + } + + const mapIconsToEntries = entries.map((item: any) => ({ + ...item, + icon: "virtual-host", + icon_status: IconStatus.Up, + })); + this.groupedDataHistory.push(mapIconsToEntries); + const widgetInput = this.getOutput(entries); + + return widgetInput; + }) + ) + .toPromise(); + } + + public ngOnDestroy(): void { + this.outputsSubject.complete(); + } + + // redefine parent method + public async applyFilters(): Promise { + this.applyFilters$.next(this.getFilters()); + } + + private getQuery(key: string, value: string) { + const groupToRequestMap: Record = { + Region: \\\`{ Region { name } }\\\`, + Subregion: \\\`{ Subregion(filter: { region: { name: "\\\${value}" } } ) { name } }\\\`, + Country: \\\`{ Country(filter: { subregion: { name: "\\\${value}" } } ) { name capital } }\\\`, + }; + + return gql\\\` + \\\${groupToRequestMap[key]} + \\\`; + } + + private getData(filters: INovaFilters): Observable { + this.drillState = filters.drillstate?.value; + this.groupBy = filters.group?.value; + const group = this.groupBy[this.drillState.length]; + const isDrillUp = this.drillState.length < this.lastDrillState.length; + + this.lastDrillState = [...this.drillState]; + + if (!this.drillState.length) { + this.groupedDataHistory.length = 0; + } + + this.busy.next(true); + + if (this.cache && (isDrillUp || this.isHome())) { + return of(this.cache).pipe( + map((data) => data.data[group]), + finalize(() => this.busy.next(false)) + ); + } else { + return this.apollo + .use(APOLLO_API_NAMESPACE.COUNTRIES) + .query({ + query: this.getQuery( + group || this.leafGroup, + getLast(this.drillState) + ), + }) + .pipe( + tap( + (data) => + (this.cache = { + data: { ...this.cache?.data, ...data?.data }, + }) + ), + map((data) => data.data[group || this.leafGroup]), + finalize(() => this.busy.next(false)) + ); + } + } + + private getTransformedDataForGroup( + data: any, + group: string, + drillStateValue: string + ) { + const fallback: string = \\\`No \\\${group} for \\\${drillStateValue}\\\`; + const dataArr = Object.values(data).map((val: any) => ({ + id: val.name || fallback, + label: val.name || fallback, + statuses: [ + { key: "state_ok", value: val.name?.length }, + { + key: "status_unreachable", + value: generateNumberUpTo(100000), + }, + { key: "status_warning", value: generateNumberUpTo(10000) }, + { key: "status_unknown", value: generateNumberUpTo(1000) }, + ], + })); + + return dataArr; + } + + private isHome(): boolean { + return this.drillState.length === 0; + } + + private isDrillDown(): boolean { + return this.drillState.length !== this.groupBy.length; + } + + private getOutput(data: any) { + if (this.isHome()) { + this.groupedDataHistory.length = 0; + } + + const lastHistoryValue = getLast(this.groupedDataHistory); + + if (!lastHistoryValue) { + return data; + } + + return lastHistoryValue[getLast(this.drillState)] || lastHistoryValue; + } +} + +@Component({ + selector: "drilldown-multi-request-widget-example", + templateUrl: "./drilldown-multi-request-widget-example.component.html", + styleUrls: ["./drilldown-multi-request-widget-example.component.less"], + standalone: false, +}) +export class DrilldownMultiRequestWidgetExampleComponent implements OnInit { + // This variable will hold all the data needed to define the layout and behavior of the widgets. + // Pass this to the dashboard component's dashboard input in the template. + public dashboard: IDashboard | undefined; + + // Angular gridster requires a configuration object even if it's empty. + // Pass this to the dashboard component's gridsterConfig input in the template. + public gridsterConfig: GridsterConfig = {}; + + // Boolean passed as an input to the dashboard. When true, widgets can be moved, resized, removed, or edited + public editMode: boolean = false; + + constructor( + // WidgetTypesService provides the widget's necessary structure information + private widgetTypesService: WidgetTypesService, + private providerRegistry: ProviderRegistryService, + private changeDetectorRef: ChangeDetectorRef + ) {} + + public ngOnInit(): void { + // this.prepareNovaDashboards(); + this.initializeDashboard(); + const widgetTemplate = this.widgetTypesService.getWidgetType( + "drilldown", + 1 + ); + this.widgetTypesService.setNode( + widgetTemplate, + "configurator", + WellKnownPathKey.DataSourceProviders, + [DrilldownDataSource.providerId] + ); + + // Registering the data source for injection into the KPI tile. + // Note: Each tile of a KPI widget is assigned its own instance of the data source + this.providerRegistry.setProviders({ + [DrilldownDataSource.providerId]: { + provide: DATA_SOURCE, + useClass: DrilldownDataSource, + // Any dependencies that need to be injected into the provider must be listed here + deps: [HttpClient, Apollo], + }, + }); + } + + /** Used for restoring widgets state */ + public reInitializeDashboard(): void { + // destroys the components and their providers so the dashboard can re init data + this.dashboard = undefined; + this.changeDetectorRef.detectChanges(); + + this.initializeDashboard(); + } + + public initializeDashboard(): void { + // We're using a static configuration object for this example, but this is where + // the widget's configuration could potentially be populated from a database + const drilldownWidget = widgetConfig; + const widgets: IWidgets = { + // Complete the widget with information coming from its type definition + [drilldownWidget.id]: + this.widgetTypesService.mergeWithWidgetType(drilldownWidget), + }; + + // Setting the widget dimensions and position (this is for gridster) + const positions: Record = { + [drilldownWidget.id]: { + cols: 10, + rows: 10, + y: 0, + x: 0, + }, + }; + + // Finally, assigning the variables we created above to the dashboard + this.dashboard = { positions, widgets }; + } +} + +const widgetConfig: IWidget = { + id: "drilldown", + type: "drilldown", + pizzagna: { + [PizzagnaLayer.Configuration]: { + header: { + properties: { + title: "Drilldown Widget", + subtitle: "Countries BY continent THEN currency", + }, + }, + [DEFAULT_PIZZAGNA_ROOT]: { + providers: { + [WellKnownProviders.DataSource]: { + // Setting the data source providerId for the tile with id "kpi1" + providerId: DrilldownDataSource.providerId, + properties: {}, + } as IProviderConfiguration, + }, + }, + listWidget: { + providers: { + [WellKnownProviders.Adapter]: { + providerId: NOVA_DRILLDOWN_DATASOURCE_ADAPTER, + properties: { + // widget + navigationBarId: "navigationBar", + componentId: "listWidget", + dataPath: "data", + + // adapter props + drillstate: [], + groups: ["Region", "Subregion"], + groupBy: ["Region", "Subregion"], + + // components + componentsConfig: { + group: { + componentType: + ListGroupItemComponent.lateLoadKey, + properties: { + dataFieldIds: { + id: "id", + label: "label", + statuses: "statuses", + }, + }, + itemProperties: { + canNavigate: true, + }, + }, + leaf: { + componentType: + ListLeafItemComponent.lateLoadKey, + properties: { + dataFieldIds: { + icon: "icon", + status: "icon_status", + detailedUrl: "capital", + label: "name", + }, + }, + itemProperties: { + canNavigate: false, + }, + }, + } as IDrilldownComponentsConfiguration, + }, + }, + }, + properties: { + configuration: { + // FORMAT: + // componentType: ListLeafItemComponent.lateLoadKey, + // properties: { + // dataFieldIds: { + // icon: "", + // status: "code", + // detailedUrl: "capital", + // label: "name", + // }, + // }, + // + } as IListWidgetConfiguration, + }, + }, + }, + }, +}; + +const getLast = (arr: any[]) => arr[arr.length - 1]; + +const generateNumberUpTo = (upperLimit: number): number => + Math.floor(Math.random() * upperLimit + 1); +\`, + "widget-types/drilldown/drilldown-widget/data-mock.ts": \`// © 2022 SolarWinds Worldwide, LLC. All rights reserved. +// +// Permission is hereby granted, free of charge, to any person obtaining a copy +// of this software and associated documentation files (the "Software"), to +// deal in the Software without restriction, including without limitation the +// rights to use, copy, modify, merge, publish, distribute, sublicense, and/or +// sell copies of the Software, and to permit persons to whom the Software is +// furnished to do so, subject to the following conditions: +// +// The above copyright notice and this permission notice shall be included in +// all copies or substantial portions of the Software. +// +// THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR +// IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, +// FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE +// AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER +// LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, +// OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN +// THE SOFTWARE. + +import { IconStatus } from "@nova-ui/bits"; + +export const GRAPH_DATA_MOCK = { + data: { + countries: [ + { + name: "Andorra", + code: "AD", + icon: "virtual-host", + icon_status: IconStatus.Up, + capital: "Andorra la Vella", + continent: { + name: "Europe", + }, + currency: "EUR", + languages: [ + { + name: "Catalan", + }, + ], + url: "https://en.wikipedia.org/wiki/Andorra", + }, + { + name: "United Arab Emirates", + code: "AE", + icon: "virtual-host", + icon_status: IconStatus.Up, + capital: "Abu Dhabi", + continent: { + name: "Asia", + }, + currency: "AED", + languages: [ + { + name: "Arabic", + }, + ], + url: "https://en.wikipedia.org/wiki/United_Arab_Emirates", + }, + { + name: "Afghanistan", + code: "AF", + icon: "virtual-host", + icon_status: IconStatus.Up, + capital: "Kabul", + continent: { + name: "Asia", + }, + currency: "AFN", + languages: [ + { + name: "Pashto", + }, + { + name: "Uzbek", + }, + { + name: "Turkmen", + }, + ], + url: "https://en.wikipedia.org/wiki/Afghanistan", + }, + { + name: "Antigua and Barbuda", + code: "AG", + icon: "virtual-host", + icon_status: IconStatus.Up, + capital: "Saint John's", + continent: { + name: "North America", + }, + currency: "XCD", + languages: [ + { + name: "English", + }, + ], + url: "https://en.wikipedia.org/wiki/Antigua_and_Barbuda", + }, + { + name: "Anguilla", + code: "AI", + icon: "virtual-host", + icon_status: IconStatus.Up, + capital: "The Valley", + continent: { + name: "North America", + }, + currency: "XCD", + languages: [ + { + name: "English", + }, + ], + url: "https://en.wikipedia.org/wiki/Anguilla", + }, + { + name: "Albania", + code: "AL", + icon: "virtual-host", + icon_status: IconStatus.Up, + capital: "Tirana", + continent: { + name: "Europe", + }, + currency: "ALL", + languages: [ + { + name: "Albanian", + }, + ], + url: "https://en.wikipedia.org/wiki/Albania", + }, + { + name: "Armenia", + code: "AM", + icon: "virtual-host", + icon_status: IconStatus.Up, + capital: "Yerevan", + continent: { + name: "Asia", + }, + currency: "AMD", + languages: [ + { + name: "Armenian", + }, + { + name: "Russian", + }, + ], + url: "https://en.wikipedia.org/wiki/Armenia", + }, + { + name: "Angola", + code: "AO", + icon: "virtual-host", + icon_status: IconStatus.Up, + capital: "Luanda", + continent: { + name: "Africa", + }, + currency: "AOA", + languages: [ + { + name: "Portuguese", + }, + ], + }, + { + name: "Antarctica", + code: "AQ", + icon: "virtual-host", + icon_status: IconStatus.Up, + capital: null, + continent: { + name: "Antarctica", + }, + currency: null, + languages: [], + }, + { + name: "Argentina", + code: "AR", + icon: "virtual-host", + icon_status: IconStatus.Up, + capital: "Buenos Aires", + continent: { + name: "South America", + }, + currency: "ARS", + languages: [ + { + name: "Spanish", + }, + { + name: "Guarani", + }, + ], + }, + { + name: "American Samoa", + code: "AS", + icon: "virtual-host", + icon_status: IconStatus.Up, + capital: "Pago Pago", + continent: { + name: "Oceania", + }, + currency: "USD", + languages: [ + { + name: "English", + }, + { + name: "Samoan", + }, + ], + }, + { + name: "Austria", + code: "AT", + icon: "virtual-host", + icon_status: IconStatus.Up, + capital: "Vienna", + continent: { + name: "Europe", + }, + currency: "EUR", + languages: [ + { + name: "German", + }, + ], + }, + { + name: "Australia", + code: "AU", + icon: "virtual-host", + icon_status: IconStatus.Up, + capital: "Canberra", + continent: { + name: "Oceania", + }, + currency: "AUD", + languages: [ + { + name: "English", + }, + ], + }, + { + name: "Aruba", + code: "AW", + icon: "virtual-host", + icon_status: IconStatus.Up, + capital: "Oranjestad", + continent: { + name: "North America", + }, + currency: "AWG", + languages: [ + { + name: "Dutch", + }, + { + name: "Panjabi / Punjabi", + }, + ], + }, + { + name: "Åland", + code: "AX", + icon: "virtual-host", + icon_status: IconStatus.Up, + capital: "Mariehamn", + continent: { + name: "Europe", + }, + currency: "EUR", + languages: [ + { + name: "Swedish", + }, + ], + }, + { + name: "Azerbaijan", + code: "AZ", + icon: "virtual-host", + icon_status: IconStatus.Up, + capital: "Baku", + continent: { + name: "Asia", + }, + currency: "AZN", + languages: [ + { + name: "Azerbaijani", + }, + ], + }, + { + name: "Bosnia and Herzegovina", + code: "BA", + icon: "virtual-host", + icon_status: IconStatus.Up, + capital: "Sarajevo", + continent: { + name: "Europe", + }, + currency: "BAM", + languages: [ + { + name: "Bosnian", + }, + { + name: "Croatian", + }, + { + name: "Serbian", + }, + ], + }, + { + name: "Barbados", + code: "BB", + icon: "virtual-host", + icon_status: IconStatus.Up, + capital: "Bridgetown", + continent: { + name: "North America", + }, + currency: "BBD", + languages: [ + { + name: "English", + }, + ], + }, + { + name: "Bangladesh", + code: "BD", + icon: "virtual-host", + icon_status: IconStatus.Up, + capital: "Dhaka", + continent: { + name: "Asia", + }, + currency: "BDT", + languages: [ + { + name: "Bengali", + }, + ], + }, + { + name: "Belgium", + code: "BE", + icon: "virtual-host", + icon_status: IconStatus.Up, + capital: "Brussels", + continent: { + name: "Europe", + }, + currency: "EUR", + languages: [ + { + name: "Dutch", + }, + { + name: "French", + }, + { + name: "German", + }, + ], + }, + { + name: "Burkina Faso", + code: "BF", + icon: "virtual-host", + icon_status: IconStatus.Up, + capital: "Ouagadougou", + continent: { + name: "Africa", + }, + currency: "XOF", + languages: [ + { + name: "French", + }, + { + name: "Peul", + }, + ], + }, + { + name: "Bulgaria", + code: "BG", + icon: "virtual-host", + icon_status: IconStatus.Up, + capital: "Sofia", + continent: { + name: "Europe", + }, + currency: "BGN", + languages: [ + { + name: "Bulgarian", + }, + ], + }, + { + name: "Bahrain", + code: "BH", + icon: "virtual-host", + icon_status: IconStatus.Up, + capital: "Manama", + continent: { + name: "Asia", + }, + currency: "BHD", + languages: [ + { + name: "Arabic", + }, + ], + }, + { + name: "Burundi", + code: "BI", + icon: "virtual-host", + icon_status: IconStatus.Up, + capital: "Bujumbura", + continent: { + name: "Africa", + }, + currency: "BIF", + languages: [ + { + name: "French", + }, + { + name: "Kirundi", + }, + ], + }, + { + name: "Benin", + code: "BJ", + icon: "virtual-host", + icon_status: IconStatus.Up, + capital: "Porto-Novo", + continent: { + name: "Africa", + }, + currency: "XOF", + languages: [ + { + name: "French", + }, + ], + }, + { + name: "Saint Barthélemy", + code: "BL", + icon: "virtual-host", + icon_status: IconStatus.Up, + capital: "Gustavia", + continent: { + name: "North America", + }, + currency: "EUR", + languages: [ + { + name: "French", + }, + ], + }, + { + name: "Bermuda", + code: "BM", + icon: "virtual-host", + icon_status: IconStatus.Up, + capital: "Hamilton", + continent: { + name: "North America", + }, + currency: "BMD", + languages: [ + { + name: "English", + }, + ], + }, + { + name: "Brunei", + code: "BN", + icon: "virtual-host", + icon_status: IconStatus.Up, + capital: "Bandar Seri Begawan", + continent: { + name: "Asia", + }, + currency: "BND", + languages: [ + { + name: "Malay", + }, + ], + }, + { + name: "Bolivia", + code: "BO", + icon: "virtual-host", + icon_status: IconStatus.Up, + capital: "Sucre", + continent: { + name: "South America", + }, + currency: "BOB,BOV", + languages: [ + { + name: "Spanish", + }, + { + name: "Aymara", + }, + { + name: "Quechua", + }, + ], + }, + { + name: "Bonaire", + code: "BQ", + icon: "virtual-host", + icon_status: IconStatus.Up, + capital: "Kralendijk", + continent: { + name: "North America", + }, + currency: "USD", + languages: [ + { + name: "Dutch", + }, + ], + }, + { + name: "Brazil", + code: "BR", + icon: "virtual-host", + icon_status: IconStatus.Up, + capital: "Brasília", + continent: { + name: "South America", + }, + currency: "BRL", + languages: [ + { + name: "Portuguese", + }, + ], + }, + { + name: "Bahamas", + code: "BS", + icon: "virtual-host", + icon_status: IconStatus.Up, + capital: "Nassau", + continent: { + name: "North America", + }, + currency: "BSD", + languages: [ + { + name: "English", + }, + ], + }, + { + name: "Bhutan", + code: "BT", + icon: "virtual-host", + icon_status: IconStatus.Up, + capital: "Thimphu", + continent: { + name: "Asia", + }, + currency: "BTN,INR", + languages: [ + { + name: "Dzongkha", + }, + ], + }, + { + name: "Bouvet Island", + code: "BV", + icon: "virtual-host", + icon_status: IconStatus.Up, + capital: null, + continent: { + name: "Antarctica", + }, + currency: "NOK", + languages: [ + { + name: "Norwegian", + }, + { + name: "Norwegian Bokmål", + }, + { + name: "Norwegian Nynorsk", + }, + ], + }, + { + name: "Botswana", + code: "BW", + icon: "virtual-host", + icon_status: IconStatus.Up, + capital: "Gaborone", + continent: { + name: "Africa", + }, + currency: "BWP", + languages: [ + { + name: "English", + }, + { + name: "Tswana", + }, + ], + }, + { + name: "Belarus", + code: "BY", + icon: "virtual-host", + icon_status: IconStatus.Up, + capital: "Minsk", + continent: { + name: "Europe", + }, + currency: "BYN", + languages: [ + { + name: "Belarusian", + }, + { + name: "Russian", + }, + ], + }, + { + name: "Belize", + code: "BZ", + icon: "virtual-host", + icon_status: IconStatus.Up, + capital: "Belmopan", + continent: { + name: "North America", + }, + currency: "BZD", + languages: [ + { + name: "English", + }, + { + name: "Spanish", + }, + ], + }, + { + name: "Canada", + code: "CA", + icon: "virtual-host", + icon_status: IconStatus.Up, + capital: "Ottawa", + continent: { + name: "North America", + }, + currency: "CAD", + languages: [ + { + name: "English", + }, + { + name: "French", + }, + ], + }, + { + name: "Cocos [Keeling] Islands", + code: "CC", + icon: "virtual-host", + icon_status: IconStatus.Up, + capital: "West Island", + continent: { + name: "Asia", + }, + currency: "AUD", + languages: [ + { + name: "English", + }, + ], + }, + { + name: "Democratic Republic of the Congo", + code: "CD", + icon: "virtual-host", + icon_status: IconStatus.Up, + capital: "Kinshasa", + continent: { + name: "Africa", + }, + currency: "CDF", + languages: [ + { + name: "French", + }, + { + name: "Lingala", + }, + { + name: "Kongo", + }, + { + name: "Swahili", + }, + { + name: "Luba-Katanga", + }, + ], + }, + { + name: "Central African Republic", + code: "CF", + icon: "virtual-host", + icon_status: IconStatus.Up, + capital: "Bangui", + continent: { + name: "Africa", + }, + currency: "XAF", + languages: [ + { + name: "French", + }, + { + name: "Sango", + }, + ], + }, + { + name: "Republic of the Congo", + code: "CG", + icon: "virtual-host", + icon_status: IconStatus.Up, + capital: "Brazzaville", + continent: { + name: "Africa", + }, + currency: "XAF", + languages: [ + { + name: "French", + }, + { + name: "Lingala", + }, + ], + }, + { + name: "Switzerland", + code: "CH", + icon: "virtual-host", + icon_status: IconStatus.Up, + capital: "Bern", + continent: { + name: "Europe", + }, + currency: "CHE,CHF,CHW", + languages: [ + { + name: "German", + }, + { + name: "French", + }, + { + name: "Italian", + }, + ], + }, + { + name: "Ivory Coast", + code: "CI", + icon: "virtual-host", + icon_status: IconStatus.Up, + capital: "Yamoussoukro", + continent: { + name: "Africa", + }, + currency: "XOF", + languages: [ + { + name: "French", + }, + ], + }, + { + name: "Cook Islands", + code: "CK", + icon: "virtual-host", + icon_status: IconStatus.Up, + capital: "Avarua", + continent: { + name: "Oceania", + }, + currency: "NZD", + languages: [ + { + name: "English", + }, + ], + }, + { + name: "Chile", + code: "CL", + icon: "virtual-host", + icon_status: IconStatus.Up, + capital: "Santiago", + continent: { + name: "South America", + }, + currency: "CLF,CLP", + languages: [ + { + name: "Spanish", + }, + ], + }, + { + name: "Cameroon", + code: "CM", + icon: "virtual-host", + icon_status: IconStatus.Up, + capital: "Yaoundé", + continent: { + name: "Africa", + }, + currency: "XAF", + languages: [ + { + name: "English", + }, + { + name: "French", + }, + ], + }, + { + name: "China", + code: "CN", + icon: "virtual-host", + icon_status: IconStatus.Up, + capital: "Beijing", + continent: { + name: "Asia", + }, + currency: "CNY", + languages: [ + { + name: "Chinese", + }, + ], + }, + { + name: "Colombia", + code: "CO", + icon: "virtual-host", + icon_status: IconStatus.Up, + capital: "Bogotá", + continent: { + name: "South America", + }, + currency: "COP", + languages: [ + { + name: "Spanish", + }, + ], + }, + { + name: "Costa Rica", + code: "CR", + icon: "virtual-host", + icon_status: IconStatus.Up, + capital: "San José", + continent: { + name: "North America", + }, + currency: "CRC", + languages: [ + { + name: "Spanish", + }, + ], + }, + { + name: "Cuba", + code: "CU", + icon: "virtual-host", + icon_status: IconStatus.Up, + capital: "Havana", + continent: { + name: "North America", + }, + currency: "CUC,CUP", + languages: [ + { + name: "Spanish", + }, + ], + }, + { + name: "Cape Verde", + code: "CV", + icon: "virtual-host", + icon_status: IconStatus.Up, + capital: "Praia", + continent: { + name: "Africa", + }, + currency: "CVE", + languages: [ + { + name: "Portuguese", + }, + ], + }, + { + name: "Curacao", + code: "CW", + icon: "virtual-host", + icon_status: IconStatus.Up, + capital: "Willemstad", + continent: { + name: "North America", + }, + currency: "ANG", + languages: [ + { + name: "Dutch", + }, + { + name: "Panjabi / Punjabi", + }, + { + name: "English", + }, + ], + }, + { + name: "Christmas Island", + code: "CX", + icon: "virtual-host", + icon_status: IconStatus.Up, + capital: "Flying Fish Cove", + continent: { + name: "Asia", + }, + currency: "AUD", + languages: [ + { + name: "English", + }, + ], + }, + { + name: "Cyprus", + code: "CY", + icon: "virtual-host", + icon_status: IconStatus.Up, + capital: "Nicosia", + continent: { + name: "Europe", + }, + currency: "EUR", + languages: [ + { + name: "Greek", + }, + { + name: "Turkish", + }, + { + name: "Armenian", + }, + ], + }, + { + name: "Czech Republic", + code: "CZ", + icon: "virtual-host", + icon_status: IconStatus.Up, + capital: "Prague", + continent: { + name: "Europe", + }, + currency: "CZK", + languages: [ + { + name: "Czech", + }, + { + name: "Slovak", + }, + ], + }, + { + name: "Germany", + code: "DE", + icon: "virtual-host", + icon_status: IconStatus.Up, + capital: "Berlin", + continent: { + name: "Europe", + }, + currency: "EUR", + languages: [ + { + name: "German", + }, + ], + }, + { + name: "Djibouti", + code: "DJ", + icon: "virtual-host", + icon_status: IconStatus.Up, + capital: "Djibouti", + continent: { + name: "Africa", + }, + currency: "DJF", + languages: [ + { + name: "French", + }, + { + name: "Arabic", + }, + ], + }, + { + name: "Denmark", + code: "DK", + icon: "virtual-host", + icon_status: IconStatus.Up, + capital: "Copenhagen", + continent: { + name: "Europe", + }, + currency: "DKK", + languages: [ + { + name: "Danish", + }, + ], + }, + { + name: "Dominica", + code: "DM", + icon: "virtual-host", + icon_status: IconStatus.Up, + capital: "Roseau", + continent: { + name: "North America", + }, + currency: "XCD", + languages: [ + { + name: "English", + }, + ], + }, + { + name: "Dominican Republic", + code: "DO", + icon: "virtual-host", + icon_status: IconStatus.Up, + capital: "Santo Domingo", + continent: { + name: "North America", + }, + currency: "DOP", + languages: [ + { + name: "Spanish", + }, + ], + }, + { + name: "Algeria", + code: "DZ", + icon: "virtual-host", + icon_status: IconStatus.Up, + capital: "Algiers", + continent: { + name: "Africa", + }, + currency: "DZD", + languages: [ + { + name: "Arabic", + }, + ], + }, + { + name: "Ecuador", + code: "EC", + icon: "virtual-host", + icon_status: IconStatus.Up, + capital: "Quito", + continent: { + name: "South America", + }, + currency: "USD", + languages: [ + { + name: "Spanish", + }, + ], + }, + { + name: "Estonia", + code: "EE", + icon: "virtual-host", + icon_status: IconStatus.Up, + capital: "Tallinn", + continent: { + name: "Europe", + }, + currency: "EUR", + languages: [ + { + name: "Estonian", + }, + ], + }, + { + name: "Egypt", + code: "EG", + icon: "virtual-host", + icon_status: IconStatus.Up, + capital: "Cairo", + continent: { + name: "Africa", + }, + currency: "EGP", + languages: [ + { + name: "Arabic", + }, + ], + }, + { + name: "Western Sahara", + code: "EH", + icon: "virtual-host", + icon_status: IconStatus.Up, + capital: "El Aaiún", + continent: { + name: "Africa", + }, + currency: "MAD,DZD,MRU", + languages: [ + { + name: "Spanish", + }, + ], + }, + { + name: "Eritrea", + code: "ER", + icon: "virtual-host", + icon_status: IconStatus.Up, + capital: "Asmara", + continent: { + name: "Africa", + }, + currency: "ERN", + languages: [ + { + name: "Tigrinya", + }, + { + name: "Arabic", + }, + { + name: "English", + }, + ], + }, + { + name: "Spain", + code: "ES", + icon: "virtual-host", + icon_status: IconStatus.Up, + capital: "Madrid", + continent: { + name: "Europe", + }, + currency: "EUR", + languages: [ + { + name: "Spanish", + }, + { + name: "Basque", + }, + { + name: "Catalan", + }, + { + name: "Galician", + }, + { + name: "Occitan", + }, + ], + }, + { + name: "Ethiopia", + code: "ET", + icon: "virtual-host", + icon_status: IconStatus.Up, + capital: "Addis Ababa", + continent: { + name: "Africa", + }, + currency: "ETB", + languages: [ + { + name: "Amharic", + }, + ], + }, + { + name: "Finland", + code: "FI", + icon: "virtual-host", + icon_status: IconStatus.Up, + capital: "Helsinki", + continent: { + name: "Europe", + }, + currency: "EUR", + languages: [ + { + name: "Finnish", + }, + { + name: "Swedish", + }, + ], + }, + { + name: "Fiji", + code: "FJ", + icon: "virtual-host", + icon_status: IconStatus.Up, + capital: "Suva", + continent: { + name: "Oceania", + }, + currency: "FJD", + languages: [ + { + name: "English", + }, + { + name: "Fijian", + }, + { + name: "Hindi", + }, + { + name: "Urdu", + }, + ], + }, + { + name: "Falkland Islands", + code: "FK", + icon: "virtual-host", + icon_status: IconStatus.Up, + capital: "Stanley", + continent: { + name: "South America", + }, + currency: "FKP", + languages: [ + { + name: "English", + }, + ], + }, + { + name: "Micronesia", + code: "FM", + icon: "virtual-host", + icon_status: IconStatus.Up, + capital: "Palikir", + continent: { + name: "Oceania", + }, + currency: "USD", + languages: [ + { + name: "English", + }, + ], + }, + { + name: "Faroe Islands", + code: "FO", + icon: "virtual-host", + icon_status: IconStatus.Up, + capital: "Tórshavn", + continent: { + name: "Europe", + }, + currency: "DKK", + languages: [ + { + name: "Faroese", + }, + ], + }, + { + name: "France", + code: "FR", + icon: "virtual-host", + icon_status: IconStatus.Up, + capital: "Paris", + continent: { + name: "Europe", + }, + currency: "EUR", + languages: [ + { + name: "French", + }, + ], + }, + { + name: "Gabon", + code: "GA", + icon: "virtual-host", + icon_status: IconStatus.Up, + capital: "Libreville", + continent: { + name: "Africa", + }, + currency: "XAF", + languages: [ + { + name: "French", + }, + ], + }, + { + name: "United Kingdom", + code: "GB", + icon: "virtual-host", + icon_status: IconStatus.Up, + capital: "London", + continent: { + name: "Europe", + }, + currency: "GBP", + languages: [ + { + name: "English", + }, + ], + }, + { + name: "Grenada", + code: "GD", + icon: "virtual-host", + icon_status: IconStatus.Up, + capital: "St. George's", + continent: { + name: "North America", + }, + currency: "XCD", + languages: [ + { + name: "English", + }, + ], + }, + { + name: "Georgia", + code: "GE", + icon: "virtual-host", + icon_status: IconStatus.Up, + capital: "Tbilisi", + continent: { + name: "Asia", + }, + currency: "GEL", + languages: [ + { + name: "Georgian", + }, + ], + }, + { + name: "French Guiana", + code: "GF", + icon: "virtual-host", + icon_status: IconStatus.Up, + capital: "Cayenne", + continent: { + name: "South America", + }, + currency: "EUR", + languages: [ + { + name: "French", + }, + ], + }, + { + name: "Guernsey", + code: "GG", + icon: "virtual-host", + icon_status: IconStatus.Up, + capital: "St. Peter Port", + continent: { + name: "Europe", + }, + currency: "GBP", + languages: [ + { + name: "English", + }, + { + name: "French", + }, + ], + }, + { + name: "Ghana", + code: "GH", + icon: "virtual-host", + icon_status: IconStatus.Up, + capital: "Accra", + continent: { + name: "Africa", + }, + currency: "GHS", + languages: [ + { + name: "English", + }, + ], + }, + { + name: "Gibraltar", + code: "GI", + icon: "virtual-host", + icon_status: IconStatus.Up, + capital: "Gibraltar", + continent: { + name: "Europe", + }, + currency: "GIP", + languages: [ + { + name: "English", + }, + ], + }, + { + name: "Greenland", + code: "GL", + icon: "virtual-host", + icon_status: IconStatus.Up, + capital: "Nuuk", + continent: { + name: "North America", + }, + currency: "DKK", + languages: [ + { + name: "Greenlandic", + }, + ], + }, + { + name: "Gambia", + code: "GM", + icon: "virtual-host", + icon_status: IconStatus.Up, + capital: "Banjul", + continent: { + name: "Africa", + }, + currency: "GMD", + languages: [ + { + name: "English", + }, + ], + }, + { + name: "Guinea", + code: "GN", + icon: "virtual-host", + icon_status: IconStatus.Up, + capital: "Conakry", + continent: { + name: "Africa", + }, + currency: "GNF", + languages: [ + { + name: "French", + }, + { + name: "Peul", + }, + ], + }, + { + name: "Guadeloupe", + code: "GP", + icon: "virtual-host", + icon_status: IconStatus.Up, + capital: "Basse-Terre", + continent: { + name: "North America", + }, + currency: "EUR", + languages: [ + { + name: "French", + }, + ], + }, + { + name: "Equatorial Guinea", + code: "GQ", + icon: "virtual-host", + icon_status: IconStatus.Up, + capital: "Malabo", + continent: { + name: "Africa", + }, + currency: "XAF", + languages: [ + { + name: "Spanish", + }, + { + name: "French", + }, + ], + }, + { + name: "Greece", + code: "GR", + icon: "virtual-host", + icon_status: IconStatus.Up, + capital: "Athens", + continent: { + name: "Europe", + }, + currency: "EUR", + languages: [ + { + name: "Greek", + }, + ], + }, + { + name: "South Georgia and the South Sandwich Islands", + code: "GS", + icon: "virtual-host", + icon_status: IconStatus.Up, + capital: "King Edward Point", + continent: { + name: "Antarctica", + }, + currency: "GBP", + languages: [ + { + name: "English", + }, + ], + }, + { + name: "Guatemala", + code: "GT", + icon: "virtual-host", + icon_status: IconStatus.Up, + capital: "Guatemala City", + continent: { + name: "North America", + }, + currency: "GTQ", + languages: [ + { + name: "Spanish", + }, + ], + }, + { + name: "Guam", + code: "GU", + icon: "virtual-host", + icon_status: IconStatus.Up, + capital: "Hagåtña", + continent: { + name: "Oceania", + }, + currency: "USD", + languages: [ + { + name: "English", + }, + { + name: "Chamorro", + }, + { + name: "Spanish", + }, + ], + }, + { + name: "Guinea-Bissau", + code: "GW", + icon: "virtual-host", + icon_status: IconStatus.Up, + capital: "Bissau", + continent: { + name: "Africa", + }, + currency: "XOF", + languages: [ + { + name: "Portuguese", + }, + ], + }, + { + name: "Guyana", + code: "GY", + icon: "virtual-host", + icon_status: IconStatus.Up, + capital: "Georgetown", + continent: { + name: "South America", + }, + currency: "GYD", + languages: [ + { + name: "English", + }, + ], + }, + { + name: "Hong Kong", + code: "HK", + icon: "virtual-host", + icon_status: IconStatus.Up, + capital: "City of Victoria", + continent: { + name: "Asia", + }, + currency: "HKD", + languages: [ + { + name: "Chinese", + }, + { + name: "English", + }, + ], + }, + { + name: "Heard Island and McDonald Islands", + code: "HM", + icon: "virtual-host", + icon_status: IconStatus.Up, + capital: null, + continent: { + name: "Antarctica", + }, + currency: "AUD", + languages: [ + { + name: "English", + }, + ], + }, + { + name: "Honduras", + code: "HN", + icon: "virtual-host", + icon_status: IconStatus.Up, + capital: "Tegucigalpa", + continent: { + name: "North America", + }, + currency: "HNL", + languages: [ + { + name: "Spanish", + }, + ], + }, + { + name: "Croatia", + code: "HR", + icon: "virtual-host", + icon_status: IconStatus.Up, + capital: "Zagreb", + continent: { + name: "Europe", + }, + currency: "HRK", + languages: [ + { + name: "Croatian", + }, + ], + }, + { + name: "Haiti", + code: "HT", + icon: "virtual-host", + icon_status: IconStatus.Up, + capital: "Port-au-Prince", + continent: { + name: "North America", + }, + currency: "HTG,USD", + languages: [ + { + name: "French", + }, + { + name: "Haitian", + }, + ], + }, + { + name: "Hungary", + code: "HU", + icon: "virtual-host", + icon_status: IconStatus.Up, + capital: "Budapest", + continent: { + name: "Europe", + }, + currency: "HUF", + languages: [ + { + name: "Hungarian", + }, + ], + }, + { + name: "Indonesia", + code: "ID", + icon: "virtual-host", + icon_status: IconStatus.Up, + capital: "Jakarta", + continent: { + name: "Asia", + }, + currency: "IDR", + languages: [ + { + name: "Indonesian", + }, + ], + }, + { + name: "Ireland", + code: "IE", + icon: "virtual-host", + icon_status: IconStatus.Up, + capital: "Dublin", + continent: { + name: "Europe", + }, + currency: "EUR", + languages: [ + { + name: "Irish", + }, + { + name: "English", + }, + ], + }, + { + name: "Israel", + code: "IL", + icon: "virtual-host", + icon_status: IconStatus.Up, + capital: "Jerusalem", + continent: { + name: "Asia", + }, + currency: "ILS", + languages: [ + { + name: "Hebrew", + }, + { + name: "Arabic", + }, + ], + }, + { + name: "Isle of Man", + code: "IM", + icon: "virtual-host", + icon_status: IconStatus.Up, + capital: "Douglas", + continent: { + name: "Europe", + }, + currency: "GBP", + languages: [ + { + name: "English", + }, + { + name: "Manx", + }, + ], + }, + { + name: "India", + code: "IN", + icon: "virtual-host", + icon_status: IconStatus.Up, + capital: "New Delhi", + continent: { + name: "Asia", + }, + currency: "INR", + languages: [ + { + name: "Hindi", + }, + { + name: "English", + }, + ], + }, + { + name: "British Indian Ocean Territory", + code: "IO", + icon: "virtual-host", + icon_status: IconStatus.Up, + capital: "Diego Garcia", + continent: { + name: "Asia", + }, + currency: "USD", + languages: [ + { + name: "English", + }, + ], + }, + { + name: "Iraq", + code: "IQ", + icon: "virtual-host", + icon_status: IconStatus.Up, + capital: "Baghdad", + continent: { + name: "Asia", + }, + currency: "IQD", + languages: [ + { + name: "Arabic", + }, + { + name: "Kurdish", + }, + ], + }, + { + name: "Iran", + code: "IR", + icon: "virtual-host", + icon_status: IconStatus.Up, + capital: "Tehran", + continent: { + name: "Asia", + }, + currency: "IRR", + languages: [ + { + name: "Persian", + }, + ], + }, + { + name: "Iceland", + code: "IS", + icon: "virtual-host", + icon_status: IconStatus.Up, + capital: "Reykjavik", + continent: { + name: "Europe", + }, + currency: "ISK", + languages: [ + { + name: "Icelandic", + }, + ], + }, + { + name: "Italy", + code: "IT", + icon: "virtual-host", + icon_status: IconStatus.Up, + capital: "Rome", + continent: { + name: "Europe", + }, + currency: "EUR", + languages: [ + { + name: "Italian", + }, + ], + }, + { + name: "Jersey", + code: "JE", + icon: "virtual-host", + icon_status: IconStatus.Up, + capital: "Saint Helier", + continent: { + name: "Europe", + }, + currency: "GBP", + languages: [ + { + name: "English", + }, + { + name: "French", + }, + ], + }, + { + name: "Jamaica", + code: "JM", + icon: "virtual-host", + icon_status: IconStatus.Up, + capital: "Kingston", + continent: { + name: "North America", + }, + currency: "JMD", + languages: [ + { + name: "English", + }, + ], + }, + { + name: "Jordan", + code: "JO", + icon: "virtual-host", + icon_status: IconStatus.Up, + capital: "Amman", + continent: { + name: "Asia", + }, + currency: "JOD", + languages: [ + { + name: "Arabic", + }, + ], + }, + { + name: "Japan", + code: "JP", + icon: "virtual-host", + icon_status: IconStatus.Up, + capital: "Tokyo", + continent: { + name: "Asia", + }, + currency: "JPY", + languages: [ + { + name: "Japanese", + }, + ], + }, + { + name: "Kenya", + code: "KE", + icon: "virtual-host", + icon_status: IconStatus.Up, + capital: "Nairobi", + continent: { + name: "Africa", + }, + currency: "KES", + languages: [ + { + name: "English", + }, + { + name: "Swahili", + }, + ], + }, + { + name: "Kyrgyzstan", + code: "KG", + icon: "virtual-host", + icon_status: IconStatus.Up, + capital: "Bishkek", + continent: { + name: "Asia", + }, + currency: "KGS", + languages: [ + { + name: "Kirghiz", + }, + { + name: "Russian", + }, + ], + }, + { + name: "Cambodia", + code: "KH", + icon: "virtual-host", + icon_status: IconStatus.Up, + capital: "Phnom Penh", + continent: { + name: "Asia", + }, + currency: "KHR", + languages: [ + { + name: "Cambodian", + }, + ], + }, + { + name: "Kiribati", + code: "KI", + icon: "virtual-host", + icon_status: IconStatus.Up, + capital: "South Tarawa", + continent: { + name: "Oceania", + }, + currency: "AUD", + languages: [ + { + name: "English", + }, + ], + }, + { + name: "Comoros", + code: "KM", + icon: "virtual-host", + icon_status: IconStatus.Up, + capital: "Moroni", + continent: { + name: "Africa", + }, + currency: "KMF", + languages: [ + { + name: "Arabic", + }, + { + name: "French", + }, + ], + }, + { + name: "Saint Kitts and Nevis", + code: "KN", + icon: "virtual-host", + icon_status: IconStatus.Up, + capital: "Basseterre", + continent: { + name: "North America", + }, + currency: "XCD", + languages: [ + { + name: "English", + }, + ], + }, + { + name: "North Korea", + code: "KP", + icon: "virtual-host", + icon_status: IconStatus.Up, + capital: "Pyongyang", + continent: { + name: "Asia", + }, + currency: "KPW", + languages: [ + { + name: "Korean", + }, + ], + }, + { + name: "South Korea", + code: "KR", + icon: "virtual-host", + icon_status: IconStatus.Up, + capital: "Seoul", + continent: { + name: "Asia", + }, + currency: "KRW", + languages: [ + { + name: "Korean", + }, + ], + }, + { + name: "Kuwait", + code: "KW", + icon: "virtual-host", + icon_status: IconStatus.Up, + capital: "Kuwait City", + continent: { + name: "Asia", + }, + currency: "KWD", + languages: [ + { + name: "Arabic", + }, + ], + }, + { + name: "Cayman Islands", + code: "KY", + icon: "virtual-host", + icon_status: IconStatus.Up, + capital: "George Town", + continent: { + name: "North America", + }, + currency: "KYD", + languages: [ + { + name: "English", + }, + ], + }, + { + name: "Kazakhstan", + code: "KZ", + icon: "virtual-host", + icon_status: IconStatus.Up, + capital: "Astana", + continent: { + name: "Asia", + }, + currency: "KZT", + languages: [ + { + name: "Kazakh", + }, + { + name: "Russian", + }, + ], + }, + { + name: "Laos", + code: "LA", + icon: "virtual-host", + icon_status: IconStatus.Up, + capital: "Vientiane", + continent: { + name: "Asia", + }, + currency: "LAK", + languages: [ + { + name: "Laotian", + }, + ], + }, + { + name: "Lebanon", + code: "LB", + icon: "virtual-host", + icon_status: IconStatus.Up, + capital: "Beirut", + continent: { + name: "Asia", + }, + currency: "LBP", + languages: [ + { + name: "Arabic", + }, + { + name: "French", + }, + ], + }, + { + name: "Saint Lucia", + code: "LC", + icon: "virtual-host", + icon_status: IconStatus.Up, + capital: "Castries", + continent: { + name: "North America", + }, + currency: "XCD", + languages: [ + { + name: "English", + }, + ], + }, + { + name: "Liechtenstein", + code: "LI", + icon: "virtual-host", + icon_status: IconStatus.Up, + capital: "Vaduz", + continent: { + name: "Europe", + }, + currency: "CHF", + languages: [ + { + name: "German", + }, + ], + }, + { + name: "Sri Lanka", + code: "LK", + icon: "virtual-host", + icon_status: IconStatus.Up, + capital: "Colombo", + continent: { + name: "Asia", + }, + currency: "LKR", + languages: [ + { + name: "Sinhalese", + }, + { + name: "Tamil", + }, + ], + }, + { + name: "Liberia", + code: "LR", + icon: "virtual-host", + icon_status: IconStatus.Up, + capital: "Monrovia", + continent: { + name: "Africa", + }, + currency: "LRD", + languages: [ + { + name: "English", + }, + ], + }, + { + name: "Lesotho", + code: "LS", + icon: "virtual-host", + icon_status: IconStatus.Up, + capital: "Maseru", + continent: { + name: "Africa", + }, + currency: "LSL,ZAR", + languages: [ + { + name: "English", + }, + { + name: "Southern Sotho", + }, + ], + }, + { + name: "Lithuania", + code: "LT", + icon: "virtual-host", + icon_status: IconStatus.Up, + capital: "Vilnius", + continent: { + name: "Europe", + }, + currency: "EUR", + languages: [ + { + name: "Lithuanian", + }, + ], + }, + { + name: "Luxembourg", + code: "LU", + icon: "virtual-host", + icon_status: IconStatus.Up, + capital: "Luxembourg", + continent: { + name: "Europe", + }, + currency: "EUR", + languages: [ + { + name: "French", + }, + { + name: "German", + }, + { + name: "Luxembourgish", + }, + ], + }, + { + name: "Latvia", + code: "LV", + icon: "virtual-host", + icon_status: IconStatus.Up, + capital: "Riga", + continent: { + name: "Europe", + }, + currency: "EUR", + languages: [ + { + name: "Latvian", + }, + ], + }, + { + name: "Libya", + code: "LY", + icon: "virtual-host", + icon_status: IconStatus.Up, + capital: "Tripoli", + continent: { + name: "Africa", + }, + currency: "LYD", + languages: [ + { + name: "Arabic", + }, + ], + }, + { + name: "Morocco", + code: "MA", + icon: "virtual-host", + icon_status: IconStatus.Up, + capital: "Rabat", + continent: { + name: "Africa", + }, + currency: "MAD", + languages: [ + { + name: "Arabic", + }, + ], + }, + { + name: "Monaco", + code: "MC", + icon: "virtual-host", + icon_status: IconStatus.Up, + capital: "Monaco", + continent: { + name: "Europe", + }, + currency: "EUR", + languages: [ + { + name: "French", + }, + ], + }, + { + name: "Moldova", + code: "MD", + icon: "virtual-host", + icon_status: IconStatus.Up, + capital: "Chișinău", + continent: { + name: "Europe", + }, + currency: "MDL", + languages: [ + { + name: "Romanian", + }, + ], + }, + { + name: "Montenegro", + code: "ME", + icon: "virtual-host", + icon_status: IconStatus.Up, + capital: "Podgorica", + continent: { + name: "Europe", + }, + currency: "EUR", + languages: [ + { + name: "Serbian", + }, + { + name: "Bosnian", + }, + { + name: "Albanian", + }, + { + name: "Croatian", + }, + ], + }, + { + name: "Saint Martin", + code: "MF", + icon: "virtual-host", + icon_status: IconStatus.Up, + capital: "Marigot", + continent: { + name: "North America", + }, + currency: "EUR", + languages: [ + { + name: "English", + }, + { + name: "French", + }, + { + name: "Dutch", + }, + ], + }, + { + name: "Madagascar", + code: "MG", + icon: "virtual-host", + icon_status: IconStatus.Up, + capital: "Antananarivo", + continent: { + name: "Africa", + }, + currency: "MGA", + languages: [ + { + name: "French", + }, + { + name: "Malagasy", + }, + ], + }, + { + name: "Marshall Islands", + code: "MH", + icon: "virtual-host", + icon_status: IconStatus.Up, + capital: "Majuro", + continent: { + name: "Oceania", + }, + currency: "USD", + languages: [ + { + name: "English", + }, + { + name: "Marshallese", + }, + ], + }, + { + name: "North Macedonia", + code: "MK", + icon: "virtual-host", + icon_status: IconStatus.Up, + capital: "Skopje", + continent: { + name: "Europe", + }, + currency: "MKD", + languages: [ + { + name: "Macedonian", + }, + ], + }, + { + name: "Mali", + code: "ML", + icon: "virtual-host", + icon_status: IconStatus.Up, + capital: "Bamako", + continent: { + name: "Africa", + }, + currency: "XOF", + languages: [ + { + name: "French", + }, + ], + }, + { + name: "Myanmar [Burma]", + code: "MM", + icon: "virtual-host", + icon_status: IconStatus.Up, + capital: "Naypyidaw", + continent: { + name: "Asia", + }, + currency: "MMK", + languages: [ + { + name: "Burmese", + }, + ], + }, + { + name: "Mongolia", + code: "MN", + icon: "virtual-host", + icon_status: IconStatus.Up, + capital: "Ulan Bator", + continent: { + name: "Asia", + }, + currency: "MNT", + languages: [ + { + name: "Mongolian", + }, + ], + }, + { + name: "Macao", + code: "MO", + icon: "virtual-host", + icon_status: IconStatus.Up, + capital: null, + continent: { + name: "Asia", + }, + currency: "MOP", + languages: [ + { + name: "Chinese", + }, + { + name: "Portuguese", + }, + ], + }, + { + name: "Northern Mariana Islands", + code: "MP", + icon: "virtual-host", + icon_status: IconStatus.Up, + capital: "Saipan", + continent: { + name: "Oceania", + }, + currency: "USD", + languages: [ + { + name: "English", + }, + { + name: "Chamorro", + }, + ], + }, + { + name: "Martinique", + code: "MQ", + icon: "virtual-host", + icon_status: IconStatus.Up, + capital: "Fort-de-France", + continent: { + name: "North America", + }, + currency: "EUR", + languages: [ + { + name: "French", + }, + ], + }, + { + name: "Mauritania", + code: "MR", + icon: "virtual-host", + icon_status: IconStatus.Up, + capital: "Nouakchott", + continent: { + name: "Africa", + }, + currency: "MRU", + languages: [ + { + name: "Arabic", + }, + ], + }, + { + name: "Montserrat", + code: "MS", + icon: "virtual-host", + icon_status: IconStatus.Up, + capital: "Plymouth", + continent: { + name: "North America", + }, + currency: "XCD", + languages: [ + { + name: "English", + }, + ], + }, + { + name: "Malta", + code: "MT", + icon: "virtual-host", + icon_status: IconStatus.Up, + capital: "Valletta", + continent: { + name: "Europe", + }, + currency: "EUR", + languages: [ + { + name: "Maltese", + }, + { + name: "English", + }, + ], + }, + { + name: "Mauritius", + code: "MU", + icon: "virtual-host", + icon_status: IconStatus.Up, + capital: "Port Louis", + continent: { + name: "Africa", + }, + currency: "MUR", + languages: [ + { + name: "English", + }, + ], + }, + { + name: "Maldives", + code: "MV", + icon: "virtual-host", + icon_status: IconStatus.Up, + capital: "Malé", + continent: { + name: "Asia", + }, + currency: "MVR", + languages: [ + { + name: "Divehi", + }, + ], + }, + { + name: "Malawi", + code: "MW", + icon: "virtual-host", + icon_status: IconStatus.Up, + capital: "Lilongwe", + continent: { + name: "Africa", + }, + currency: "MWK", + languages: [ + { + name: "English", + }, + { + name: "Chichewa", + }, + ], + }, + { + name: "Mexico", + code: "MX", + icon: "virtual-host", + icon_status: IconStatus.Up, + capital: "Mexico City", + continent: { + name: "North America", + }, + currency: "MXN", + languages: [ + { + name: "Spanish", + }, + ], + }, + { + name: "Malaysia", + code: "MY", + icon: "virtual-host", + icon_status: IconStatus.Up, + capital: "Kuala Lumpur", + continent: { + name: "Asia", + }, + currency: "MYR", + languages: [ + { + name: "Malay", + }, + ], + }, + { + name: "Mozambique", + code: "MZ", + icon: "virtual-host", + icon_status: IconStatus.Up, + capital: "Maputo", + continent: { + name: "Africa", + }, + currency: "MZN", + languages: [ + { + name: "Portuguese", + }, + ], + }, + { + name: "Namibia", + code: "NA", + icon: "virtual-host", + icon_status: IconStatus.Up, + capital: "Windhoek", + continent: { + name: "Africa", + }, + currency: "NAD,ZAR", + languages: [ + { + name: "English", + }, + { + name: "Afrikaans", + }, + ], + }, + { + name: "New Caledonia", + code: "NC", + icon: "virtual-host", + icon_status: IconStatus.Up, + capital: "Nouméa", + continent: { + name: "Oceania", + }, + currency: "XPF", + languages: [ + { + name: "French", + }, + ], + }, + { + name: "Niger", + code: "NE", + icon: "virtual-host", + icon_status: IconStatus.Up, + capital: "Niamey", + continent: { + name: "Africa", + }, + currency: "XOF", + languages: [ + { + name: "French", + }, + ], + }, + { + name: "Norfolk Island", + code: "NF", + icon: "virtual-host", + icon_status: IconStatus.Up, + capital: "Kingston", + continent: { + name: "Oceania", + }, + currency: "AUD", + languages: [ + { + name: "English", + }, + ], + }, + { + name: "Nigeria", + code: "NG", + icon: "virtual-host", + icon_status: IconStatus.Up, + capital: "Abuja", + continent: { + name: "Africa", + }, + currency: "NGN", + languages: [ + { + name: "English", + }, + ], + }, + { + name: "Nicaragua", + code: "NI", + icon: "virtual-host", + icon_status: IconStatus.Up, + capital: "Managua", + continent: { + name: "North America", + }, + currency: "NIO", + languages: [ + { + name: "Spanish", + }, + ], + }, + { + name: "Netherlands", + code: "NL", + icon: "virtual-host", + icon_status: IconStatus.Up, + capital: "Amsterdam", + continent: { + name: "Europe", + }, + currency: "EUR", + languages: [ + { + name: "Dutch", + }, + ], + }, + { + name: "Norway", + code: "NO", + icon: "virtual-host", + icon_status: IconStatus.Up, + capital: "Oslo", + continent: { + name: "Europe", + }, + currency: "NOK", + languages: [ + { + name: "Norwegian", + }, + { + name: "Norwegian Bokmål", + }, + { + name: "Norwegian Nynorsk", + }, + ], + }, + { + name: "Nepal", + code: "NP", + icon: "virtual-host", + icon_status: IconStatus.Up, + capital: "Kathmandu", + continent: { + name: "Asia", + }, + currency: "NPR", + languages: [ + { + name: "Nepali", + }, + ], + }, + { + name: "Nauru", + code: "NR", + icon: "virtual-host", + icon_status: IconStatus.Up, + capital: "Yaren", + continent: { + name: "Oceania", + }, + currency: "AUD", + languages: [ + { + name: "English", + }, + { + name: "Nauruan", + }, + ], + }, + { + name: "Niue", + code: "NU", + icon: "virtual-host", + icon_status: IconStatus.Up, + capital: "Alofi", + continent: { + name: "Oceania", + }, + currency: "NZD", + languages: [ + { + name: "English", + }, + ], + }, + { + name: "New Zealand", + code: "NZ", + icon: "virtual-host", + icon_status: IconStatus.Up, + capital: "Wellington", + continent: { + name: "Oceania", + }, + currency: "NZD", + languages: [ + { + name: "English", + }, + { + name: "Maori", + }, + ], + }, + { + name: "Oman", + code: "OM", + icon: "virtual-host", + icon_status: IconStatus.Up, + capital: "Muscat", + continent: { + name: "Asia", + }, + currency: "OMR", + languages: [ + { + name: "Arabic", + }, + ], + }, + { + name: "Panama", + code: "PA", + icon: "virtual-host", + icon_status: IconStatus.Up, + capital: "Panama City", + continent: { + name: "North America", + }, + currency: "PAB,USD", + languages: [ + { + name: "Spanish", + }, + ], + }, + { + name: "Peru", + code: "PE", + icon: "virtual-host", + icon_status: IconStatus.Up, + capital: "Lima", + continent: { + name: "South America", + }, + currency: "PEN", + languages: [ + { + name: "Spanish", + }, + ], + }, + { + name: "French Polynesia", + code: "PF", + icon: "virtual-host", + icon_status: IconStatus.Up, + capital: "Papeetē", + continent: { + name: "Oceania", + }, + currency: "XPF", + languages: [ + { + name: "French", + }, + ], + }, + { + name: "Papua New Guinea", + code: "PG", + icon: "virtual-host", + icon_status: IconStatus.Up, + capital: "Port Moresby", + continent: { + name: "Oceania", + }, + currency: "PGK", + languages: [ + { + name: "English", + }, + ], + }, + { + name: "Philippines", + code: "PH", + icon: "virtual-host", + icon_status: IconStatus.Up, + capital: "Manila", + continent: { + name: "Asia", + }, + currency: "PHP", + languages: [ + { + name: "English", + }, + ], + }, + { + name: "Pakistan", + code: "PK", + icon: "virtual-host", + icon_status: IconStatus.Up, + capital: "Islamabad", + continent: { + name: "Asia", + }, + currency: "PKR", + languages: [ + { + name: "English", + }, + { + name: "Urdu", + }, + ], + }, + { + name: "Poland", + code: "PL", + icon: "virtual-host", + icon_status: IconStatus.Up, + capital: "Warsaw", + continent: { + name: "Europe", + }, + currency: "PLN", + languages: [ + { + name: "Polish", + }, + ], + }, + { + name: "Saint Pierre and Miquelon", + code: "PM", + icon: "virtual-host", + icon_status: IconStatus.Up, + capital: "Saint-Pierre", + continent: { + name: "North America", + }, + currency: "EUR", + languages: [ + { + name: "French", + }, + ], + }, + { + name: "Pitcairn Islands", + code: "PN", + icon: "virtual-host", + icon_status: IconStatus.Up, + capital: "Adamstown", + continent: { + name: "Oceania", + }, + currency: "NZD", + languages: [ + { + name: "English", + }, + ], + }, + { + name: "Puerto Rico", + code: "PR", + icon: "virtual-host", + icon_status: IconStatus.Up, + capital: "San Juan", + continent: { + name: "North America", + }, + currency: "USD", + languages: [ + { + name: "Spanish", + }, + { + name: "English", + }, + ], + }, + { + name: "Palestine", + code: "PS", + icon: "virtual-host", + icon_status: IconStatus.Up, + capital: "Ramallah", + continent: { + name: "Asia", + }, + currency: "ILS", + languages: [ + { + name: "Arabic", + }, + ], + }, + { + name: "Portugal", + code: "PT", + icon: "virtual-host", + icon_status: IconStatus.Up, + capital: "Lisbon", + continent: { + name: "Europe", + }, + currency: "EUR", + languages: [ + { + name: "Portuguese", + }, + ], + }, + { + name: "Palau", + code: "PW", + icon: "virtual-host", + icon_status: IconStatus.Up, + capital: "Ngerulmud", + continent: { + name: "Oceania", + }, + currency: "USD", + languages: [ + { + name: "English", + }, + ], + }, + { + name: "Paraguay", + code: "PY", + icon: "virtual-host", + icon_status: IconStatus.Up, + capital: "Asunción", + continent: { + name: "South America", + }, + currency: "PYG", + languages: [ + { + name: "Spanish", + }, + { + name: "Guarani", + }, + ], + }, + { + name: "Qatar", + code: "QA", + icon: "virtual-host", + icon_status: IconStatus.Up, + capital: "Doha", + continent: { + name: "Asia", + }, + currency: "QAR", + languages: [ + { + name: "Arabic", + }, + ], + }, + { + name: "Réunion", + code: "RE", + icon: "virtual-host", + icon_status: IconStatus.Up, + capital: "Saint-Denis", + continent: { + name: "Africa", + }, + currency: "EUR", + languages: [ + { + name: "French", + }, + ], + }, + { + name: "Romania", + code: "RO", + icon: "virtual-host", + icon_status: IconStatus.Up, + capital: "Bucharest", + continent: { + name: "Europe", + }, + currency: "RON", + languages: [ + { + name: "Romanian", + }, + ], + }, + { + name: "Serbia", + code: "RS", + icon: "virtual-host", + icon_status: IconStatus.Up, + capital: "Belgrade", + continent: { + name: "Europe", + }, + currency: "RSD", + languages: [ + { + name: "Serbian", + }, + ], + }, + { + name: "Russia", + code: "RU", + icon: "virtual-host", + icon_status: IconStatus.Up, + capital: "Moscow", + continent: { + name: "Europe", + }, + currency: "RUB", + languages: [ + { + name: "Russian", + }, + ], + }, + { + name: "Rwanda", + code: "RW", + icon: "virtual-host", + icon_status: IconStatus.Up, + capital: "Kigali", + continent: { + name: "Africa", + }, + currency: "RWF", + languages: [ + { + name: "Rwandi", + }, + { + name: "English", + }, + { + name: "French", + }, + ], + }, + { + name: "Saudi Arabia", + code: "SA", + icon: "virtual-host", + icon_status: IconStatus.Up, + capital: "Riyadh", + continent: { + name: "Asia", + }, + currency: "SAR", + languages: [ + { + name: "Arabic", + }, + ], + }, + { + name: "Solomon Islands", + code: "SB", + icon: "virtual-host", + icon_status: IconStatus.Up, + capital: "Honiara", + continent: { + name: "Oceania", + }, + currency: "SBD", + languages: [ + { + name: "English", + }, + ], + }, + { + name: "Seychelles", + code: "SC", + icon: "virtual-host", + icon_status: IconStatus.Up, + capital: "Victoria", + continent: { + name: "Africa", + }, + currency: "SCR", + languages: [ + { + name: "French", + }, + { + name: "English", + }, + ], + }, + { + name: "Sudan", + code: "SD", + icon: "virtual-host", + icon_status: IconStatus.Up, + capital: "Khartoum", + continent: { + name: "Africa", + }, + currency: "SDG", + languages: [ + { + name: "Arabic", + }, + { + name: "English", + }, + ], + }, + { + name: "Sweden", + code: "SE", + icon: "virtual-host", + icon_status: IconStatus.Up, + capital: "Stockholm", + continent: { + name: "Europe", + }, + currency: "SEK", + languages: [ + { + name: "Swedish", + }, + ], + }, + { + name: "Singapore", + code: "SG", + icon: "virtual-host", + icon_status: IconStatus.Up, + capital: "Singapore", + continent: { + name: "Asia", + }, + currency: "SGD", + languages: [ + { + name: "English", + }, + { + name: "Malay", + }, + { + name: "Tamil", + }, + { + name: "Chinese", + }, + ], + }, + { + name: "Saint Helena", + code: "SH", + icon: "virtual-host", + icon_status: IconStatus.Up, + capital: "Jamestown", + continent: { + name: "Africa", + }, + currency: "SHP", + languages: [ + { + name: "English", + }, + ], + }, + { + name: "Slovenia", + code: "SI", + icon: "virtual-host", + icon_status: IconStatus.Up, + capital: "Ljubljana", + continent: { + name: "Europe", + }, + currency: "EUR", + languages: [ + { + name: "Slovenian", + }, + ], + }, + { + name: "Svalbard and Jan Mayen", + code: "SJ", + icon: "virtual-host", + icon_status: IconStatus.Up, + capital: "Longyearbyen", + continent: { + name: "Europe", + }, + currency: "NOK", + languages: [ + { + name: "Norwegian", + }, + ], + }, + { + name: "Slovakia", + code: "SK", + icon: "virtual-host", + icon_status: IconStatus.Up, + capital: "Bratislava", + continent: { + name: "Europe", + }, + currency: "EUR", + languages: [ + { + name: "Slovak", + }, + ], + }, + { + name: "Sierra Leone", + code: "SL", + icon: "virtual-host", + icon_status: IconStatus.Up, + capital: "Freetown", + continent: { + name: "Africa", + }, + currency: "SLL", + languages: [ + { + name: "English", + }, + ], + }, + { + name: "San Marino", + code: "SM", + icon: "virtual-host", + icon_status: IconStatus.Up, + capital: "City of San Marino", + continent: { + name: "Europe", + }, + currency: "EUR", + languages: [ + { + name: "Italian", + }, + ], + }, + { + name: "Senegal", + code: "SN", + icon: "virtual-host", + icon_status: IconStatus.Up, + capital: "Dakar", + continent: { + name: "Africa", + }, + currency: "XOF", + languages: [ + { + name: "French", + }, + ], + }, + { + name: "Somalia", + code: "SO", + icon: "virtual-host", + icon_status: IconStatus.Up, + capital: "Mogadishu", + continent: { + name: "Africa", + }, + currency: "SOS", + languages: [ + { + name: "Somalia", + }, + { + name: "Arabic", + }, + ], + }, + { + name: "Suriname", + code: "SR", + icon: "virtual-host", + icon_status: IconStatus.Up, + capital: "Paramaribo", + continent: { + name: "South America", + }, + currency: "SRD", + languages: [ + { + name: "Dutch", + }, + ], + }, + { + name: "South Sudan", + code: "SS", + icon: "virtual-host", + icon_status: IconStatus.Up, + capital: "Juba", + continent: { + name: "Africa", + }, + currency: "SSP", + languages: [ + { + name: "English", + }, + ], + }, + { + name: "São Tomé and Príncipe", + code: "ST", + icon: "virtual-host", + icon_status: IconStatus.Up, + capital: "São Tomé", + continent: { + name: "Africa", + }, + currency: "STN", + languages: [ + { + name: "Portuguese", + }, + ], + }, + { + name: "El Salvador", + code: "SV", + icon: "virtual-host", + icon_status: IconStatus.Up, + capital: "San Salvador", + continent: { + name: "North America", + }, + currency: "SVC,USD", + languages: [ + { + name: "Spanish", + }, + ], + }, + { + name: "Sint Maarten", + code: "SX", + icon: "virtual-host", + icon_status: IconStatus.Up, + capital: "Philipsburg", + continent: { + name: "North America", + }, + currency: "ANG", + languages: [ + { + name: "Dutch", + }, + { + name: "English", + }, + ], + }, + { + name: "Syria", + code: "SY", + icon: "virtual-host", + icon_status: IconStatus.Up, + capital: "Damascus", + continent: { + name: "Asia", + }, + currency: "SYP", + languages: [ + { + name: "Arabic", + }, + ], + }, + { + name: "Swaziland", + code: "SZ", + icon: "virtual-host", + icon_status: IconStatus.Up, + capital: "Lobamba", + continent: { + name: "Africa", + }, + currency: "SZL", + languages: [ + { + name: "English", + }, + { + name: "Swati", + }, + ], + }, + { + name: "Turks and Caicos Islands", + code: "TC", + icon: "virtual-host", + icon_status: IconStatus.Up, + capital: "Cockburn Town", + continent: { + name: "North America", + }, + currency: "USD", + languages: [ + { + name: "English", + }, + ], + }, + { + name: "Chad", + code: "TD", + icon: "virtual-host", + icon_status: IconStatus.Up, + capital: "N'Djamena", + continent: { + name: "Africa", + }, + currency: "XAF", + languages: [ + { + name: "French", + }, + { + name: "Arabic", + }, + ], + }, + { + name: "French Southern Territories", + code: "TF", + icon: "virtual-host", + icon_status: IconStatus.Up, + capital: "Port-aux-Français", + continent: { + name: "Antarctica", + }, + currency: "EUR", + languages: [ + { + name: "French", + }, + ], + }, + { + name: "Togo", + code: "TG", + icon: "virtual-host", + icon_status: IconStatus.Up, + capital: "Lomé", + continent: { + name: "Africa", + }, + currency: "XOF", + languages: [ + { + name: "French", + }, + ], + }, + { + name: "Thailand", + code: "TH", + icon: "virtual-host", + icon_status: IconStatus.Up, + capital: "Bangkok", + continent: { + name: "Asia", + }, + currency: "THB", + languages: [ + { + name: "Thai", + }, + ], + }, + { + name: "Tajikistan", + code: "TJ", + icon: "virtual-host", + icon_status: IconStatus.Up, + capital: "Dushanbe", + continent: { + name: "Asia", + }, + currency: "TJS", + languages: [ + { + name: "Tajik", + }, + { + name: "Russian", + }, + ], + }, + { + name: "Tokelau", + code: "TK", + icon: "virtual-host", + icon_status: IconStatus.Up, + capital: "Fakaofo", + continent: { + name: "Oceania", + }, + currency: "NZD", + languages: [ + { + name: "English", + }, + ], + }, + { + name: "East Timor", + code: "TL", + icon: "virtual-host", + icon_status: IconStatus.Up, + capital: "Dili", + continent: { + name: "Oceania", + }, + currency: "USD", + languages: [ + { + name: "Portuguese", + }, + ], + }, + { + name: "Turkmenistan", + code: "TM", + icon: "virtual-host", + icon_status: IconStatus.Up, + capital: "Ashgabat", + continent: { + name: "Asia", + }, + currency: "TMT", + languages: [ + { + name: "Turkmen", + }, + { + name: "Russian", + }, + ], + }, + { + name: "Tunisia", + code: "TN", + icon: "virtual-host", + icon_status: IconStatus.Up, + capital: "Tunis", + continent: { + name: "Africa", + }, + currency: "TND", + languages: [ + { + name: "Arabic", + }, + ], + }, + { + name: "Tonga", + code: "TO", + icon: "virtual-host", + icon_status: IconStatus.Up, + capital: "Nuku'alofa", + continent: { + name: "Oceania", + }, + currency: "TOP", + languages: [ + { + name: "English", + }, + { + name: "Tonga", + }, + ], + }, + { + name: "Turkey", + code: "TR", + icon: "virtual-host", + icon_status: IconStatus.Up, + capital: "Ankara", + continent: { + name: "Asia", + }, + currency: "TRY", + languages: [ + { + name: "Turkish", + }, + ], + }, + { + name: "Trinidad and Tobago", + code: "TT", + icon: "virtual-host", + icon_status: IconStatus.Up, + capital: "Port of Spain", + continent: { + name: "North America", + }, + currency: "TTD", + languages: [ + { + name: "English", + }, + ], + }, + { + name: "Tuvalu", + code: "TV", + icon: "virtual-host", + icon_status: IconStatus.Up, + capital: "Funafuti", + continent: { + name: "Oceania", + }, + currency: "AUD", + languages: [ + { + name: "English", + }, + ], + }, + { + name: "Taiwan", + code: "TW", + icon: "virtual-host", + icon_status: IconStatus.Up, + capital: "Taipei", + continent: { + name: "Asia", + }, + currency: "TWD", + languages: [ + { + name: "Chinese", + }, + ], + }, + { + name: "Tanzania", + code: "TZ", + icon: "virtual-host", + icon_status: IconStatus.Up, + capital: "Dodoma", + continent: { + name: "Africa", + }, + currency: "TZS", + languages: [ + { + name: "Swahili", + }, + { + name: "English", + }, + ], + }, + { + name: "Ukraine", + code: "UA", + icon: "virtual-host", + icon_status: IconStatus.Up, + capital: "Kyiv", + continent: { + name: "Europe", + }, + currency: "UAH", + languages: [ + { + name: "Ukrainian", + }, + ], + }, + { + name: "Uganda", + code: "UG", + icon: "virtual-host", + icon_status: IconStatus.Up, + capital: "Kampala", + continent: { + name: "Africa", + }, + currency: "UGX", + languages: [ + { + name: "English", + }, + { + name: "Swahili", + }, + ], + }, + { + name: "U.S. Minor Outlying Islands", + code: "UM", + icon: "virtual-host", + icon_status: IconStatus.Up, + capital: null, + continent: { + name: "Oceania", + }, + currency: "USD", + languages: [ + { + name: "English", + }, + ], + }, + { + name: "United States", + code: "US", + icon: "virtual-host", + icon_status: IconStatus.Up, + capital: "Washington D.C.", + continent: { + name: "North America", + }, + currency: "USD,USN,USS", + languages: [ + { + name: "English", + }, + ], + }, + { + name: "Uruguay", + code: "UY", + icon: "virtual-host", + icon_status: IconStatus.Up, + capital: "Montevideo", + continent: { + name: "South America", + }, + currency: "UYI,UYU", + languages: [ + { + name: "Spanish", + }, + ], + }, + { + name: "Uzbekistan", + code: "UZ", + icon: "virtual-host", + icon_status: IconStatus.Up, + capital: "Tashkent", + continent: { + name: "Asia", + }, + currency: "UZS", + languages: [ + { + name: "Uzbek", + }, + { + name: "Russian", + }, + ], + }, + { + name: "Vatican City", + code: "VA", + icon: "virtual-host", + icon_status: IconStatus.Up, + capital: "Vatican City", + continent: { + name: "Europe", + }, + currency: "EUR", + languages: [ + { + name: "Italian", + }, + { + name: "Latin", + }, + ], + }, + { + name: "Saint Vincent and the Grenadines", + code: "VC", + icon: "virtual-host", + icon_status: IconStatus.Up, + capital: "Kingstown", + continent: { + name: "North America", + }, + currency: "XCD", + languages: [ + { + name: "English", + }, + ], + }, + { + name: "Venezuela", + code: "VE", + icon: "virtual-host", + icon_status: IconStatus.Up, + capital: "Caracas", + continent: { + name: "South America", + }, + currency: "VES", + languages: [ + { + name: "Spanish", + }, + ], + }, + { + name: "British Virgin Islands", + code: "VG", + icon: "virtual-host", + icon_status: IconStatus.Up, + capital: "Road Town", + continent: { + name: "North America", + }, + currency: "USD", + languages: [ + { + name: "English", + }, + ], + }, + { + name: "U.S. Virgin Islands", + code: "VI", + icon: "virtual-host", + icon_status: IconStatus.Up, + capital: "Charlotte Amalie", + continent: { + name: "North America", + }, + currency: "USD", + languages: [ + { + name: "English", + }, + ], + }, + { + name: "Vietnam", + code: "VN", + icon: "virtual-host", + icon_status: IconStatus.Up, + capital: "Hanoi", + continent: { + name: "Asia", + }, + currency: "VND", + languages: [ + { + name: "Vietnamese", + }, + ], + }, + { + name: "Vanuatu", + code: "VU", + icon: "virtual-host", + icon_status: IconStatus.Up, + capital: "Port Vila", + continent: { + name: "Oceania", + }, + currency: "VUV", + languages: [ + { + name: "Bislama", + }, + { + name: "English", + }, + { + name: "French", + }, + ], + }, + { + name: "Wallis and Futuna", + code: "WF", + icon: "virtual-host", + icon_status: IconStatus.Up, + capital: "Mata-Utu", + continent: { + name: "Oceania", + }, + currency: "XPF", + languages: [ + { + name: "French", + }, + ], + }, + { + name: "Samoa", + code: "WS", + icon: "virtual-host", + icon_status: IconStatus.Up, + capital: "Apia", + continent: { + name: "Oceania", + }, + currency: "WST", + languages: [ + { + name: "Samoan", + }, + { + name: "English", + }, + ], + }, + { + name: "Kosovo", + code: "XK", + icon: "virtual-host", + icon_status: IconStatus.Up, + capital: "Pristina", + continent: { + name: "Europe", + }, + currency: "EUR", + languages: [ + { + name: "Albanian", + }, + { + name: "Serbian", + }, + ], + }, + { + name: "Yemen", + code: "YE", + icon: "virtual-host", + icon_status: IconStatus.Up, + capital: "Sana'a", + continent: { + name: "Asia", + }, + currency: "YER", + languages: [ + { + name: "Arabic", + }, + ], + }, + { + name: "Mayotte", + code: "YT", + icon: "virtual-host", + icon_status: IconStatus.Up, + capital: "Mamoudzou", + continent: { + name: "Africa", + }, + currency: "EUR", + languages: [ + { + name: "French", + }, + ], + }, + { + name: "South Africa", + code: "ZA", + icon: "virtual-host", + icon_status: IconStatus.Up, + capital: "Pretoria", + continent: { + name: "Africa", + }, + currency: "ZAR", + languages: [ + { + name: "Afrikaans", + }, + { + name: "English", + }, + { + name: "South Ndebele", + }, + { + name: "Southern Sotho", + }, + { + name: "Swati", + }, + { + name: "Tswana", + }, + { + name: "Tsonga", + }, + { + name: "Venda", + }, + { + name: "Xhosa", + }, + { + name: "Zulu", + }, + ], + }, + { + name: "Zambia", + code: "ZM", + icon: "virtual-host", + icon_status: IconStatus.Up, + capital: "Lusaka", + continent: { + name: "Africa", + }, + currency: "ZMW", + languages: [ + { + name: "English", + }, + ], + }, + { + name: "Zimbabwe", + code: "ZW", + icon: "virtual-host", + icon_status: IconStatus.Up, + capital: "Harare", + continent: { + name: "Africa", + }, + currency: "USD,ZAR,BWP,GBP,AUD,CNY,INR,JPY", + languages: [ + { + name: "English", + }, + { + name: "Shona", + }, + { + name: "North Ndebele", + }, + ], + }, + ], + }, +}; +\`, + "widget-types/drilldown/drilldown-widget/drilldown-widget-example.component.ts": \`// © 2022 SolarWinds Worldwide, LLC. All rights reserved. +// +// Permission is hereby granted, free of charge, to any person obtaining a copy +// of this software and associated documentation files (the "Software"), to +// deal in the Software without restriction, including without limitation the +// rights to use, copy, modify, merge, publish, distribute, sublicense, and/or +// sell copies of the Software, and to permit persons to whom the Software is +// furnished to do so, subject to the following conditions: +// +// The above copyright notice and this permission notice shall be included in +// all copies or substantial portions of the Software. +// +// THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR +// IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, +// FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE +// AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER +// LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, +// OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN +// THE SOFTWARE. + +import { HttpClient } from "@angular/common/http"; +import { + ChangeDetectorRef, + Component, + Injectable, + OnDestroy, + OnInit, +} from "@angular/core"; +import { GridsterConfig, GridsterItem } from "angular-gridster2"; +import { Apollo, gql } from "apollo-angular"; +import groupBy from "lodash/groupBy"; +import { BehaviorSubject, Observable, of } from "rxjs"; +import { catchError, delay, filter, map } from "rxjs/operators"; + +import { + DataSourceFeatures, + IconStatus, + IDataField, + IDataSource, + IDataSourceFeatures, + IDataSourceFeaturesConfiguration, + INovaFilters, + LoggerService, + ServerSideDataSource, + IFilters, +} from "@nova-ui/bits"; +import { + DATA_SOURCE, + DEFAULT_PIZZAGNA_ROOT, + IDashboard, + IDrilldownComponentsConfiguration, + IListWidgetConfiguration, + IProviderConfiguration, + IWidget, + IWidgets, + ListGroupItemComponent, + ListLeafItemComponent, + NOVA_DRILLDOWN_DATASOURCE_ADAPTER, + PizzagnaLayer, + ProviderRegistryService, + WellKnownPathKey, + WellKnownProviders, + WidgetTypesService, +} from "@nova-ui/dashboards"; + +import { DrilldownDataSource } from "./mock-data-source"; + +/** + * A simple KPI data source to retrieve the average rating of Harry Potter and the Sorcerer's Stone (book) via googleapis + */ +@Injectable() +export class DrilldownDataSourceRealApi + extends ServerSideDataSource + implements OnDestroy, IDataSource +{ + // This is the ID we'll use to identify the provider + public static providerId = "DrilldownDataSourceRealApi"; + + // Use this subject to communicate the data source's busy state + public busy = new BehaviorSubject(false); + public dataFields: Partial[] = [ + { id: "regionName", label: "Region name" }, + { id: "subregionName", label: "Subregion name" }, + ]; + + public features: IDataSourceFeaturesConfiguration; + private supportedFeatures: IDataSourceFeatures = { + search: { enabled: true }, + }; + + private drillState: string[] = []; + private groupBy: string[]; + + constructor( + private logger: LoggerService, + private http: HttpClient, + private apollo: Apollo + ) { + super(); + this.features = new DataSourceFeatures(this.supportedFeatures); + // TODO: remove Partial in vNext after marking dataType field as optional - NUI-5838 + ( + this.dataFieldsConfig.dataFields$ as BehaviorSubject< + Partial[] + > + ).next(this.dataFields); + } + + private groupedDataHistory: Array> = []; + + // In this example, getFilteredData is invoked every 10 minutes (Take a look at the refresher + // provider definition in the widget configuration below to see how the interval is set) + public async getFilteredData(data: IFilters): Promise { + return of(data) + .pipe( + filter(() => !!this.drillState), + map((countries) => { + const lastHistory = () => getLast(this.groupedDataHistory); + + if (!this.drillState.length && !this.groupBy.length) { + return countries; + } + + // adding "ROOT" as a root level for drilling + const fullDrillState = ["ROOT", ...this.drillState]; + const activeDrillLvl = fullDrillState.length; + const historyLvl = this.groupedDataHistory.length; + + // checking how many lvls we have to group for drilling, in case some are missed + const drillLvlDiff = activeDrillLvl - historyLvl; + + if (!drillLvlDiff) { + return lastHistory() || countries; + } + + const drillToGroup = fullDrillState.slice( + fullDrillState.length - drillLvlDiff + ); + + for (const drill of drillToGroup) { + const drillIdx = fullDrillState.findIndex( + (v) => v === drill + ); + const group = this.groupBy[drillIdx]; + + if (group) { + const dataToGroup = lastHistory() + ? lastHistory()[drill] + : countries; + const lastGroupedValue = groupBy( + dataToGroup, + group + ); + + this.groupedDataHistory.push(lastGroupedValue); + } + } + + // take last if we have all data grouped + if (this.groupBy.length === this.drillState.length) { + return lastHistory()[getLast(this.drillState)]; + } + + // get groping and transform to raw data format + return this.getGroupsWidgetData(lastHistory()); + }) + ) + .toPromise(); + } + + public ngOnDestroy(): void { + this.outputsSubject.complete(); + } + + // This method is expected to return all data needed for repeat/paginator/filterGroups in order to work. + // In case of custom filtering participants feel free to extend INovaFilteringOutputs. + protected getBackendData(filters: INovaFilters): Observable { + const mainRequest = this.apollo.watchQuery<{ countries: any }>({ + query: this.generateQuery(filters), + }); + + return mainRequest.valueChanges.pipe( + // mock delay + delay(300), + // data mapping, !DS specific! + map((res) => res.data.countries), + // adds mock icons to be displayed on leaf nodes !DS specific! + map((res: any[]) => + res.map((v) => ({ + ...v, + icon: "virtual-host", + icon_status: IconStatus.Up, + subregionName: + v.subregion?.name || "No Subregion Specified", + regionName: + v.subregion?.region?.name || "No Region Specified", + })) + ), + catchError((e) => { + this.logger.error(e); + return of({} as any); + }) + ); + } + + private generateQuery(filters: INovaFilters) { + const { search } = filters; + const searchValue = search?.value ? \\\`^[\\\${search.value}]*\\\` : ""; + + const queryString = \\\` + query { + countries(filter: {name: {regex: "\\\${searchValue}"} }) { + name + native + capital + languages { + name + } + currencies + subdivisions { + name + } + } + } + \\\`; + + return gql\\\` + \\\${queryString} + \\\`; + } + + // Overrides default ServerSideDataSource.beforeApplyFilters implementation + // to save some filters that are used internally + // -- !DS specific + protected beforeApplyFilters(filters: INovaFilters): void { + this.busy.next(true); + + this.drillState = filters.drillstate?.value; + this.groupBy = filters.group?.value; + + if (this.isHome()) { + this.groupedDataHistory.length = 0; + } + + if (this.isBack()) { + this.groupedDataHistory.length = this.groupedDataHistory.length - 1; + } + + if (this.getFilters()["search"] && this.filterChanged("search")) { + this.groupedDataHistory.length = 0; + } + } + + private getGroupsWidgetData(groupByObj: Record) { + return Object.keys(groupByObj).map((property) => ({ + id: property, + label: property, + // statuses that will be displayed on group item + statuses: [ + { key: "virtual-host", value: groupByObj[property].length }, + { + key: "acknowledge", + value: this.getPopulation(groupByObj[property]), + }, + ], + })); + } + + private isHome(): boolean { + return this.drillState?.length === 0; + } + + private isBack(): boolean { + return ( + this.groupedDataHistory?.length > this.drillState?.length && + !this.isHome() + ); + } + + /** + * Gets population for the country(ies) + */ + private getPopulation(countries: any[]) { + const totalPopulation = countries.reduce( + (acc, next) => (acc += next.population), + 0 + ); + return \\\`\\\${totalPopulation * Math.pow(10, -3)} k\\\`; + } +} + +@Component({ + selector: "drilldown-widget-example", + templateUrl: "./drilldown-widget-example.component.html", + styleUrls: ["./drilldown-widget-example.component.less"], + standalone: false, +}) +export class DrilldownWidgetExampleComponent implements OnInit { + // This variable will hold all the data needed to define the layout and behavior of the widgets. + // Pass this to the dashboard component's dashboard input in the template. + public dashboard: IDashboard | undefined; + + // Angular gridster requires a configuration object even if it's empty. + // Pass this to the dashboard component's gridsterConfig input in the template. + public gridsterConfig: GridsterConfig = {}; + + // Boolean passed as an input to the dashboard. When true, widgets can be moved, resized, removed, or edited + public editMode: boolean = false; + + constructor( + // WidgetTypesService provides the widget's necessary structure information + private widgetTypesService: WidgetTypesService, + private providerRegistry: ProviderRegistryService, + private changeDetectorRef: ChangeDetectorRef + ) {} + + public ngOnInit(): void { + // Registering the data source for injection into the KPI tile. + // Note: Each tile of a KPI widget is assigned its own instance of the data source + this.providerRegistry.setProviders({ + [DrilldownDataSource.providerId]: { + provide: DATA_SOURCE, + useClass: DrilldownDataSource, + // Any dependencies that need to be injected into the provider must be listed here + deps: [HttpClient], + }, + [DrilldownDataSourceRealApi.providerId]: { + provide: DATA_SOURCE, + useClass: DrilldownDataSourceRealApi, + // Any dependencies that need to be injected into the provider must be listed here + deps: [LoggerService, HttpClient, Apollo], + }, + }); + + this.initializeDashboard(); + const widgetTemplate = this.widgetTypesService.getWidgetType( + "drilldown", + 1 + ); + this.widgetTypesService.setNode( + widgetTemplate, + "configurator", + WellKnownPathKey.DataSourceProviders, + [ + DrilldownDataSourceRealApi.providerId, + DrilldownDataSource.providerId, + ] + ); + } + + public initializeDashboard(): void { + // We're using a static configuration object for this example, but this is where + // the widget's configuration could potentially be populated from a database + const drilldownWidget = widgetConfig; + const widgets: IWidgets = { + // Complete the widget with information coming from its type definition + [drilldownWidget.id]: + this.widgetTypesService.mergeWithWidgetType(drilldownWidget), + }; + + // Setting the widget dimensions and position (this is for gridster) + const positions: Record = { + [drilldownWidget.id]: { + cols: 10, + rows: 10, + y: 0, + x: 0, + }, + }; + + // Finally, assigning the variables we created above to the dashboard + this.dashboard = { positions, widgets }; + } + + public reInitializeDashboard(): void { + // destroys the components and their providers so the dashboard can re init data + this.dashboard = undefined; + this.changeDetectorRef.detectChanges(); + + const adapterProperties = + widgetConfig.pizzagna[PizzagnaLayer.Configuration].listWidget + .providers?.adapter?.properties; + + if (adapterProperties) { + adapterProperties.drillstate = []; + } + + this.initializeDashboard(); + } +} + +const widgetConfig: IWidget = { + id: "drilldown", + type: "drilldown", + pizzagna: { + [PizzagnaLayer.Configuration]: { + [DEFAULT_PIZZAGNA_ROOT]: { + providers: { + [WellKnownProviders.DataSource]: { + // Setting the data source providerId for the tile with id "kpi1" + providerId: DrilldownDataSourceRealApi.providerId, + properties: {}, + } as IProviderConfiguration, + }, + }, + header: { + properties: { + title: "Drilldown Widget", + subtitle: "Search is case sensitive!", + }, + }, + listWidget: { + providers: { + [WellKnownProviders.Adapter]: { + providerId: NOVA_DRILLDOWN_DATASOURCE_ADAPTER, + properties: { + // widget + navigationBarId: "navigationBar", + componentId: "listWidget", + dataPath: "data", + + // adapter props + drillstate: [], + groupBy: ["regionName", "subregionName"], + groups: ["regionName", "subregionName"], + + // components + componentsConfig: { + group: { + componentType: + ListGroupItemComponent.lateLoadKey, + properties: { + dataFieldIds: { + id: "id", + label: "label", + statuses: "statuses", + }, + }, + itemProperties: { + canNavigate: true, + }, + }, + leaf: { + componentType: + ListLeafItemComponent.lateLoadKey, + properties: { + dataFieldIds: { + icon: "icon", + status: "icon_status", + detailedUrl: "capital", + label: "name", + }, + }, + itemProperties: { + canNavigate: false, + }, + }, + } as IDrilldownComponentsConfiguration, + }, + }, + }, + properties: { + configuration: { + // FORMAT: + // componentType: ListLeafItemComponent.lateLoadKey, + // properties: { + // dataFieldIds: { + // icon: "", + // status: "code", + // detailedUrl: "capital", + // label: "name", + // }, + // }, + // + } as IListWidgetConfiguration, + }, + }, + }, + }, +}; + +const getLast = (arr: any[]) => arr[arr.length - 1]; +\`, + "widget-types/drilldown/drilldown-widget/mock-data-source.ts": \`// © 2022 SolarWinds Worldwide, LLC. All rights reserved. +// +// Permission is hereby granted, free of charge, to any person obtaining a copy +// of this software and associated documentation files (the "Software"), to +// deal in the Software without restriction, including without limitation the +// rights to use, copy, modify, merge, publish, distribute, sublicense, and/or +// sell copies of the Software, and to permit persons to whom the Software is +// furnished to do so, subject to the following conditions: +// +// The above copyright notice and this permission notice shall be included in +// all copies or substantial portions of the Software. +// +// THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR +// IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, +// FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE +// AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER +// LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, +// OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN +// THE SOFTWARE. + +import { Injectable, OnDestroy } from "@angular/core"; +import groupBy from "lodash/groupBy"; +import { BehaviorSubject, Observable, of, Subject } from "rxjs"; +import { + catchError, + delay, + finalize, + map, + // eslint-disable-next-line import/no-deprecated + switchMap, + tap, +} from "rxjs/operators"; + +import { + DataSourceService, + IDataField, + IDataSource, + IFilters, + INovaFilters, +} from "@nova-ui/bits"; + +import { GRAPH_DATA_MOCK } from "./data-mock"; + +/** + * A simple KPI data source to retrieve the average rating of Harry Potter and the Sorcerer's Stone (book) via googleapis + */ +@Injectable() +export class DrilldownDataSource + extends DataSourceService + implements IDataSource, OnDestroy +{ + // This is the ID we'll use to identify the provider + public static providerId = "DrilldownDataSource"; + + // Use this subject to communicate the data source's busy state + public busy = new BehaviorSubject(false); + public dataFields: Partial[] = [ + { id: "continent.name", label: "Continent name" }, + { id: "currency", label: "Currency" }, + ]; + + private drillState: string[] = []; + private groupBy: string[]; + private cache: any; + private applyFilters$ = new Subject(); + + constructor() { + super(); + + // TODO: remove Partial in vNext after marking dataType field as optional - NUI-5838 + ( + this.dataFieldsConfig.dataFields$ as BehaviorSubject< + Partial[] + > + ).next(this.dataFields); + + this.applyFilters$ + // eslint-disable-next-line import/no-deprecated + .pipe(switchMap((filters) => this.getData(filters))) + .subscribe(async (res) => { + this.outputsSubject.next(await this.getFilteredData(res)); + }); + } + + private groupedDataHistory: any[] = []; + + // In this example, getFilteredData is invoked every 10 minutes (Take a look at the refresher + // provider definition in the widget configuration below to see how the interval is set) + public async getFilteredData(data: any): Promise { + return of(data) + .pipe( + map((countries) => { + const widgetInput = this.getOutput(countries); + + if (this.isDrillDown()) { + const activeDrillLvl = this.drillState.length; + const group = this.groupBy[activeDrillLvl]; + const [lastGroupedValue, groupedData] = + this.getTransformedDataForGroup(widgetInput, group); + + this.groupedDataHistory.push(lastGroupedValue); + + return groupedData; + } + + return widgetInput; + }) + ) + .toPromise(); + } + + public ngOnDestroy(): void { + this.outputsSubject.complete(); + } + + // redefine parent method + public async applyFilters(): Promise { + this.applyFilters$.next(this.getFilters()); + } + + private getData(filters: INovaFilters): Observable { + this.drillState = filters.drillstate?.value; + this.groupBy = filters.group?.value; + + this.busy.next(true); + + return of(this.cache || GRAPH_DATA_MOCK).pipe( + delay(1000), + tap((data) => (this.cache = data)), + map((data) => data.data.countries), + catchError((e) => of([])), + finalize(() => this.busy.next(false)) + ); + } + + private getTransformedDataForGroup(data: any, groupName: string) { + const groupedDict = groupBy(data, groupName); + const dataArr = Object.keys(groupedDict).map((property) => ({ + id: property, + label: property, + // TODO: apply groups mapping here + statuses: [ + { key: "state_ok", value: groupedDict[property].length }, + { + key: "status_unreachable", + value: generateNumberUpTo(100000), + }, + { key: "status_warning", value: generateNumberUpTo(10000) }, + { key: "status_unknown", value: generateNumberUpTo(1000) }, + ], + })); + + return [groupedDict, dataArr]; + } + + private isHome(): boolean { + return !this.drillState || this.drillState.length === 0; + } + + private isBack(): boolean { + return ( + this.groupedDataHistory.length > this.drillState?.length && + !this.isHome() + ); + } + + private isDrillDown(): boolean { + return this.drillState?.length !== this.groupBy?.length; + } + + private getOutput(data: any) { + if (this.isHome()) { + this.groupedDataHistory.length = 0; + } + + if (this.isBack()) { + this.groupedDataHistory.length = this.groupedDataHistory.length - 1; + } + + const lastHistoryValue = getLast(this.groupedDataHistory); + + if (!lastHistoryValue) { + return data; + } + + return lastHistoryValue[getLast(this.drillState)] || lastHistoryValue; + } +} + +const getLast = (arr: any[]) => arr[arr.length - 1]; +const generateNumberUpTo = (upperLimit: number): number => + Math.floor(Math.random() * upperLimit + 1); +\`, + "widget-types/drilldown/drilldown-widget-docs.component.ts": \`// © 2022 SolarWinds Worldwide, LLC. All rights reserved. +// +// Permission is hereby granted, free of charge, to any person obtaining a copy +// of this software and associated documentation files (the "Software"), to +// deal in the Software without restriction, including without limitation the +// rights to use, copy, modify, merge, publish, distribute, sublicense, and/or +// sell copies of the Software, and to permit persons to whom the Software is +// furnished to do so, subject to the following conditions: +// +// The above copyright notice and this permission notice shall be included in +// all copies or substantial portions of the Software. +// +// THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR +// IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, +// FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE +// AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER +// LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, +// OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN +// THE SOFTWARE. + +import { Component, OnInit } from "@angular/core"; + +import { mapContentFile } from "../../../../demo-files-factory"; + +@Component({ + selector: "nui-drilldown-docs", + templateUrl: "./drilldown-widget-docs.component.html", + standalone: false, +}) +export class DrilldownDocsComponent implements OnInit { + public widgetFileText = ""; + public configuratorFileText = ""; + + public predefinedGroping = \\\` +listWidget: { + providers: { + [WellKnownProviders.Adapter]: { + providerId: NOVA_DRILLDOWN_DATASOURCE_ADAPTER, + properties: { + ... + // adapter props + drillstate: [], + groupBy: ["regionName", "subregionName"], + groups: ["regionName", "subregionName"], + ... + }, + }, + }, +}, +\\\`; + public featuredDeclaredText = \\\` + private supportedFeatures: IDataSourceFeatures = { + search: { enabled: true }, + };\\\`; + public featuresUsedText = \\\` + this.features = new DataSourceFeatures(this.supportedFeatures); + \\\`; + + public async ngOnInit(): Promise { + this.widgetFileText = await import( + "./../../../../../../src/lib/widget-types/drilldown/drilldown-widget" + ).then(mapContentFile); + this.configuratorFileText = await import( + "./../../../../../../src/lib/widget-types/drilldown/drilldown-configurator" + ).then(mapContentFile); + } +} +\`, + "widget-types/drilldown/drilldown-widget-docs.module.ts": \`// © 2022 SolarWinds Worldwide, LLC. All rights reserved. +// +// Permission is hereby granted, free of charge, to any person obtaining a copy +// of this software and associated documentation files (the "Software"), to +// deal in the Software without restriction, including without limitation the +// rights to use, copy, modify, merge, publish, distribute, sublicense, and/or +// sell copies of the Software, and to permit persons to whom the Software is +// furnished to do so, subject to the following conditions: +// +// The above copyright notice and this permission notice shall be included in +// all copies or substantial portions of the Software. +// +// THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR +// IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, +// FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE +// AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER +// LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, +// OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN +// THE SOFTWARE. + +import { NgModule } from "@angular/core"; +import { RouterModule, Routes } from "@angular/router"; + +// eslint-disable-next-line max-len +import { + NuiButtonModule, + NuiDocsModule, + NuiMessageModule, + NuiSwitchModule, + DEMO_PATH_TOKEN, +} from "@nova-ui/bits"; +import { NuiDashboardsModule } from "@nova-ui/dashboards"; + +import { DrilldownMultiRequestWidgetExampleComponent } from "./drilldown-multi-request-widget/drilldown-multi-request-widget-example.component"; +import { DrilldownWidgetExampleComponent } from "./drilldown-widget/drilldown-widget-example.component"; +import { DrilldownDocsComponent } from "./drilldown-widget-docs.component"; +import { getDemoFiles } from "../../../../demo-files-factory"; + +const routes: Routes = [ + { + path: "", + component: DrilldownDocsComponent, + data: { + srlc: { + hideIndicator: true, + }, + }, + }, + { + path: "example", + component: DrilldownWidgetExampleComponent, + data: { + srlc: { + hideIndicator: true, + }, + }, + }, + { + path: "multiple-requests", + component: DrilldownMultiRequestWidgetExampleComponent, + data: { + srlc: { + hideIndicator: true, + }, + }, + }, +]; + +@NgModule({ + imports: [ + RouterModule.forChild(routes), + NuiButtonModule, + NuiDocsModule, + NuiMessageModule, + NuiDashboardsModule, + NuiSwitchModule, + ], + declarations: [ + DrilldownDocsComponent, + DrilldownWidgetExampleComponent, + DrilldownMultiRequestWidgetExampleComponent, + ], + providers: [ + { + provide: DEMO_PATH_TOKEN, + useValue: getDemoFiles("drilldown"), + }, + ], +}) +export default class DrilldownDocsModule {} +\`, + "widget-types/embedded-content/embedded-content-docs.component.ts": \`// © 2022 SolarWinds Worldwide, LLC. All rights reserved. +// +// Permission is hereby granted, free of charge, to any person obtaining a copy +// of this software and associated documentation files (the "Software"), to +// deal in the Software without restriction, including without limitation the +// rights to use, copy, modify, merge, publish, distribute, sublicense, and/or +// sell copies of the Software, and to permit persons to whom the Software is +// furnished to do so, subject to the following conditions: +// +// The above copyright notice and this permission notice shall be included in +// all copies or substantial portions of the Software. +// +// THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR +// IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, +// FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE +// AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER +// LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, +// OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN +// THE SOFTWARE. + +import { Component, OnInit } from "@angular/core"; + +import { mapContentFile } from "../../../../demo-files-factory"; + +@Component({ + selector: "nui-embedded-content-docs", + templateUrl: "./embedded-content-docs.component.html", + standalone: false, +}) +export class EmbeddedContentDocsComponent implements OnInit { + public embeddedContentWidgetFileText = ""; + public embeddedContentConfiguratorFileText = ""; + + public async ngOnInit(): Promise { + this.embeddedContentWidgetFileText = await import( + "./../../../../../../src/lib/widget-types/embedded-content/embedded-content-widget" + ).then(mapContentFile); + this.embeddedContentWidgetFileText = await import( + "./../../../../../../src/lib/widget-types/embedded-content/embedded-content-configurator" + ).then(mapContentFile); + } +} +\`, + "widget-types/embedded-content/embedded-content-docs.module.ts": \`// © 2022 SolarWinds Worldwide, LLC. All rights reserved. +// +// Permission is hereby granted, free of charge, to any person obtaining a copy +// of this software and associated documentation files (the "Software"), to +// deal in the Software without restriction, including without limitation the +// rights to use, copy, modify, merge, publish, distribute, sublicense, and/or +// sell copies of the Software, and to permit persons to whom the Software is +// furnished to do so, subject to the following conditions: +// +// The above copyright notice and this permission notice shall be included in +// all copies or substantial portions of the Software. +// +// THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR +// IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, +// FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE +// AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER +// LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, +// OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN +// THE SOFTWARE. + +import { NgModule } from "@angular/core"; +import { RouterModule, Routes } from "@angular/router"; + +// eslint-disable-next-line max-len +import { + NuiButtonModule, + NuiDocsModule, + NuiMessageModule, + NuiSwitchModule, + DEMO_PATH_TOKEN, +} from "@nova-ui/bits"; +import { NuiDashboardsModule } from "@nova-ui/dashboards"; + +import { EmbeddedContentDocsComponent } from "./embedded-content-docs.component"; +import { EmbeddedContentWidgetExampleComponent } from "./embedded-content-widget-example/embedded-content-widget-example.component"; +import { getDemoFiles } from "../../../../demo-files-factory"; + +const routes: Routes = [ + { + path: "", + component: EmbeddedContentDocsComponent, + data: { + srlc: { + hideIndicator: true, + }, + }, + }, + { + path: "example", + component: EmbeddedContentWidgetExampleComponent, + data: { + srlc: { + hideIndicator: true, + }, + }, + }, +]; + +@NgModule({ + imports: [ + RouterModule.forChild(routes), + NuiButtonModule, + NuiDocsModule, + NuiMessageModule, + NuiDashboardsModule, + NuiSwitchModule, + ], + declarations: [ + EmbeddedContentDocsComponent, + EmbeddedContentWidgetExampleComponent, + ], + providers: [ + { + provide: DEMO_PATH_TOKEN, + useValue: getDemoFiles("embedded-content"), + }, + ], +}) +export default class EmbeddedContentDocsModule {} +\`, + "widget-types/embedded-content/embedded-content-widget-example/embedded-content-widget-example.component.ts": \`// © 2022 SolarWinds Worldwide, LLC. All rights reserved. +// +// Permission is hereby granted, free of charge, to any person obtaining a copy +// of this software and associated documentation files (the "Software"), to +// deal in the Software without restriction, including without limitation the +// rights to use, copy, modify, merge, publish, distribute, sublicense, and/or +// sell copies of the Software, and to permit persons to whom the Software is +// furnished to do so, subject to the following conditions: +// +// The above copyright notice and this permission notice shall be included in +// all copies or substantial portions of the Software. +// +// THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR +// IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, +// FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE +// AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER +// LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, +// OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN +// THE SOFTWARE. + +import { ChangeDetectorRef, Component, OnInit } from "@angular/core"; +import { GridsterConfig, GridsterItem } from "angular-gridster2"; + +import { + ComponentRegistryService, + EmbeddedContentComponent, + EmbeddedContentConfigurationComponent, + EmbeddedContentMode, + IDashboard, + IWidget, + IWidgets, + PizzagnaLayer, + WidgetTypesService, +} from "@nova-ui/dashboards"; + +@Component({ + selector: "embedded-content-widget-example", + templateUrl: "./embedded-content-widget-example.component.html", + styleUrls: ["./embedded-content-widget-example.component.less"], + standalone: false, +}) +export class EmbeddedContentWidgetExampleComponent implements OnInit { + // This variable will hold all the data needed to define the layout and behavior of the widgets. + // Pass this to the dashboard component's dashboard input in the template. + public dashboard: IDashboard | undefined; + + // Angular gridster requires a configuration object even if it's empty. + // Pass this to the dashboard component's gridsterConfig input in the template. + public gridsterConfig: GridsterConfig = {}; + + // Boolean passed as an input to the dashboard. When true, widgets can be moved, resized, removed, or edited + public editMode: boolean = false; + + constructor( + // WidgetTypesService provides the widget's necessary structure information + private widgetTypesService: WidgetTypesService, + + private componentRegistry: ComponentRegistryService, + private changeDetectorRef: ChangeDetectorRef + ) {} + + public ngOnInit(): void { + this.prepareNovaDashboards(); + this.initializeDashboard(); + } + + /** Used for restoring widgets state */ + public reInitializeDashboard(): void { + // destroys the components and their providers so the dashboard can re init data + this.dashboard = undefined; + this.changeDetectorRef.detectChanges(); + + this.initializeDashboard(); + } + + public initializeDashboard(): void { + // We're using a static configuration object for this example, but this is where + // the widget's configuration could potentially be populated from a database + const embeddedContentWidget = widgetConfig; + const widgets: IWidgets = { + // Complete the widget with information coming from its type definition + [embeddedContentWidget.id]: + this.widgetTypesService.mergeWithWidgetType( + embeddedContentWidget + ), + }; + + // Setting the widget dimensions and position (this is for gridster) + const positions: Record = { + [embeddedContentWidget.id]: { + cols: 10, + rows: 10, + y: 0, + x: 0, + }, + }; + + // Finally, assigning the variables we created above to the dashboard + this.dashboard = { positions, widgets }; + } + + private prepareNovaDashboards() { + this.componentRegistry.registerByLateLoadKey(EmbeddedContentComponent); + this.componentRegistry.registerByLateLoadKey( + EmbeddedContentConfigurationComponent + ); + } +} + +const widgetConfig: IWidget = { + id: "embeddedContentWidgetId", + type: "embedded-content", + pizzagna: { + [PizzagnaLayer.Configuration]: { + header: { + properties: { + title: "Embedded Content Widget", + subtitle: "", + }, + }, + mainContent: { + properties: { + sanitized: true, + mode: EmbeddedContentMode.URL, + customEmbeddedContent: "https://www.ventusky.com/", + }, + }, + }, + }, +}; +\`, + "widget-types/kpi/kpi-docs.component.ts": \`// © 2022 SolarWinds Worldwide, LLC. All rights reserved. +// +// Permission is hereby granted, free of charge, to any person obtaining a copy +// of this software and associated documentation files (the "Software"), to +// deal in the Software without restriction, including without limitation the +// rights to use, copy, modify, merge, publish, distribute, sublicense, and/or +// sell copies of the Software, and to permit persons to whom the Software is +// furnished to do so, subject to the following conditions: +// +// The above copyright notice and this permission notice shall be included in +// all copies or substantial portions of the Software. +// +// THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR +// IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, +// FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE +// AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER +// LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, +// OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN +// THE SOFTWARE. + +import { Component, OnInit } from "@angular/core"; + +import { mapContentFile } from "../../../../demo-files-factory"; + +@Component({ + selector: "nui-kpi-docs", + templateUrl: "./kpi-docs.component.html", + standalone: false, +}) +export class KpiDocsComponent implements OnInit { + public kpiWidgetFileText = ""; + public kpiConfiguratorFileText = ""; + + public async ngOnInit(): Promise { + this.kpiWidgetFileText = await import( + "./../../../../../../src/lib/widget-types/kpi/kpi-widget" + ).then(mapContentFile); + this.kpiConfiguratorFileText = await import( + "./../../../../../../src/lib/widget-types/kpi/kpi-configurator" + ).then(mapContentFile); + } +} +\`, + "widget-types/kpi/kpi-docs.module.ts": \`// © 2022 SolarWinds Worldwide, LLC. All rights reserved. +// +// Permission is hereby granted, free of charge, to any person obtaining a copy +// of this software and associated documentation files (the "Software"), to +// deal in the Software without restriction, including without limitation the +// rights to use, copy, modify, merge, publish, distribute, sublicense, and/or +// sell copies of the Software, and to permit persons to whom the Software is +// furnished to do so, subject to the following conditions: +// +// The above copyright notice and this permission notice shall be included in +// all copies or substantial portions of the Software. +// +// THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR +// IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, +// FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE +// AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER +// LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, +// OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN +// THE SOFTWARE. + +import { NgModule } from "@angular/core"; +import { RouterModule, Routes } from "@angular/router"; + +import { + NuiButtonModule, + NuiDocsModule, + NuiMessageModule, + NuiSwitchModule, + DEMO_PATH_TOKEN, +} from "@nova-ui/bits"; +import { + KpiColorComparatorsRegistryService, + NuiDashboardsModule, +} from "@nova-ui/dashboards"; + +import { KpiDocsComponent } from "./kpi-docs.component"; +import { KpiSyncBrokerExampleComponent } from "./kpi-sync-broker/kpi-sync-broker-example.component"; +import { KpiSyncBrokerDocsComponent } from "./kpi-sync-broker-docs.component"; +import { KpiSyncBrokerForAllTilesExampleComponent } from "./kpi-sync-broker-for-all-tiles/kpi-sync-broker-for-all-tiles-example.component"; +import { KpiWidgetExampleComponent } from "./kpi-widget/kpi-widget-example.component"; +import { KpiWidgetBackgroundColorExampleComponent } from "./kpi-widget-background-color/kpi-widget-background-color-example.component"; +import { KpiWidgetBackgroundColorDocsComponent } from "./kpi-widget-background-color-docs.component"; +import { KpiWidgetInteractiveExampleComponent } from "./kpi-widget-interactive/kpi-widget-interactive-example.component"; +import { getDemoFiles } from "../../../../demo-files-factory"; + +const routes: Routes = [ + { + path: "", + component: KpiDocsComponent, + data: { + srlc: { + hideIndicator: true, + }, + showThemeSwitcher: true, + }, + }, + { + path: "example", + component: KpiWidgetExampleComponent, + data: { + srlc: { + hideIndicator: true, + }, + }, + }, + { + path: "background-color", + component: KpiWidgetBackgroundColorDocsComponent, + data: { + srlc: { + hideIndicator: true, + }, + }, + }, + { + path: "sync-broker", + component: KpiSyncBrokerDocsComponent, + data: { + srlc: { + hideIndicator: true, + }, + }, + }, +]; + +@NgModule({ + imports: [ + RouterModule.forChild(routes), + NuiButtonModule, + NuiDocsModule, + NuiMessageModule, + NuiDashboardsModule, + NuiSwitchModule, + ], + declarations: [ + KpiDocsComponent, + KpiWidgetExampleComponent, + KpiWidgetInteractiveExampleComponent, + KpiWidgetBackgroundColorDocsComponent, + KpiWidgetBackgroundColorExampleComponent, + KpiSyncBrokerDocsComponent, + KpiSyncBrokerExampleComponent, + KpiSyncBrokerForAllTilesExampleComponent, + ], + providers: [ + KpiColorComparatorsRegistryService, + { + provide: DEMO_PATH_TOKEN, + useValue: getDemoFiles("kpi"), + }, + ], +}) +export default class KpiDocsModule { + constructor( + private comparatorsRegistry: KpiColorComparatorsRegistryService + ) { + this.backgroundColorDocsSetup(); + } + + private backgroundColorDocsSetup() { + this.comparatorsRegistry.registerComparators({ + "!=": { + comparatorFn: (actual: any, reference: any) => + // eslint-disable-next-line eqeqeq + actual != reference, + label: "Not equal", + }, + }); + } +} +\`, + "widget-types/kpi/kpi-sync-broker/kpi-sync-broker-example.component.ts": \`// © 2022 SolarWinds Worldwide, LLC. All rights reserved. +// +// Permission is hereby granted, free of charge, to any person obtaining a copy +// of this software and associated documentation files (the "Software"), to +// deal in the Software without restriction, including without limitation the +// rights to use, copy, modify, merge, publish, distribute, sublicense, and/or +// sell copies of the Software, and to permit persons to whom the Software is +// furnished to do so, subject to the following conditions: +// +// The above copyright notice and this permission notice shall be included in +// all copies or substantial portions of the Software. +// +// THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR +// IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, +// FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE +// AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER +// LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, +// OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN +// THE SOFTWARE. + +import { HttpClient, HttpErrorResponse } from "@angular/common/http"; +import { + ChangeDetectorRef, + Component, + Injectable, + OnDestroy, + OnInit, +} from "@angular/core"; +import { GridsterConfig, GridsterItem } from "angular-gridster2"; +import keyBy from "lodash/keyBy"; +import { BehaviorSubject, of } from "rxjs"; +import { delay, finalize, take } from "rxjs/operators"; + +import { DataSourceService, IFilteringOutputs } from "@nova-ui/bits"; +import { + DATA_SOURCE, + IDashboard, + IKpiData, + IProviderConfiguration, + IWidget, + KpiComponent, + NOVA_KPI_DATASOURCE_ADAPTER, + NOVA_KPI_SCALE_SYNC_BROKER, + PizzagnaLayer, + ProviderRegistryService, + WellKnownPathKey, + WellKnownProviders, + WidgetTypesService, +} from "@nova-ui/dashboards"; + +/** + * A simple KPI data source to retrieve the average rating of Harry Potter and the Sorcerer's Stone (book) via googleapis + */ +@Injectable() +export class AverageRatingKpiDataSource + extends DataSourceService + implements OnDestroy +{ + public static providerId = "AverageRatingKpiDataSource"; + public busy = new BehaviorSubject(false); + + constructor(private http: HttpClient) { + super(); + } + + public async getFilteredData(): Promise { + this.busy.next(true); + return new Promise((resolve) => { + // *** Make a resource request to an external API (if needed) + this.http + .get("https://www.googleapis.com/books/v1/volumes/5MQFrgEACAAJ") + .pipe(finalize(() => this.busy.next(false))) + .subscribe({ + next: (data: any) => { + resolve({ + result: { + value: data.volumeInfo.averageRating, + }, + }); + }, + error: (error: HttpErrorResponse) => { + resolve({ + result: null, + error: { + type: error.status, + }, + }); + }, + }); + }); + } + + public ngOnDestroy(): void { + this.outputsSubject.complete(); + } +} + +/** + * A simple KPI data source to retrieve the ratings count of Harry Potter and the Sorcerer's Stone (book) via googleapis + */ +@Injectable() +export class RatingsCountKpiDataSource + extends DataSourceService + implements OnDestroy +{ + public static providerId = "RatingsCountKpiDataSource"; + + // Use this subject to communicate the data source's busy state + public busy = new BehaviorSubject(false); + + constructor(private http: HttpClient) { + super(); + } + + public async getFilteredData(): Promise { + this.busy.next(true); + return new Promise((resolve) => { + this.http + .get("https://www.googleapis.com/books/v1/volumes/5MQFrgEACAAJ") + .pipe( + delay(2000), + finalize(() => this.busy.next(false)) + ) + .subscribe({ + next: (data: any) => { + resolve({ + result: { + value: data.volumeInfo.ratingsCount, + }, + }); + }, + error: (error: HttpErrorResponse) => { + resolve({ + result: null, + error: { + type: error.status, + }, + }); + }, + }); + }); + } + + public ngOnDestroy(): void { + this.outputsSubject.complete(); + } +} +/** + * A simple KPI data source to retrieve the ratings count of Harry Potter and the Sorcerer's Stone (book) via googleapis + */ +@Injectable() +export class MockKpiDataSource + extends DataSourceService + implements OnDestroy +{ + public static providerId = "MockKpiDataSource"; + + // Use this subject to communicate the data source's busy state + public busy = new BehaviorSubject(false); + + constructor() { + super(); + } + + public async getFilteredData(): Promise { + this.busy.next(true); + return new Promise((resolve) => { + of(3381342) + .pipe( + delay(5000), + take(1), + finalize(() => this.busy.next(false)) + ) + .subscribe({ + next: (data: any) => { + resolve({ + result: { + value: data, + }, + }); + }, + }); + }); + } + + public ngOnDestroy(): void { + this.outputsSubject.complete(); + } +} + +/** + * A component that instantiates the dashboard + */ +@Component({ + selector: "kpi-sync-broker-example", + templateUrl: "./kpi-sync-broker-example.component.html", + styleUrls: ["./kpi-sync-broker-example.component.less"], + standalone: false, +}) +export class KpiSyncBrokerExampleComponent implements OnInit { + public dashboard: IDashboard | undefined; + public gridsterConfig: GridsterConfig = {}; + public editMode: boolean = false; + + constructor( + private widgetTypesService: WidgetTypesService, + private providerRegistry: ProviderRegistryService, + private changeDetectorRef: ChangeDetectorRef + ) {} + + public ngOnInit(): void { + this.setupDashboard(); + + this.initializeDashboard(); + } + + private setupDashboard() { + const widgetTemplate = this.widgetTypesService.getWidgetType("kpi", 1); + + this.widgetTypesService.setNode( + widgetTemplate, + "configurator", + WellKnownPathKey.DataSourceProviders, + [ + AverageRatingKpiDataSource.providerId, + RatingsCountKpiDataSource.providerId, + MockKpiDataSource.providerId, + ] + ); + + this.providerRegistry.setProviders({ + [AverageRatingKpiDataSource.providerId]: { + provide: DATA_SOURCE, + useClass: AverageRatingKpiDataSource, + deps: [HttpClient], + }, + [RatingsCountKpiDataSource.providerId]: { + provide: DATA_SOURCE, + useClass: RatingsCountKpiDataSource, + deps: [HttpClient], + }, + [MockKpiDataSource.providerId]: { + provide: DATA_SOURCE, + useClass: MockKpiDataSource, + deps: [], + }, + }); + } + + /** Used for restoring widgets state */ + public reInitializeDashboard(): void { + // destroys the components and their providers so the dashboard can re init data + this.dashboard = undefined; + this.changeDetectorRef.detectChanges(); + + this.initializeDashboard(); + } + + private initializeDashboard(): void { + const widgetsWithStructure = widgetsConfig.map((w) => + this.widgetTypesService.mergeWithWidgetType(w) + ); + const widgetsIndex = keyBy(widgetsWithStructure, (w: IWidget) => w.id); + + const positions: Record = { + kpiWidgetId: { + cols: 3, + rows: 6, + y: 0, + x: 0, + }, + kpiWidgetId2: { + cols: 3, + rows: 6, + y: 0, + x: 0, + }, + }; + + this.dashboard = { + positions, + widgets: widgetsIndex, + }; + } +} + +const widgetsConfig: IWidget[] = [ + { + id: "kpiWidgetId", + type: "kpi", + pizzagna: { + [PizzagnaLayer.Configuration]: { + header: { + properties: { + title: "NO Sync Broker", + subtitle: "Values sizes are being not synced", + }, + }, + tiles: { + properties: { + nodes: ["kpi1", "kpi2", "kpi3"], + }, + }, + kpi1: { + id: "kpi1", + componentType: KpiComponent.lateLoadKey, + properties: { + widgetData: { + units: \\\`out of 5 Stars\\\`, + label: \\\`Average Rating\\\`, + backgroundColor: "lightpink", + }, + }, + providers: { + [WellKnownProviders.DataSource]: { + providerId: AverageRatingKpiDataSource.providerId, + } as IProviderConfiguration, + [WellKnownProviders.Adapter]: { + providerId: NOVA_KPI_DATASOURCE_ADAPTER, + properties: { + componentId: "kpi1", + propertyPath: "widgetData", + }, + } as IProviderConfiguration, + }, + }, + kpi2: { + id: "kpi2", + componentType: KpiComponent.lateLoadKey, + properties: { + widgetData: { + label: \\\`Another label which might be a pretty long one\\\`, + units: \\\`Which comes from somewhere\\\`, + backgroundColor: "skyblue", + }, + }, + providers: { + [WellKnownProviders.DataSource]: { + providerId: RatingsCountKpiDataSource.providerId, + } as IProviderConfiguration, + [WellKnownProviders.Adapter]: { + providerId: NOVA_KPI_DATASOURCE_ADAPTER, + properties: { + componentId: "kpi2", + propertyPath: "widgetData", + }, + } as IProviderConfiguration, + }, + }, + kpi3: { + id: "kpi3", + componentType: KpiComponent.lateLoadKey, + properties: { + widgetData: { + label: \\\`Random\\\`, + units: \\\`Data\\\`, + }, + }, + providers: { + [WellKnownProviders.DataSource]: { + providerId: MockKpiDataSource.providerId, + } as IProviderConfiguration, + [WellKnownProviders.Adapter]: { + providerId: NOVA_KPI_DATASOURCE_ADAPTER, + properties: { + componentId: "kpi3", + propertyPath: "widgetData", + }, + } as IProviderConfiguration, + }, + }, + }, + }, + }, + { + id: "kpiWidgetId2", + type: "kpi", + pizzagna: { + [PizzagnaLayer.Configuration]: { + header: { + properties: { + title: "WITH Sync Broker", + subtitle: + "Now the values of label, units, and value are being synced", + }, + }, + tiles: { + properties: { + nodes: ["kpi4", "kpi5", "kpi6"], + }, + providers: { + // This is where and how you set the sync broker provider + kpiScaleSyncBroker: { + providerId: NOVA_KPI_SCALE_SYNC_BROKER, + properties: { + scaleSyncConfig: [ + // You can decide which values to keep in sync. For instance, you can leave only 'label' id in the array below + { id: "value" }, + { id: "label" }, + { id: "units" }, + ], + }, + }, + }, + }, + kpi4: { + id: "kpi4", + componentType: KpiComponent.lateLoadKey, + properties: { + widgetData: { + units: \\\`out of 5 Stars\\\`, + label: \\\`Average Rating\\\`, + backgroundColor: "lightpink", + }, + }, + providers: { + [WellKnownProviders.DataSource]: { + providerId: AverageRatingKpiDataSource.providerId, + } as IProviderConfiguration, + [WellKnownProviders.Adapter]: { + providerId: NOVA_KPI_DATASOURCE_ADAPTER, + properties: { + componentId: "kpi4", + propertyPath: "widgetData", + }, + } as IProviderConfiguration, + }, + }, + kpi5: { + id: "kpi5", + componentType: KpiComponent.lateLoadKey, + properties: { + widgetData: { + label: \\\`Another label which might be a pretty long one\\\`, + units: \\\`Which comes from somewhere\\\`, + backgroundColor: "skyblue", + }, + }, + providers: { + [WellKnownProviders.DataSource]: { + providerId: RatingsCountKpiDataSource.providerId, + } as IProviderConfiguration, + [WellKnownProviders.Adapter]: { + providerId: NOVA_KPI_DATASOURCE_ADAPTER, + properties: { + componentId: "kpi5", + propertyPath: "widgetData", + }, + } as IProviderConfiguration, + }, + }, + kpi6: { + id: "kpi6", + componentType: KpiComponent.lateLoadKey, + properties: { + widgetData: { + label: \\\`Random\\\`, + units: \\\`Data\\\`, + }, + }, + providers: { + [WellKnownProviders.DataSource]: { + providerId: MockKpiDataSource.providerId, + } as IProviderConfiguration, + [WellKnownProviders.Adapter]: { + providerId: NOVA_KPI_DATASOURCE_ADAPTER, + properties: { + componentId: "kpi6", + propertyPath: "widgetData", + }, + } as IProviderConfiguration, + }, + }, + }, + }, + }, +]; +\`, + "widget-types/kpi/kpi-sync-broker-docs.component.ts": \`// © 2022 SolarWinds Worldwide, LLC. All rights reserved. +// +// Permission is hereby granted, free of charge, to any person obtaining a copy +// of this software and associated documentation files (the "Software"), to +// deal in the Software without restriction, including without limitation the +// rights to use, copy, modify, merge, publish, distribute, sublicense, and/or +// sell copies of the Software, and to permit persons to whom the Software is +// furnished to do so, subject to the following conditions: +// +// The above copyright notice and this permission notice shall be included in +// all copies or substantial portions of the Software. +// +// THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR +// IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, +// FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE +// AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER +// LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, +// OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN +// THE SOFTWARE. + +import { Component } from "@angular/core"; + +@Component({ + selector: "kpi-sync-broker-docs", + templateUrl: "./kpi-sync-broker-docs.component.html", + standalone: false, +}) +export class KpiSyncBrokerDocsComponent { + public kpiScaleSyncBroker = \\\` +"tiles": { + "providers": { + kpiScaleSyncBroker: { + providerId: NOVA_KPI_SCALE_SYNC_BROKER, + properties: { + scaleSyncConfig: [ + { id: "value" }, + { id: "label" }, + { id: "units" }, + ], + }, + }, + }, +}, +\\\`; + + public defineScaleBrokerOnDashboardSetup = \\\` +// To add the sync broker globally to all the kpi tiles you may start with setting up the broker config +// Here you define which values to keep in sync +const brokerConfig = { + providerId: NOVA_KPI_SCALE_SYNC_BROKER, + properties: { + scaleSyncConfig: [ + { id: "value" }, + { id: "label" }, + { id: "units" }, + ], + }, + }; + +// And here is how you set the sync broker for every KPI widget in the dashboard. +// Later, you will be able to override this setting for each separate KPI widget in the configuration (just like it is shown in the third +// width of the example with the 'kpiWidgetId3') +this.widgetTypesService.setNode( + widgetTemplate, + "widget", + "tiles.providers.kpiScaleSyncBroker", + brokerConfig +); +\\\`; +} +\`, + "widget-types/kpi/kpi-sync-broker-for-all-tiles/kpi-sync-broker-for-all-tiles-example.component.ts": \`// © 2022 SolarWinds Worldwide, LLC. All rights reserved. +// +// Permission is hereby granted, free of charge, to any person obtaining a copy +// of this software and associated documentation files (the "Software"), to +// deal in the Software without restriction, including without limitation the +// rights to use, copy, modify, merge, publish, distribute, sublicense, and/or +// sell copies of the Software, and to permit persons to whom the Software is +// furnished to do so, subject to the following conditions: +// +// The above copyright notice and this permission notice shall be included in +// all copies or substantial portions of the Software. +// +// THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR +// IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, +// FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE +// AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER +// LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, +// OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN +// THE SOFTWARE. + +import { HttpClient, HttpErrorResponse } from "@angular/common/http"; +import { + ChangeDetectorRef, + Component, + Injectable, + OnDestroy, + OnInit, +} from "@angular/core"; +import { GridsterConfig, GridsterItem } from "angular-gridster2"; +import keyBy from "lodash/keyBy"; +import { BehaviorSubject, of } from "rxjs"; +import { delay, finalize, take } from "rxjs/operators"; + +import { DataSourceService, IFilteringOutputs } from "@nova-ui/bits"; +import { + DATA_SOURCE, + IDashboard, + IKpiData, + IProviderConfiguration, + IWidget, + KpiComponent, + NOVA_KPI_DATASOURCE_ADAPTER, + NOVA_KPI_SCALE_SYNC_BROKER, + PizzagnaLayer, + ProviderRegistryService, + WellKnownPathKey, + WellKnownProviders, + WidgetTypesService, +} from "@nova-ui/dashboards"; + +/** + * A simple KPI data source to retrieve the average rating of Harry Potter and the Sorcerer's Stone (book) via googleapis + */ +@Injectable() +export class AverageRatingKpiDataSource + extends DataSourceService + implements OnDestroy +{ + public static providerId = "AverageRatingKpiDataSource"; + public busy = new BehaviorSubject(false); + + constructor(private http: HttpClient) { + super(); + } + + public async getFilteredData(): Promise { + this.busy.next(true); + return new Promise((resolve) => { + // *** Make a resource request to an external API (if needed) + this.http + .get("https://www.googleapis.com/books/v1/volumes/5MQFrgEACAAJ") + .pipe(finalize(() => this.busy.next(false))) + .subscribe({ + next: (data: any) => { + resolve({ + result: { + value: data.volumeInfo.averageRating, + }, + }); + }, + error: (error: HttpErrorResponse) => { + resolve({ + result: null, + error: { + type: error.status, + }, + }); + }, + }); + }); + } + + public ngOnDestroy(): void { + this.outputsSubject.complete(); + } +} + +/** + * A simple KPI data source to retrieve the ratings count of Harry Potter and the Sorcerer's Stone (book) via googleapis + */ +@Injectable() +export class RatingsCountKpiDataSource + extends DataSourceService + implements OnDestroy +{ + public static providerId = "RatingsCountKpiDataSource"; + + // Use this subject to communicate the data source's busy state + public busy = new BehaviorSubject(false); + + constructor(private http: HttpClient) { + super(); + } + + public async getFilteredData(): Promise { + this.busy.next(true); + return new Promise((resolve) => { + this.http + .get("https://www.googleapis.com/books/v1/volumes/5MQFrgEACAAJ") + .pipe( + delay(2000), + finalize(() => this.busy.next(false)) + ) + .subscribe({ + next: (data: any) => { + resolve({ + result: { + value: data.volumeInfo.ratingsCount, + }, + }); + }, + error: (error: HttpErrorResponse) => { + resolve({ + result: null, + error: { + type: error.status, + }, + }); + }, + }); + }); + } + + public ngOnDestroy(): void { + this.outputsSubject.complete(); + } +} +/** + * A simple KPI data source to retrieve the ratings count of Harry Potter and the Sorcerer's Stone (book) via googleapis + */ +@Injectable() +export class MockKpiDataSource + extends DataSourceService + implements OnDestroy +{ + public static providerId = "MockKpiDataSource"; + + // Use this subject to communicate the data source's busy state + public busy = new BehaviorSubject(false); + public value: number = 3381342; + + constructor() { + super(); + } + + public async getFilteredData(): Promise { + this.busy.next(true); + return new Promise((resolve) => { + of(this.value) + .pipe( + delay(5000), + take(1), + finalize(() => this.busy.next(false)) + ) + .subscribe({ + next: (data: any) => { + resolve({ + result: { + value: data, + }, + }); + }, + }); + }); + } + + public ngOnDestroy(): void { + this.outputsSubject.complete(); + } +} + +/** + * A component that instantiates the dashboard + */ +@Component({ + selector: "kpi-sync-broker-for-all-tiles-example", + templateUrl: "./kpi-sync-broker-for-all-tiles-example.component.html", + styleUrls: ["./kpi-sync-broker-for-all-tiles-example.component.less"], + standalone: false, +}) +export class KpiSyncBrokerForAllTilesExampleComponent implements OnInit { + public dashboard: IDashboard | undefined; + public gridsterConfig: GridsterConfig = {}; + public editMode: boolean = false; + + constructor( + private widgetTypesService: WidgetTypesService, + private providerRegistry: ProviderRegistryService, + private changeDetectorRef: ChangeDetectorRef + ) {} + + public ngOnInit(): void { + this.setupDashboard(); + + this.initializeDashboard(); + } + + /** Used for restoring widgets state */ + public reInitializeDashboard(): void { + // destroys the components and their providers so the dashboard can re init data + this.dashboard = undefined; + this.changeDetectorRef.detectChanges(); + + this.initializeDashboard(); + } + + private setupDashboard() { + // To add the sync broker globally to all the kpi tiles you may start with setting up the broker config + // Here you define which values to keep in sync + const brokerConfig = { + providerId: NOVA_KPI_SCALE_SYNC_BROKER, + properties: { + scaleSyncConfig: [ + { id: "value" }, + { id: "label" }, + { id: "units" }, + ], + }, + }; + const widgetTemplate = this.widgetTypesService.getWidgetType("kpi", 1); + + this.widgetTypesService.setNode( + widgetTemplate, + "configurator", + WellKnownPathKey.DataSourceProviders, + [ + AverageRatingKpiDataSource.providerId, + RatingsCountKpiDataSource.providerId, + MockKpiDataSource.providerId, + ] + ); + + // And here is how you set the sync broker for every KPI widget in the dashboard. + // Later, you will be able to override this setting for each separate KPI widget in the configuration (just like it is shown in the third + // width of the example with the 'kpiWidgetId3') + this.widgetTypesService.setNode( + widgetTemplate, + "widget", + "tiles.providers.kpiScaleSyncBroker", + brokerConfig + ); + + this.providerRegistry.setProviders({ + [AverageRatingKpiDataSource.providerId]: { + provide: DATA_SOURCE, + useClass: AverageRatingKpiDataSource, + deps: [HttpClient], + }, + [RatingsCountKpiDataSource.providerId]: { + provide: DATA_SOURCE, + useClass: RatingsCountKpiDataSource, + deps: [HttpClient], + }, + [MockKpiDataSource.providerId]: { + provide: DATA_SOURCE, + useClass: MockKpiDataSource, + deps: [], + }, + }); + } + + private initializeDashboard(): void { + const widgetsWithStructure = widgetsConfig.map((w) => + this.widgetTypesService.mergeWithWidgetType(w) + ); + const widgetsIndex = keyBy(widgetsWithStructure, (w: IWidget) => w.id); + + const positions: Record = { + kpiWidgetId: { + cols: 3, + rows: 6, + y: 0, + x: 0, + }, + kpiWidgetId2: { + cols: 3, + rows: 6, + y: 0, + x: 3, + }, + kpiWidgetId3: { + cols: 3, + rows: 6, + y: 0, + x: 6, + }, + }; + + this.dashboard = { + positions, + widgets: widgetsIndex, + }; + } +} + +const widgetsConfig: IWidget[] = [ + { + id: "kpiWidgetId", + type: "kpi", + pizzagna: { + [PizzagnaLayer.Configuration]: { + header: { + properties: { + title: "Sync Broker Applied for ALL Widgets", + subtitle: "Values are being synced", + }, + }, + tiles: { + properties: { + nodes: ["kpi1", "kpi2", "kpi3"], + }, + }, + kpi1: { + id: "kpi1", + componentType: KpiComponent.lateLoadKey, + properties: { + widgetData: { + units: \\\`out of 5 Stars\\\`, + label: \\\`Average Rating\\\`, + backgroundColor: "lightpink", + }, + }, + providers: { + [WellKnownProviders.DataSource]: { + providerId: AverageRatingKpiDataSource.providerId, + } as IProviderConfiguration, + [WellKnownProviders.Adapter]: { + providerId: NOVA_KPI_DATASOURCE_ADAPTER, + properties: { + componentId: "kpi1", + propertyPath: "widgetData", + }, + } as IProviderConfiguration, + }, + }, + kpi2: { + id: "kpi2", + componentType: KpiComponent.lateLoadKey, + properties: { + widgetData: { + label: \\\`Another label which might be a pretty long one\\\`, + units: \\\`Which comes from somewhere\\\`, + backgroundColor: "skyblue", + }, + }, + providers: { + [WellKnownProviders.DataSource]: { + providerId: RatingsCountKpiDataSource.providerId, + } as IProviderConfiguration, + [WellKnownProviders.Adapter]: { + providerId: NOVA_KPI_DATASOURCE_ADAPTER, + properties: { + componentId: "kpi2", + propertyPath: "widgetData", + }, + } as IProviderConfiguration, + }, + }, + kpi3: { + id: "kpi3", + componentType: KpiComponent.lateLoadKey, + properties: { + widgetData: { + label: \\\`Random\\\`, + units: \\\`Data\\\`, + }, + }, + providers: { + [WellKnownProviders.DataSource]: { + providerId: MockKpiDataSource.providerId, + } as IProviderConfiguration, + [WellKnownProviders.Adapter]: { + providerId: NOVA_KPI_DATASOURCE_ADAPTER, + properties: { + componentId: "kpi3", + propertyPath: "widgetData", + }, + } as IProviderConfiguration, + }, + }, + }, + }, + }, + { + id: "kpiWidgetId2", + type: "kpi", + pizzagna: { + [PizzagnaLayer.Configuration]: { + header: { + properties: { + title: "Sync Broker Applied for ALL Widgets", + subtitle: + "Now the values of label, units, and value are being synced", + }, + }, + tiles: { + properties: { + nodes: ["kpi1", "kpi2", "kpi3"], + }, + }, + kpi1: { + id: "kpi1", + componentType: KpiComponent.lateLoadKey, + properties: { + widgetData: { + units: \\\`out of 5 Stars\\\`, + label: \\\`Average Rating\\\`, + backgroundColor: "lightpink", + }, + }, + providers: { + [WellKnownProviders.DataSource]: { + providerId: AverageRatingKpiDataSource.providerId, + } as IProviderConfiguration, + [WellKnownProviders.Adapter]: { + providerId: NOVA_KPI_DATASOURCE_ADAPTER, + properties: { + componentId: "kpi1", + propertyPath: "widgetData", + }, + } as IProviderConfiguration, + }, + }, + kpi2: { + id: "kpi2", + componentType: KpiComponent.lateLoadKey, + properties: { + widgetData: { + label: \\\`Another label which might be a pretty long one\\\`, + units: \\\`Which comes from somewhere\\\`, + backgroundColor: "skyblue", + }, + }, + providers: { + [WellKnownProviders.DataSource]: { + providerId: RatingsCountKpiDataSource.providerId, + } as IProviderConfiguration, + [WellKnownProviders.Adapter]: { + providerId: NOVA_KPI_DATASOURCE_ADAPTER, + properties: { + componentId: "kpi2", + propertyPath: "widgetData", + }, + } as IProviderConfiguration, + }, + }, + kpi3: { + id: "kpi3", + componentType: KpiComponent.lateLoadKey, + properties: { + widgetData: { + label: \\\`Random\\\`, + units: \\\`Data\\\`, + }, + }, + providers: { + [WellKnownProviders.DataSource]: { + providerId: MockKpiDataSource.providerId, + } as IProviderConfiguration, + [WellKnownProviders.Adapter]: { + providerId: NOVA_KPI_DATASOURCE_ADAPTER, + properties: { + componentId: "kpi3", + propertyPath: "widgetData", + }, + } as IProviderConfiguration, + }, + }, + }, + }, + }, + { + id: "kpiWidgetId3", + type: "kpi", + pizzagna: { + [PizzagnaLayer.Configuration]: { + header: { + properties: { + title: "Here We Sync Only Labels and Units", + subtitle: + "Now only the label, and units are being synced", + }, + }, + tiles: { + properties: { + nodes: ["kpi1", "kpi2", "kpi3"], + }, + providers: { + // This is where and how you can override the globally set broker config + kpiScaleSyncBroker: { + providerId: NOVA_KPI_SCALE_SYNC_BROKER, + properties: { + scaleSyncConfig: [ + { id: "label" }, + { id: "units" }, + ], + }, + }, + }, + }, + kpi1: { + id: "kpi1", + componentType: KpiComponent.lateLoadKey, + properties: { + widgetData: { + units: \\\`out of 5 Stars\\\`, + label: \\\`Average Rating\\\`, + backgroundColor: "lightpink", + }, + }, + providers: { + [WellKnownProviders.DataSource]: { + providerId: AverageRatingKpiDataSource.providerId, + } as IProviderConfiguration, + [WellKnownProviders.Adapter]: { + providerId: NOVA_KPI_DATASOURCE_ADAPTER, + properties: { + componentId: "kpi1", + propertyPath: "widgetData", + }, + } as IProviderConfiguration, + }, + }, + kpi2: { + id: "kpi2", + componentType: KpiComponent.lateLoadKey, + properties: { + widgetData: { + label: \\\`Another label which might be a pretty long one\\\`, + units: \\\`Which comes from somewhere\\\`, + backgroundColor: "skyblue", + }, + }, + providers: { + [WellKnownProviders.DataSource]: { + providerId: RatingsCountKpiDataSource.providerId, + } as IProviderConfiguration, + [WellKnownProviders.Adapter]: { + providerId: NOVA_KPI_DATASOURCE_ADAPTER, + properties: { + componentId: "kpi2", + propertyPath: "widgetData", + }, + } as IProviderConfiguration, + }, + }, + kpi3: { + id: "kpi3", + componentType: KpiComponent.lateLoadKey, + properties: { + widgetData: { + label: \\\`Random\\\`, + units: \\\`Data\\\`, + }, + }, + providers: { + [WellKnownProviders.DataSource]: { + providerId: MockKpiDataSource.providerId, + } as IProviderConfiguration, + [WellKnownProviders.Adapter]: { + providerId: NOVA_KPI_DATASOURCE_ADAPTER, + properties: { + componentId: "kpi3", + propertyPath: "widgetData", + }, + } as IProviderConfiguration, + }, + }, + }, + }, + }, +]; +\`, + "widget-types/kpi/kpi-widget/kpi-widget-example.component.ts": \`// © 2022 SolarWinds Worldwide, LLC. All rights reserved. +// +// Permission is hereby granted, free of charge, to any person obtaining a copy +// of this software and associated documentation files (the "Software"), to +// deal in the Software without restriction, including without limitation the +// rights to use, copy, modify, merge, publish, distribute, sublicense, and/or +// sell copies of the Software, and to permit persons to whom the Software is +// furnished to do so, subject to the following conditions: +// +// The above copyright notice and this permission notice shall be included in +// all copies or substantial portions of the Software. +// +// THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR +// IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, +// FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE +// AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER +// LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, +// OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN +// THE SOFTWARE. + +import { HttpClient, HttpErrorResponse } from "@angular/common/http"; +import { Component, Injectable, OnDestroy, OnInit } from "@angular/core"; +import { GridsterConfig, GridsterItem } from "angular-gridster2"; +import { BehaviorSubject } from "rxjs"; +import { finalize } from "rxjs/operators"; + +import { DataSourceService, IFilteringOutputs } from "@nova-ui/bits"; +import { + DATA_SOURCE, + DEFAULT_PIZZAGNA_ROOT, + IDashboard, + IKpiData, + IProviderConfiguration, + IRefresherProperties, + IWidget, + IWidgets, + KpiComponent, + NOVA_KPI_DATASOURCE_ADAPTER, + PizzagnaLayer, + ProviderRegistryService, + WellKnownPathKey, + WellKnownProviders, + WidgetTypesService, +} from "@nova-ui/dashboards"; + +/** + * A simple KPI data source to retrieve the average rating of Harry Potter and the Sorcerer's Stone (book) via googleapis + */ +@Injectable() +export class AverageRatingKpiDataSource + extends DataSourceService + implements OnDestroy +{ + // This is the ID we'll use to identify the provider + public static providerId = "AverageRatingKpiDataSource"; + + // Use this subject to communicate the data source's busy state + public busy = new BehaviorSubject(false); + + constructor(private http: HttpClient) { + super(); + } + + // In this example, getFilteredData is invoked every 10 minutes (Take a look at the refresher + // provider definition in the widget configuration below to see how the interval is set) + public async getFilteredData(): Promise { + this.busy.next(true); + return new Promise((resolve) => { + // *** Make a resource request to an external API (if needed) + this.http + .get("https://www.googleapis.com/books/v1/volumes/5MQFrgEACAAJ") + .pipe(finalize(() => this.busy.next(false))) + .subscribe({ + next: (data: any) => { + resolve({ + result: { + value: data.volumeInfo.averageRating, + }, + }); + }, + error: (error: HttpErrorResponse) => { + resolve({ + result: null, + error: { + type: error.status, + }, + }); + }, + }); + }); + } + + public ngOnDestroy(): void { + this.outputsSubject.complete(); + } +} + +/** + * A component that instantiates the dashboard + */ +@Component({ + selector: "kpi-widget-example", + templateUrl: "./kpi-widget-example.component.html", + styleUrls: ["./kpi-widget-example.component.less"], + standalone: false, +}) +export class KpiWidgetExampleComponent implements OnInit { + // This variable will hold all the data needed to define the layout and behavior of the widgets. + // Pass this to the dashboard component's dashboard input in the template. + public dashboard: IDashboard; + + // Angular gridster requires a configuration object even if it's empty. + // Pass this to the dashboard component's gridsterConfig input in the template. + public gridsterConfig: GridsterConfig = {}; + + // Boolean passed as an input to the dashboard. When true, widgets can be moved, resized, removed, or edited + public editMode: boolean = false; + + constructor( + // WidgetTypesService provides the widget's necessary structure information + private widgetTypesService: WidgetTypesService, + + // In general, the ProviderRegistryService is used for making entities available for injection into dynamically loaded components. + private providerRegistry: ProviderRegistryService + ) {} + + public ngOnInit(): void { + // Grabbing the widget's default template which will be needed as a parameter for setNode + const widgetTemplate = this.widgetTypesService.getWidgetType("kpi", 1); + // Registering our data sources as dropdown options in the widget editor/configurator + // Note: This could also be done in the parent module's constructor so that + // multiple dashboards could have access to the same widget template modification. + this.widgetTypesService.setNode( + // This is the template we grabbed above with getWidgetType + widgetTemplate, + // We are setting the editor/configurator part of the widget template + "configurator", + // This indicates which node you are changing and we want to change + // the data source providers available for selection in the editor. + WellKnownPathKey.DataSourceProviders, + // We are setting the data sources available for selection in the editor + [AverageRatingKpiDataSource.providerId] + ); + + // Registering the data source for injection into the KPI tile. + // Note: Each tile of a KPI widget is assigned its own instance of the data source + this.providerRegistry.setProviders({ + [AverageRatingKpiDataSource.providerId]: { + provide: DATA_SOURCE, + useClass: AverageRatingKpiDataSource, + // Any dependencies that need to be injected into the provider must be listed here + deps: [HttpClient], + }, + }); + + this.initializeDashboard(); + } + + public initializeDashboard(): void { + // We're using a static configuration object for this example, but this is where + // the widget's configuration could potentially be populated from a database + const kpiWidget = widgetConfig; + const widgetIndex: IWidgets = { + // Complete the KPI widget with information coming from its type definition + [kpiWidget.id]: + this.widgetTypesService.mergeWithWidgetType(kpiWidget), + }; + + // Setting the widget dimensions and position (this is for gridster) + const positions: Record = { + [kpiWidget.id]: { + cols: 4, + rows: 6, + y: 0, + x: 0, + }, + }; + + // Finally, assigning the variables we created above to the dashboard + this.dashboard = { + positions, + widgets: widgetIndex, + }; + } +} + +const widgetConfig: IWidget = { + id: "kpiWidgetId", + type: "kpi", + pizzagna: { + [PizzagnaLayer.Configuration]: { + [DEFAULT_PIZZAGNA_ROOT]: { + providers: { + [WellKnownProviders.Refresher]: { + properties: { + // Configuring the refresher interval so that our data source is invoked every ten minutes + interval: 60 * 10, + enabled: true, + } as IRefresherProperties, + } as Partial, + }, + }, + header: { + properties: { + title: "Harry Potter and the Sorcerer's Stone", + subtitle: "By J. K. Rowling", + }, + }, + tiles: { + properties: { + nodes: ["kpi1"], + }, + }, + kpi1: { + id: "kpi1", + componentType: KpiComponent.lateLoadKey, + properties: { + widgetData: { + units: \\\`out of 5 Stars\\\`, + label: \\\`Average Rating\\\`, + }, + }, + providers: { + [WellKnownProviders.DataSource]: { + // Setting the data source providerId for the tile with id "kpi1" + providerId: AverageRatingKpiDataSource.providerId, + } as IProviderConfiguration, + [WellKnownProviders.Adapter]: { + providerId: NOVA_KPI_DATASOURCE_ADAPTER, + properties: { + componentId: "kpi1", + propertyPath: "widgetData", + }, + } as IProviderConfiguration, + }, + }, + }, + }, +}; +\`, + "widget-types/kpi/kpi-widget-background-color/kpi-widget-background-color-example.component.ts": \`// © 2022 SolarWinds Worldwide, LLC. All rights reserved. +// +// Permission is hereby granted, free of charge, to any person obtaining a copy +// of this software and associated documentation files (the "Software"), to +// deal in the Software without restriction, including without limitation the +// rights to use, copy, modify, merge, publish, distribute, sublicense, and/or +// sell copies of the Software, and to permit persons to whom the Software is +// furnished to do so, subject to the following conditions: +// +// The above copyright notice and this permission notice shall be included in +// all copies or substantial portions of the Software. +// +// THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR +// IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, +// FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE +// AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER +// LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, +// OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN +// THE SOFTWARE. + +import { HttpClient, HttpErrorResponse } from "@angular/common/http"; +import { + ChangeDetectorRef, + Component, + Injectable, + OnDestroy, + OnInit, +} from "@angular/core"; +import { GridsterConfig, GridsterItem } from "angular-gridster2"; +import { BehaviorSubject } from "rxjs"; +import { finalize } from "rxjs/operators"; + +import { DataSourceService, IFilteringOutputs } from "@nova-ui/bits"; +import { + DATA_SOURCE, + DEFAULT_KPI_BACKGROUND_COLORS, + IDashboard, + IKpiColorRules, + IKpiData, + IProviderConfiguration, + IWidget, + IWidgets, + KpiComponent, + NOVA_KPI_COLOR_PRIORITIZER, + NOVA_KPI_DATASOURCE_ADAPTER, + PizzagnaLayer, + ProviderRegistryService, + WellKnownPathKey, + WellKnownProviders, + WidgetTypesService, +} from "@nova-ui/dashboards"; + +/** + * A simple KPI data source to retrieve the average rating of Harry Potter and the Sorcerer's Stone (book) via googleapis + */ +@Injectable() +export class AverageRatingKpiDataSource + extends DataSourceService + implements OnDestroy +{ + public static providerId = "AverageRatingKpiDataSource"; + public busy = new BehaviorSubject(false); + + constructor(private http: HttpClient) { + super(); + } + + public async getFilteredData(): Promise { + this.busy.next(true); + return new Promise((resolve) => { + // *** Make a resource request to an external API (if needed) + this.http + .get("https://www.googleapis.com/books/v1/volumes/5MQFrgEACAAJ") + .pipe(finalize(() => this.busy.next(false))) + .subscribe({ + next: (data: any) => { + resolve({ + result: { + value: data.volumeInfo.averageRating, + // setting the color on the dataSource "Sea Green", + // uncomment to get the background color update from the "Data" layer + // backgroundColor: "var(--nui-color-chart-three)", + }, + }); + }, + error: (error: HttpErrorResponse) => { + resolve({ + result: null, + error: { + type: error.status, + }, + }); + }, + }); + }); + } + + public ngOnDestroy(): void { + this.outputsSubject.complete(); + } +} + +/** + * A component that instantiates the dashboard + */ +@Component({ + selector: "kpi-widget-background-color-example", + templateUrl: "./kpi-widget-background-color-example.component.html", + styleUrls: ["./kpi-widget-background-color-example.component.less"], + standalone: false, +}) +export class KpiWidgetBackgroundColorExampleComponent implements OnInit { + public dashboard: IDashboard | undefined; + public gridsterConfig: GridsterConfig = {}; + public editMode: boolean = false; + + constructor( + private widgetTypesService: WidgetTypesService, + private providerRegistry: ProviderRegistryService, + private changeDetectorRef: ChangeDetectorRef + ) {} + + public ngOnInit(): void { + this.setupDashboard(); + + // KPI tile default color setup + this.setupDefaultColorStructure(); + + // Sets the custom pallette to the 'Description' section + this.setupCustomPalletteDescription(); + + // Sets the custom pallette to the 'Background color rules' section + this.setupCustomPalletteRules(); + + this.initializeDashboard(); + } + + /** Used for restoring widgets state */ + public reInitializeDashboard(): void { + // destroys the components and their providers so the dashboard can re init data + this.dashboard = undefined; + this.changeDetectorRef.detectChanges(); + + this.initializeDashboard(); + } + + private setupCustomPalletteDescription() { + const kpiWidgetTemplate = this.widgetTypesService.getWidgetType( + "kpi", + 1 + ); + this.widgetTypesService.setNode( + kpiWidgetTemplate, + "configurator", + WellKnownPathKey.TileDescriptionBackgroundColors, + [ + { color: "var(--nui-color-chart-one)", label: "Blue" }, + { + color: "var(--nui-color-chart-one-light)", + label: "Blue Light", + }, + { + color: "var(--nui-color-chart-one-dark)", + label: "Blue Dark", + }, + ] + ); + } + + private setupCustomPalletteRules() { + const kpiWidgetTemplate = this.widgetTypesService.getWidgetType( + "kpi", + 1 + ); + this.widgetTypesService.setNode( + kpiWidgetTemplate, + "configurator", + WellKnownPathKey.TileBackgroundColorRulesBackgroundColors, + [ + { color: "red", label: "Native Red" }, + ...DEFAULT_KPI_BACKGROUND_COLORS, + ] + ); + } + + private setupDefaultColorStructure() { + const widgetTemplate = this.widgetTypesService.getWidgetType("kpi", 1); + this.widgetTypesService.setNode( + widgetTemplate, + "widget", + "tiles.properties.template.properties.widgetData.backgroundColor", + "red" + ); + } + + private setupDashboard() { + const widgetTemplate = this.widgetTypesService.getWidgetType("kpi", 1); + this.widgetTypesService.setNode( + widgetTemplate, + "configurator", + WellKnownPathKey.DataSourceProviders, + [AverageRatingKpiDataSource.providerId] + ); + + this.providerRegistry.setProviders({ + [AverageRatingKpiDataSource.providerId]: { + provide: DATA_SOURCE, + useClass: AverageRatingKpiDataSource, + deps: [HttpClient], + }, + }); + } + + private initializeDashboard(): void { + const kpiWidget = widgetConfig; + const widgetIndex: IWidgets = { + [kpiWidget.id]: + this.widgetTypesService.mergeWithWidgetType(kpiWidget), + }; + + const positions: Record = { + [kpiWidget.id]: { + cols: 4, + rows: 6, + y: 0, + x: 0, + }, + }; + + this.dashboard = { + positions, + widgets: widgetIndex, + }; + } +} + +const widgetConfig: IWidget = { + id: "kpiWidgetId", + type: "kpi", + pizzagna: { + [PizzagnaLayer.Configuration]: { + header: { + properties: { + title: "Harry Potter and the Sorcerer's Stone", + subtitle: "By J. K. Rowling", + }, + }, + tiles: { + properties: { + nodes: ["kpi1"], + }, + }, + kpi1: { + id: "kpi1", + componentType: KpiComponent.lateLoadKey, + properties: { + widgetData: { + units: \\\`out of 5 Stars\\\`, + label: \\\`Average Rating\\\`, + // Configuration color "Blue" + backgroundColor: "var(--nui-color-chart-one)", + }, + }, + providers: { + [WellKnownProviders.DataSource]: { + providerId: AverageRatingKpiDataSource.providerId, + } as IProviderConfiguration, + [WellKnownProviders.Adapter]: { + providerId: NOVA_KPI_DATASOURCE_ADAPTER, + properties: { + componentId: "kpi1", + propertyPath: "widgetData", + }, + } as IProviderConfiguration, + [WellKnownProviders.KpiColorPrioritizer]: { + providerId: NOVA_KPI_COLOR_PRIORITIZER, + properties: { + // Color Prioritizer Rules + // settings rules - if the value is more than "2" display "Violet" color + rules: [ + { + comparisonType: ">", + value: 2, + color: "var(--nui-color-chart-four)", + }, + ] as IKpiColorRules[], + }, + } as IProviderConfiguration, + }, + }, + }, + }, +}; +\`, + "widget-types/kpi/kpi-widget-background-color-docs.component.ts": \`// © 2022 SolarWinds Worldwide, LLC. All rights reserved. +// +// Permission is hereby granted, free of charge, to any person obtaining a copy +// of this software and associated documentation files (the "Software"), to +// deal in the Software without restriction, including without limitation the +// rights to use, copy, modify, merge, publish, distribute, sublicense, and/or +// sell copies of the Software, and to permit persons to whom the Software is +// furnished to do so, subject to the following conditions: +// +// The above copyright notice and this permission notice shall be included in +// all copies or substantial portions of the Software. +// +// THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR +// IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, +// FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE +// AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER +// LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, +// OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN +// THE SOFTWARE. + +import { Component } from "@angular/core"; + +@Component({ + selector: "nui-kpi-background-color-docs", + templateUrl: "./kpi-widget-background-color-docs.component.html", + standalone: false, +}) +export class KpiWidgetBackgroundColorDocsComponent { + public comparatorsRegistryCode = \\\` + this.comparatorsRegistry.registerComparators({ + "!=": { + comparatorFn: (actual: any, reference: any) => actual != reference, + label: "Not equal", + }, + }); + \\\`; +} +\`, + "widget-types/kpi/kpi-widget-interactive/kpi-widget-interactive-example.component.ts": \`// © 2022 SolarWinds Worldwide, LLC. All rights reserved. +// +// Permission is hereby granted, free of charge, to any person obtaining a copy +// of this software and associated documentation files (the "Software"), to +// deal in the Software without restriction, including without limitation the +// rights to use, copy, modify, merge, publish, distribute, sublicense, and/or +// sell copies of the Software, and to permit persons to whom the Software is +// furnished to do so, subject to the following conditions: +// +// The above copyright notice and this permission notice shall be included in +// all copies or substantial portions of the Software. +// +// THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR +// IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, +// FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE +// AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER +// LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, +// OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN +// THE SOFTWARE. + +import { HttpClient, HttpErrorResponse } from "@angular/common/http"; +import { Component, Injectable, OnDestroy, OnInit } from "@angular/core"; +import { GridsterConfig, GridsterItem } from "angular-gridster2"; +import { BehaviorSubject } from "rxjs"; +import { finalize } from "rxjs/operators"; + +import { DataSourceService, IFilteringOutputs } from "@nova-ui/bits"; +import { + DATA_SOURCE, + IDashboard, + IKpiData, + IProviderConfiguration, + IWidget, + IWidgets, + KpiComponent, + NOVA_KPI_DATASOURCE_ADAPTER, + NOVA_URL_INTERACTION_HANDLER, + PizzagnaLayer, + ProviderRegistryService, + WellKnownPathKey, + WellKnownProviders, + WidgetTypesService, +} from "@nova-ui/dashboards"; + +/** + * A simple KPI data source to retrieve the average rating of Harry Potter and the Sorcerer's Stone (book) via googleapis + */ +@Injectable() +export class BookRatingDataSource + extends DataSourceService + implements OnDestroy +{ + // This is the ID we'll use to identify the provider + public static providerId = "BookRatingDataSource"; + + // Use this subject to communicate the data source's busy state + public busy = new BehaviorSubject(false); + + constructor(private http: HttpClient) { + super(); + } + + // In this example, getFilteredData is invoked every 10 minutes (Take a look at the refresher + // provider definition in the widget configuration below to see how the interval is set) + public async getFilteredData(): Promise { + this.busy.next(true); + return new Promise((resolve) => { + // *** Make a resource request to an external API (if needed) + this.http + .get("https://www.googleapis.com/books/v1/volumes/zpvysRGsBlwC") + .pipe(finalize(() => this.busy.next(false))) + .subscribe({ + next: (data: any) => { + resolve({ + result: { + value: data.volumeInfo.averageRating, + link: data.volumeInfo.infoLink, + }, + }); + }, + error: (error: HttpErrorResponse) => { + resolve({ + result: null, + error: { + type: error.status, + }, + }); + }, + }); + }); + } + + public ngOnDestroy(): void { + this.outputsSubject.complete(); + } +} + +/** + * A component that instantiates the dashboard + */ +@Component({ + selector: "kpi-widget-interactive-example", + templateUrl: "./kpi-widget-interactive-example.component.html", + styleUrls: ["./kpi-widget-interactive-example.component.less"], + standalone: false, +}) +export class KpiWidgetInteractiveExampleComponent implements OnInit { + // This variable will hold all the data needed to define the layout and behavior of the widgets. + // Pass this to the dashboard component's dashboard input in the template. + public dashboard: IDashboard; + + // Angular gridster requires a configuration object even if it's empty. + // Pass this to the dashboard component's gridsterConfig input in the template. + public gridsterConfig: GridsterConfig = {}; + + // Boolean passed as an input to the dashboard. When true, widgets can be moved, resized, removed, or edited + public editMode: boolean = false; + + constructor( + // WidgetTypesService provides the widget's necessary structure information + private widgetTypesService: WidgetTypesService, + + // In general, the ProviderRegistryService is used for making entities available for injection into dynamically loaded components. + private providerRegistry: ProviderRegistryService + ) {} + + public ngOnInit(): void { + // Grabbing the widget's default template which will be needed as a parameter for setNode + const widgetTemplate = this.widgetTypesService.getWidgetType("kpi", 1); + // Registering our data sources as dropdown options in the widget editor/configurator + // Note: This could also be done in the parent module's constructor so that + // multiple dashboards could have access to the same widget template modification. + this.widgetTypesService.setNode( + // This is the template we grabbed above with getWidgetType + widgetTemplate, + // We are setting the editor/configurator part of the widget template + "configurator", + // This indicates which node you are changing and we want to change + // the data source providers available for selection in the editor. + WellKnownPathKey.DataSourceProviders, + // We are setting the data sources available for selection in the editor + [BookRatingDataSource.providerId] + ); + + // Registering the data source for injection into the KPI tile. + // Note: Each tile of a KPI widget is assigned its own instance of the data source + this.providerRegistry.setProviders({ + [BookRatingDataSource.providerId]: { + provide: DATA_SOURCE, + useClass: BookRatingDataSource, + // Any dependencies that need to be injected into the provider must be listed here + deps: [HttpClient], + }, + }); + + this.initializeDashboard(); + } + + public initializeDashboard(): void { + // We're using a static configuration object for this example, but this is where + // the widget's configuration could potentially be populated from a database + const kpiWidget = widgetConfig; + const widgetIndex: IWidgets = { + // Complete the KPI widget with information coming from its type definition + [kpiWidget.id]: + this.widgetTypesService.mergeWithWidgetType(kpiWidget), + }; + + // Setting the widget dimensions and position (this is for gridster) + const positions: Record = { + [kpiWidget.id]: { + cols: 4, + rows: 6, + y: 0, + x: 0, + }, + }; + + // Finally, assigning the variables we created above to the dashboard + this.dashboard = { + positions, + widgets: widgetIndex, + }; + } +} + +const widgetConfig: IWidget = { + id: "kpiWidgetId", + type: "kpi", + pizzagna: { + [PizzagnaLayer.Configuration]: { + header: { + properties: { + title: "Harry Potter and the Order of the Phoenix", + subtitle: "By: J. K. Rowling", + }, + }, + tiles: { + providers: { + interaction: { + // Configuring the UrlInteractionHandler for interactions on the tiles + providerId: NOVA_URL_INTERACTION_HANDLER, + properties: { + // the 'url' property tells the handler what link to use when interaction occurs on the series + url: "\\\${data.link}", + }, + }, + }, + properties: { + nodes: ["kpi1"], + }, + }, + kpi1: { + id: "kpi1", + componentType: KpiComponent.lateLoadKey, + properties: { + widgetData: { + units: \\\`out of 5 stars\\\`, + label: \\\`Average Rating\\\`, + value: 0, + // the link property that is passed to the UrlInteractionHandler when the title is clicked + // this will be updated in BookRatingDataSource's 'getFilteredData' call. + link: "http://www.google.com", + }, + }, + providers: { + [WellKnownProviders.DataSource]: { + // Setting the data source providerId for the tile with id "kpi1" + providerId: BookRatingDataSource.providerId, + } as IProviderConfiguration, + [WellKnownProviders.Adapter]: { + providerId: NOVA_KPI_DATASOURCE_ADAPTER, + properties: { + componentId: "kpi1", + propertyPath: "widgetData", + }, + } as IProviderConfiguration, + }, + }, + }, + }, +}; +\`, + "widget-types/proportional/models.ts": \`export interface IMockBeerReview { + id: string; + name: string; + data: number[]; + icon: string; + link?: string; + value: string; + color?: string; +} +\`, + "widget-types/proportional/proportional-docs.component.ts": \`// © 2022 SolarWinds Worldwide, LLC. All rights reserved. +// +// Permission is hereby granted, free of charge, to any person obtaining a copy +// of this software and associated documentation files (the "Software"), to +// deal in the Software without restriction, including without limitation the +// rights to use, copy, modify, merge, publish, distribute, sublicense, and/or +// sell copies of the Software, and to permit persons to whom the Software is +// furnished to do so, subject to the following conditions: +// +// The above copyright notice and this permission notice shall be included in +// all copies or substantial portions of the Software. +// +// THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR +// IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, +// FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE +// AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER +// LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, +// OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN +// THE SOFTWARE. + +import { Component, OnInit } from "@angular/core"; + +import { mapContentFile } from "../../../../demo-files-factory"; + +@Component({ + selector: "nui-proportional-docs", + templateUrl: "./proportional-docs.component.html", + standalone: false, +}) +export class ProportionalDocsComponent implements OnInit { + public proportionalWidgetFileText = ""; + public proportionalConfiguratorFileText = ""; + + public async ngOnInit(): Promise { + this.proportionalWidgetFileText = await import( + "./../../../../../../src/lib/widget-types/proportional/proportional-widget" + ).then(mapContentFile); + this.proportionalConfiguratorFileText = await import( + "./../../../../../../src/lib/widget-types/proportional/proportional-configurator" + ).then(mapContentFile); + } +} +\`, + "widget-types/proportional/proportional-docs.module.ts": \`// © 2022 SolarWinds Worldwide, LLC. All rights reserved. +// +// Permission is hereby granted, free of charge, to any person obtaining a copy +// of this software and associated documentation files (the "Software"), to +// deal in the Software without restriction, including without limitation the +// rights to use, copy, modify, merge, publish, distribute, sublicense, and/or +// sell copies of the Software, and to permit persons to whom the Software is +// furnished to do so, subject to the following conditions: +// +// The above copyright notice and this permission notice shall be included in +// all copies or substantial portions of the Software. +// +// THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR +// IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, +// FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE +// AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER +// LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, +// OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN +// THE SOFTWARE. + +import { NgModule } from "@angular/core"; +import { RouterModule, Routes } from "@angular/router"; + +import { + NuiButtonModule, + NuiDocsModule, + NuiMessageModule, + NuiSwitchModule, + DEMO_PATH_TOKEN, +} from "@nova-ui/bits"; +import { NuiDashboardsModule } from "@nova-ui/dashboards"; + +import { ProportionalDocsComponent } from "./proportional-docs.component"; +import { ProportionalDonutContentDocsComponent } from "./proportional-donut-content-docs.component"; +import { ProportionalWidgetDonutContentFormattersExampleComponent } from "./proportional-donut-content-formatters/proportional-donut-content-formatters-example.component"; +import { ProportionalWidgetExampleComponent } from "./proportional-widget/proportional-widget-example.component"; +import { ProportionalWidgetInteractiveExampleComponent } from "./proportional-widget-interactive/proportional-widget-interactive-example.component"; +import { getDemoFiles } from "../../../../demo-files-factory"; + +const routes: Routes = [ + { + path: "", + component: ProportionalDocsComponent, + data: { + srlc: { + hideIndicator: true, + }, + showThemeSwitcher: true, + }, + }, + { + path: "example", + component: ProportionalWidgetExampleComponent, + data: { + srlc: { + hideIndicator: true, + }, + }, + }, + { + path: "donut-content-formatters", + component: ProportionalDonutContentDocsComponent, + data: { + srlc: { + hideIndicator: true, + }, + }, + }, + { + path: "donut-content-formatters-example", + component: ProportionalWidgetDonutContentFormattersExampleComponent, + data: { + srlc: { + hideIndicator: true, + }, + }, + }, + { + path: "proportional-widget-interactive-example", + component: ProportionalWidgetInteractiveExampleComponent, + data: { + srlc: { + hideIndicator: true, + }, + }, + }, +]; + +@NgModule({ + imports: [ + RouterModule.forChild(routes), + NuiButtonModule, + NuiDocsModule, + NuiDashboardsModule, + NuiMessageModule, + NuiSwitchModule, + ], + declarations: [ + ProportionalDocsComponent, + ProportionalWidgetExampleComponent, + ProportionalWidgetInteractiveExampleComponent, + ProportionalWidgetDonutContentFormattersExampleComponent, + ProportionalDonutContentDocsComponent, + ], + providers: [ + { + provide: DEMO_PATH_TOKEN, + useValue: getDemoFiles("proportional"), + }, + ], +}) +export default class ProportionalDocsModule {} +\`, + "widget-types/proportional/proportional-donut-content-docs.component.ts": \`// © 2022 SolarWinds Worldwide, LLC. All rights reserved. +// +// Permission is hereby granted, free of charge, to any person obtaining a copy +// of this software and associated documentation files (the "Software"), to +// deal in the Software without restriction, including without limitation the +// rights to use, copy, modify, merge, publish, distribute, sublicense, and/or +// sell copies of the Software, and to permit persons to whom the Software is +// furnished to do so, subject to the following conditions: +// +// The above copyright notice and this permission notice shall be included in +// all copies or substantial portions of the Software. +// +// THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR +// IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, +// FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE +// AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER +// LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, +// OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN +// THE SOFTWARE. + +import { Component } from "@angular/core"; + +@Component({ + selector: "nui-proportional-donut-content-docs", + templateUrl: "./proportional-donut-content-docs.component.html", + standalone: false, +}) +export class ProportionalDonutContentDocsComponent { + public dataSourceDataFieldsConfig = \\\` +public dataFieldsConfig: IProportionalDataFieldsConfig = { + dataFields$: new BehaviorSubject(this.dataFields), + chartSeriesDataFields$: new BehaviorSubject(this.chartSeriesDataFields), +}; + \\\`; + + public widgetConfigSlice = \\\` +"properties": { + "configuration": { + "chartOptions": { + donutContentConfig: { + formatter: { + componentType: SiUnitsFormatterComponent.lateLoadKey, + }, + aggregator: { + aggregatorType: sumAggregator.aggregatorType, + }, + }, + } + } +} + + \\\`; +} +\`, + "widget-types/proportional/proportional-donut-content-formatters/proportional-donut-content-formatters-example.component.ts": \`// © 2022 SolarWinds Worldwide, LLC. All rights reserved. +// +// Permission is hereby granted, free of charge, to any person obtaining a copy +// of this software and associated documentation files (the "Software"), to +// deal in the Software without restriction, including without limitation the +// rights to use, copy, modify, merge, publish, distribute, sublicense, and/or +// sell copies of the Software, and to permit persons to whom the Software is +// furnished to do so, subject to the following conditions: +// +// The above copyright notice and this permission notice shall be included in +// all copies or substantial portions of the Software. +// +// THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR +// IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, +// FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE +// AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER +// LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, +// OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN +// THE SOFTWARE. + +import { + ChangeDetectorRef, + Component, + Injectable, + OnDestroy, + OnInit, +} from "@angular/core"; +import { GridsterConfig, GridsterItem } from "angular-gridster2"; +import { BehaviorSubject } from "rxjs"; + +import { + DataSourceService, + IDataField, + IDataSource, + IFilteringOutputs, +} from "@nova-ui/bits"; +import { IAccessors, IChartAssistSeries } from "@nova-ui/charts"; +import { + DATA_SOURCE, + DEFAULT_LEGEND_FORMATTERS, + DEFAULT_PIZZAGNA_ROOT, + DEFAULT_PROPORTIONAL_CONTENT_AGGREGATORS, + DEFAULT_PROPORTIONAL_CONTENT_FORMATTERS, + DONUT_CONTENT_CONFIGURATION_SLICE, + IDashboard, + IDonutContentConfig, + IProportionalDataFieldsConfig, + IProportionalWidgetChartOptions, + IProportionalWidgetConfig, + IProviderConfiguration, + IWidget, + IWidgets, + LegendPlacement, + PizzagnaLayer, + ProportionalContentAggregatorsRegistryService, + ProportionalDonutContentFormattersRegistryService, + ProportionalLegendFormattersRegistryService, + ProportionalWidgetChartTypes, + ProviderRegistryService, + SiUnitsFormatterComponent, + sumAggregator, + WellKnownPathKey, + WellKnownProviders, + WidgetTypesService, +} from "@nova-ui/dashboards"; + +import { IMockBeerReview } from "../models"; + +/** + * A simple proportional data source to retrieve beer review counts by city + */ +@Injectable() +export class BeerReviewCountsByCityMockDataSource + extends DataSourceService> + implements IDataSource>, OnDestroy +{ + public static providerId = "BeerReviewCountsByCityMockDataSource"; + public busy = new BehaviorSubject(false); + + protected dataFields: IDataField[] = [ + { + id: "Brno", + label: "Brno", + // @ts-ignore + dataType: null, + }, + { + id: "kyiv", + label: "Kyiv", + // @ts-ignore + dataType: null, + }, + { + id: "austin", + label: "Austin", + // @ts-ignore + dataType: null, + }, + { + id: "lisbon", + label: "Lisbon", + // @ts-ignore + dataType: null, + }, + { + id: "sydney", + label: "Sydney", + // @ts-ignore + dataType: null, + }, + { + id: "nur-sultan", + label: "Nur-Sultan", + // @ts-ignore + dataType: null, + }, + ]; + protected chartSeriesDataFields: IDataField[] = [ + // default field in the chart series that is used for the aggregation + { + id: "data[0]", + label: "data", + // @ts-ignore + dataType: null, + }, + // any custom field in the chart series that is used for the aggregation + { + id: "customDonutContent", + label: "Custom Donut Content", + // @ts-ignore + dataType: null, + }, + ]; + + /** + * DataSource needs to implement the "IDataFieldsConfig" for this scenario. + * + * It's necessary to provide the "chartSeriesDataFields", + * that's why proportional widget dataSource has it's own interface for that - IProportionalDataFieldsConfig. + * + * dataFields$ - stands for possible series fields + * chartSeriesDataFields$ - stands for the fields IN the series + * + * see declaration of "dataFields" and "chartSeriesDataFields" for the example. + */ + public dataFieldsConfig: IProportionalDataFieldsConfig = { + dataFields$: new BehaviorSubject(this.dataFields), + chartSeriesDataFields$: new BehaviorSubject( + this.chartSeriesDataFields + ), + }; + + public async getFilteredData(): Promise { + this.busy.next(true); + return new Promise((resolve) => { + setTimeout(() => { + this.outputsSubject.next({ + result: getMockBeerReviewCountsByCity(), + }); + this.busy.next(false); + }, 300); + }); + } + + public ngOnDestroy(): void { + this.outputsSubject.complete(); + } +} + +/** + * A component that instantiates the dashboard + */ +@Component({ + selector: "proportional-widget-donut-content-formatters-example", + templateUrl: "./proportional-donut-content-formatters-example.component.html", + styleUrls: [ + "./proportional-donut-content-formatters-example.component.less", + ], + standalone: false, +}) +export class ProportionalWidgetDonutContentFormattersExampleComponent + implements OnInit +{ + public dashboard: IDashboard | undefined; + + // Angular gridster requires a configuration object even if it's empty. + // Pass this to the dashboard component's gridsterConfig input in the template. + public gridsterConfig: GridsterConfig = {}; + + // Boolean passed as an input to the dashboard. When true, widgets can be moved, resized, removed, or edited + public editMode: boolean = false; + + constructor( + // WidgetTypesService provides the widget's necessary structure information + private widgetTypesService: WidgetTypesService, + // In general, the ProviderRegistryService is used for making entities available for injection into dynamically loaded components. + private providerRegistry: ProviderRegistryService, + // registry for adding the formatter for donut content + contentFormattersRegistry: ProportionalDonutContentFormattersRegistryService, + // registry for adding the formatter for proportional legend + legendFormattersRegistry: ProportionalLegendFormattersRegistryService, + // registry for adding the aggregators for donut content + aggregatorRegistry: ProportionalContentAggregatorsRegistryService, + private changeDetectorRef: ChangeDetectorRef + ) { + // on the dashboard startup, it's necessary to add possible content formatters, legend formatters and content aggregators to the registry. + // using registry is a way for setting the available formatters. + legendFormattersRegistry.addItems(DEFAULT_LEGEND_FORMATTERS); + contentFormattersRegistry.addItems( + DEFAULT_PROPORTIONAL_CONTENT_FORMATTERS + ); + aggregatorRegistry.addItems(DEFAULT_PROPORTIONAL_CONTENT_AGGREGATORS); + } + + public ngOnInit(): void { + // Grabbing the widget's default template which will be needed as a parameter for setNode + const widgetTemplate = this.widgetTypesService.getWidgetType( + "proportional", + 1 + ); + + // Registering our data sources as dropdown options in the widget editor/configurator + // Note: This could also be done in the parent module's constructor so that + // multiple dashboards could have access to the same widget template modification. + this.widgetTypesService.setNode( + // This is the template we grabbed above with getWidgetType + widgetTemplate, + // We are setting the editor/configurator part of the widget template + "configurator", + // This indicates which node you are changing and we want to change + // the data source providers available for selection in the editor. + WellKnownPathKey.DataSourceProviders, + // We are setting the data sources available for selection in the editor + [BeerReviewCountsByCityMockDataSource.providerId] + ); + + // Setup of the configurator is done here + this.setupConfigurator(); + + // Registering the data source for injection into the Proportional widget. + this.providerRegistry.setProviders({ + [BeerReviewCountsByCityMockDataSource.providerId]: { + provide: DATA_SOURCE, + useClass: BeerReviewCountsByCityMockDataSource, + deps: [], + }, + }); + + this.initializeDashboard(); + } + + /** Used for restoring widgets state */ + public reInitializeDashboard(): void { + // destroys the components and their providers so the dashboard can re init data + this.dashboard = undefined; + this.changeDetectorRef.detectChanges(); + + this.initializeDashboard(); + } + + private initializeDashboard(): void { + // We're using a static configuration object for this example, but this is where + // the widget's configuration could potentially be populated from a database + const widgetIndex: IWidgets = { + // Complete the proportional widget with information coming from its type definition + [widgetConfig.id]: + this.widgetTypesService.mergeWithWidgetType(widgetConfig), + }; + + // Setting the widget dimensions and position (this is for gridster) + const positions: Record = { + [widgetConfig.id]: { + cols: 6, + rows: 6, + y: 0, + x: 0, + }, + }; + + // Finally, assigning the variables we created above to the dashboard + this.dashboard = { + positions, + widgets: widgetIndex, + }; + } + + /** + * Sets up the configurator sections for proportional donut + */ + private setupConfigurator() { + const widgetTemplate = this.widgetTypesService.getWidgetType( + "proportional", + 1 + ); + + // remove old "presentation", "chartOptionsEditor" and "donutContentConfiguration" sections from the configurator + delete widgetTemplate.configurator?.structure?.presentation; + delete widgetTemplate.configurator?.structure?.chartOptionsEditor; + delete widgetTemplate.configurator?.structure + ?.donutContentConfiguration; + + // add new "presentation" section + this.widgetTypesService.setNode( + widgetTemplate, + "configurator", + "presentation", + DONUT_CONTENT_CONFIGURATION_SLICE.presentation + ); + // add new "chartOptionsEditor" section + this.widgetTypesService.setNode( + widgetTemplate, + "configurator", + "chartOptionsEditor", + DONUT_CONTENT_CONFIGURATION_SLICE.chartOptionsEditor + ); + // add new "donutContentConfiguration" section + this.widgetTypesService.setNode( + widgetTemplate, + "configurator", + "donutContentConfiguration", + DONUT_CONTENT_CONFIGURATION_SLICE.donutContentConfiguration + ); + } +} + +const widgetConfig: IWidget = { + id: "proportionalWidgetId", + type: "proportional", + pizzagna: { + [PizzagnaLayer.Configuration]: { + [DEFAULT_PIZZAGNA_ROOT]: { + providers: {}, + }, + header: { + properties: { + title: "Beer Review Tally by City", + subtitle: "These People Love Beer", + }, + }, + chart: { + providers: { + [WellKnownProviders.DataSource]: { + // Setting the data source providerId for the chart + providerId: + BeerReviewCountsByCityMockDataSource.providerId, + } as IProviderConfiguration, + }, + properties: { + configuration: { + chartOptions: { + type: ProportionalWidgetChartTypes.DonutChart, + legendPlacement: LegendPlacement.Right, + // old configuration looks like this + // contentFormatter: { + // componentType: DonutContentSumFormatterComponent.lateLoadKey, + // }, + + // NEW configuration looks like this + donutContentConfig: { + formatter: { + componentType: + SiUnitsFormatterComponent.lateLoadKey, + }, + aggregator: { + aggregatorType: + sumAggregator.aggregatorType, + properties: { + // example of a default metric to be used for the percentage calculation + // activeMetricId: "austin", + }, + }, + } as IDonutContentConfig, + } as IProportionalWidgetChartOptions, + } as IProportionalWidgetConfig, + }, + }, + }, + }, +}; + +export function getMockBeerReviewCountsByCity(): IMockBeerReview[] { + return [ + { + id: "Brno", + name: "Brno", + data: [Math.round(Math.random() * 1000000)], + icon: "status_down", + link: "https://en.wikipedia.org/wiki/Brno", + value: "Brno", + customDonutContent: "Custom Brno", + }, + { + id: "kyiv", + name: "Kyiv", + data: [Math.round(Math.random() * 1000000)], + icon: "status_critical", + link: "https://en.wikipedia.org/wiki/Kyiv", + value: "Kyiv", + customDonutContent: "Custom Kyiv", + }, + { + id: "austin", + name: "Austin", + data: [Math.round(Math.random() * 1000000)], + icon: "status_warning", + link: "https://en.wikipedia.org/wiki/Austin", + value: "Austin", + customDonutContent: "Custom Austin", + }, + { + id: "lisbon", + name: "Lisbon", + data: [Math.round(Math.random() * 1000000)], + icon: "status_unknown", + link: "https://en.wikipedia.org/wiki/Lisbon", + value: "Lisbon", + customDonutContent: "Custom Lisbon", + }, + { + id: "sydney", + name: "Sydney", + data: [Math.round(Math.random() * 1000000)], + icon: "status_up", + link: "https://en.wikipedia.org/wiki/Sydney", + value: "Sydney", + customDonutContent: "Custom Sydney", + }, + { + id: "nur-sultan", + name: "Nur-Sultan", + data: [Math.round(Math.random() * 1000000)], + icon: "status_unmanaged", + link: "https://en.wikipedia.org/wiki/Nur-Sultan", + value: "Nur-Sultan", + customDonutContent: "Custom Nur-Sultan", + }, + ].sort((a, b) => a.data[0] - b.data[0]); +} +\`, + "widget-types/proportional/proportional-widget/proportional-widget-example.component.ts": \`// © 2022 SolarWinds Worldwide, LLC. All rights reserved. +// +// Permission is hereby granted, free of charge, to any person obtaining a copy +// of this software and associated documentation files (the "Software"), to +// deal in the Software without restriction, including without limitation the +// rights to use, copy, modify, merge, publish, distribute, sublicense, and/or +// sell copies of the Software, and to permit persons to whom the Software is +// furnished to do so, subject to the following conditions: +// +// The above copyright notice and this permission notice shall be included in +// all copies or substantial portions of the Software. +// +// THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR +// IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, +// FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE +// AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER +// LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, +// OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN +// THE SOFTWARE. + +import { + ChangeDetectorRef, + Component, + Injectable, + OnDestroy, + OnInit, +} from "@angular/core"; +import { GridsterConfig, GridsterItem } from "angular-gridster2"; +import { BehaviorSubject } from "rxjs"; + +import { + DataSourceService, + IDataSource, + IFilteringOutputs, +} from "@nova-ui/bits"; +import { + DATA_SOURCE, + DEFAULT_PIZZAGNA_ROOT, + IDashboard, + IProportionalWidgetChartOptions, + IProportionalWidgetConfig, + IProportionalWidgetData, + IProviderConfiguration, + IRefresherProperties, + IWidget, + IWidgets, + LegendPlacement, + PizzagnaLayer, + ProportionalWidgetChartTypes, + ProviderRegistryService, + WellKnownPathKey, + WellKnownProviders, + WidgetTypesService, +} from "@nova-ui/dashboards"; + +import { IMockBeerReview } from "../models"; + +/** + * A simple proportional data source to retrieve beer review counts by city + */ +@Injectable() +export class BeerReviewCountsByCityMockDataSource + extends DataSourceService + implements IDataSource, OnDestroy +{ + // This is the ID we'll use to identify the provider + public static providerId = "BeerReviewCountsByCityMockDataSource"; + public busy = new BehaviorSubject(false); + + public async getFilteredData(): Promise { + this.busy.next(true); + return new Promise((resolve) => { + setTimeout(() => { + this.outputsSubject.next({ + result: getMockBeerReviewCountsByCity(), + }); + this.busy.next(false); + }, 300); + }); + } + + public ngOnDestroy(): void { + this.outputsSubject.complete(); + } +} + +/** + * A component that instantiates the dashboard + */ +@Component({ + selector: "proportional-widget-example", + templateUrl: "./proportional-widget-example.component.html", + styleUrls: ["./proportional-widget-example.component.less"], + standalone: false, +}) +export class ProportionalWidgetExampleComponent implements OnInit { + // This variable will hold all the data needed to define the layout and behavior of the widgets. + // Pass this to the dashboard component's dashboard input in the template. + public dashboard: IDashboard | undefined; + + // Angular gridster requires a configuration object even if it's empty. + // Pass this to the dashboard component's gridsterConfig input in the template. + public gridsterConfig: GridsterConfig = {}; + + // Boolean passed as an input to the dashboard. When true, widgets can be moved, resized, removed, or edited + public editMode: boolean = false; + + constructor( + // WidgetTypesService provides the widget's necessary structure information + private widgetTypesService: WidgetTypesService, + // In general, the ProviderRegistryService is used for making entities available for injection into dynamically loaded components. + private providerRegistry: ProviderRegistryService, + private changeDetectorRef: ChangeDetectorRef + ) {} + + public ngOnInit(): void { + // Grabbing the widget's default template which will be needed as a parameter for setNode + const widgetTemplate = this.widgetTypesService.getWidgetType( + "proportional", + 1 + ); + + // Registering our data sources as dropdown options in the widget editor/configurator + // Note: This could also be done in the parent module's constructor so that + // multiple dashboards could have access to the same widget template modification. + this.widgetTypesService.setNode( + // This is the template we grabbed above with getWidgetType + widgetTemplate, + // We are setting the editor/configurator part of the widget template + "configurator", + // This indicates which node you are changing and we want to change + // the data source providers available for selection in the editor. + WellKnownPathKey.DataSourceProviders, + // We are setting the data sources available for selection in the editor + [BeerReviewCountsByCityMockDataSource.providerId] + ); + + // Registering the data source for injection into the Proportional widget. + this.providerRegistry.setProviders({ + [BeerReviewCountsByCityMockDataSource.providerId]: { + provide: DATA_SOURCE, + useClass: BeerReviewCountsByCityMockDataSource, + deps: [], + }, + }); + + this.initializeDashboard(); + } + + /** Used for restoring widgets state */ + public reInitializeDashboard(): void { + // destroys the components and their providers so the dashboard can re init data + this.dashboard = undefined; + this.changeDetectorRef.detectChanges(); + + this.initializeDashboard(); + } + + public initializeDashboard(): void { + // We're using a static configuration object for this example, but this is where + // the widget's configuration could potentially be populated from a database + const widgetIndex: IWidgets = { + // Complete the proportional widget with information coming from its type definition + [widgetConfig.id]: + this.widgetTypesService.mergeWithWidgetType(widgetConfig), + }; + + // Setting the widget dimensions and position (this is for gridster) + const positions: Record = { + [widgetConfig.id]: { + cols: 5, + rows: 6, + y: 0, + x: 0, + }, + }; + + // Finally, assigning the variables we created above to the dashboard + this.dashboard = { + positions, + widgets: widgetIndex, + }; + } +} + +const widgetConfig: IWidget = { + id: "proportionalWidgetId", + type: "proportional", + pizzagna: { + [PizzagnaLayer.Configuration]: { + [DEFAULT_PIZZAGNA_ROOT]: { + providers: { + [WellKnownProviders.Refresher]: { + properties: { + // Configuring the refresher interval so that our data source is invoked every ten minutes + interval: 60 * 10, + enabled: true, + } as IRefresherProperties, + } as Partial, + }, + }, + header: { + properties: { + title: "Beer Review Tally by City", + subtitle: "These People Love Beer", + }, + }, + chart: { + providers: { + [WellKnownProviders.DataSource]: { + // Setting the data source providerId for the chart + providerId: + BeerReviewCountsByCityMockDataSource.providerId, + } as IProviderConfiguration, + }, + properties: { + configuration: { + chartOptions: { + type: ProportionalWidgetChartTypes.DonutChart, + legendPlacement: LegendPlacement.Right, + } as IProportionalWidgetChartOptions, + // You can optionally define custom colors for the chart by setting the 'chartColors' configuration property + // "chartColors": [ + // "var(--nui-color-chart-five)", + // "var(--nui-color-chart-six)", + // "var(--nui-color-chart-seven)", + // "var(--nui-color-chart-eight)", + // "var(--nui-color-chart-nine)", + // "var(--nui-color-chart-ten)", + // ], + // or use-mapped structure + chartColors: { + Brno: "var(--nui-color-chart-five)", + kyiv: "var(--nui-color-chart-six)", + austin: "var(--nui-color-chart-seven)", + lisbon: "var(--nui-color-chart-eight)", + sydney: "var(--nui-color-chart-nine)", + "nur-sultan": "var(--nui-color-chart-ten)", + }, + prioritizeWidgetColors: false, + } as IProportionalWidgetConfig, + }, + }, + }, + }, +}; + +export function getMockBeerReviewCountsByCity(): IMockBeerReview[] { + return [ + { + id: "Brno", + name: "Brno", + data: [Math.round(Math.random() * 100000)], + icon: "status_down", + link: "https://en.wikipedia.org/wiki/Brno", + value: "Brno", + color: "var(--nui-color-chart-one)", + }, + { + id: "kyiv", + name: "Kyiv", + data: [Math.round(Math.random() * 100000)], + icon: "status_critical", + link: "https://en.wikipedia.org/wiki/Kyiv", + value: "Kyiv", + color: "var(--nui-color-chart-two)", + }, + { + id: "austin", + name: "Austin", + data: [Math.round(Math.random() * 100000)], + icon: "status_warning", + link: "https://en.wikipedia.org/wiki/Austin", + value: "Austin", + color: "var(--nui-color-chart-three)", + }, + { + id: "lisbon", + name: "Lisbon", + data: [Math.round(Math.random() * 100000)], + icon: "status_unknown", + link: "https://en.wikipedia.org/wiki/Lisbon", + value: "Lisbon", + color: "var(--nui-color-chart-four)", + }, + { + id: "sydney", + name: "Sydney", + data: [Math.round(Math.random() * 100000)], + icon: "status_up", + link: "https://en.wikipedia.org/wiki/Sydney", + value: "Sydney", + color: "var(--nui-color-chart-five)", + }, + { + id: "nur-sultan", + name: "Nur-Sultan", + data: [Math.round(Math.random() * 100000)], + icon: "status_unmanaged", + link: "https://en.wikipedia.org/wiki/Nur-Sultan", + value: "Nur-Sultan", + color: "var(--nui-color-chart-six)", + }, + ].sort((a, b) => a.data[0] - b.data[0]); +} +\`, + "widget-types/proportional/proportional-widget-interactive/proportional-widget-interactive-example.component.ts": \`// © 2022 SolarWinds Worldwide, LLC. All rights reserved. +// +// Permission is hereby granted, free of charge, to any person obtaining a copy +// of this software and associated documentation files (the "Software"), to +// deal in the Software without restriction, including without limitation the +// rights to use, copy, modify, merge, publish, distribute, sublicense, and/or +// sell copies of the Software, and to permit persons to whom the Software is +// furnished to do so, subject to the following conditions: +// +// The above copyright notice and this permission notice shall be included in +// all copies or substantial portions of the Software. +// +// THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR +// IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, +// FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE +// AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER +// LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, +// OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN +// THE SOFTWARE. + +import { + ChangeDetectorRef, + Component, + Injectable, + OnDestroy, + OnInit, +} from "@angular/core"; +import { GridsterConfig, GridsterItem } from "angular-gridster2"; +import keyBy from "lodash/keyBy"; +import { BehaviorSubject } from "rxjs"; + +import { + DataSourceService, + IDataSource, + IFilteringOutputs, +} from "@nova-ui/bits"; +import { + DATA_SOURCE, + DEFAULT_PIZZAGNA_ROOT, + IDashboard, + IProportionalWidgetChartOptions, + IProportionalWidgetConfig, + IProportionalWidgetData, + IProviderConfiguration, + IWidget, + LegendPlacement, + NOVA_URL_INTERACTION_HANDLER, + PizzagnaLayer, + ProportionalWidgetChartTypes, + ProviderRegistryService, + WellKnownPathKey, + WellKnownProviders, + WidgetTypesService, +} from "@nova-ui/dashboards"; + +import { IMockBeerReview } from "../models"; + +/** + * A simple proportional data source to retrieve beer review counts by city + */ +@Injectable() +export class ReviewCountsByCityMockDataSource + extends DataSourceService + implements IDataSource, OnDestroy +{ + // This is the ID we'll use to identify the provider + public static providerId = "ReviewCountsByCityMockDataSource"; + public busy = new BehaviorSubject(false); + + public async getFilteredData(): Promise { + this.busy.next(true); + return new Promise((resolve) => { + setTimeout(() => { + this.outputsSubject.next({ + result: getMockBeerReviewCountsByCity(), + }); + this.busy.next(false); + }, 300); + }); + } + + public ngOnDestroy(): void { + this.outputsSubject.complete(); + } +} + +/** + * A component that instantiates the dashboard + */ +@Component({ + selector: "proportional-widget-interactive-example", + templateUrl: "./proportional-widget-interactive-example.component.html", + styleUrls: ["./proportional-widget-interactive-example.component.less"], + standalone: false, +}) +export class ProportionalWidgetInteractiveExampleComponent implements OnInit { + // This variable will hold all the data needed to define the layout and behavior of the widgets. + // Pass this to the dashboard component's dashboard input in the template. + public dashboard: IDashboard | undefined; + + // Angular gridster requires a configuration object even if it's empty. + // Pass this to the dashboard component's gridsterConfig input in the template. + public gridsterConfig: GridsterConfig = {}; + + // Boolean passed as an input to the dashboard. When true, widgets can be moved, resized, removed, or edited + public editMode: boolean = false; + + constructor( + // WidgetTypesService provides the widget's necessary structure information + private widgetTypesService: WidgetTypesService, + // In general, the ProviderRegistryService is used for making entities available for injection into dynamically loaded components. + private providerRegistry: ProviderRegistryService, + private changeDetectorRef: ChangeDetectorRef + ) {} + + public ngOnInit(): void { + // Grabbing the widget's default template which will be needed as a parameter for setNode + const widgetTemplate = this.widgetTypesService.getWidgetType( + "proportional", + 1 + ); + + // Registering our data sources as dropdown options in the widget editor/configurator + // Note: This could also be done in the parent module's constructor so that + // multiple dashboards could have access to the same widget template modification. + this.widgetTypesService.setNode( + // This is the template we grabbed above with getWidgetType + widgetTemplate, + // We are setting the editor/configurator part of the widget template + "configurator", + // This indicates which node you are changing and we want to change + // the data source providers available for selection in the editor. + WellKnownPathKey.DataSourceProviders, + // We are setting the data sources available for selection in the editor + [ReviewCountsByCityMockDataSource.providerId] + ); + + // Registering the data source for injection into the Proportional widget. + this.providerRegistry.setProviders({ + [ReviewCountsByCityMockDataSource.providerId]: { + provide: DATA_SOURCE, + useClass: ReviewCountsByCityMockDataSource, + deps: [], + }, + }); + + this.initializeDashboard(); + } + + /** Used for restoring widgets state */ + public reInitializeDashboard(): void { + // destroys the components and their providers so the dashboard can re init data + this.dashboard = undefined; + this.changeDetectorRef.detectChanges(); + + this.initializeDashboard(); + } + + public initializeDashboard(): void { + // We're using a static configuration object for this example, but this is where + // the widget's configuration could potentially be populated from a database + const widgetsWithStructure = widgetConfigs.map((w) => + this.widgetTypesService.mergeWithWidgetType(w) + ); + const widgetsIndex = keyBy(widgetsWithStructure, (w: IWidget) => w.id); + + // Setting the widget dimensions and position (this is for gridster) + const positions: Record = { + [widgetConfigs[0].id]: { + cols: 6, + rows: 6, + y: 0, + x: 0, + }, + [widgetConfigs[1].id]: { + cols: 6, + rows: 6, + y: 0, + x: 6, + }, + }; + + // Finally, assigning the variables we created above to the dashboard + this.dashboard = { + positions, + widgets: widgetsIndex, + }; + } +} + +const widgetConfigs: IWidget[] = [ + { + id: "widget1", + type: "proportional", + pizzagna: { + [PizzagnaLayer.Configuration]: { + [DEFAULT_PIZZAGNA_ROOT]: { + providers: { + // Configuring the UrlInteractionHandler to handle interactions + [WellKnownProviders.InteractionHandler]: { + providerId: NOVA_URL_INTERACTION_HANDLER, + properties: { + // the 'url' property tells the handler what link to use when interaction occurs on the series + // if the series does not have a link we are passing one to the handler + url: "\\\${data.link || 'https://en.wikipedia.org/wiki/'+data.id}", + // by default the link is opened in the current window, set 'newWindow' to true to open in a new tab instead + // newWindow: true, + }, + }, + }, + }, + header: { + properties: { + title: "Proportional Widget", + subtitle: "With interaction handler", + }, + }, + chart: { + providers: { + [WellKnownProviders.DataSource]: { + // Setting the data source providerId for the chart + providerId: + ReviewCountsByCityMockDataSource.providerId, + } as IProviderConfiguration, + }, + properties: { + configuration: { + // Setting the interactive to true + interactive: true, + chartOptions: { + type: ProportionalWidgetChartTypes.VerticalBarChart, + legendPlacement: LegendPlacement.Bottom, + } as IProportionalWidgetChartOptions, + prioritizeWidgetColors: false, + } as IProportionalWidgetConfig, + }, + }, + }, + }, + }, + { + id: "widget2", + type: "proportional", + pizzagna: { + [PizzagnaLayer.Configuration]: { + header: { + properties: { + title: "Proportional Widget", + subtitle: "Without interaction handler", + }, + }, + chart: { + providers: { + [WellKnownProviders.DataSource]: { + // Setting the data source providerId for the chart + providerId: + ReviewCountsByCityMockDataSource.providerId, + } as IProviderConfiguration, + }, + properties: { + configuration: { + // interactive set to false so series without links are not styled like a link + interactive: false, + chartOptions: { + type: ProportionalWidgetChartTypes.HorizontalBarChart, + legendPlacement: LegendPlacement.Bottom, + } as IProportionalWidgetChartOptions, + prioritizeWidgetColors: false, + } as IProportionalWidgetConfig, + }, + }, + }, + }, + }, +]; + +export function getMockBeerReviewCountsByCity(): IMockBeerReview[] { + return [ + { + id: "Brno", + name: "Brno", + data: [Math.round(Math.random() * 100000)], + icon: "status_down", + link: "https://en.wikipedia.org/wiki/Brno", + value: "Brno", + color: "var(--nui-color-chart-one)", + }, + { + id: "kyiv", + name: "Kyiv", + data: [Math.round(Math.random() * 100000)], + icon: "status_critical", + link: "https://en.wikipedia.org/wiki/Kyiv", + value: "Kyiv", + color: "var(--nui-color-chart-two)", + }, + { + id: "austin", + name: "Austin", + data: [Math.round(Math.random() * 100000)], + icon: "status_warning", + value: "Austin", + color: "var(--nui-color-chart-three)", + }, + { + id: "lisbon", + name: "Lisbon", + data: [Math.round(Math.random() * 100000)], + icon: "status_unknown", + link: "https://en.wikipedia.org/wiki/Lisbon", + value: "Lisbon", + color: "var(--nui-color-chart-four)", + }, + { + id: "sydney", + name: "Sydney", + data: [Math.round(Math.random() * 100000)], + icon: "status_up", + value: "Sydney", + color: "var(--nui-color-chart-five)", + }, + { + id: "nur-sultan", + name: "Nur-Sultan", + data: [Math.round(Math.random() * 100000)], + icon: "status_unmanaged", + value: "Nur-Sultan", + color: "var(--nui-color-chart-six)", + }, + ].sort((a, b) => a.data[0] - b.data[0]); +} +\`, + "widget-types/risk-score/risk-score-docs.component.ts": \`// © 2023 SolarWinds Worldwide, LLC. All rights reserved. +// +// Permission is hereby granted, free of charge, to any person obtaining a copy +// of this software and associated documentation files (the "Software"), to +// deal in the Software without restriction, including without limitation the +// rights to use, copy, modify, merge, publish, distribute, sublicense, and/or +// sell copies of the Software, and to permit persons to whom the Software is +// furnished to do so, subject to the following conditions: +// +// The above copyright notice and this permission notice shall be included in +// all copies or substantial portions of the Software. +// +// THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR +// IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, +// FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE +// AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER +// LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, +// OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN +// THE SOFTWARE. + +import { Component, OnInit } from "@angular/core"; + +import { mapContentFile } from "../../../../demo-files-factory"; + +@Component({ + selector: "nui-risk-score-docs", + templateUrl: "./risk-score-docs.component.html", + standalone: false, +}) +export class RiskScoreDocsComponent implements OnInit { + public riskScoreWidgetFileText = ""; + public riskScoreConfiguratorFileText = ""; + + public async ngOnInit(): Promise { + this.riskScoreWidgetFileText = await import( + "./../../../../../../src/lib/widget-types/risk-score/risk-score-widget" + ).then(mapContentFile); + this.riskScoreConfiguratorFileText = await import( + "./../../../../../../src/lib/widget-types/risk-score/risk-score-configurator" + ).then(mapContentFile); + } +} +\`, + "widget-types/risk-score/risk-score-docs.module.ts": \`// © 2023 SolarWinds Worldwide, LLC. All rights reserved. +// +// Permission is hereby granted, free of charge, to any person obtaining a copy +// of this software and associated documentation files (the "Software"), to +// deal in the Software without restriction, including without limitation the +// rights to use, copy, modify, merge, publish, distribute, sublicense, and/or +// sell copies of the Software, and to permit persons to whom the Software is +// furnished to do so, subject to the following conditions: +// +// The above copyright notice and this permission notice shall be included in +// all copies or substantial portions of the Software. +// +// THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR +// IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, +// FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE +// AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER +// LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, +// OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN +// THE SOFTWARE. + +import { NgModule } from "@angular/core"; +import { RouterModule, Routes } from "@angular/router"; + +import { + DEMO_PATH_TOKEN, + NuiButtonModule, + NuiDocsModule, + NuiMessageModule, + NuiSwitchModule, +} from "@nova-ui/bits"; +import { NuiDashboardsModule } from "@nova-ui/dashboards"; + +import { RiskScoreDocsComponent } from "./risk-score-docs.component"; +import { RiskScoreWidgetExampleComponent } from "./risk-score-widget-example/risk-score-widget-example.component"; +import { getDemoFiles } from "../../../../demo-files-factory"; + +const routes: Routes = [ + { + path: "", + component: RiskScoreDocsComponent, + data: { + srlc: { + hideIndicator: true, + }, + showThemeSwitcher: true, + }, + }, + { + path: "example", + component: RiskScoreWidgetExampleComponent, + data: { + srlc: { + hideIndicator: true, + }, + }, + }, +]; + +@NgModule({ + imports: [ + RouterModule.forChild(routes), + NuiButtonModule, + NuiDocsModule, + NuiMessageModule, + NuiDashboardsModule, + NuiSwitchModule, + ], + declarations: [RiskScoreDocsComponent, RiskScoreWidgetExampleComponent], + providers: [ + { + provide: DEMO_PATH_TOKEN, + useValue: getDemoFiles("risk-score"), + }, + ], +}) +export default class RiskScoreDocsModule {} +\`, + "widget-types/risk-score/risk-score-widget-example/risk-score-widget-example.component.ts": \`// © 2023 SolarWinds Worldwide, LLC. All rights reserved. +// +// Permission is hereby granted, free of charge, to any person obtaining a copy +// of this software and associated documentation files (the "Software"), to +// deal in the Software without restriction, including without limitation the +// rights to use, copy, modify, merge, publish, distribute, sublicense, and/or +// sell copies of the Software, and to permit persons to whom the Software is +// furnished to do so, subject to the following conditions: +// +// The above copyright notice and this permission notice shall be included in +// all copies or substantial portions of the Software. +// +// THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR +// IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, +// FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE +// AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER +// LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, +// OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN +// THE SOFTWARE. + +import { HttpClient, HttpErrorResponse } from "@angular/common/http"; +import { Component, Injectable, OnDestroy, OnInit } from "@angular/core"; +import { GridsterConfig, GridsterItem } from "angular-gridster2"; +import { BehaviorSubject } from "rxjs"; +import { finalize } from "rxjs/operators"; + +import { DataSourceService, IFilteringOutputs } from "@nova-ui/bits"; +import { + DATA_SOURCE, + DEFAULT_PIZZAGNA_ROOT, + IDashboard, + IRiskScoreData, + IProviderConfiguration, + IRefresherProperties, + IWidget, + IWidgets, + RiskScoreTileComponent, + NOVA_KPI_DATASOURCE_ADAPTER, + PizzagnaLayer, + ProviderRegistryService, + WellKnownPathKey, + WellKnownProviders, + WidgetTypesService, +} from "@nova-ui/dashboards"; + +/** + * A simple KPI data source to retrieve the average rating of Harry Potter and the Sorcerer's Stone (book) via googleapis + */ +@Injectable() +export class AverageRatingRiskScoreDataSource + extends DataSourceService + implements OnDestroy +{ + // This is the ID we'll use to identify the provider + public static providerId = "AverageRatingRiskScoreDataSource"; + + // Use this subject to communicate the data source's busy state + public busy = new BehaviorSubject(false); + + constructor(private http: HttpClient) { + super(); + } + + // In this example, getFilteredData is invoked every 10 minutes (Take a look at the refresher + // provider definition in the widget configuration below to see how the interval is set) + public async getFilteredData(): Promise { + this.busy.next(true); + return new Promise((resolve) => { + // *** Make a resource request to an external API (if needed) + this.http + .get("https://www.googleapis.com/books/v1/volumes/5MQFrgEACAAJ") + .pipe(finalize(() => this.busy.next(false))) + .subscribe({ + next: (data: any) => { + resolve({ + result: { + value: data.volumeInfo.averageRating, + }, + }); + }, + error: (error: HttpErrorResponse) => { + resolve({ + result: null, + error: { + type: error.status, + }, + }); + }, + }); + }); + } + + public ngOnDestroy(): void { + this.outputsSubject.complete(); + } +} + +/** + * A component that instantiates the dashboard + */ +@Component({ + selector: "risk-score-widget-example", + templateUrl: "./risk-score-widget-example.component.html", + styleUrls: ["./risk-score-widget-example.component.less"], + standalone: false, +}) +export class RiskScoreWidgetExampleComponent implements OnInit { + // This variable will hold all the data needed to define the layout and behavior of the widgets. + // Pass this to the dashboard component's dashboard input in the template. + public dashboard: IDashboard; + + // Angular gridster requires a configuration object even if it's empty. + // Pass this to the dashboard component's gridsterConfig input in the template. + public gridsterConfig: GridsterConfig = {}; + + // Boolean passed as an input to the dashboard. When true, widgets can be moved, resized, removed, or edited + public editMode: boolean = false; + + constructor( + // WidgetTypesService provides the widget's necessary structure information + private widgetTypesService: WidgetTypesService, + + // In general, the ProviderRegistryService is used for making entities available for injection into dynamically loaded components. + private providerRegistry: ProviderRegistryService + ) {} + + public ngOnInit(): void { + // Grabbing the widget's default template which will be needed as a parameter for setNode + const widgetTemplate = this.widgetTypesService.getWidgetType( + "risk-score", + 1 + ); + // Registering our data sources as dropdown options in the widget editor/configurator + // Note: This could also be done in the parent module's constructor so that + // multiple dashboards could have access to the same widget template modification. + this.widgetTypesService.setNode( + // This is the template we grabbed above with getWidgetType + widgetTemplate, + // We are setting the editor/configurator part of the widget template + "configurator", + // This indicates which node you are changing and we want to change + // the data source providers available for selection in the editor. + WellKnownPathKey.DataSourceProviders, + // We are setting the data sources available for selection in the editor + [AverageRatingRiskScoreDataSource.providerId] + ); + + // Registering the data source for injection into the KPI tile. + // Note: Each tile of a KPI widget is assigned its own instance of the data source + this.providerRegistry.setProviders({ + [AverageRatingRiskScoreDataSource.providerId]: { + provide: DATA_SOURCE, + useClass: AverageRatingRiskScoreDataSource, + // Any dependencies that need to be injected into the provider must be listed here + deps: [HttpClient], + }, + }); + + this.initializeDashboard(); + } + + public initializeDashboard(): void { + // We're using a static configuration object for this example, but this is where + // the widget's configuration could potentially be populated from a database + const riskScoreWidget = widgetConfig; + const widgetIndex: IWidgets = { + // Complete the KPI widget with information coming from its type definition + [riskScoreWidget.id]: + this.widgetTypesService.mergeWithWidgetType(riskScoreWidget), + }; + + // Setting the widget dimensions and position (this is for gridster) + const positions: Record = { + [riskScoreWidget.id]: { + cols: 4, + rows: 6, + y: 0, + x: 0, + }, + }; + + // Finally, assigning the variables we created above to the dashboard + this.dashboard = { + positions, + widgets: widgetIndex, + }; + } +} + +const widgetConfig: IWidget = { + id: "riskScoreWidgetId", + type: "risk-score", + pizzagna: { + [PizzagnaLayer.Configuration]: { + [DEFAULT_PIZZAGNA_ROOT]: { + providers: { + [WellKnownProviders.Refresher]: { + properties: { + // Configuring the refresher interval so that our data source is invoked every ten minutes + interval: 60 * 10, + enabled: true, + } as IRefresherProperties, + } as Partial, + }, + }, + header: { + properties: { + title: "Harry Potter and the Sorcerer's Stone", + subtitle: "By J. K. Rowling", + }, + }, + tiles: { + properties: { + nodes: ["riskScore1"], + }, + }, + riskScore1: { + id: "riskScore1", + componentType: RiskScoreTileComponent.lateLoadKey, + properties: { + widgetData: { + minValue: 0, + maxValue: 5, + useStaticLabel: false, + staticLabel: undefined, + label: \\\`Average Rating\\\`, + description: \\\`Harry Potter and the Sorcerer's Stone By J. K. Rowling Average Rating Risk Score\\\`, + }, + }, + providers: { + [WellKnownProviders.DataSource]: { + // Setting the data source providerId for the tile with id "kpi1" + providerId: AverageRatingRiskScoreDataSource.providerId, + } as IProviderConfiguration, + [WellKnownProviders.Adapter]: { + providerId: NOVA_KPI_DATASOURCE_ADAPTER, + properties: { + componentId: "riskScore1", + propertyPath: "widgetData", + }, + } as IProviderConfiguration, + }, + }, + }, + }, +}; +\`, + "widget-types/table/table-docs.component.ts": \`// © 2022 SolarWinds Worldwide, LLC. All rights reserved. +// +// Permission is hereby granted, free of charge, to any person obtaining a copy +// of this software and associated documentation files (the "Software"), to +// deal in the Software without restriction, including without limitation the +// rights to use, copy, modify, merge, publish, distribute, sublicense, and/or +// sell copies of the Software, and to permit persons to whom the Software is +// furnished to do so, subject to the following conditions: +// +// The above copyright notice and this permission notice shall be included in +// all copies or substantial portions of the Software. +// +// THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR +// IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, +// FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE +// AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER +// LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, +// OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN +// THE SOFTWARE. + +import { Component, OnInit } from "@angular/core"; + +import { mapContentFile } from "../../../../demo-files-factory"; + +@Component({ + selector: "nui-table-docs", + templateUrl: "./table-docs.component.html", + standalone: false, +}) +export class TableDocsComponent implements OnInit { + public widgetFileText = ""; + public configuratorFileText = ""; + + public async ngOnInit(): Promise { + this.widgetFileText = await import( + "./../../../../../../src/lib/widget-types/table/table-widget" + ).then(mapContentFile); + this.configuratorFileText = await import( + "./../../../../../../src/lib/widget-types/table/table-configurator" + ).then(mapContentFile); + } +} +\`, + "widget-types/table/table-docs.module.ts": \`// © 2022 SolarWinds Worldwide, LLC. All rights reserved. +// +// Permission is hereby granted, free of charge, to any person obtaining a copy +// of this software and associated documentation files (the "Software"), to +// deal in the Software without restriction, including without limitation the +// rights to use, copy, modify, merge, publish, distribute, sublicense, and/or +// sell copies of the Software, and to permit persons to whom the Software is +// furnished to do so, subject to the following conditions: +// +// The above copyright notice and this permission notice shall be included in +// all copies or substantial portions of the Software. +// +// THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR +// IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, +// FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE +// AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER +// LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, +// OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN +// THE SOFTWARE. + +import { NgModule } from "@angular/core"; +import { RouterModule, Routes } from "@angular/router"; + +import { DEMO_PATH_TOKEN } from "@nova-ui/bits"; +import { + NuiButtonModule, + NuiDocsModule, + NuiMessageModule, + NuiSwitchModule, +} from "@nova-ui/bits"; +import { + NuiDashboardsModule, + TableFormatterRegistryService, +} from "@nova-ui/dashboards"; + +import { TableDocsComponent } from "./table-docs.component"; +import { TablePaginatorDocsComponent } from "./table-paginator-docs.component"; +import { TableSelectableDocsComponent } from "./table-selectable-docs.component"; +import { TableWidgetExampleComponent } from "./table-widget/table-widget-example.component"; +import { TableWidgetInteractiveExampleComponent } from "./table-widget-interactive/table-widget-interactive-example.component"; +import { TableWidgetPaginatorExampleComponent } from "./table-widget-paginator/table-widget-paginator-example.component"; +import { TableWidgetSearchExampleComponent } from "./table-widget-search/table-widget-search-example.component"; +import { TableSearchDocsComponent } from "./table-widget-search-docs.component"; +import { TableWidgetSelectableMultiExampleComponent } from "./table-widget-selectable/table-widget-selectable-multi/table-widget-selectable-multi.example.component"; +import { TableWidgetSelectableRadioExampleComponent } from "./table-widget-selectable/table-widget-selectable-radio/table-widget-selectable-radio.example.component"; +import { TableWidgetSelectableSingleExampleComponent } from "./table-widget-selectable/table-widget-selectable-single/table-widget-selectable-single.example.component"; +import { TableWidgetSelectableExampleComponent } from "./table-widget-selectable/table-widget-selectable.example.component"; +import { DEFAULT_TABLE_FORMATTERS } from "../../../../../../src/lib/widget-types/table/default-table-formatters"; +import { getDemoFiles } from "../../../../demo-files-factory"; + +const routes: Routes = [ + { + path: "", + component: TableDocsComponent, + data: { + srlc: { + hideIndicator: true, + }, + showThemeSwitcher: true, + }, + }, + { + path: "example", + component: TableWidgetExampleComponent, + data: { + srlc: { + hideIndicator: true, + }, + }, + }, + { + path: "table-search", + component: TableSearchDocsComponent, + data: { + srlc: { + hideIndicator: true, + }, + showThemeSwitcher: true, + }, + }, + { + path: "table-paginator", + component: TablePaginatorDocsComponent, + data: { + srlc: { + hideIndicator: true, + }, + showThemeSwitcher: true, + }, + }, + { + path: "table-select", + component: TableSelectableDocsComponent, + data: { + srlc: { + hideIndicator: true, + }, + showThemeSwitcher: true, + }, + }, +]; + +@NgModule({ + imports: [ + RouterModule.forChild(routes), + NuiButtonModule, + NuiDocsModule, + NuiMessageModule, + NuiSwitchModule, + NuiDashboardsModule, + ], + declarations: [ + TableDocsComponent, + TableSearchDocsComponent, + TablePaginatorDocsComponent, + TableWidgetPaginatorExampleComponent, + TableSelectableDocsComponent, + TableWidgetInteractiveExampleComponent, + TableWidgetExampleComponent, + TableWidgetSearchExampleComponent, + TableWidgetSelectableExampleComponent, + TableWidgetSelectableMultiExampleComponent, + TableWidgetSelectableSingleExampleComponent, + TableWidgetSelectableRadioExampleComponent, + ], + providers: [ + { + provide: DEMO_PATH_TOKEN, + useValue: getDemoFiles("table"), + }, + ], +}) +export default class TableDocsModule { + constructor(tableFormattersRegistryService: TableFormatterRegistryService) { + tableFormattersRegistryService.addItems(DEFAULT_TABLE_FORMATTERS); + } +} +\`, + "widget-types/table/table-paginator-docs.component.ts": \`// © 2022 SolarWinds Worldwide, LLC. All rights reserved. +// +// Permission is hereby granted, free of charge, to any person obtaining a copy +// of this software and associated documentation files (the "Software"), to +// deal in the Software without restriction, including without limitation the +// rights to use, copy, modify, merge, publish, distribute, sublicense, and/or +// sell copies of the Software, and to permit persons to whom the Software is +// furnished to do so, subject to the following conditions: +// +// The above copyright notice and this permission notice shall be included in +// all copies or substantial portions of the Software. +// +// THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR +// IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, +// FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE +// AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER +// LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, +// OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN +// THE SOFTWARE. + +import { Component } from "@angular/core"; + +@Component({ + selector: "nui-table-paginator-docs", + templateUrl: "./table-paginator-docs.component.html", + standalone: false, +}) +export class TablePaginatorDocsComponent { + public tableConfigurationText = \\\` + "table": { + ... + properties: { + configuration: { + // define paginator configuration here + scrollType: ScrollType.paginator, + paginatorConfiguration: { + pageSize: 10, // Value have to be one of pageSizeSet values + pageSizeSet: [10, 20, 30], + }, + // If not specified, default is set to + // pageSize: 10, + // pageSizeSet: [10, 20, 50], + hasVirtualScroll: false, // Has to be speciefied because of backward compatibility + } as ITableWidgetConfig, + }, + }, + \\\`; +} +\`, + "widget-types/table/table-selectable-docs.component.ts": \`// © 2022 SolarWinds Worldwide, LLC. All rights reserved. +// +// Permission is hereby granted, free of charge, to any person obtaining a copy +// of this software and associated documentation files (the "Software"), to +// deal in the Software without restriction, including without limitation the +// rights to use, copy, modify, merge, publish, distribute, sublicense, and/or +// sell copies of the Software, and to permit persons to whom the Software is +// furnished to do so, subject to the following conditions: +// +// The above copyright notice and this permission notice shall be included in +// all copies or substantial portions of the Software. +// +// THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR +// IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, +// FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE +// AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER +// LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, +// OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN +// THE SOFTWARE. + +import { Component } from "@angular/core"; + +@Component({ + selector: "nui-table-selectable-docs", + templateUrl: "./table-selectable-docs.component.html", + standalone: false, +}) +export class TableSelectableDocsComponent { + public tableConfigurationText = \\\` + "table": { + ... + properties: { + // enabling selection here + selectionConfiguration: { + // whether the selection is enabled or disabled + enabled: true, + // can be Multi | Radio | Single + selectionMode: TableSelectionMode.Multi, + // property that uniquely identifies row in a table + trackByProperty: "id", + // whether clicking on row should select it + clickableRow: true, + }, + }, + }, + \\\`; + + public eventSubscriptionText = \\\` +... +constructor(Inject(PIZZAGNA_EVENT_BUS) eventBus: EventBus) { + eventBus + .getStream(SELECTION) + // don't forget to unsubscribe! + .subscribe((selection: ISelection) => ...) +} +... + \\\`; +} +\`, + "widget-types/table/table-widget/table-widget-example.component.ts": \`// © 2022 SolarWinds Worldwide, LLC. All rights reserved. +// +// Permission is hereby granted, free of charge, to any person obtaining a copy +// of this software and associated documentation files (the "Software"), to +// deal in the Software without restriction, including without limitation the +// rights to use, copy, modify, merge, publish, distribute, sublicense, and/or +// sell copies of the Software, and to permit persons to whom the Software is +// furnished to do so, subject to the following conditions: +// +// The above copyright notice and this permission notice shall be included in +// all copies or substantial portions of the Software. +// +// THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR +// IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, +// FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE +// AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER +// LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, +// OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN +// THE SOFTWARE. + +import { ChangeDetectorRef, Component, OnInit } from "@angular/core"; +import { GridsterConfig, GridsterItem } from "angular-gridster2"; +import orderBy from "lodash/orderBy"; +import { BehaviorSubject, firstValueFrom, from } from "rxjs"; +import { map, tap } from "rxjs/operators"; + +import { + DataSourceService, + IDataField, + INovaFilteringOutputs, + INovaFilters, + nameof, +} from "@nova-ui/bits"; +import { + DATA_SOURCE, + IDashboard, + ITableWidgetColumnConfig, + IWidget, + IWidgets, + ProviderRegistryService, + WellKnownPathKey, + WellKnownProviders, + WidgetTypesService, +} from "@nova-ui/dashboards"; + +export const BREW_API_URL = "https://api.punkapi.com/v2/beers"; + +export interface IBrewInfo { + id: string; + name: string; + tagline: string; + first_brewed: string; + description: string; + brewers_tips: string; +} + +export interface IBrewDatasourceResponse { + brewInfo: IBrewInfo[]; + total: number; +} + +export class BeerDataSource extends DataSourceService { + public static providerId = "BeerDataSource"; + + private cache: IBrewInfo[] = []; + + public busy = new BehaviorSubject(false); + + public dataFields: Array = [ + { + id: nameof("id"), + label: "No", + dataType: "number", + sortable: true, + }, + // To indicate that a column should not be sortable, set the optional IDataField 'sortable' property to false + { + id: nameof("name"), + label: "Name", + dataType: "string", + sortable: true, + }, + { + id: nameof("tagline"), + label: "Tagline", + dataType: "string", + sortable: true, + }, + { + id: nameof("first_brewed"), + label: "First Brewed", + dataType: "string", + sortable: true, + }, + { + id: nameof("description"), + label: "Description", + dataType: "string", + sortable: false, + }, + { + id: nameof("brewers_tips"), + label: "Brewer's Tips", + dataType: "string", + sortable: false, + }, + ]; + + public async getFilteredData( + filters: INovaFilters + ): Promise { + const start = filters.virtualScroll?.value?.start ?? 0; + const end = filters.virtualScroll?.value?.end ?? 0; + + // Resetting cache on first page request + if (start === 0) { + this.cache = []; + } + + // extract sorter settings to send to the backend + // filters.sorterValue.sortBy; filters.sorterValue.direction + return firstValueFrom( + from(this.fetch(start, end)).pipe( + tap((response: IBrewDatasourceResponse | undefined) => { + if (!response) { + return; + } + this.cache = this.sortData( + this.cache.concat(response.brewInfo), + filters + ); + this.dataSubject.next(this.cache); + }), + map(() => ({ + repeat: { itemsSource: this.cache }, + dataFields: this.dataFields, + })) + ) + ); + } + + public async fetch( + start: number, + end: number + ): Promise { + const delta: number = end - start; + const currentPage: number = end / delta || 0; + const response: object | Array = await ( + await fetch( + \\\`\\\${BREW_API_URL}/?page=\\\${currentPage}&per_page=\\\${delta}\\\` + ) + ).json(); + + // Note: In case request fails we should not proceed with mapping + if (!Array.isArray(response)) { + return undefined; + } + + return { + brewInfo: response.map((result: IBrewInfo) => ({ + id: result.id, + name: result.name, + tagline: result.tagline, + first_brewed: result.first_brewed, + description: result.description, + brewers_tips: result.brewers_tips, + })), + total: response.length, + }; + } + + private sortData(data: IBrewInfo[], filters: INovaFilters): IBrewInfo[] { + return orderBy( + data, + filters.sorter?.value?.sortBy, + filters.sorter?.value?.direction as "desc" | "asc" + ); + } +} + +/** + * A component that instantiates the dashboard + */ +@Component({ + selector: "table-widget-example", + templateUrl: "./table-widget-example.component.html", + styleUrls: ["./table-widget-example.component.less"], + standalone: false, +}) +export class TableWidgetExampleComponent implements OnInit { + // This variable will hold all the data needed to define the layout and behavior of the widgets. + // Pass this to the dashboard component's dashboard input in the template. + public dashboard: IDashboard | undefined; + + // Angular gridster requires a configuration object even if it's empty. + // Pass this to the dashboard component's gridsterConfig input in the template. + public gridsterConfig: GridsterConfig = {}; + + // Boolean passed as an input to the dashboard. When true, widgets can be moved, resized, removed, or edited + public editMode: boolean = false; + + constructor( + // WidgetTypesService provides the widget's necessary structure information + private widgetTypesService: WidgetTypesService, + // In general, the ProviderRegistryService is used for making entities available for injection into dynamically loaded components. + private providerRegistry: ProviderRegistryService, + private changeDetectorRef: ChangeDetectorRef + ) {} + + public ngOnInit(): void { + // Grabbing the widget's default template which will be needed as a parameter for setNode + const widgetTemplate = this.widgetTypesService.getWidgetType( + "table", + 1 + ); + + // Registering our data sources as dropdown options in the widget editor/configurator + // Note: This could also be done in the parent module's constructor so that + // multiple dashboards could have access to the same widget template modification. + this.widgetTypesService.setNode( + // This is the template we grabbed above with getWidgetType + widgetTemplate, + // We are setting the editor/configurator part of the widget template + "configurator", + // This indicates which node you are changing and we want to change + // the data source providers available for selection in the editor. + WellKnownPathKey.DataSourceProviders, + // We are setting the data sources available for selection in the editor + [BeerDataSource.providerId] + ); + + // Registering the data source for injection into the widget. + this.providerRegistry.setProviders({ + [BeerDataSource.providerId]: { + provide: DATA_SOURCE, + useClass: BeerDataSource, + // Any dependencies that need to be injected into the provider must be listed here + deps: [], + }, + }); + + this.initializeDashboard(); + } + + /** Used for restoring widgets state */ + public reInitializeDashboard(): void { + // destroys the components and their providers so the dashboard can re init data + this.dashboard = undefined; + this.changeDetectorRef.detectChanges(); + + this.initializeDashboard(); + } + + public initializeDashboard(): void { + // We're using a static configuration object for this example, but this is where + // the widget's configuration could potentially be populated from a database + const tableWidget = widgetConfig; + const widgetIndex: IWidgets = { + // Enhance the widget with information coming from it's type definition + [tableWidget.id]: + this.widgetTypesService.mergeWithWidgetType(tableWidget), + }; + + // Setting the widget dimensions and position (this is for gridster) + const positions: Record = { + [tableWidget.id]: { + cols: 12, + rows: 6, + y: 0, + x: 0, + }, + }; + + // Finally, assigning the variables we created above to the dashboard + this.dashboard = { + positions, + widgets: widgetIndex, + }; + } +} + +const TABLE_COLUMNS: ITableWidgetColumnConfig[] = [ + { + id: "column1", + label: $localize\\\`Beer Name\\\`, + isActive: true, + width: 185, + formatter: { + componentType: "RawFormatterComponent", + properties: { + dataFieldIds: { + value: "name", + }, + }, + }, + }, + { + id: "column2", + label: $localize\\\`Tagline\\\`, + isActive: true, + width: 250, + formatter: { + componentType: "RawFormatterComponent", + properties: { + dataFieldIds: { + value: "tagline", + }, + }, + }, + }, + { + id: "column3", + label: $localize\\\`First Brewed\\\`, + isActive: true, + width: 100, + formatter: { + componentType: "RawFormatterComponent", + properties: { + dataFieldIds: { + value: "first_brewed", + }, + }, + }, + }, + { + id: "column4", + label: $localize\\\`Description\\\`, + isActive: true, + formatter: { + componentType: "RawFormatterComponent", + properties: { + dataFieldIds: { + value: "description", + }, + }, + }, + }, +]; + +export const widgetConfig: IWidget = { + id: "tableWidgetId", + type: "table", + pizzagna: { + configuration: { + header: { + properties: { + title: "Stupendous Suds", + subtitle: "Try These Brilliant Brews", + }, + }, + table: { + providers: { + [WellKnownProviders.DataSource]: { + providerId: BeerDataSource.providerId, + }, + }, + properties: { + configuration: { + columns: TABLE_COLUMNS, + sortable: true, + sorterConfiguration: { + descendantSorting: false, + sortBy: "", + }, + hasVirtualScroll: true, + }, + }, + }, + }, + }, +}; +\`, + "widget-types/table/table-widget-interactive/table-widget-interactive-example.component.ts": \`// © 2022 SolarWinds Worldwide, LLC. All rights reserved. +// +// Permission is hereby granted, free of charge, to any person obtaining a copy +// of this software and associated documentation files (the "Software"), to +// deal in the Software without restriction, including without limitation the +// rights to use, copy, modify, merge, publish, distribute, sublicense, and/or +// sell copies of the Software, and to permit persons to whom the Software is +// furnished to do so, subject to the following conditions: +// +// The above copyright notice and this permission notice shall be included in +// all copies or substantial portions of the Software. +// +// THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR +// IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, +// FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE +// AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER +// LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, +// OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN +// THE SOFTWARE. + +import { ChangeDetectorRef, Component, OnInit } from "@angular/core"; +import { GridsterConfig, GridsterItem } from "angular-gridster2"; +import orderBy from "lodash/orderBy"; +import { BehaviorSubject, firstValueFrom, from } from "rxjs"; +import { map, tap } from "rxjs/operators"; + +import { + DataSourceService, + IDataField, + INovaFilteringOutputs, + INovaFilters, + nameof, +} from "@nova-ui/bits"; +import { + DATA_SOURCE, + DEFAULT_PIZZAGNA_ROOT, + IDashboard, + ITableWidgetColumnConfig, + IWidget, + IWidgets, + NOVA_URL_INTERACTION_HANDLER, + ProviderRegistryService, + WellKnownPathKey, + WellKnownProviders, + WidgetTypesService, +} from "@nova-ui/dashboards"; + +export const BREW_API_URL = "https://api.punkapi.com/v2/beers"; + +export interface IBrewInfo { + id: string; + name: string; + tagline: string; + first_brewed: string; + description: string; + brewers_tips: string; +} + +export interface IBrewDatasourceResponse { + brewInfo: IBrewInfo[]; + total: number; +} + +export class MockBeerDataSource extends DataSourceService { + public static providerId = "MockBeerDataSource"; + + private cache: IBrewInfo[] = []; + + public busy = new BehaviorSubject(false); + + public dataFields: Array = [ + { + id: nameof("id"), + label: "No", + dataType: "number", + sortable: true, + }, + // To indicate that a column should not be sortable, set the optional IDataField 'sortable' property to false + { + id: nameof("name"), + label: "Name", + dataType: "string", + sortable: true, + }, + { + id: nameof("tagline"), + label: "Tagline", + dataType: "string", + sortable: true, + }, + { + id: nameof("first_brewed"), + label: "First Brewed", + dataType: "string", + sortable: true, + }, + { + id: nameof("description"), + label: "Description", + dataType: "string", + sortable: false, + }, + { + id: nameof("brewers_tips"), + label: "Brewer's Tips", + dataType: "string", + sortable: false, + }, + ]; + + public async getFilteredData( + filters: INovaFilters + ): Promise { + const start = filters.virtualScroll?.value?.start ?? 0; + const end = filters.virtualScroll?.value?.end ?? 0; + + // Resetting cache on first page request + if (start === 0) { + this.cache = []; + } + + // extract sorter settings to send to the backend + // filters.sorterValue.sortBy; filters.sorterValue.direction + return firstValueFrom( + from(this.fetch(start, end)).pipe( + tap((response) => { + if (!response) { + return; + } + this.cache = this.sortData( + this.cache.concat(response.brewInfo), + filters + ); + this.dataSubject.next(this.cache); + }), + map(() => ({ + repeat: { itemsSource: this.cache }, + dataFields: this.dataFields, + })) + ) + ); + } + + public async fetch( + start: number, + end: number + ): Promise { + const delta: number = end - start; + const currentPage: number = end / delta || 0; + const response: object | Array = await ( + await fetch( + \\\`\\\${BREW_API_URL}/?page=\\\${currentPage}&per_page=\\\${delta}\\\` + ) + ).json(); + console.log( + "📘 table-widget-interactive-example.component: 85# -> response:", + response + ); + + // Note: In case request fails we should not proceed with mapping + if (!Array.isArray(response)) { + return undefined; + } + + return { + brewInfo: response.map((result: IBrewInfo) => ({ + id: result.id, + name: result.name, + tagline: result.tagline, + first_brewed: result.first_brewed, + description: result.description, + brewers_tips: result.brewers_tips, + })), + total: response.length, + }; + } + + private sortData(data: IBrewInfo[], filters: INovaFilters): IBrewInfo[] { + return orderBy( + data, + filters.sorter?.value?.sortBy, + filters.sorter?.value?.direction as "desc" | "asc" + ); + } +} + +/** + * A component that instantiates the dashboard + */ +@Component({ + selector: "table-widget-interactive-example", + templateUrl: "./table-widget-interactive-example.component.html", + styleUrls: ["./table-widget-interactive-example.component.less"], + standalone: false, +}) +export class TableWidgetInteractiveExampleComponent implements OnInit { + // This variable will hold all the data needed to define the layout and behavior of the widgets. + // Pass this to the dashboard component's dashboard input in the template. + public dashboard: IDashboard | undefined; + + // Angular gridster requires a configuration object even if it's empty. + // Pass this to the dashboard component's gridsterConfig input in the template. + public gridsterConfig: GridsterConfig = {}; + + // Boolean passed as an input to the dashboard. When true, widgets can be moved, resized, removed, or edited + public editMode: boolean = false; + + constructor( + // WidgetTypesService provides the widget's necessary structure information + private widgetTypesService: WidgetTypesService, + // In general, the ProviderRegistryService is used for making entities available for injection into dynamically loaded components. + private providerRegistry: ProviderRegistryService, + private changeDetectorRef: ChangeDetectorRef + ) {} + + public ngOnInit(): void { + // Grabbing the widget's default template which will be needed as a parameter for setNode + const widgetTemplate = this.widgetTypesService.getWidgetType( + "table", + 1 + ); + + // Registering our data sources as dropdown options in the widget editor/configurator + // Note: This could also be done in the parent module's constructor so that + // multiple dashboards could have access to the same widget template modification. + this.widgetTypesService.setNode( + // This is the template we grabbed above with getWidgetType + widgetTemplate, + // We are setting the editor/configurator part of the widget template + "configurator", + // This indicates which node you are changing and we want to change + // the data source providers available for selection in the editor. + WellKnownPathKey.DataSourceProviders, + // We are setting the data sources available for selection in the editor + [MockBeerDataSource.providerId] + ); + + // Registering the data source for injection into the widget. + this.providerRegistry.setProviders({ + [MockBeerDataSource.providerId]: { + provide: DATA_SOURCE, + useClass: MockBeerDataSource, + // Any dependencies that need to be injected into the provider must be listed here + deps: [], + }, + }); + + this.initializeDashboard(); + } + + /** Used for restoring widgets state */ + public reInitializeDashboard(): void { + // destroys the components and their providers so the dashboard can re init data + this.dashboard = undefined; + this.changeDetectorRef.detectChanges(); + + this.initializeDashboard(); + } + + public initializeDashboard(): void { + // We're using a static configuration object for this example, but this is where + // the widget's configuration could potentially be populated from a database + const tableWidget = widgetConfig; + const widgetIndex: IWidgets = { + // Enhance the widget with information coming from it's type definition + [tableWidget.id]: + this.widgetTypesService.mergeWithWidgetType(tableWidget), + }; + + // Setting the widget dimensions and position (this is for gridster) + const positions: Record = { + [tableWidget.id]: { + cols: 12, + rows: 6, + y: 0, + x: 0, + }, + }; + + // Finally, assigning the variables we created above to the dashboard + this.dashboard = { + positions, + widgets: widgetIndex, + }; + } +} + +const TABLE_COLUMNS: ITableWidgetColumnConfig[] = [ + { + id: "column1", + label: $localize\\\`Beer Name\\\`, + isActive: true, + width: 185, + formatter: { + componentType: "RawFormatterComponent", + properties: { + dataFieldIds: { + value: "name", + }, + }, + }, + }, + { + id: "column2", + label: $localize\\\`Tagline\\\`, + isActive: true, + width: 250, + formatter: { + componentType: "RawFormatterComponent", + properties: { + dataFieldIds: { + value: "tagline", + }, + }, + }, + }, + { + id: "column3", + label: $localize\\\`First Brewed\\\`, + isActive: true, + width: 100, + formatter: { + componentType: "RawFormatterComponent", + properties: { + dataFieldIds: { + value: "first_brewed", + }, + }, + }, + }, + { + id: "column4", + label: $localize\\\`Description\\\`, + isActive: true, + formatter: { + componentType: "RawFormatterComponent", + properties: { + dataFieldIds: { + value: "description", + }, + }, + }, + }, +]; + +export const widgetConfig: IWidget = { + id: "tableWidgetId", + type: "table", + pizzagna: { + configuration: { + [DEFAULT_PIZZAGNA_ROOT]: { + providers: { + [WellKnownProviders.InteractionHandler]: { + // Configuring the UrlInteractionHandler to handle interactions + providerId: NOVA_URL_INTERACTION_HANDLER, + properties: { + // the 'url' property tells the handler what link to use when interaction occurs on the series + url: "\\\${'https://untappd.com/search?q='+data.name}", + // by default the link is opened in the current window, set 'newWindow' to true to open in a new tab instead + newWindow: true, + }, + }, + }, + }, + header: { + properties: { + title: "Stupendous Suds", + subtitle: "Try These Brilliant Brews", + }, + }, + table: { + providers: { + [WellKnownProviders.DataSource]: { + providerId: MockBeerDataSource.providerId, + }, + }, + properties: { + configuration: { + // set interactions to true on the table + interactive: true, + columns: TABLE_COLUMNS, + sortable: true, + sorterConfiguration: { + descendantSorting: false, + sortBy: "", + }, + hasVirtualScroll: true, + }, + }, + }, + }, + }, +}; +\`, + "widget-types/table/table-widget-paginator/table-widget-paginator-example.component.ts": \`// © 2022 SolarWinds Worldwide, LLC. All rights reserved. +// +// Permission is hereby granted, free of charge, to any person obtaining a copy +// of this software and associated documentation files (the "Software"), to +// deal in the Software without restriction, including without limitation the +// rights to use, copy, modify, merge, publish, distribute, sublicense, and/or +// sell copies of the Software, and to permit persons to whom the Software is +// furnished to do so, subject to the following conditions: +// +// The above copyright notice and this permission notice shall be included in +// all copies or substantial portions of the Software. +// +// THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR +// IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, +// FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE +// AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER +// LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, +// OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN +// THE SOFTWARE. + +import { HttpClient } from "@angular/common/http"; +import { ChangeDetectorRef, Component, OnInit } from "@angular/core"; +import { GridsterConfig, GridsterItem } from "angular-gridster2"; + +import { LoggerService } from "@nova-ui/bits"; +import { + DATA_SOURCE, + DEFAULT_PIZZAGNA_ROOT, + IDashboard, + IProviderConfiguration, + ITableWidgetConfig, + IWidget, + IWidgets, + NOVA_URL_INTERACTION_HANDLER, + PizzagnaLayer, + ProviderRegistryService, + RawFormatterComponent, + ScrollType, + WellKnownPathKey, + WellKnownProviders, + WidgetTypesService, +} from "@nova-ui/dashboards"; + +import { AcmeTableMockDataSource } from "../../../../prototypes/data/table/acme-table-mock-data-source.service"; + +/** + * A component that instantiates the dashboard + */ +@Component({ + selector: "table-widget-paginator-example", + templateUrl: "./table-widget-paginator-example.component.html", + styleUrls: ["./table-widget-paginator-example.component.less"], + standalone: false, +}) +export class TableWidgetPaginatorExampleComponent implements OnInit { + public dashboard: IDashboard | undefined; + public gridsterConfig: GridsterConfig = {}; + public editMode: boolean = false; + + constructor( + private widgetTypesService: WidgetTypesService, + private providerRegistry: ProviderRegistryService, + private changeDetectorRef: ChangeDetectorRef + ) {} + + public ngOnInit(): void { + const widgetTemplate = this.widgetTypesService.getWidgetType( + "table", + 1 + ); + this.widgetTypesService.setNode( + widgetTemplate, + "configurator", + WellKnownPathKey.DataSourceProviders, + [AcmeTableMockDataSource.providerId] + ); + + this.providerRegistry.setProviders({ + [AcmeTableMockDataSource.providerId]: { + provide: DATA_SOURCE, + useClass: AcmeTableMockDataSource, + deps: [LoggerService, HttpClient], + }, + }); + + this.initializeDashboard(); + } + + /** Used for restoring widgets state */ + public reInitializeDashboard(): void { + // destroys the components and their providers so the dashboard can re init data + this.dashboard = undefined; + this.changeDetectorRef.detectChanges(); + + this.initializeDashboard(); + } + + public initializeDashboard(): void { + const tableWithPaginator = tableWidgetWithPaginator; + const tableWithVirtualScroll = tableWidgetWithVirtualScroll; + + const widgetIndex: IWidgets = { + [tableWithPaginator.id]: + this.widgetTypesService.mergeWithWidgetType(tableWithPaginator), + [tableWithVirtualScroll.id]: + this.widgetTypesService.mergeWithWidgetType( + tableWithVirtualScroll + ), + }; + + const positions: Record = { + [tableWithPaginator.id]: { + cols: 6, + rows: 6, + y: 0, + x: 0, + }, + [tableWithVirtualScroll.id]: { + cols: 6, + rows: 6, + y: 0, + x: 0, + }, + }; + + this.dashboard = { + positions, + widgets: widgetIndex, + }; + } +} + +export const tableWidgetWithPaginator: IWidget = { + id: "widget1", + type: "table", + pizzagna: { + [PizzagnaLayer.Configuration]: { + [DEFAULT_PIZZAGNA_ROOT]: { + providers: { + [WellKnownProviders.InteractionHandler]: { + providerId: NOVA_URL_INTERACTION_HANDLER, + }, + }, + }, + header: { + properties: { + title: "Table Widget with paginator!", + subtitle: "Basic table widget", + collapsible: true, + }, + }, + table: { + providers: { + [WellKnownProviders.DataSource]: { + providerId: AcmeTableMockDataSource.providerId, + } as IProviderConfiguration, + }, + properties: { + configuration: { + interactive: true, + columns: [ + { + id: "column1", + label: "No.", + isActive: true, + formatter: { + componentType: + RawFormatterComponent.lateLoadKey, + properties: { + dataFieldIds: { + value: "position", + }, + }, + }, + }, + { + id: "column2", + label: "Name", + isActive: true, + formatter: { + componentType: + RawFormatterComponent.lateLoadKey, + properties: { + dataFieldIds: { + value: "name", + }, + }, + }, + }, + { + id: "column3", + label: "Status", + isActive: true, + formatter: { + componentType: + RawFormatterComponent.lateLoadKey, + properties: { + dataFieldIds: { + value: "status", + }, + }, + }, + }, + ], + sorterConfiguration: { + descendantSorting: false, + sortBy: "column1", + }, + scrollType: ScrollType.paginator, + paginatorConfiguration: { + pageSize: 5, + pageSizeSet: [5, 10, 20, 30], + }, + hasVirtualScroll: false, + searchConfiguration: { + enabled: true, + }, + } as ITableWidgetConfig, + }, + }, + }, + }, +}; + +export const tableWidgetWithVirtualScroll: IWidget = { + id: "widget2", + type: "table", + pizzagna: { + [PizzagnaLayer.Configuration]: { + [DEFAULT_PIZZAGNA_ROOT]: { + providers: { + [WellKnownProviders.InteractionHandler]: { + providerId: NOVA_URL_INTERACTION_HANDLER, + }, + }, + }, + header: { + properties: { + title: "Table Widget with virtual scroll!", + subtitle: "Basic table widget", + collapsible: true, + }, + }, + table: { + providers: { + [WellKnownProviders.DataSource]: { + providerId: AcmeTableMockDataSource.providerId, + } as IProviderConfiguration, + }, + properties: { + configuration: { + interactive: true, + columns: [ + { + id: "column1", + label: "No.", + isActive: true, + formatter: { + componentType: + RawFormatterComponent.lateLoadKey, + properties: { + dataFieldIds: { + value: "position", + }, + }, + }, + }, + { + id: "column2", + label: "Name", + isActive: true, + formatter: { + componentType: + RawFormatterComponent.lateLoadKey, + properties: { + dataFieldIds: { + value: "name", + }, + }, + }, + }, + { + id: "column3", + label: "Status", + isActive: true, + formatter: { + componentType: + RawFormatterComponent.lateLoadKey, + properties: { + dataFieldIds: { + value: "status", + }, + }, + }, + }, + ], + sorterConfiguration: { + descendantSorting: false, + sortBy: "column1", + }, + hasVirtualScroll: true, + searchConfiguration: { + enabled: true, + }, + } as ITableWidgetConfig, + }, + }, + }, + }, +}; +\`, + "widget-types/table/table-widget-search/table-widget-search-example.component.ts": \`// © 2022 SolarWinds Worldwide, LLC. All rights reserved. +// +// Permission is hereby granted, free of charge, to any person obtaining a copy +// of this software and associated documentation files (the "Software"), to +// deal in the Software without restriction, including without limitation the +// rights to use, copy, modify, merge, publish, distribute, sublicense, and/or +// sell copies of the Software, and to permit persons to whom the Software is +// furnished to do so, subject to the following conditions: +// +// The above copyright notice and this permission notice shall be included in +// all copies or substantial portions of the Software. +// +// THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR +// IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, +// FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE +// AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER +// LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, +// OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN +// THE SOFTWARE. + +import { HttpClient } from "@angular/common/http"; +import { + ChangeDetectorRef, + Component, + Injectable, + OnInit, +} from "@angular/core"; +import { GridsterConfig, GridsterItem } from "angular-gridster2"; +import isEqual from "lodash/isEqual"; +import isNil from "lodash/isNil"; +import { BehaviorSubject, firstValueFrom, Observable, of, Subject } from "rxjs"; +import { + catchError, + delay, + finalize, + map, + // eslint-disable-next-line import/no-deprecated + switchMap, + tap, +} from "rxjs/operators"; + +import { + DataSourceFeatures, + DataSourceService, + IDataField, + IDataSource, + IDataSourceFeatures, + IDataSourceFeaturesConfiguration, + IDataSourceOutput, + IFilter, + IFilters, + INovaFilteringOutputs, + INovaFilters, + LoggerService, +} from "@nova-ui/bits"; +import { + DATA_SOURCE, + IDashboard, + ITableWidgetConfig, + IWidget, + IWidgets, + ProviderRegistryService, + WellKnownPathKey, + WellKnownProviders, + WidgetTypesService, +} from "@nova-ui/dashboards"; + +import { GBOOKS_API_URL } from "../../../../prototypes/data/table/constants"; + +interface IGBooksApiResponse { + kind: string; + totalItems: number; + items: IGBooksItemModel[]; + [key: string]: any; +} + +interface IGBooksItemModel { + id: string; + volumeInfo: { + title: string; + subtitle: string; + authors: string[]; + [key: string]: any; + }; + accessInfo: { [key: string]: any }; + saleInfo: { [key: string]: any }; +} + +interface IGBooksData { + books: IGBooksVolume[]; + totalItems: number; +} + +interface IGBooksVolume { + title: string; + authors: string; +} + +type searchableColumnType = "title" | "authors"; + +@Injectable() +export class AcmeTableGBooksDataSource + extends DataSourceService + implements IDataSource +{ + public static providerId = "AcmeTableGBooksDataSource"; + public static mockError = false; + + public searchableColumn: searchableColumnType = "title"; + + public page: number = 1; + public busy = new BehaviorSubject(false); + public features: IDataSourceFeaturesConfiguration; + + private cache = Array.from({ length: 0 }); + private previousFilters: INovaFilters; + // DataSource Features declared + private supportedFeatures: IDataSourceFeatures = { + search: { enabled: true }, + pagination: { enabled: true }, + }; + private columnToQueryParamMap: { [k in searchableColumnType]: string } = { + title: "intitle", + authors: "inauthor", + }; + + private applyFilters$ = new Subject(); + + public dataFields: Array = [ + { + id: "title", + label: $localize\\\`Title\\\`, + dataType: "string", + sortable: false, + }, + { + id: "authors", + label: $localize\\\`Authors\\\`, + dataType: "string", + sortable: false, + }, + ]; + + constructor(private logger: LoggerService, private http: HttpClient) { + super(); + // Using Nova DataSourceFeatures implementation for the features + this.features = new DataSourceFeatures(this.supportedFeatures); + + this.applyFilters$ + // eslint-disable-next-line import/no-deprecated + .pipe(switchMap((filters) => this.getData(filters))) + .subscribe(async (res) => { + this.outputsSubject.next(await this.getFilteredData(res)); + }); + } + + public async getFilteredData( + booksData: IGBooksData + ): Promise> { + return firstValueFrom( + of(booksData).pipe( + tap((response) => { + this.cache = this.cache.concat(response.books); + }), + map((response) => ({ + result: { + repeat: { itemsSource: this.cache }, + paginator: { total: response.totalItems }, + dataFields: this.dataFields, + }, + })) + ) + ); + } + + private getData(filters: INovaFilters): Observable { + if ( + this.isNewSearchTerm(filters.search) && + filters.virtualScroll?.value.start === 0 + ) { + this.cache = []; + } + + return this.http + .get(this.getComposedUrl(filters)) + .pipe( + tap(() => this.busy.next(true)), + delay(300), // mock + map((response) => ({ + books: + response.items?.map((volume) => ({ + title: volume.volumeInfo.title, + authors: + volume.volumeInfo.authors?.join(", ") || "", + })) || [], + totalItems: response.totalItems, + })), + catchError((e) => { + this.logger.error(e); + return of({ + books: [], + totalItems: 0, + }); + }), + finalize(() => { + this.busy.next(false); + this.previousFilters = filters; + }) + ); + } + + private getComposedUrl(filters: INovaFilters) { + const initialUrl = \\\`\\\${GBOOKS_API_URL}?q=\\\`; + const maxResults = \\\`maxResults=\\\${ + (filters.virtualScroll?.value.end || 0) - + (filters.virtualScroll?.value.start || 0) + }\\\`; + + const virtualScrollPart = filters.virtualScroll + ? \\\`startIndex=\\\${filters.virtualScroll.value.start}\\\` + : ""; + + const searchQueryParam = + this.columnToQueryParamMap[this.searchableColumn]; + const searchPart = filters.search + ? \\\`\\\${searchQueryParam}:\\\${filters.search.value}\\\` + : "_"; // google books api requires some criteria to do the search + + return \\\`\\\${initialUrl}\\\${searchPart}&\\\${maxResults}&\\\${virtualScrollPart}&filter=full\\\`; + } + + private isNewSearchTerm(search: IFilter | undefined) { + return ( + !isNil(search?.value) && + !isEqual(search?.value, this.previousFilters?.search?.value) + ); + } + + // redefine parent method + public async applyFilters(): Promise { + this.applyFilters$.next(this.getFilters()); + } +} + +/** + * A component that instantiates the dashboard + */ +@Component({ + selector: "table-widget-search-example", + templateUrl: "./table-widget-search-example.component.html", + styleUrls: ["./table-widget-search-example.component.less"], + standalone: false, +}) +export class TableWidgetSearchExampleComponent implements OnInit { + public dashboard: IDashboard | undefined; + public gridsterConfig: GridsterConfig = {}; + public editMode: boolean = false; + + constructor( + private widgetTypesService: WidgetTypesService, + private providerRegistry: ProviderRegistryService, + private changeDetectorRef: ChangeDetectorRef + ) {} + + public ngOnInit(): void { + const widgetTemplate = this.widgetTypesService.getWidgetType( + "table", + 1 + ); + this.widgetTypesService.setNode( + widgetTemplate, + "configurator", + WellKnownPathKey.DataSourceProviders, + [AcmeTableGBooksDataSource.providerId] + ); + + this.providerRegistry.setProviders({ + [AcmeTableGBooksDataSource.providerId]: { + provide: DATA_SOURCE, + useClass: AcmeTableGBooksDataSource, + deps: [LoggerService, HttpClient], + }, + }); + + this.initializeDashboard(); + } + + /** Used for restoring widgets state */ + public reInitializeDashboard(): void { + // destroys the components and their providers so the dashboard can re init data + this.dashboard = undefined; + this.changeDetectorRef.detectChanges(); + + this.initializeDashboard(); + } + + public initializeDashboard(): void { + const tableWidget = widgetConfig; + const widgetIndex: IWidgets = { + [tableWidget.id]: + this.widgetTypesService.mergeWithWidgetType(tableWidget), + }; + + const positions: Record = { + [tableWidget.id]: { + cols: 12, + rows: 6, + y: 0, + x: 0, + }, + }; + + this.dashboard = { + positions, + widgets: widgetIndex, + }; + } +} + +export const widgetConfig: IWidget = { + id: "tableWidgetId", + type: "table", + pizzagna: { + configuration: { + header: { + properties: { + title: "Google Books", + }, + }, + table: { + providers: { + [WellKnownProviders.DataSource]: { + providerId: AcmeTableGBooksDataSource.providerId, + }, + }, + properties: { + configuration: { + columns: [ + { + id: "column1", + label: $localize\\\`Title\\\`, + isActive: true, + formatter: { + componentType: "RawFormatterComponent", + properties: { + dataFieldIds: { + value: "title", + }, + }, + }, + }, + { + id: "column2", + label: $localize\\\`Author\\\`, + isActive: true, + formatter: { + componentType: "RawFormatterComponent", + properties: { + dataFieldIds: { + value: "authors", + }, + }, + }, + }, + ], + sortable: false, + // define search configuration here + searchConfiguration: { + enabled: true, + // following properties below can be configured as well + // searchTerm: "search criteria here", + // searchDebounce: 300, + }, + hasVirtualScroll: true, + } as ITableWidgetConfig, + }, + }, + }, + }, +}; +\`, + "widget-types/table/table-widget-search-docs.component.ts": \`// © 2022 SolarWinds Worldwide, LLC. All rights reserved. +// +// Permission is hereby granted, free of charge, to any person obtaining a copy +// of this software and associated documentation files (the "Software"), to +// deal in the Software without restriction, including without limitation the +// rights to use, copy, modify, merge, publish, distribute, sublicense, and/or +// sell copies of the Software, and to permit persons to whom the Software is +// furnished to do so, subject to the following conditions: +// +// The above copyright notice and this permission notice shall be included in +// all copies or substantial portions of the Software. +// +// THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR +// IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, +// FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE +// AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER +// LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, +// OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN +// THE SOFTWARE. + +import { Component } from "@angular/core"; + +@Component({ + selector: "nui-table-search-docs", + templateUrl: "./table-widget-search-docs.component.html", + standalone: false, +}) +export class TableSearchDocsComponent { + public featuredDeclaredText = \\\` + private supportedFeatures: IDataSourceFeatures = { + search: { enabled: true }, + pagination: { enabled: true }, + };\\\`; + public featuresUsedText = \\\` + this.features = new DataSourceFeatures(this.supportedFeatures); + \\\`; + public tableConfigurationText = \\\` + "table": { + ... + properties: { + configuration: { + // define search configuration here + searchConfiguration: { + enabled: true, + // following optional properties below can be configured as well + // searchTerm: "search criteria here", + // searchDebounce: 300, + }, + } as ITableWidgetConfig, + }, + }, + \\\`; +} +\`, + "widget-types/table/table-widget-selectable/table-widget-selectable-multi/table-widget-selectable-multi.example.component.ts": \`// © 2022 SolarWinds Worldwide, LLC. All rights reserved. +// +// Permission is hereby granted, free of charge, to any person obtaining a copy +// of this software and associated documentation files (the "Software"), to +// deal in the Software without restriction, including without limitation the +// rights to use, copy, modify, merge, publish, distribute, sublicense, and/or +// sell copies of the Software, and to permit persons to whom the Software is +// furnished to do so, subject to the following conditions: +// +// The above copyright notice and this permission notice shall be included in +// all copies or substantial portions of the Software. +// +// THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR +// IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, +// FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE +// AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER +// LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, +// OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN +// THE SOFTWARE. + +import { Component } from "@angular/core"; + +import { TableSelectionMode } from "@nova-ui/bits"; +import { TableWidgetSelectionConfig } from "@nova-ui/dashboards"; + +/** + * A component that instantiates the dashboard + */ +@Component({ + selector: "table-widget-selectable-multi-example", + templateUrl: "./table-widget-selectable-multi.example.component.html", + styleUrls: ["./table-widget-selectable-multi.example.component.less"], + standalone: false, +}) +export class TableWidgetSelectableMultiExampleComponent { + public selectionConfiguration: TableWidgetSelectionConfig = { + enabled: true, + selectionMode: TableSelectionMode.Multi, + trackByProperty: "id", + clickableRow: true, + }; +} +\`, + "widget-types/table/table-widget-selectable/table-widget-selectable-radio/table-widget-selectable-radio.example.component.ts": \`// © 2022 SolarWinds Worldwide, LLC. All rights reserved. +// +// Permission is hereby granted, free of charge, to any person obtaining a copy +// of this software and associated documentation files (the "Software"), to +// deal in the Software without restriction, including without limitation the +// rights to use, copy, modify, merge, publish, distribute, sublicense, and/or +// sell copies of the Software, and to permit persons to whom the Software is +// furnished to do so, subject to the following conditions: +// +// The above copyright notice and this permission notice shall be included in +// all copies or substantial portions of the Software. +// +// THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR +// IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, +// FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE +// AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER +// LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, +// OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN +// THE SOFTWARE. + +import { Component } from "@angular/core"; + +import { TableSelectionMode } from "@nova-ui/bits"; +import { TableWidgetSelectionConfig } from "@nova-ui/dashboards"; + +/** + * A component that instantiates the dashboard + */ +@Component({ + selector: "table-widget-selectable-radio-example", + templateUrl: "./table-widget-selectable-radio.example.component.html", + styleUrls: ["./table-widget-selectable-radio.example.component.less"], + standalone: false, +}) +export class TableWidgetSelectableRadioExampleComponent { + public selectionConfiguration: TableWidgetSelectionConfig = { + enabled: true, + selectionMode: TableSelectionMode.Radio, + trackByProperty: "id", + clickableRow: true, + }; +} +\`, + "widget-types/table/table-widget-selectable/table-widget-selectable-single/table-widget-selectable-single.example.component.ts": \`// © 2022 SolarWinds Worldwide, LLC. All rights reserved. +// +// Permission is hereby granted, free of charge, to any person obtaining a copy +// of this software and associated documentation files (the "Software"), to +// deal in the Software without restriction, including without limitation the +// rights to use, copy, modify, merge, publish, distribute, sublicense, and/or +// sell copies of the Software, and to permit persons to whom the Software is +// furnished to do so, subject to the following conditions: +// +// The above copyright notice and this permission notice shall be included in +// all copies or substantial portions of the Software. +// +// THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR +// IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, +// FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE +// AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER +// LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, +// OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN +// THE SOFTWARE. + +import { Component } from "@angular/core"; + +import { TableSelectionMode } from "@nova-ui/bits"; +import { TableWidgetSelectionConfig } from "@nova-ui/dashboards"; + +/** + * A component that instantiates the dashboard + */ +@Component({ + selector: "table-widget-selectable-single-example", + templateUrl: "./table-widget-selectable-single.example.component.html", + styleUrls: ["./table-widget-selectable-single.example.component.less"], + standalone: false, +}) +export class TableWidgetSelectableSingleExampleComponent { + public selectionConfiguration: TableWidgetSelectionConfig = { + enabled: true, + selectionMode: TableSelectionMode.Single, + trackByProperty: "id", + }; +} +\`, + "widget-types/table/table-widget-selectable/table-widget-selectable.example.component.ts": \`// © 2022 SolarWinds Worldwide, LLC. All rights reserved. +// +// Permission is hereby granted, free of charge, to any person obtaining a copy +// of this software and associated documentation files (the "Software"), to +// deal in the Software without restriction, including without limitation the +// rights to use, copy, modify, merge, publish, distribute, sublicense, and/or +// sell copies of the Software, and to permit persons to whom the Software is +// furnished to do so, subject to the following conditions: +// +// The above copyright notice and this permission notice shall be included in +// all copies or substantial portions of the Software. +// +// THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR +// IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, +// FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE +// AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER +// LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, +// OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN +// THE SOFTWARE. + +import { HttpClient } from "@angular/common/http"; +import { ChangeDetectorRef, Component, Input, OnInit } from "@angular/core"; +import { GridsterConfig, GridsterItem } from "angular-gridster2"; + +import { LoggerService, TableSelectionMode } from "@nova-ui/bits"; +import { + DATA_SOURCE, + DEFAULT_PIZZAGNA_ROOT, + IDashboard, + IProviderConfiguration, + ITableWidgetConfig, + IWidget, + IWidgets, + NOVA_URL_INTERACTION_HANDLER, + PizzagnaLayer, + ProviderRegistryService, + RawFormatterComponent, + TableWidgetSelectionConfig, + WellKnownPathKey, + WellKnownProviders, + WidgetTypesService, +} from "@nova-ui/dashboards"; + +import { AcmeTableMockDataSource } from "../../../../prototypes/data/table/acme-table-mock-data-source.service"; + +/** + * A component that instantiates the dashboard + */ +@Component({ + selector: "table-widget-selectable-example", + templateUrl: "./table-widget-selectable.example.component.html", + styleUrls: ["./table-widget-selectable.example.component.less"], + standalone: false, +}) +export class TableWidgetSelectableExampleComponent implements OnInit { + public dashboard: IDashboard | undefined; + public gridsterConfig: GridsterConfig = {}; + public editMode: boolean = false; + + @Input() public selectionConfiguration: TableWidgetSelectionConfig = { + enabled: false, + selectionMode: TableSelectionMode.None, + }; + + constructor( + private widgetTypesService: WidgetTypesService, + private providerRegistry: ProviderRegistryService, + private changeDetectorRef: ChangeDetectorRef + ) {} + + public ngOnInit(): void { + const widgetTemplate = this.widgetTypesService.getWidgetType( + "table", + 1 + ); + this.widgetTypesService.setNode( + widgetTemplate, + "configurator", + WellKnownPathKey.DataSourceProviders, + [AcmeTableMockDataSource.providerId] + ); + + this.providerRegistry.setProviders({ + [AcmeTableMockDataSource.providerId]: { + provide: DATA_SOURCE, + useClass: AcmeTableMockDataSource, + deps: [LoggerService, HttpClient], + }, + }); + + this.initializeDashboard(); + } + + /** Used for restoring widgets state */ + public reInitializeDashboard(): void { + // destroys the components and their providers so the dashboard can re init data + this.dashboard = undefined; + this.changeDetectorRef.detectChanges(); + + this.initializeDashboard(); + } + + public initializeDashboard(): void { + const tableWidget = this.widgetConfig; + const widgetIndex: IWidgets = { + [tableWidget.id]: + this.widgetTypesService.mergeWithWidgetType(tableWidget), + }; + + const positions: Record = { + [tableWidget.id]: { + cols: 12, + rows: 6, + y: 0, + x: 0, + }, + }; + + this.dashboard = { + positions, + widgets: widgetIndex, + }; + } + + private get widgetConfig(): IWidget { + return { + id: "widget1", + type: "table", + pizzagna: { + [PizzagnaLayer.Configuration]: { + [DEFAULT_PIZZAGNA_ROOT]: { + providers: { + [WellKnownProviders.InteractionHandler]: { + providerId: NOVA_URL_INTERACTION_HANDLER, + }, + }, + }, + header: { + properties: { + title: "Table Widget with Selection!", + subtitle: "Basic table widget", + collapsible: true, + }, + }, + table: { + providers: { + [WellKnownProviders.DataSource]: { + providerId: AcmeTableMockDataSource.providerId, + } as IProviderConfiguration, + }, + properties: { + configuration: { + // enabling selection here + selectionConfiguration: + this.selectionConfiguration, + columns: [ + { + id: "column1", + label: "No.", + isActive: true, + formatter: { + componentType: + RawFormatterComponent.lateLoadKey, + properties: { + dataFieldIds: { + value: "position", + }, + }, + }, + }, + { + id: "column2", + label: "Name", + isActive: true, + formatter: { + componentType: + RawFormatterComponent.lateLoadKey, + properties: { + dataFieldIds: { + value: "name", + }, + }, + }, + }, + { + id: "column3", + label: "Status", + isActive: true, + formatter: { + componentType: + RawFormatterComponent.lateLoadKey, + properties: { + dataFieldIds: { + value: "status", + }, + }, + }, + }, + ], + } as ITableWidgetConfig, + }, + }, + }, + }, + }; + } +} +\`, + "widget-types/timeseries/timeseries-docs.component.ts": \`// © 2022 SolarWinds Worldwide, LLC. All rights reserved. +// +// Permission is hereby granted, free of charge, to any person obtaining a copy +// of this software and associated documentation files (the "Software"), to +// deal in the Software without restriction, including without limitation the +// rights to use, copy, modify, merge, publish, distribute, sublicense, and/or +// sell copies of the Software, and to permit persons to whom the Software is +// furnished to do so, subject to the following conditions: +// +// The above copyright notice and this permission notice shall be included in +// all copies or substantial portions of the Software. +// +// THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR +// IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, +// FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE +// AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER +// LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, +// OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN +// THE SOFTWARE. + +import { Component, OnInit } from "@angular/core"; + +import { mapContentFile } from "../../../../demo-files-factory"; + +@Component({ + selector: "nui-timeseries-docs", + templateUrl: "./timeseries-docs.component.html", + standalone: false, +}) +export class TimeseriesDocsComponent implements OnInit { + public timeseriesWidgetFileText = ""; + public timeseriesConfiguratorFileText = ""; + + async ngOnInit(): Promise { + this.timeseriesWidgetFileText = await import( + "./../../../../../../src/lib/widget-types/timeseries/timeseries-widget" + ).then(mapContentFile); + this.timeseriesConfiguratorFileText = await import( + "./../../../../../../src/lib/widget-types/timeseries/timeseries-configurator" + ).then(mapContentFile); + } +} +\`, + "widget-types/timeseries/timeseries-docs.module.ts": \`// © 2022 SolarWinds Worldwide, LLC. All rights reserved. +// +// Permission is hereby granted, free of charge, to any person obtaining a copy +// of this software and associated documentation files (the "Software"), to +// deal in the Software without restriction, including without limitation the +// rights to use, copy, modify, merge, publish, distribute, sublicense, and/or +// sell copies of the Software, and to permit persons to whom the Software is +// furnished to do so, subject to the following conditions: +// +// The above copyright notice and this permission notice shall be included in +// all copies or substantial portions of the Software. +// +// THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR +// IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, +// FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE +// AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER +// LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, +// OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN +// THE SOFTWARE. + +import { NgModule } from "@angular/core"; +import { RouterModule, Routes } from "@angular/router"; + +import { DEMO_PATH_TOKEN } from "@nova-ui/bits"; +import { + NuiButtonModule, + NuiDocsModule, + NuiMessageModule, + NuiSwitchModule, +} from "@nova-ui/bits"; +import { NuiDashboardsModule } from "@nova-ui/dashboards"; + +import { TimeseriesDocsComponent } from "./timeseries-docs.component"; +import { TimeseriesWidgetExampleComponent } from "./timeseries-widget-example/timeseries-widget-example.component"; +import { TimeseriesWidgetInteractiveExampleComponent } from "./timeseries-widget-interactive-example/timeseries-widget-interactive-example.component"; +import { TimeseriesWidgetStatusBarExampleComponent } from "./timeseries-widget-status-bar-example/timeseries-widget-status-bar-example.component"; +import { getDemoFiles } from "../../../../demo-files-factory"; + +const routes: Routes = [ + { + path: "", + component: TimeseriesDocsComponent, + data: { + srlc: { + hideIndicator: true, + }, + showThemeSwitcher: true, + }, + }, + { + path: "example", + component: TimeseriesWidgetExampleComponent, + data: { + srlc: { + hideIndicator: true, + }, + }, + }, +]; + +@NgModule({ + imports: [ + RouterModule.forChild(routes), + NuiButtonModule, + NuiDocsModule, + NuiMessageModule, + NuiSwitchModule, + NuiDashboardsModule, + ], + declarations: [ + TimeseriesDocsComponent, + TimeseriesWidgetExampleComponent, + TimeseriesWidgetInteractiveExampleComponent, + TimeseriesWidgetStatusBarExampleComponent, + ], + providers: [ + { + provide: DEMO_PATH_TOKEN, + useValue: getDemoFiles("timeseries"), + }, + ], +}) +export default class TimeseriesDocsModule {} +\`, + "widget-types/timeseries/timeseries-widget-example/timeseries-widget-example.component.ts": \`// © 2022 SolarWinds Worldwide, LLC. All rights reserved. +// +// Permission is hereby granted, free of charge, to any person obtaining a copy +// of this software and associated documentation files (the "Software"), to +// deal in the Software without restriction, including without limitation the +// rights to use, copy, modify, merge, publish, distribute, sublicense, and/or +// sell copies of the Software, and to permit persons to whom the Software is +// furnished to do so, subject to the following conditions: +// +// The above copyright notice and this permission notice shall be included in +// all copies or substantial portions of the Software. +// +// THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR +// IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, +// FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE +// AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER +// LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, +// OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN +// THE SOFTWARE. + +import { + ChangeDetectorRef, + Component, + Injectable, + OnInit, +} from "@angular/core"; +import { GridsterConfig, GridsterItem } from "angular-gridster2"; +import cloneDeep from "lodash/cloneDeep"; +import keyBy from "lodash/keyBy"; +import moment, { Moment } from "moment/moment"; +import { BehaviorSubject } from "rxjs"; + +import { + DataSourceService, + IDataSource, + INovaFilters, + ITimeframe, +} from "@nova-ui/bits"; +import { + DATA_SOURCE, + DEFAULT_PIZZAGNA_ROOT, + IDashboard, + IDataSourceOutput, + IProviderConfiguration, + ISerializableTimeframe, + ITimeseriesItemConfiguration, + ITimeseriesOutput, + ITimeseriesScaleConfig, + ITimeseriesWidgetConfig, + ITimeseriesWidgetData, + ITimeseriesWidgetSeriesData, + IWidget, + LegendPlacement, + PizzagnaLayer, + ProviderRegistryService, + TimeseriesChartPreset, + TimeseriesScaleType, + WellKnownPathKey, + WellKnownProviders, + WidgetTypesService, +} from "@nova-ui/dashboards"; + +/** + * A simple Timeseries data source implementation + */ +@Injectable() +export class BeerVsReadingMockDataSource + extends DataSourceService + implements IDataSource +{ + public static providerId = "BeerVsReadingMockDataSource"; + + public busy = new BehaviorSubject(false); + + public async getFilteredData( + filters: INovaFilters + ): Promise> { + // In this example we're using some static mock data located at the bottom of this file. In a real-world + // scenario, the data for the chart would likely be retrieved via an asynchronous backend call. + let filteredData = getData(); + + this.busy.next(true); + + // Filtering using the filter registered by the TimeFrameBar + const timeframeFilter = filters.timeframe?.value as ITimeframe; + if (timeframeFilter) { + filteredData = filteredData.map((item: ITimeseriesWidgetData) => ({ + id: item.id, + name: item.name, + description: item.description, + data: item.data.filter( + (seriesData: ITimeseriesWidgetSeriesData) => + filterDates( + seriesData.x, + timeframeFilter.startDatetime, + timeframeFilter.endDatetime + ) + ), + })); + } + + this.busy.next(false); + + return { result: { series: filteredData } }; + } +} + +function filterDates(dateToCheck: Date, startDate: Moment, endDate: Moment) { + const mom = moment(dateToCheck); + return ( + mom.isBetween(startDate, endDate) || + mom.isSame(startDate) || + mom.isSame(endDate) + ); +} + +/** + * A component that instantiates the dashboard + */ +@Component({ + selector: "timeseries-widget-example", + templateUrl: "./timeseries-widget-example.component.html", + styleUrls: ["./timeseries-widget-example.component.less"], + standalone: false, +}) +export class TimeseriesWidgetExampleComponent implements OnInit { + // This variable will hold all the data needed to define the layout and behavior of the widgets. + // Pass this to the dashboard component's dashboard input in the template. + public dashboard: IDashboard | undefined; + + // Angular gridster requires a configuration object even if it's empty. + // Pass this to the dashboard component's gridsterConfig input in the template. + public gridsterConfig: GridsterConfig = {}; + + // Boolean passed as an input to the dashboard. When true, widgets can be moved, resized, removed, or edited + public editMode: boolean = false; + + constructor( + // WidgetTypesService provides the widget's necessary structure information + private widgetTypesService: WidgetTypesService, + + // In general, the ProviderRegistryService is used for making entities available for injection into dynamically loaded components. + private providerRegistry: ProviderRegistryService, + + // Angular's ChangeDetectorRef + private changeDetectorRef: ChangeDetectorRef + ) {} + + public ngOnInit(): void { + // Grabbing the widget's default template which will be needed as a parameter for setNode + const widgetTemplate = this.widgetTypesService.getWidgetType( + "timeseries", + 1 + ); + // Registering our data sources as dropdown options in the widget editor/configurator + // Note: This could also be done in the parent module's constructor so that + // multiple dashboards could have access to the same widget template modification. + this.widgetTypesService.setNode( + // This is the template we grabbed above with getWidgetType + widgetTemplate, + // We are setting the editor/configurator part of the widget template + "configurator", + // This indicates which node you are changing and we want to change + // the data source providers available for selection in the editor. + WellKnownPathKey.DataSourceProviders, + // We are setting the data sources available for selection in the editor + [BeerVsReadingMockDataSource.providerId] + ); + + // Registering the data source for injection into the widget. + this.providerRegistry.setProviders({ + [BeerVsReadingMockDataSource.providerId]: { + provide: DATA_SOURCE, + useClass: BeerVsReadingMockDataSource, + deps: [], + }, + }); + + this.initializeDashboard(); + } + + public initializeDashboard(): void { + // We're using a static configuration object for this example, but this is where + // the widget's configuration could potentially be populated from a database + const widgetsWithStructure = widgetConfigs.map((w) => + this.widgetTypesService.mergeWithWidgetType(w) + ); + const widgetsIndex = keyBy(widgetsWithStructure, (w: IWidget) => w.id); + + // Finally, assigning the variables we created above to the dashboard + this.dashboard = { + positions: cloneDeep(positions), + widgets: widgetsIndex, + }; + } + + /** Used for restoring widgets state */ + public reInitializeDashboard(): void { + // destroys the components and their providers so the dashboard can re init data + this.dashboard = undefined; + this.changeDetectorRef.detectChanges(); + + this.initializeDashboard(); + } +} + +const widgetConfigs: IWidget[] = [ + { + id: "lineWidgetId", + type: "timeseries", + pizzagna: { + [PizzagnaLayer.Configuration]: { + [DEFAULT_PIZZAGNA_ROOT]: { + providers: { + [WellKnownProviders.DataSource]: { + // Setting the initially selected data source providerId + providerId: BeerVsReadingMockDataSource.providerId, + } as IProviderConfiguration, + }, + }, + header: { + properties: { + title: "Line Chart", + subtitle: "Survey of 1000 Solarians", + }, + }, + chart: { + providers: { + [WellKnownProviders.Adapter]: { + properties: { + // Setting the series and corresponding labels to initially display on the chart + series: [ + { + id: "series-1", + label: "Beer Tasting", + selectedSeriesId: "series-1", + }, + { + id: "series-2", + label: "Reading", + selectedSeriesId: "series-2", + }, + ] as ITimeseriesItemConfiguration[], + }, + } as Partial, + }, + properties: { + // Setting the general chart configuration + configuration: { + legendPlacement: LegendPlacement.Right, + enableZoom: true, + leftAxisLabel: "Solarians (%)", + // You can optionally define custom colors for the chart by setting the 'chartColors' configuration property + // "chartColors": [ + // "var(--nui-color-chart-eight)", + // "var(--nui-color-chart-nine)", + // "var(--nui-color-chart-ten)", + // ], + } as ITimeseriesWidgetConfig, + }, + }, + timeframeSelection: { + properties: { + // Setting the initial timeframe selected in the timeframe bar + timeframe: { + selectedPresetId: "last7Days", + } as ISerializableTimeframe, + minDate: moment().subtract(60, "days").format(), + maxDate: moment().format(), + }, + }, + }, + }, + }, + { + id: "stackedAreaWidgetId", + type: "timeseries", + pizzagna: { + [PizzagnaLayer.Configuration]: { + [DEFAULT_PIZZAGNA_ROOT]: { + providers: { + [WellKnownProviders.DataSource]: { + // Setting the initially selected data source providerId + providerId: BeerVsReadingMockDataSource.providerId, + } as IProviderConfiguration, + }, + }, + header: { + properties: { + title: "Stacked Area Chart", + subtitle: "Survey of 1000 Solarians", + }, + }, + chart: { + providers: { + [WellKnownProviders.Adapter]: { + properties: { + // Setting the series and corresponding labels to initially display on the chart + series: [ + { + id: "series-1", + label: "Beer Tasting", + selectedSeriesId: "series-1", + }, + { + id: "series-2", + label: "Reading", + selectedSeriesId: "series-2", + }, + ] as ITimeseriesItemConfiguration[], + }, + } as Partial, + }, + properties: { + // Setting the general chart configuration + configuration: { + legendPlacement: LegendPlacement.Right, + enableZoom: true, + // Setting the preset to stacked area + preset: TimeseriesChartPreset.StackedArea, + leftAxisLabel: "Solarians (%)", + // You can optionally define custom colors for the chart by setting the 'chartColors' configuration property + // "chartColors": [ + // "var(--nui-color-chart-eight)", + // "var(--nui-color-chart-nine)", + // "var(--nui-color-chart-ten)", + // ], + } as ITimeseriesWidgetConfig, + }, + }, + timeframeSelection: { + properties: { + // Setting the initial timeframe selected in the timeframe bar + timeframe: { + selectedPresetId: "last7Days", + } as ISerializableTimeframe, + minDate: moment().subtract(60, "days").format(), + maxDate: moment().format(), + }, + }, + }, + }, + }, + { + id: "stackedPercentageAreaWidgetId", + type: "timeseries", + pizzagna: { + [PizzagnaLayer.Configuration]: { + [DEFAULT_PIZZAGNA_ROOT]: { + providers: { + [WellKnownProviders.DataSource]: { + // Setting the initially selected data source providerId + providerId: BeerVsReadingMockDataSource.providerId, + } as IProviderConfiguration, + }, + }, + header: { + properties: { + title: "Stacked Percentage Area Chart", + subtitle: "Survey of 1000 Solarians", + }, + }, + chart: { + providers: { + [WellKnownProviders.Adapter]: { + properties: { + // Setting the series and corresponding labels to initially display on the chart + series: [ + { + id: "series-1", + label: "Beer Tasting", + selectedSeriesId: "series-1", + }, + { + id: "series-2", + label: "Reading", + selectedSeriesId: "series-2", + }, + ] as ITimeseriesItemConfiguration[], + }, + } as Partial, + }, + properties: { + // Setting the general chart configuration + configuration: { + legendPlacement: LegendPlacement.Right, + enableZoom: true, + // Setting the preset to stacked percentage area + preset: TimeseriesChartPreset.StackedPercentageArea, + leftAxisLabel: "Solarians (%)", + // You can optionally define custom colors for the chart by setting the 'chartColors' configuration property + // "chartColors": [ + // "var(--nui-color-chart-eight)", + // "var(--nui-color-chart-nine)", + // "var(--nui-color-chart-ten)", + // ], + } as ITimeseriesWidgetConfig, + }, + }, + timeframeSelection: { + properties: { + // Setting the initial timeframe selected in the timeframe bar + timeframe: { + selectedPresetId: "last7Days", + } as ISerializableTimeframe, + minDate: moment().subtract(60, "days").format(), + maxDate: moment().format(), + }, + }, + }, + }, + }, + { + id: "stackedBarWidgetId", + type: "timeseries", + pizzagna: { + [PizzagnaLayer.Configuration]: { + [DEFAULT_PIZZAGNA_ROOT]: { + providers: { + [WellKnownProviders.DataSource]: { + // Setting the initially selected data source providerId + providerId: BeerVsReadingMockDataSource.providerId, + } as IProviderConfiguration, + }, + }, + header: { + properties: { + title: "Stacked Bar Chart", + subtitle: "Survey of 1000 Solarians", + }, + }, + chart: { + providers: { + [WellKnownProviders.Adapter]: { + properties: { + // Setting the series and corresponding labels to initially display on the chart + series: [ + { + id: "series-1", + label: "Beer Tasting", + selectedSeriesId: "series-1", + }, + { + id: "series-2", + label: "Reading", + selectedSeriesId: "series-2", + }, + ] as ITimeseriesItemConfiguration[], + }, + } as Partial, + }, + properties: { + configuration: { + legendPlacement: LegendPlacement.Right, + enableZoom: true, + leftAxisLabel: "Solarians (%)", + // Setting the preset to stacked bar + preset: TimeseriesChartPreset.StackedBar, + scales: { + x: { + type: TimeseriesScaleType.TimeInterval, + properties: { + interval: 24 * 60 * 60, + }, + } as ITimeseriesScaleConfig, + }, + } as ITimeseriesWidgetConfig, + }, + }, + timeframeSelection: { + properties: { + // Setting the initial timeframe selected in the timeframe bar + timeframe: { + selectedPresetId: "last7Days", + } as ISerializableTimeframe, + minDate: moment().subtract(60, "days").format(), + maxDate: moment().format(), + }, + }, + }, + }, + }, +]; + +// using startOf("day") so that each band for the bar chart corresponds to a calendar day +const now = moment().startOf("day"); + +export const getData = (): ITimeseriesWidgetData[] => [ + { + id: "series-1", + name: "Beer Tasting", + description: "Havin' some suds", + data: [ + { x: now.clone().subtract(20, "day").toDate(), y: 30 }, + { x: now.clone().subtract(19, "day").toDate(), y: 35 }, + { x: now.clone().subtract(18, "day").toDate(), y: 33 }, + { x: now.clone().subtract(17, "day").toDate(), y: 40 }, + { x: now.clone().subtract(16, "day").toDate(), y: 35 }, + { x: now.clone().subtract(15, "day").toDate(), y: 30 }, + { x: now.clone().subtract(14, "day").toDate(), y: 35 }, + { x: now.clone().subtract(13, "day").toDate(), y: 15 }, + { x: now.clone().subtract(12, "day").toDate(), y: 30 }, + { x: now.clone().subtract(11, "day").toDate(), y: 45 }, + { x: now.clone().subtract(10, "day").toDate(), y: 60 }, + { x: now.clone().subtract(9, "day").toDate(), y: 54 }, + { x: now.clone().subtract(8, "day").toDate(), y: 42 }, + { x: now.clone().subtract(7, "day").toDate(), y: 44 }, + { x: now.clone().subtract(6, "day").toDate(), y: 54 }, + { x: now.clone().subtract(5, "day").toDate(), y: 43 }, + { x: now.clone().subtract(4, "day").toDate(), y: 76 }, + { x: now.clone().subtract(3, "day").toDate(), y: 54 }, + { x: now.clone().subtract(2, "day").toDate(), y: 42 }, + { x: now.clone().subtract(1, "day").toDate(), y: 34 }, + ], + }, + { + id: "series-2", + name: "Reading", + description: "Hittin' the books", + data: [ + { x: now.clone().subtract(20, "day").toDate(), y: 60 }, + { x: now.clone().subtract(19, "day").toDate(), y: 64 }, + { x: now.clone().subtract(18, "day").toDate(), y: 70 }, + { x: now.clone().subtract(17, "day").toDate(), y: 55 }, + { x: now.clone().subtract(16, "day").toDate(), y: 55 }, + { x: now.clone().subtract(15, "day").toDate(), y: 45 }, + { x: now.clone().subtract(14, "day").toDate(), y: 60 }, + { x: now.clone().subtract(13, "day").toDate(), y: 65 }, + { x: now.clone().subtract(12, "day").toDate(), y: 63 }, + { x: now.clone().subtract(11, "day").toDate(), y: 60 }, + { x: now.clone().subtract(10, "day").toDate(), y: 61 }, + { x: now.clone().subtract(9, "day").toDate(), y: 65 }, + { x: now.clone().subtract(8, "day").toDate(), y: 63 }, + { x: now.clone().subtract(7, "day").toDate(), y: 58 }, + { x: now.clone().subtract(6, "day").toDate(), y: 64 }, + { x: now.clone().subtract(5, "day").toDate(), y: 63 }, + { x: now.clone().subtract(4, "day").toDate(), y: 60 }, + { x: now.clone().subtract(3, "day").toDate(), y: 62 }, + { x: now.clone().subtract(2, "day").toDate(), y: 61 }, + { x: now.clone().subtract(1, "day").toDate(), y: 62 }, + ], + }, +]; + +// Setting the widget dimensions and position (this is for gridster) +const positions: Record = { + [widgetConfigs[0].id]: { + cols: 6, + rows: 6, + y: 0, + x: 0, + }, + [widgetConfigs[1].id]: { + cols: 6, + rows: 6, + y: 0, + x: 6, + }, + [widgetConfigs[3].id]: { + cols: 6, + rows: 6, + y: 6, + x: 0, + }, + [widgetConfigs[2].id]: { + cols: 6, + rows: 6, + y: 6, + x: 6, + }, +}; +\`, + "widget-types/timeseries/timeseries-widget-interactive-example/timeseries-widget-interactive-example.component.ts": \`// © 2022 SolarWinds Worldwide, LLC. All rights reserved. +// +// Permission is hereby granted, free of charge, to any person obtaining a copy +// of this software and associated documentation files (the "Software"), to +// deal in the Software without restriction, including without limitation the +// rights to use, copy, modify, merge, publish, distribute, sublicense, and/or +// sell copies of the Software, and to permit persons to whom the Software is +// furnished to do so, subject to the following conditions: +// +// The above copyright notice and this permission notice shall be included in +// all copies or substantial portions of the Software. +// +// THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR +// IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, +// FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE +// AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER +// LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, +// OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN +// THE SOFTWARE. + +import { + ChangeDetectorRef, + Component, + Injectable, + OnInit, +} from "@angular/core"; +import { GridsterConfig, GridsterItem } from "angular-gridster2"; +import cloneDeep from "lodash/cloneDeep"; +import keyBy from "lodash/keyBy"; +import moment, { Moment } from "moment/moment"; +import { BehaviorSubject } from "rxjs"; + +import { + DataSourceService, + IDataSource, + INovaFilters, + ITimeframe, +} from "@nova-ui/bits"; +import { + DATA_SOURCE, + DEFAULT_PIZZAGNA_ROOT, + IDashboard, + IDataSourceOutput, + IProviderConfiguration, + ISerializableTimeframe, + ITimeseriesItemConfiguration, + ITimeseriesOutput, + ITimeseriesScaleConfig, + ITimeseriesWidgetConfig, + ITimeseriesWidgetData, + ITimeseriesWidgetSeriesData, + IWidget, + NOVA_URL_INTERACTION_HANDLER, + LegendPlacement, + PizzagnaLayer, + ProviderRegistryService, + TimeseriesChartPreset, + TimeseriesScaleType, + WellKnownPathKey, + WellKnownProviders, + WidgetTypesService, +} from "@nova-ui/dashboards"; + +/** + * A simple Timeseries data source implementation + */ +@Injectable() +export class TimeseriesMockDataSource + extends DataSourceService + implements IDataSource +{ + public static providerId = "TimeseriesMockDataSource"; + + public busy = new BehaviorSubject(false); + + public async getFilteredData( + filters: INovaFilters + ): Promise> { + // In this example we're using some static mock data located at the bottom of this file. In a real-world + // scenario, the data for the chart would likely be retrieved via an asynchronous backend call. + let filteredData = getData(); + + this.busy.next(true); + + // Filtering using the filter registered by the TimeFrameBar + const timeframeFilter = filters.timeframe?.value as ITimeframe; + if (timeframeFilter) { + filteredData = filteredData.map((item: ITimeseriesWidgetData) => ({ + id: item.id, + name: item.name, + description: item.description, + // the filtered data should return the provided links if they are set. + link: item?.link, + secondaryLink: item?.secondaryLink, + data: item.data.filter( + (seriesData: ITimeseriesWidgetSeriesData) => + filterDates( + seriesData.x, + timeframeFilter.startDatetime, + timeframeFilter.endDatetime + ) + ), + })); + } + + this.busy.next(false); + + return { result: { series: filteredData } }; + } +} + +function filterDates(dateToCheck: Date, startDate: Moment, endDate: Moment) { + const mom = moment(dateToCheck); + return ( + mom.isBetween(startDate, endDate) || + mom.isSame(startDate) || + mom.isSame(endDate) + ); +} + +/** + * A component that instantiates the dashboard + */ +@Component({ + selector: "timeseries-widget-interactive-example", + templateUrl: "./timeseries-widget-interactive-example.component.html", + styleUrls: ["./timeseries-widget-interactive-example.component.less"], + standalone: false, +}) +export class TimeseriesWidgetInteractiveExampleComponent implements OnInit { + // This variable will hold all the data needed to define the layout and behavior of the widgets. + // Pass this to the dashboard component's dashboard input in the template. + public dashboard: IDashboard | undefined; + + // Angular gridster requires a configuration object even if it's empty. + // Pass this to the dashboard component's gridsterConfig input in the template. + public gridsterConfig: GridsterConfig = {}; + + // Boolean passed as an input to the dashboard. When true, widgets can be moved, resized, removed, or edited + public editMode: boolean = false; + + constructor( + // WidgetTypesService provides the widget's necessary structure information + private widgetTypesService: WidgetTypesService, + + // In general, the ProviderRegistryService is used for making entities available for injection into dynamically loaded components. + private providerRegistry: ProviderRegistryService, + + // Angular's ChangeDetectorRef + private changeDetectorRef: ChangeDetectorRef + ) {} + + public ngOnInit(): void { + // Grabbing the widget's default template which will be needed as a parameter for setNode + const widgetTemplate = this.widgetTypesService.getWidgetType( + "timeseries", + 1 + ); + // Registering our data sources as dropdown options in the widget editor/configurator + // Note: This could also be done in the parent module's constructor so that + // multiple dashboards could have access to the same widget template modification. + this.widgetTypesService.setNode( + // This is the template we grabbed above with getWidgetType + widgetTemplate, + // We are setting the editor/configurator part of the widget template + "configurator", + // This indicates which node you are changing and we want to change + // the data source providers available for selection in the editor. + WellKnownPathKey.DataSourceProviders, + // We are setting the data sources available for selection in the editor + [TimeseriesMockDataSource.providerId] + ); + + // Registering the data source for injection into the widget. + this.providerRegistry.setProviders({ + [TimeseriesMockDataSource.providerId]: { + provide: DATA_SOURCE, + useClass: TimeseriesMockDataSource, + deps: [], + }, + }); + + this.initializeDashboard(); + } + + public initializeDashboard(): void { + // We're using a static configuration object for this example, but this is where + // the widget's configuration could potentially be populated from a database + const widgetsWithStructure = widgetConfigs.map((w) => + this.widgetTypesService.mergeWithWidgetType(w) + ); + const widgetsIndex = keyBy(widgetsWithStructure, (w: IWidget) => w.id); + + // Finally, assigning the variables we created above to the dashboard + this.dashboard = { + positions: cloneDeep(positions), + widgets: widgetsIndex, + }; + } + + /** Used for restoring widgets state */ + public reInitializeDashboard(): void { + // destroys the components and their providers so the dashboard can re init data + this.dashboard = undefined; + this.changeDetectorRef.detectChanges(); + + this.initializeDashboard(); + } +} + +const widgetConfigs: IWidget[] = [ + { + id: "lineWidgetId", + type: "timeseries", + pizzagna: { + [PizzagnaLayer.Configuration]: { + [DEFAULT_PIZZAGNA_ROOT]: { + providers: { + [WellKnownProviders.DataSource]: { + // Setting the initially selected data source providerId + providerId: TimeseriesMockDataSource.providerId, + } as IProviderConfiguration, + [WellKnownProviders.InteractionHandler]: { + // Setting the UrlInteractionHandler as an interactionHandler + providerId: NOVA_URL_INTERACTION_HANDLER, + properties: { + // the 'url' property tells the handler what link to use when interaction occurs on the series + url: "\\\${data.link || 'https://en.wikipedia.org/wiki/'+data.legendDescriptionPrimary}", + // by default the link is opened in the current window, set 'newWindow' to true to open in a new tab instead + // newWindow: true, + }, + }, + }, + }, + header: { + properties: { + title: "Line Chart", + subtitle: "Basic Timeseries with Interaction", + }, + }, + chart: { + providers: { + [WellKnownProviders.Adapter]: { + properties: { + // Setting the series and corresponding labels to initially display on the chart + series: [ + { + id: "series-1", + label: "Nur-Sultan", + selectedSeriesId: "series-1", + }, + { + id: "series-2", + label: "Brno", + selectedSeriesId: "series-2", + }, + { + id: "series-3", + label: "Lisbon", + selectedSeriesId: "series-3", + }, + { + id: "series-4", + label: "Austin", + selectedSeriesId: "series-4", + }, + ] as ITimeseriesItemConfiguration[], + }, + } as Partial, + }, + properties: { + // Setting the general chart configuration + configuration: { + // setting interaction to 'series' will make all series in the chart interactable + interaction: "series", + legendPlacement: LegendPlacement.Right, + enableZoom: true, + } as ITimeseriesWidgetConfig, + }, + }, + timeframeSelection: { + properties: { + timeframe: { + selectedPresetId: "last7Days", + } as ISerializableTimeframe, + minDate: moment().subtract(60, "days").format(), + maxDate: moment().format(), + }, + }, + }, + }, + }, + { + id: "stackedBarWidgetId", + type: "timeseries", + pizzagna: { + [PizzagnaLayer.Configuration]: { + [DEFAULT_PIZZAGNA_ROOT]: { + providers: { + [WellKnownProviders.DataSource]: { + // Setting the initially selected data source providerId + providerId: TimeseriesMockDataSource.providerId, + } as IProviderConfiguration, + }, + }, + header: { + properties: { + title: "Stacked Bar Chart", + subtitle: + "Basic Timeseries without Interaction Handler", + }, + }, + chart: { + providers: { + [WellKnownProviders.Adapter]: { + properties: { + // Setting the series and corresponding labels to initially display on the chart + series: [ + { + id: "series-1", + label: "Nur-Sultan", + selectedSeriesId: "series-1", + }, + { + id: "series-2", + label: "Brno", + selectedSeriesId: "series-2", + }, + { + id: "series-3", + label: "Lisbon", + selectedSeriesId: "series-3", + }, + { + id: "series-4", + label: "Austin", + selectedSeriesId: "series-4", + }, + ] as ITimeseriesItemConfiguration[], + }, + } as Partial, + }, + properties: { + // Setting the general chart configuration + configuration: { + legendPlacement: LegendPlacement.Right, + enableZoom: true, + // Setting the preset to stacked bar + preset: TimeseriesChartPreset.StackedBar, + scales: { + x: { + type: TimeseriesScaleType.TimeInterval, + properties: { + interval: 24 * 60 * 60, + }, + } as ITimeseriesScaleConfig, + }, + } as ITimeseriesWidgetConfig, + }, + }, + timeframeSelection: { + properties: { + // Setting the initial timeframe selected in the timeframe bar + timeframe: { + selectedPresetId: "last7Days", + } as ISerializableTimeframe, + minDate: moment().subtract(60, "days").format(), + maxDate: moment().format(), + }, + }, + }, + }, + }, +]; + +// using startOf("day") so that each band for the bar chart corresponds to a calendar day +const startOfToday = moment().startOf("day").toDate(); + +export const getData = (): ITimeseriesWidgetData[] => [ + { + id: "series-1", + name: "Nur-Sultan", + description: "'link' only", + link: "https://en.wikipedia.org/wiki/Nur-Sultan", + data: [ + { x: moment(startOfToday).subtract(59, "day").toDate(), y: 35 }, + { x: moment(startOfToday).subtract(58, "day").toDate(), y: 33 }, + { x: moment(startOfToday).subtract(57, "day").toDate(), y: 40 }, + { x: moment(startOfToday).subtract(56, "day").toDate(), y: 35 }, + { x: moment(startOfToday).subtract(55, "day").toDate(), y: 30 }, + { x: moment(startOfToday).subtract(54, "day").toDate(), y: 35 }, + { x: moment(startOfToday).subtract(53, "day").toDate(), y: 15 }, + { x: moment(startOfToday).subtract(52, "day").toDate(), y: 30 }, + { x: moment(startOfToday).subtract(51, "day").toDate(), y: 35 }, + { x: moment(startOfToday).subtract(50, "day").toDate(), y: 34 }, + { x: moment(startOfToday).subtract(49, "day").toDate(), y: 35 }, + { x: moment(startOfToday).subtract(48, "day").toDate(), y: 33 }, + { x: moment(startOfToday).subtract(47, "day").toDate(), y: 40 }, + { x: moment(startOfToday).subtract(46, "day").toDate(), y: 35 }, + { x: moment(startOfToday).subtract(45, "day").toDate(), y: 30 }, + { x: moment(startOfToday).subtract(44, "day").toDate(), y: 35 }, + { x: moment(startOfToday).subtract(43, "day").toDate(), y: 15 }, + { x: moment(startOfToday).subtract(42, "day").toDate(), y: 30 }, + { x: moment(startOfToday).subtract(41, "day").toDate(), y: 35 }, + { x: moment(startOfToday).subtract(40, "day").toDate(), y: 34 }, + { x: moment(startOfToday).subtract(39, "day").toDate(), y: 35 }, + { x: moment(startOfToday).subtract(38, "day").toDate(), y: 33 }, + { x: moment(startOfToday).subtract(37, "day").toDate(), y: 40 }, + { x: moment(startOfToday).subtract(36, "day").toDate(), y: 35 }, + { x: moment(startOfToday).subtract(35, "day").toDate(), y: 30 }, + { x: moment(startOfToday).subtract(34, "day").toDate(), y: 35 }, + { x: moment(startOfToday).subtract(33, "day").toDate(), y: 15 }, + { x: moment(startOfToday).subtract(32, "day").toDate(), y: 30 }, + { x: moment(startOfToday).subtract(31, "day").toDate(), y: 35 }, + { x: moment(startOfToday).subtract(30, "day").toDate(), y: 34 }, + { x: moment(startOfToday).subtract(29, "day").toDate(), y: 35 }, + { x: moment(startOfToday).subtract(28, "day").toDate(), y: 33 }, + { x: moment(startOfToday).subtract(27, "day").toDate(), y: 40 }, + { x: moment(startOfToday).subtract(26, "day").toDate(), y: 35 }, + { x: moment(startOfToday).subtract(25, "day").toDate(), y: 30 }, + { x: moment(startOfToday).subtract(24, "day").toDate(), y: 35 }, + { x: moment(startOfToday).subtract(23, "day").toDate(), y: 15 }, + { x: moment(startOfToday).subtract(22, "day").toDate(), y: 30 }, + { x: moment(startOfToday).subtract(21, "day").toDate(), y: 35 }, + { x: moment(startOfToday).subtract(20, "day").toDate(), y: 34 }, + { x: moment(startOfToday).subtract(19, "day").toDate(), y: 35 }, + { x: moment(startOfToday).subtract(18, "day").toDate(), y: 33 }, + { x: moment(startOfToday).subtract(17, "day").toDate(), y: 40 }, + { x: moment(startOfToday).subtract(16, "day").toDate(), y: 35 }, + { x: moment(startOfToday).subtract(15, "day").toDate(), y: 30 }, + { x: moment(startOfToday).subtract(14, "day").toDate(), y: 35 }, + { x: moment(startOfToday).subtract(13, "day").toDate(), y: 15 }, + { x: moment(startOfToday).subtract(12, "day").toDate(), y: 30 }, + { x: moment(startOfToday).subtract(11, "day").toDate(), y: 35 }, + { x: moment(startOfToday).subtract(10, "day").toDate(), y: 34 }, + { x: moment(startOfToday).subtract(9, "day").toDate(), y: 33 }, + { x: moment(startOfToday).subtract(8, "day").toDate(), y: 35 }, + { x: moment(startOfToday).subtract(7, "day").toDate(), y: 36 }, + { x: moment(startOfToday).subtract(6, "day").toDate(), y: 34 }, + { x: moment(startOfToday).subtract(5, "day").toDate(), y: 33 }, + { x: moment(startOfToday).subtract(4, "day").toDate(), y: 30 }, + { x: moment(startOfToday).subtract(3, "day").toDate(), y: 32 }, + { x: moment(startOfToday).subtract(2, "day").toDate(), y: 31 }, + { x: moment(startOfToday).subtract(1, "day").toDate(), y: 34 }, + { x: moment(startOfToday).toDate(), y: 25 }, + ], + }, + { + id: "series-2", + name: "Brno", + description: "'link' and 'secondaryLink'", + link: "https://en.wikipedia.org/wiki/Brno", + secondaryLink: "https://en.wikipedia.org/wiki/Europe", + data: [ + { x: moment(startOfToday).subtract(59, "day").toDate(), y: 35 }, + { x: moment(startOfToday).subtract(58, "day").toDate(), y: 33 }, + { x: moment(startOfToday).subtract(57, "day").toDate(), y: 40 }, + { x: moment(startOfToday).subtract(56, "day").toDate(), y: 35 }, + { x: moment(startOfToday).subtract(55, "day").toDate(), y: 30 }, + { x: moment(startOfToday).subtract(54, "day").toDate(), y: 35 }, + { x: moment(startOfToday).subtract(53, "day").toDate(), y: 15 }, + { x: moment(startOfToday).subtract(52, "day").toDate(), y: 30 }, + { x: moment(startOfToday).subtract(51, "day").toDate(), y: 35 }, + { x: moment(startOfToday).subtract(50, "day").toDate(), y: 34 }, + { x: moment(startOfToday).subtract(49, "day").toDate(), y: 35 }, + { x: moment(startOfToday).subtract(48, "day").toDate(), y: 33 }, + { x: moment(startOfToday).subtract(47, "day").toDate(), y: 40 }, + { x: moment(startOfToday).subtract(46, "day").toDate(), y: 35 }, + { x: moment(startOfToday).subtract(45, "day").toDate(), y: 30 }, + { x: moment(startOfToday).subtract(44, "day").toDate(), y: 35 }, + { x: moment(startOfToday).subtract(43, "day").toDate(), y: 15 }, + { x: moment(startOfToday).subtract(42, "day").toDate(), y: 30 }, + { x: moment(startOfToday).subtract(41, "day").toDate(), y: 35 }, + { x: moment(startOfToday).subtract(40, "day").toDate(), y: 34 }, + { x: moment(startOfToday).subtract(39, "day").toDate(), y: 35 }, + { x: moment(startOfToday).subtract(38, "day").toDate(), y: 33 }, + { x: moment(startOfToday).subtract(37, "day").toDate(), y: 40 }, + { x: moment(startOfToday).subtract(36, "day").toDate(), y: 35 }, + { x: moment(startOfToday).subtract(35, "day").toDate(), y: 30 }, + { x: moment(startOfToday).subtract(34, "day").toDate(), y: 35 }, + { x: moment(startOfToday).subtract(33, "day").toDate(), y: 15 }, + { x: moment(startOfToday).subtract(32, "day").toDate(), y: 30 }, + { x: moment(startOfToday).subtract(31, "day").toDate(), y: 35 }, + { x: moment(startOfToday).subtract(30, "day").toDate(), y: 34 }, + { x: moment(startOfToday).subtract(29, "day").toDate(), y: 35 }, + { x: moment(startOfToday).subtract(28, "day").toDate(), y: 33 }, + { x: moment(startOfToday).subtract(27, "day").toDate(), y: 40 }, + { x: moment(startOfToday).subtract(26, "day").toDate(), y: 35 }, + { x: moment(startOfToday).subtract(25, "day").toDate(), y: 30 }, + { x: moment(startOfToday).subtract(24, "day").toDate(), y: 35 }, + { x: moment(startOfToday).subtract(23, "day").toDate(), y: 15 }, + { x: moment(startOfToday).subtract(22, "day").toDate(), y: 30 }, + { x: moment(startOfToday).subtract(21, "day").toDate(), y: 35 }, + { x: moment(startOfToday).subtract(20, "day").toDate(), y: 34 }, + { x: moment(startOfToday).subtract(19, "day").toDate(), y: 64 }, + { x: moment(startOfToday).subtract(18, "day").toDate(), y: 70 }, + { x: moment(startOfToday).subtract(17, "day").toDate(), y: 55 }, + { x: moment(startOfToday).subtract(16, "day").toDate(), y: 55 }, + { x: moment(startOfToday).subtract(15, "day").toDate(), y: 45 }, + { x: moment(startOfToday).subtract(14, "day").toDate(), y: 10 }, + { x: moment(startOfToday).subtract(13, "day").toDate(), y: 65 }, + { x: moment(startOfToday).subtract(12, "day").toDate(), y: 35 }, + { x: moment(startOfToday).subtract(11, "day").toDate(), y: 60 }, + { x: moment(startOfToday).subtract(10, "day").toDate(), y: 61 }, + { x: moment(startOfToday).subtract(9, "day").toDate(), y: 65 }, + { x: moment(startOfToday).subtract(8, "day").toDate(), y: 63 }, + { x: moment(startOfToday).subtract(7, "day").toDate(), y: 58 }, + { x: moment(startOfToday).subtract(6, "day").toDate(), y: 64 }, + { x: moment(startOfToday).subtract(5, "day").toDate(), y: 63 }, + { x: moment(startOfToday).subtract(4, "day").toDate(), y: 60 }, + { x: moment(startOfToday).subtract(3, "day").toDate(), y: 62 }, + { x: moment(startOfToday).subtract(2, "day").toDate(), y: 61 }, + { x: moment(startOfToday).subtract(1, "day").toDate(), y: 62 }, + { x: moment(startOfToday).toDate(), y: 55 }, + ], + }, + { + id: "series-3", + name: "Lisbon", + description: "'secondaryLink' only", + secondaryLink: "https://en.wikipedia.org/wiki/Lisbon", + data: [ + { x: moment(startOfToday).subtract(59, "day").toDate(), y: 35 }, + { x: moment(startOfToday).subtract(58, "day").toDate(), y: 33 }, + { x: moment(startOfToday).subtract(57, "day").toDate(), y: 40 }, + { x: moment(startOfToday).subtract(56, "day").toDate(), y: 35 }, + { x: moment(startOfToday).subtract(55, "day").toDate(), y: 30 }, + { x: moment(startOfToday).subtract(54, "day").toDate(), y: 35 }, + { x: moment(startOfToday).subtract(53, "day").toDate(), y: 15 }, + { x: moment(startOfToday).subtract(52, "day").toDate(), y: 30 }, + { x: moment(startOfToday).subtract(51, "day").toDate(), y: 35 }, + { x: moment(startOfToday).subtract(50, "day").toDate(), y: 34 }, + { x: moment(startOfToday).subtract(49, "day").toDate(), y: 35 }, + { x: moment(startOfToday).subtract(48, "day").toDate(), y: 33 }, + { x: moment(startOfToday).subtract(47, "day").toDate(), y: 40 }, + { x: moment(startOfToday).subtract(46, "day").toDate(), y: 35 }, + { x: moment(startOfToday).subtract(45, "day").toDate(), y: 30 }, + { x: moment(startOfToday).subtract(44, "day").toDate(), y: 35 }, + { x: moment(startOfToday).subtract(43, "day").toDate(), y: 15 }, + { x: moment(startOfToday).subtract(42, "day").toDate(), y: 30 }, + { x: moment(startOfToday).subtract(41, "day").toDate(), y: 35 }, + { x: moment(startOfToday).subtract(40, "day").toDate(), y: 34 }, + { x: moment(startOfToday).subtract(39, "day").toDate(), y: 35 }, + { x: moment(startOfToday).subtract(38, "day").toDate(), y: 33 }, + { x: moment(startOfToday).subtract(37, "day").toDate(), y: 40 }, + { x: moment(startOfToday).subtract(36, "day").toDate(), y: 35 }, + { x: moment(startOfToday).subtract(35, "day").toDate(), y: 30 }, + { x: moment(startOfToday).subtract(34, "day").toDate(), y: 35 }, + { x: moment(startOfToday).subtract(33, "day").toDate(), y: 15 }, + { x: moment(startOfToday).subtract(32, "day").toDate(), y: 30 }, + { x: moment(startOfToday).subtract(31, "day").toDate(), y: 35 }, + { x: moment(startOfToday).subtract(30, "day").toDate(), y: 34 }, + { x: moment(startOfToday).subtract(29, "day").toDate(), y: 35 }, + { x: moment(startOfToday).subtract(28, "day").toDate(), y: 33 }, + { x: moment(startOfToday).subtract(27, "day").toDate(), y: 40 }, + { x: moment(startOfToday).subtract(26, "day").toDate(), y: 35 }, + { x: moment(startOfToday).subtract(25, "day").toDate(), y: 30 }, + { x: moment(startOfToday).subtract(24, "day").toDate(), y: 35 }, + { x: moment(startOfToday).subtract(23, "day").toDate(), y: 15 }, + { x: moment(startOfToday).subtract(22, "day").toDate(), y: 30 }, + { x: moment(startOfToday).subtract(21, "day").toDate(), y: 35 }, + { x: moment(startOfToday).subtract(20, "day").toDate(), y: 34 }, + { x: moment(startOfToday).subtract(19, "day").toDate(), y: 80 }, + { x: moment(startOfToday).subtract(18, "day").toDate(), y: 70 }, + { x: moment(startOfToday).subtract(17, "day").toDate(), y: 95 }, + { x: moment(startOfToday).subtract(16, "day").toDate(), y: 90 }, + { x: moment(startOfToday).subtract(15, "day").toDate(), y: 85 }, + { x: moment(startOfToday).subtract(14, "day").toDate(), y: 70 }, + { x: moment(startOfToday).subtract(13, "day").toDate(), y: 75 }, + { x: moment(startOfToday).subtract(12, "day").toDate(), y: 69 }, + { x: moment(startOfToday).subtract(11, "day").toDate(), y: 75 }, + { x: moment(startOfToday).subtract(10, "day").toDate(), y: 81 }, + { x: moment(startOfToday).subtract(9, "day").toDate(), y: 93 }, + { x: moment(startOfToday).subtract(8, "day").toDate(), y: 83 }, + { x: moment(startOfToday).subtract(7, "day").toDate(), y: 70 }, + { x: moment(startOfToday).subtract(6, "day").toDate(), y: 74 }, + { x: moment(startOfToday).subtract(5, "day").toDate(), y: 73 }, + { x: moment(startOfToday).subtract(4, "day").toDate(), y: 68 }, + { x: moment(startOfToday).subtract(3, "day").toDate(), y: 72 }, + { x: moment(startOfToday).subtract(2, "day").toDate(), y: 61 }, + { x: moment(startOfToday).subtract(1, "day").toDate(), y: 69 }, + { x: moment(startOfToday).toDate(), y: 60 }, + ], + }, + { + id: "series-4", + name: "Austin", + description: "No links", + data: [ + { x: moment(startOfToday).subtract(59, "day").toDate(), y: 25 }, + { x: moment(startOfToday).subtract(58, "day").toDate(), y: 43 }, + { x: moment(startOfToday).subtract(57, "day").toDate(), y: 40 }, + { x: moment(startOfToday).subtract(56, "day").toDate(), y: 65 }, + { x: moment(startOfToday).subtract(55, "day").toDate(), y: 30 }, + { x: moment(startOfToday).subtract(54, "day").toDate(), y: 25 }, + { x: moment(startOfToday).subtract(53, "day").toDate(), y: 45 }, + { x: moment(startOfToday).subtract(52, "day").toDate(), y: 30 }, + { x: moment(startOfToday).subtract(51, "day").toDate(), y: 85 }, + { x: moment(startOfToday).subtract(50, "day").toDate(), y: 74 }, + { x: moment(startOfToday).subtract(49, "day").toDate(), y: 55 }, + { x: moment(startOfToday).subtract(48, "day").toDate(), y: 23 }, + { x: moment(startOfToday).subtract(47, "day").toDate(), y: 40 }, + { x: moment(startOfToday).subtract(46, "day").toDate(), y: 15 }, + { x: moment(startOfToday).subtract(45, "day").toDate(), y: 20 }, + { x: moment(startOfToday).subtract(44, "day").toDate(), y: 65 }, + { x: moment(startOfToday).subtract(43, "day").toDate(), y: 25 }, + { x: moment(startOfToday).subtract(42, "day").toDate(), y: 40 }, + { x: moment(startOfToday).subtract(41, "day").toDate(), y: 25 }, + { x: moment(startOfToday).subtract(40, "day").toDate(), y: 54 }, + { x: moment(startOfToday).subtract(39, "day").toDate(), y: 65 }, + { x: moment(startOfToday).subtract(38, "day").toDate(), y: 33 }, + { x: moment(startOfToday).subtract(37, "day").toDate(), y: 50 }, + { x: moment(startOfToday).subtract(36, "day").toDate(), y: 45 }, + { x: moment(startOfToday).subtract(35, "day").toDate(), y: 20 }, + { x: moment(startOfToday).subtract(34, "day").toDate(), y: 25 }, + { x: moment(startOfToday).subtract(33, "day").toDate(), y: 35 }, + { x: moment(startOfToday).subtract(32, "day").toDate(), y: 20 }, + { x: moment(startOfToday).subtract(31, "day").toDate(), y: 35 }, + { x: moment(startOfToday).subtract(30, "day").toDate(), y: 14 }, + { x: moment(startOfToday).subtract(29, "day").toDate(), y: 55 }, + { x: moment(startOfToday).subtract(28, "day").toDate(), y: 23 }, + { x: moment(startOfToday).subtract(27, "day").toDate(), y: 10 }, + { x: moment(startOfToday).subtract(26, "day").toDate(), y: 5 }, + { x: moment(startOfToday).subtract(25, "day").toDate(), y: 20 }, + { x: moment(startOfToday).subtract(24, "day").toDate(), y: 35 }, + { x: moment(startOfToday).subtract(23, "day").toDate(), y: 15 }, + { x: moment(startOfToday).subtract(22, "day").toDate(), y: 30 }, + { x: moment(startOfToday).subtract(21, "day").toDate(), y: 35 }, + { x: moment(startOfToday).subtract(20, "day").toDate(), y: 34 }, + { x: moment(startOfToday).subtract(19, "day").toDate(), y: 50 }, + { x: moment(startOfToday).subtract(18, "day").toDate(), y: 60 }, + { x: moment(startOfToday).subtract(17, "day").toDate(), y: 95 }, + { x: moment(startOfToday).subtract(16, "day").toDate(), y: 80 }, + { x: moment(startOfToday).subtract(15, "day").toDate(), y: 65 }, + { x: moment(startOfToday).subtract(14, "day").toDate(), y: 80 }, + { x: moment(startOfToday).subtract(13, "day").toDate(), y: 85 }, + { x: moment(startOfToday).subtract(12, "day").toDate(), y: 69 }, + { x: moment(startOfToday).subtract(11, "day").toDate(), y: 65 }, + { x: moment(startOfToday).subtract(10, "day").toDate(), y: 71 }, + { x: moment(startOfToday).subtract(9, "day").toDate(), y: 73 }, + { x: moment(startOfToday).subtract(8, "day").toDate(), y: 43 }, + { x: moment(startOfToday).subtract(7, "day").toDate(), y: 70 }, + { x: moment(startOfToday).subtract(6, "day").toDate(), y: 84 }, + { x: moment(startOfToday).subtract(5, "day").toDate(), y: 73 }, + { x: moment(startOfToday).subtract(4, "day").toDate(), y: 38 }, + { x: moment(startOfToday).subtract(3, "day").toDate(), y: 72 }, + { x: moment(startOfToday).subtract(2, "day").toDate(), y: 81 }, + { x: moment(startOfToday).subtract(1, "day").toDate(), y: 59 }, + { x: moment(startOfToday).toDate(), y: 60 }, + ], + }, +]; +// Setting the widget dimensions and position (this is for gridster) +const positions: Record = { + [widgetConfigs[0].id]: { + cols: 6, + rows: 6, + y: 0, + x: 0, + }, + [widgetConfigs[1].id]: { + cols: 6, + rows: 6, + y: 0, + x: 6, + }, +}; +\`, + "widget-types/timeseries/timeseries-widget-status-bar-example/timeseries-widget-status-bar-example.component.ts": \`// © 2022 SolarWinds Worldwide, LLC. All rights reserved. +// +// Permission is hereby granted, free of charge, to any person obtaining a copy +// of this software and associated documentation files (the "Software"), to +// deal in the Software without restriction, including without limitation the +// rights to use, copy, modify, merge, publish, distribute, sublicense, and/or +// sell copies of the Software, and to permit persons to whom the Software is +// furnished to do so, subject to the following conditions: +// +// The above copyright notice and this permission notice shall be included in +// all copies or substantial portions of the Software. +// +// THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR +// IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, +// FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE +// AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER +// LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, +// OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN +// THE SOFTWARE. + +import { + ChangeDetectorRef, + Component, + Injectable, + OnInit, +} from "@angular/core"; +import { GridsterConfig, GridsterItem } from "angular-gridster2"; +import keyBy from "lodash/keyBy"; +import moment, { Moment } from "moment/moment"; +import { BehaviorSubject } from "rxjs"; + +import { + DataSourceService, + IDataSource, + IDataSourceOutput, + INovaFilters, + ITimeframe, +} from "@nova-ui/bits"; +import { CHART_PALETTE_CS_S_EXTENDED } from "@nova-ui/charts"; +import { + applyStatusEndpoints, + DATA_SOURCE, + DEFAULT_PIZZAGNA_ROOT, + IDashboard, + IProviderConfiguration, + ISerializableTimeframe, + ITimeseriesItemConfiguration, + ITimeseriesOutput, + ITimeseriesScaleConfig, + ITimeseriesWidgetConfig, + ITimeseriesWidgetData, + ITimeseriesWidgetSeriesData, + ITimeseriesWidgetStatusData, + IWidget, + LegendPlacement, + PizzagnaLayer, + ProviderRegistryService, + TimeseriesChartPreset, + TimeseriesScaleType, + WellKnownPathKey, + WellKnownProviders, + WidgetTypesService, +} from "@nova-ui/dashboards"; + +/** + * A simple Timeseries data source implementation with continuous (non-interval-based) output + */ +@Injectable() +export class TimeseriesStatusContinuousDataSource + extends DataSourceService + implements IDataSource> +{ + public static providerId = "TimeseriesStatusContinuousDataSource"; + + public busy = new BehaviorSubject(false); + + public async getFilteredData( + filters: INovaFilters + ): Promise< + IDataSourceOutput> + > { + // In this example we're using some static mock data located at the bottom of this file. In a real-world + // scenario, the data for the chart would likely be retrieved via an asynchronous backend call. + const data = getContinuousData(); + let filteredData = data; + + this.busy.next(true); + + // Filtering using the filter registered by the TimeFrameBar + const timeframeFilter = filters.timeframe?.value as ITimeframe; + if (timeframeFilter) { + filteredData = filteredData.map((item: ITimeseriesWidgetData) => ({ + id: item.id, + name: item.name, + description: item.description, + data: item.data.filter( + (seriesData: ITimeseriesWidgetSeriesData) => + filterDates( + seriesData.x, + timeframeFilter.startDatetime, + timeframeFilter.endDatetime + ) + ), + })); + + // apply endpoints on the filtered status data so that when the status chart is zoomed (filtered), + // each status visualizations is ensured to have valid start and end values + filteredData = applyStatusEndpoints( + timeframeFilter, + filteredData, + data + ); + } + + this.busy.next(false); + return { result: { series: filteredData } }; + } +} + +/** + * A simple Timeseries data source implementation with interval-based output + */ +@Injectable() +export class TimeseriesStatusIntervalDataSource + extends DataSourceService + implements IDataSource> +{ + public static providerId = "TimeseriesStatusIntervalDataSource"; + + public busy = new BehaviorSubject(false); + + public async getFilteredData( + filters: INovaFilters + ): Promise< + IDataSourceOutput> + > { + // In this example we're using some static mock data located at the bottom of this file. In a real-world + // scenario, the data for the chart would likely be retrieved via an asynchronous backend call. + const data = getIntervalData(); + let filteredData = data; + + this.busy.next(true); + + // Filtering using the filter registered by the TimeFrameBar + const timeframeFilter = filters.timeframe?.value as ITimeframe; + if (timeframeFilter) { + filteredData = filteredData.map((item: ITimeseriesWidgetData) => ({ + id: item.id, + name: item.name, + description: item.description, + data: item.data.filter( + (seriesData: ITimeseriesWidgetSeriesData) => + filterDates( + seriesData.x, + timeframeFilter.startDatetime, + timeframeFilter.endDatetime + ) + ), + })); + + // Note: There's no need to apply filter endpoints to the status data in this case since we know it's visualized in regular intervals + } + + this.busy.next(false); + return { result: { series: filteredData } }; + } +} + +function filterDates(dateToCheck: Date, startDate: Moment, endDate: Moment) { + const mom = moment(dateToCheck); + return ( + mom.isBetween(startDate, endDate) || + mom.isSame(startDate) || + mom.isSame(endDate) + ); +} + +/** + * A component that instantiates the dashboard + */ +@Component({ + selector: "timeseries-widget-status-bar-example", + templateUrl: "./timeseries-widget-status-bar-example.component.html", + styleUrls: ["./timeseries-widget-status-bar-example.component.less"], + standalone: false, +}) +export class TimeseriesWidgetStatusBarExampleComponent implements OnInit { + // This variable will hold all the data needed to define the layout and behavior of the widgets. + // Pass this to the dashboard component's dashboard input in the template. + public dashboard: IDashboard | undefined; + + // Angular gridster requires a configuration object even if it's empty. + // Pass this to the dashboard component's gridsterConfig input in the template. + public gridsterConfig: GridsterConfig = {}; + + // Boolean passed as an input to the dashboard. When true, widgets can be moved, resized, removed, or edited + public editMode: boolean = false; + + constructor( + // WidgetTypesService provides the widget's necessary structure information + private widgetTypesService: WidgetTypesService, + + // In general, the ProviderRegistryService is used for making entities available for injection into dynamically loaded components. + private providerRegistry: ProviderRegistryService, + private changeDetectorRef: ChangeDetectorRef + ) {} + + public ngOnInit(): void { + // Grabbing the widget's default template which will be needed as a parameter for setNode + const widgetTemplate = this.widgetTypesService.getWidgetType( + "timeseries", + 1 + ); + // Registering our data sources as dropdown options in the widget editor/configurator + // Note: This could also be done in the parent module's constructor so that + // multiple dashboards could have access to the same widget template modification. + this.widgetTypesService.setNode( + // This is the template we grabbed above with getWidgetType + widgetTemplate, + // We are setting the editor/configurator part of the widget template + "configurator", + // This indicates which node you are changing and we want to change + // the data source providers available for selection in the editor. + WellKnownPathKey.DataSourceProviders, + // We are setting the data sources available for selection in the editor + [ + TimeseriesStatusContinuousDataSource.providerId, + TimeseriesStatusIntervalDataSource.providerId, + ] + ); + + // Registering the data source for injection into the widget. + this.providerRegistry.setProviders({ + [TimeseriesStatusContinuousDataSource.providerId]: { + provide: DATA_SOURCE, + useClass: TimeseriesStatusContinuousDataSource, + deps: [], + }, + [TimeseriesStatusIntervalDataSource.providerId]: { + provide: DATA_SOURCE, + useClass: TimeseriesStatusIntervalDataSource, + deps: [], + }, + }); + + this.initializeDashboard(); + } + + /** Used for restoring widgets state */ + public reInitializeDashboard(): void { + // destroys the components and their providers so the dashboard can re init data + this.dashboard = undefined; + this.changeDetectorRef.detectChanges(); + + this.initializeDashboard(); + } + + public initializeDashboard(): void { + // We're using a static configuration object for this example, but this is where + // the widget's configuration could potentially be populated from a database + const widgetsWithStructure = widgetConfigs.map((w) => + this.widgetTypesService.mergeWithWidgetType(w) + ); + const widgetsIndex = keyBy(widgetsWithStructure, (w: IWidget) => w.id); + + // Finally, assigning the variables we created above to the dashboard + this.dashboard = { + positions, + widgets: widgetsIndex, + }; + } +} + +const widgetConfigs: IWidget[] = [ + { + id: "statusChartWidgetId", + type: "timeseries", + pizzagna: { + [PizzagnaLayer.Configuration]: { + [DEFAULT_PIZZAGNA_ROOT]: { + providers: { + [WellKnownProviders.DataSource]: { + // Setting the initially selected data source providerId + providerId: + TimeseriesStatusContinuousDataSource.providerId, + } as IProviderConfiguration, + }, + }, + header: { + properties: { + title: "Status Bar Chart with Continuous (Non-Interval) Scale", + subtitle: "Basic Timeseries Widget", + }, + }, + chart: { + providers: { + [WellKnownProviders.Adapter]: { + properties: { + // Setting the series and corresponding labels to initially display on the chart + series: [ + { + id: "series-1", + label: "Node Status", + selectedSeriesId: "series-1", + }, + { + id: "series-2", + label: "Node Status", + selectedSeriesId: "series-2", + }, + ] as ITimeseriesItemConfiguration[], + }, + } as Partial, + }, + properties: { + configuration: { + legendPlacement: LegendPlacement.Right, + enableZoom: true, + // Setting the preset to status bar + preset: TimeseriesChartPreset.StatusBar, + } as ITimeseriesWidgetConfig, + }, + }, + timeframeSelection: { + properties: { + // Setting the initial timeframe selected in the timeframe bar + timeframe: { + selectedPresetId: "last7Days", + } as ISerializableTimeframe, + maxDate: moment().format(), + }, + }, + }, + }, + }, + { + id: "statusIntervalChartWidgetId", + type: "timeseries", + pizzagna: { + [PizzagnaLayer.Configuration]: { + [DEFAULT_PIZZAGNA_ROOT]: { + providers: { + [WellKnownProviders.DataSource]: { + // Setting the initially selected data source providerId + providerId: + TimeseriesStatusIntervalDataSource.providerId, + } as IProviderConfiguration, + }, + }, + header: { + properties: { + title: "Status Bar Chart with Interval Scale", + subtitle: "Basic Timeseries Widget", + }, + }, + chart: { + providers: { + [WellKnownProviders.Adapter]: { + properties: { + // Setting the series and corresponding labels to initially display on the chart + series: [ + { + id: "series-1", + label: "Node Status", + selectedSeriesId: "series-1", + }, + { + id: "series-2", + label: "Node Status", + selectedSeriesId: "series-2", + }, + ] as ITimeseriesItemConfiguration[], + }, + } as Partial, + }, + properties: { + configuration: { + legendPlacement: LegendPlacement.Right, + enableZoom: true, + // Setting the preset to status bar + preset: TimeseriesChartPreset.StatusBar, + scales: { + x: { + type: TimeseriesScaleType.TimeInterval, + properties: { + // one-day interval in seconds + interval: 24 * 60 * 60, + }, + } as ITimeseriesScaleConfig, + }, + } as ITimeseriesWidgetConfig, + }, + }, + timeframeSelection: { + properties: { + // Setting the initial timeframe selected in the timeframe bar + timeframe: { + selectedPresetId: "last7Days", + } as ISerializableTimeframe, + maxDate: moment().format(), + }, + }, + }, + }, + }, +]; + +export const startOfToday = (): Moment => moment().startOf("day"); + +export const getContinuousData = + (): ITimeseriesWidgetData[] => { + const series: ITimeseriesWidgetData[] = [ + { + id: "series-1", + name: "Node Status", + description: "lastchance.demo.lab", + data: [ + // the 'x' value is set to the time and 'y' to the status at that given time + { + x: startOfToday().subtract(20, "day").toDate(), + y: Status.Warning, + }, + { + x: startOfToday().subtract(19, "day").toDate(), + y: Status.Down, + }, + { + x: startOfToday().subtract(17, "day").toDate(), + y: Status.Critical, + }, + { + x: startOfToday().subtract(16, "day").toDate(), + y: Status.Warning, + }, + { + x: startOfToday().subtract(15, "day").toDate(), + y: Status.Down, + }, + { + x: startOfToday().subtract(14, "day").toDate(), + y: Status.Critical, + }, + { + x: startOfToday().subtract(12, "day").toDate(), + y: Status.Unknown, + }, + { + x: startOfToday().subtract(10, "day").toDate(), + y: Status.Up, + }, + { + x: startOfToday().subtract(9, "day").toDate(), + y: Status.Critical, + }, + { + x: startOfToday().subtract(6, "day").toDate(), + y: Status.Up, + }, + { + x: startOfToday().subtract(3, "day").toDate(), + y: Status.Warning, + }, + { + x: startOfToday().subtract(2, "day").toDate(), + y: Status.Critical, + }, + { + x: startOfToday().subtract(1, "day").toDate(), + y: Status.Up, + }, + // This data point will be ignored and is only here to provide an endpoint for the previous status. + { x: moment().toDate(), y: Status.Up }, + ], + }, + { + id: "series-2", + name: "Node Status", + description: "newhope.demo.lab", + data: [ + { + x: startOfToday().subtract(19, "day").toDate(), + y: Status.Critical, + }, + { + x: startOfToday().subtract(18, "day").toDate(), + y: Status.Unknown, + }, + { + x: startOfToday().subtract(17, "day").toDate(), + y: Status.Warning, + }, + { + x: startOfToday().subtract(15, "day").toDate(), + y: Status.Down, + }, + { + x: startOfToday().subtract(8, "day").toDate(), + y: Status.Critical, + }, + { + x: startOfToday().subtract(7, "day").toDate(), + y: Status.Down, + }, + { + x: startOfToday().subtract(6, "day").toDate(), + y: Status.Up, + }, + { + x: startOfToday().subtract(5, "day").toDate(), + y: Status.Critical, + }, + { + x: startOfToday().subtract(4, "day").toDate(), + y: Status.Up, + }, + { + x: startOfToday().subtract(3, "day").toDate(), + y: Status.Warning, + }, + { + x: startOfToday().subtract(2, "day").toDate(), + y: Status.Up, + }, + { + x: startOfToday().subtract(1, "day").toDate(), + y: Status.Down, + }, + // This data point will be ignored and is only here to provide an endpoint for the previous status. + { x: moment().toDate(), y: Status.Down }, + ], + }, + ]; + + for (const s of series) { + // here are we setting the color and icon associated to the status for each data point + s.data = s.data.map((d: any, i: number) => ({ + ...d, + color: statusColors[d.y as Status], + // The thickness of the line is dependant on the status. If the status equals 'Up' then 'thick' is set to false. + thick: d.y !== Status.Up, + icon: "status_" + d.y, + })); + } + + return series; + }; + +// Note that the output of this function is spaced evenly at one-day intervals +export const getIntervalData = + (): ITimeseriesWidgetData[] => { + const series: ITimeseriesWidgetData[] = [ + { + id: "series-1", + name: "Node Status", + description: "lastchance.demo.lab", + data: [ + // the 'x' value is set to the time and 'y' to the status at that given time + { + x: startOfToday().subtract(20, "day").toDate(), + y: Status.Up, + }, + { + x: startOfToday().subtract(19, "day").toDate(), + y: Status.Critical, + }, + { + x: startOfToday().subtract(18, "day").toDate(), + y: Status.Warning, + }, + { + x: startOfToday().subtract(17, "day").toDate(), + y: Status.Down, + }, + { + x: startOfToday().subtract(16, "day").toDate(), + y: Status.Critical, + }, + { + x: startOfToday().subtract(15, "day").toDate(), + y: Status.Down, + }, + { + x: startOfToday().subtract(14, "day").toDate(), + y: Status.Up, + }, + { + x: startOfToday().subtract(13, "day").toDate(), + y: Status.Warning, + }, + { + x: startOfToday().subtract(12, "day").toDate(), + y: Status.Up, + }, + { + x: startOfToday().subtract(11, "day").toDate(), + y: Status.Critical, + }, + { + x: startOfToday().subtract(10, "day").toDate(), + y: Status.Up, + }, + { + x: startOfToday().subtract(9, "day").toDate(), + y: Status.Down, + }, + { + x: startOfToday().subtract(8, "day").toDate(), + y: Status.Critical, + }, + { + x: startOfToday().subtract(7, "day").toDate(), + y: Status.Warning, + }, + { + x: startOfToday().subtract(6, "day").toDate(), + y: Status.Down, + }, + { + x: startOfToday().subtract(5, "day").toDate(), + y: Status.Critical, + }, + { + x: startOfToday().subtract(4, "day").toDate(), + y: Status.Down, + }, + { + x: startOfToday().subtract(3, "day").toDate(), + y: Status.Up, + }, + { + x: startOfToday().subtract(2, "day").toDate(), + y: Status.Warning, + }, + { + x: startOfToday().subtract(1, "day").toDate(), + y: Status.Critical, + }, + { x: startOfToday().toDate(), y: Status.Up }, + ], + }, + { + id: "series-2", + name: "Node Status", + description: "newhope.demo.lab", + data: [ + { + x: startOfToday().subtract(20, "day").toDate(), + y: Status.Up, + }, + { + x: startOfToday().subtract(19, "day").toDate(), + y: Status.Up, + }, + { + x: startOfToday().subtract(18, "day").toDate(), + y: Status.Down, + }, + { + x: startOfToday().subtract(17, "day").toDate(), + y: Status.Critical, + }, + { + x: startOfToday().subtract(16, "day").toDate(), + y: Status.Down, + }, + { + x: startOfToday().subtract(15, "day").toDate(), + y: Status.Up, + }, + { + x: startOfToday().subtract(14, "day").toDate(), + y: Status.Critical, + }, + { + x: startOfToday().subtract(13, "day").toDate(), + y: Status.Up, + }, + { + x: startOfToday().subtract(12, "day").toDate(), + y: Status.Critical, + }, + { + x: startOfToday().subtract(11, "day").toDate(), + y: Status.Warning, + }, + { + x: startOfToday().subtract(10, "day").toDate(), + y: Status.Up, + }, + { + x: startOfToday().subtract(9, "day").toDate(), + y: Status.Down, + }, + { + x: startOfToday().subtract(8, "day").toDate(), + y: Status.Up, + }, + { + x: startOfToday().subtract(7, "day").toDate(), + y: Status.Down, + }, + { + x: startOfToday().subtract(6, "day").toDate(), + y: Status.Critical, + }, + { + x: startOfToday().subtract(5, "day").toDate(), + y: Status.Down, + }, + { + x: startOfToday().subtract(4, "day").toDate(), + y: Status.Up, + }, + { + x: startOfToday().subtract(3, "day").toDate(), + y: Status.Critical, + }, + { + x: startOfToday().subtract(2, "day").toDate(), + y: Status.Up, + }, + { + x: startOfToday().subtract(1, "day").toDate(), + y: Status.Warning, + }, + { x: startOfToday().toDate(), y: Status.Critical }, + ], + }, + ]; + + for (const s of series) { + // here are we setting the color and icon associated to the status for each data point + s.data = s.data.map((d: any, i: number) => ({ + ...d, + color: statusColors[d.y as Status], + // The thickness of the line is dependant on the status. If the status equals 'Up' then 'thick' is set to false. + thick: d.y !== Status.Up, + icon: "status_" + d.y, + })); + } + + return series; + }; + +// An enumeration of statuses +enum Status { + Unknown = "unknown", + Up = "up", + Warning = "warning", + Down = "down", + Critical = "critical", +} + +// This is the map used for setting the color of each status bar +const statusColors: Record = { + [Status.Unknown]: CHART_PALETTE_CS_S_EXTENDED[6], + [Status.Up]: CHART_PALETTE_CS_S_EXTENDED[8], + [Status.Warning]: CHART_PALETTE_CS_S_EXTENDED[4], + [Status.Down]: CHART_PALETTE_CS_S_EXTENDED[0], + [Status.Critical]: CHART_PALETTE_CS_S_EXTENDED[2], +}; + +// Setting the widget dimensions and position (this is for gridster) +const positions: Record = { + [widgetConfigs[0].id]: { + cols: 12, + rows: 4, + y: 0, + x: 0, + }, + [widgetConfigs[1].id]: { + cols: 12, + rows: 4, + y: 4, + x: 0, + }, +}; +\`, + "widget-types/view-components/kpi-tile-view-basic/kpi-tile-view-basic-example.component.ts": \`// © 2022 SolarWinds Worldwide, LLC. All rights reserved. +// +// Permission is hereby granted, free of charge, to any person obtaining a copy +// of this software and associated documentation files (the "Software"), to +// deal in the Software without restriction, including without limitation the +// rights to use, copy, modify, merge, publish, distribute, sublicense, and/or +// sell copies of the Software, and to permit persons to whom the Software is +// furnished to do so, subject to the following conditions: +// +// The above copyright notice and this permission notice shall be included in +// all copies or substantial portions of the Software. +// +// THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR +// IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, +// FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE +// AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER +// LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, +// OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN +// THE SOFTWARE. + +import { Component } from "@angular/core"; +import { FormControl } from "@angular/forms"; + +type KpiTileState = "normal" | "loading" | "empty"; + +/** + * KPI Tile View - Playground example. + * Switch between all visual states (normal / loading / empty) and toggle + * interactivity to explore every variant the standalone tile supports. + */ +@Component({ + selector: "kpi-tile-view-basic-example", + templateUrl: "./kpi-tile-view-basic-example.component.html", + standalone: false, +}) +export class KpiTileViewBasicExampleComponent { + public readonly stateControl = new FormControl("normal", { + nonNullable: true, + }); + public interactive = false; + public lastClicked = ""; + + public readonly stateOptions: KpiTileState[] = ["normal", "loading", "empty"]; + + public onTileClick(label: string): void { + this.lastClicked = label; + } +} +\`, + "widget-types/view-components/kpi-tile-view-interactive/kpi-tile-view-interactive-example.component.ts": \`// © 2022 SolarWinds Worldwide, LLC. All rights reserved. +// +// Permission is hereby granted, free of charge, to any person obtaining a copy +// of this software and associated documentation files (the "Software"), to +// deal in the Software without restriction, including without limitation the +// rights to use, copy, modify, merge, publish, distribute, sublicense, and/or +// sell copies of the Software, and to permit persons to whom the Software is +// furnished to do so, subject to the following conditions: +// +// The above copyright notice and this permission notice shall be included in +// all copies or substantial portions of the Software. +// +// THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR +// IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, +// FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE +// AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER +// LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, +// OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN +// THE SOFTWARE. + +import { Component, TemplateRef, ViewChild } from "@angular/core"; + +/** + * Interactive KPI Tile View example with custom value formatting + * and click event handling. + */ +@Component({ + selector: "kpi-tile-view-interactive-example", + templateUrl: "./kpi-tile-view-interactive-example.component.html", + standalone: false, +}) +export class KpiTileViewInteractiveExampleComponent { + public currentValue = 1_247; + public lastClickedTile = ""; + + @ViewChild("customValueTpl", { static: true }) + public customValueTpl: TemplateRef; + + public onTileClick(): void { + this.lastClickedTile = "Active Sessions"; + } + + public onUptimeClick(): void { + this.lastClickedTile = "Uptime"; + } +} +\`, + "widget-types/view-components/proportional-chart-view-playground/proportional-chart-view-playground-example.component.ts": \`// © 2022 SolarWinds Worldwide, LLC. All rights reserved. +// +// Permission is hereby granted, free of charge, to any person obtaining a copy +// of this software and associated documentation files (the "Software"), to +// deal in the Software without restriction, including without limitation the +// rights to use, copy, modify, merge, publish, distribute, sublicense, and/or +// sell copies of the Software, and to permit persons to whom the Software is +// furnished to do so, subject to the following conditions: +// +// The above copyright notice and this permission notice shall be included in +// all copies or substantial portions of the Software. +// +// THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR +// IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, +// FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE +// AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER +// LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, +// OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN +// THE SOFTWARE. + +import { Component } from "@angular/core"; +import { FormControl } from "@angular/forms"; + +import { IProportionalDataItem } from "@nova-ui/dashboards"; + +type ProportionalChartType = "donut" | "pie" | "verticalBar" | "horizontalBar"; +type LegendPlacement = "right" | "bottom" | "none"; + +/** + * Proportional Chart View - Playground example. + * Lets you switch between all supported chart types and legend placements + * to see every visual variant the standalone view component provides. + */ +@Component({ + selector: "proportional-chart-view-playground-example", + templateUrl: "./proportional-chart-view-playground-example.component.html", + standalone: false, +}) +export class ProportionalChartViewPlaygroundExampleComponent { + public readonly chartTypeControl = new FormControl( + "donut", + { nonNullable: true } + ); + public readonly legendPlacementControl = new FormControl( + "right", + { nonNullable: true } + ); + + public readonly chartTypeOptions: ProportionalChartType[] = [ + "donut", + "pie", + "verticalBar", + "horizontalBar", + ]; + public readonly legendPlacementOptions: LegendPlacement[] = [ + "right", + "bottom", + "none", + ]; + + public colors: Record = { + down: "#dc3545", + up: "#2cc079", + warning: "#f3a002", + unknown: "#707070", + }; + + public chartData: Array = [ + { id: "up", name: "Up", value: 78 }, + { id: "down", name: "Down", value: 8 }, + { id: "warning", name: "Warning", value: 12 }, + { id: "unknown", name: "Unknown", value: 2 }, + ]; + + public totalOf(data: IProportionalDataItem[] | undefined): number { + return (data ?? []).reduce((sum, d) => sum + (d?.value ?? 0), 0); + } +} +\`, + "widget-types/view-components/view-components-docs.component.ts": \`// © 2022 SolarWinds Worldwide, LLC. All rights reserved. +// +// Permission is hereby granted, free of charge, to any person obtaining a copy +// of this software and associated documentation files (the "Software"), to +// deal in the Software without restriction, including without limitation the +// rights to use, copy, modify, merge, publish, distribute, sublicense, and/or +// sell copies of the Software, and to permit persons to whom the Software is +// furnished to do so, subject to the following conditions: +// +// The above copyright notice and this permission notice shall be included in +// all copies or substantial portions of the Software. +// +// THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR +// IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, +// FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE +// AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER +// LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, +// OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN +// THE SOFTWARE. + +import { Component } from "@angular/core"; + +@Component({ + selector: "nui-view-components-docs", + templateUrl: "./view-components-docs.component.html", + standalone: false, +}) +export class ViewComponentsDocsComponent { + public readonly installationSnippet = \\\`import { NuiDashboardViewsModule } from "@nova-ui/dashboards"; + +@NgModule({ + imports: [NuiDashboardViewsModule], +}) +export class MyFeatureModule {}\\\`; + + public readonly proportionalDataItemSnippet = \\\`interface IProportionalDataItem { + id: string; // Unique segment identifier + name: string; // Display name in legend + value: number; // Numeric value determining segment size + color?: string; // Optional CSS color (hex or token) + icon?: string; // Optional icon name for legend + link?: string; // Optional drill-down URL +}\\\`; +} +\`, + "widget-types/view-components/view-components-docs.module.ts": \`// © 2022 SolarWinds Worldwide, LLC. All rights reserved. +// +// Permission is hereby granted, free of charge, to any person obtaining a copy +// of this software and associated documentation files (the "Software"), to +// deal in the Software without restriction, including without limitation the +// rights to use, copy, modify, merge, publish, distribute, sublicense, and/or +// sell copies of the Software, and to permit persons to whom the Software is +// furnished to do so, subject to the following conditions: +// +// The above copyright notice and this permission notice shall be included in +// all copies or substantial portions of the Software. +// +// THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR +// IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, +// FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE +// AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER +// LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, +// OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN +// THE SOFTWARE. + +import { CommonModule } from "@angular/common"; +import { NgModule } from "@angular/core"; +import { ReactiveFormsModule } from "@angular/forms"; +import { RouterModule, Routes } from "@angular/router"; + +import { + NuiDocsModule, + NuiIconModule, + NuiMessageModule, + NuiFormFieldModule, + NuiSelectV2Module, + NuiSwitchModule, + DEMO_PATH_TOKEN, +} from "@nova-ui/bits"; +import { NuiDashboardViewsModule } from "@nova-ui/dashboards"; + +import { getDemoFiles } from "../../../../demo-files-factory"; +import { KpiTileViewBasicExampleComponent } from "./kpi-tile-view-basic/kpi-tile-view-basic-example.component"; +import { KpiTileViewInteractiveExampleComponent } from "./kpi-tile-view-interactive/kpi-tile-view-interactive-example.component"; +import { ProportionalChartViewPlaygroundExampleComponent } from "./proportional-chart-view-playground/proportional-chart-view-playground-example.component"; +import { ViewComponentsDocsComponent } from "./view-components-docs.component"; + +const routes: Routes = [ + { + path: "", + component: ViewComponentsDocsComponent, + data: { + srlc: { + hideIndicator: true, + }, + showThemeSwitcher: true, + }, + }, + { + path: "kpi-tile-view-basic", + component: KpiTileViewBasicExampleComponent, + data: { + srlc: { + hideIndicator: true, + }, + }, + }, + { + path: "proportional-chart-view-playground", + component: ProportionalChartViewPlaygroundExampleComponent, + data: { + srlc: { + hideIndicator: true, + }, + }, + }, +]; + +@NgModule({ + imports: [ + CommonModule, + ReactiveFormsModule, + RouterModule.forChild(routes), + NuiDocsModule, + NuiMessageModule, + NuiIconModule, + NuiFormFieldModule, + NuiSelectV2Module, + NuiSwitchModule, + NuiDashboardViewsModule, + ], + declarations: [ + ViewComponentsDocsComponent, + KpiTileViewBasicExampleComponent, + KpiTileViewInteractiveExampleComponent, + ProportionalChartViewPlaygroundExampleComponent, + ], + providers: [ + { + provide: DEMO_PATH_TOKEN, + useValue: getDemoFiles("view-components"), + }, + ], +}) +export default class ViewComponentsDocsModule {} +\`, + "widget-types/widget-types.module.ts": \`// © 2022 SolarWinds Worldwide, LLC. All rights reserved. +// +// Permission is hereby granted, free of charge, to any person obtaining a copy +// of this software and associated documentation files (the "Software"), to +// deal in the Software without restriction, including without limitation the +// rights to use, copy, modify, merge, publish, distribute, sublicense, and/or +// sell copies of the Software, and to permit persons to whom the Software is +// furnished to do so, subject to the following conditions: +// +// The above copyright notice and this permission notice shall be included in +// all copies or substantial portions of the Software. +// +// THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR +// IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, +// FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE +// AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER +// LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, +// OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN +// THE SOFTWARE. + +import { NgModule, Type } from "@angular/core"; +import { RouterModule, Routes } from "@angular/router"; + +import { NuiDocsModule } from "@nova-ui/bits"; +import { + ConfiguratorHeadingService, + NuiDashboardsModule, +} from "@nova-ui/dashboards"; + +export enum WidgetTypesRoute { + kpi = "kpi", + riskScore = "risk-score", + timeseries = "timeseries", + table = "table", + proportional = "proportional", + embedded = "embedded", + drilldown = "drilldown", + viewComponents = "view-components", +} + +const routes: Routes = [ + { + path: WidgetTypesRoute.kpi, + loadChildren: async () => + import("./kpi/kpi-docs.module") as object as Promise>, + data: { + srlc: { + hideIndicator: true, + }, + }, + }, + { + path: WidgetTypesRoute.riskScore, + loadChildren: async () => + import("./risk-score/risk-score-docs.module") as object as Promise< + Type + >, + data: { + srlc: { + hideIndicator: true, + }, + }, + }, + { + path: WidgetTypesRoute.timeseries, + loadChildren: async () => + import("./timeseries/timeseries-docs.module") as object as Promise< + Type + >, + data: { + srlc: { + hideIndicator: true, + }, + }, + }, + { + path: WidgetTypesRoute.table, + loadChildren: async () => + import("./table/table-docs.module") as object as Promise>, + data: { + srlc: { + hideIndicator: true, + }, + }, + }, + { + path: WidgetTypesRoute.proportional, + loadChildren: async () => + import( + "./proportional/proportional-docs.module" + ) as object as Promise>, + data: { + srlc: { + hideIndicator: true, + }, + }, + }, + { + path: WidgetTypesRoute.embedded, + loadChildren: async () => + import( + "./embedded-content/embedded-content-docs.module" + ) as object as Promise>, + data: { + srlc: { + hideIndicator: true, + }, + }, + }, + { + path: WidgetTypesRoute.drilldown, + loadChildren: async () => + import( + "./drilldown/drilldown-widget-docs.module" + ) as object as Promise>, + data: { + srlc: { + hideIndicator: true, + }, + }, + }, + { + path: WidgetTypesRoute.viewComponents, + loadChildren: async () => + import( + "./view-components/view-components-docs.module" + ) as object as Promise>, + data: { + srlc: { + hideIndicator: true, + }, + }, + }, +]; + +@NgModule({ + imports: [ + RouterModule.forChild(routes), + NuiDocsModule, + NuiDashboardsModule, + ], + providers: [ConfiguratorHeadingService], +}) +export default class WidgetTypesModule {} +\`, +}; +`, + "overview/hero/dashboard/hero-dashboard.component.ts": `// © 2022 SolarWinds Worldwide, LLC. All rights reserved. +// +// Permission is hereby granted, free of charge, to any person obtaining a copy +// of this software and associated documentation files (the "Software"), to +// deal in the Software without restriction, including without limitation the +// rights to use, copy, modify, merge, publish, distribute, sublicense, and/or +// sell copies of the Software, and to permit persons to whom the Software is +// furnished to do so, subject to the following conditions: +// +// The above copyright notice and this permission notice shall be included in +// all copies or substantial portions of the Software. +// +// THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR +// IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, +// FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE +// AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER +// LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, +// OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN +// THE SOFTWARE. + +import { HttpClient } from "@angular/common/http"; +import { + ChangeDetectionStrategy, + Component, + OnInit, + ViewEncapsulation, +} from "@angular/core"; +import keyBy from "lodash/keyBy"; + +import { LoggerService, ThemeSwitchService } from "@nova-ui/bits"; +import { + DATA_SOURCE, + IDashboard, + IWidget, + ProviderRegistryService, + WidgetTypesService, +} from "@nova-ui/dashboards"; + +import { positions, widgets } from "./widget-configs"; +import { + HarryPotterAverageRatingDataSource, + HarryPotterRatingsCountDataSource, +} from "../data/kpi-datasources"; +import { + BeerReviewCountsByCityMockDataSource, + BeerReviewCountsByCityMockDataSource2, +} from "../data/proportional-datasources"; +import { BeerDataSource } from "../data/table/beer-data-source"; +import { RandomUserDataSource } from "../data/table/random-user-data-source"; +import { + BeerVsReadingMockDataSource, + LoungingVsFrisbeeGolfMockDataSource, +} from "../data/timeseries-data-sources"; + +/** + * A component that instantiates the dashboard + */ +@Component({ + selector: "hero-dashboard", + templateUrl: "./hero-dashboard.component.html", + styleUrls: ["./hero-dashboard.component.less"], + encapsulation: ViewEncapsulation.Emulated, + changeDetection: ChangeDetectionStrategy.Default, + standalone: false, +}) +export class HeroDashboardComponent implements OnInit { + public dashboard: IDashboard = { + positions: {}, + widgets: {}, + }; + + public gridsterConfig = {}; + public editMode = false; + + constructor( + private providerRegistry: ProviderRegistryService, + public themeSwitcherService: ThemeSwitchService, + private widgetTypesService: WidgetTypesService + ) { + this.providerRegistry.setProviders({ + [HarryPotterAverageRatingDataSource.providerId]: { + provide: DATA_SOURCE, + useClass: HarryPotterAverageRatingDataSource, + deps: [HttpClient], + }, + [HarryPotterRatingsCountDataSource.providerId]: { + provide: DATA_SOURCE, + useClass: HarryPotterRatingsCountDataSource, + deps: [HttpClient], + }, + [BeerReviewCountsByCityMockDataSource.providerId]: { + provide: DATA_SOURCE, + useClass: BeerReviewCountsByCityMockDataSource, + deps: [], + }, + [BeerReviewCountsByCityMockDataSource2.providerId]: { + provide: DATA_SOURCE, + useClass: BeerReviewCountsByCityMockDataSource2, + deps: [], + }, + [RandomUserDataSource.providerId]: { + provide: DATA_SOURCE, + useClass: RandomUserDataSource, + deps: [LoggerService], + }, + [BeerDataSource.providerId]: { + provide: DATA_SOURCE, + useClass: BeerDataSource, + deps: [LoggerService], + }, + [BeerVsReadingMockDataSource.providerId]: { + provide: DATA_SOURCE, + useClass: BeerVsReadingMockDataSource, + deps: [], + }, + [LoungingVsFrisbeeGolfMockDataSource.providerId]: { + provide: DATA_SOURCE, + useClass: LoungingVsFrisbeeGolfMockDataSource, + deps: [], + }, + }); + } + + public ngOnInit(): void { + const widgetsWithStructure = widgets.map((w) => ({ + ...w, + pizzagna: { + ...this.widgetTypesService.getWidgetType(w.type, w.version) + .widget, + ...w.pizzagna, + }, + })); + const widgetsIndex = keyBy(widgetsWithStructure, (w: IWidget) => w.id); + + this.dashboard = { + positions: positions, + widgets: widgetsIndex, + }; + } +} +`, + "overview/hero/dashboard/widget-configs.ts": `// © 2022 SolarWinds Worldwide, LLC. All rights reserved. +// +// Permission is hereby granted, free of charge, to any person obtaining a copy +// of this software and associated documentation files (the "Software"), to +// deal in the Software without restriction, including without limitation the +// rights to use, copy, modify, merge, publish, distribute, sublicense, and/or +// sell copies of the Software, and to permit persons to whom the Software is +// furnished to do so, subject to the following conditions: +// +// The above copyright notice and this permission notice shall be included in +// all copies or substantial portions of the Software. +// +// THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR +// IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, +// FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE +// AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER +// LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, +// OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN +// THE SOFTWARE. + +import { GridsterItem } from "angular-gridster2"; + +import { IWidget } from "@nova-ui/dashboards"; + +import { kpiConfig } from "../widget-configs/kpi"; +import { proportionalConfig } from "../widget-configs/proportional"; +import { tableConfig } from "../widget-configs/table"; +import { timeseriesConfig } from "../widget-configs/timeseries"; + +export const positions: Record = { + [tableConfig.id]: { + cols: 7, + rows: 7, + y: 0, + x: 0, + }, + [proportionalConfig.id]: { + cols: 5, + rows: 7, + y: 0, + x: 7, + }, + [kpiConfig.id]: { + cols: 6, + rows: 7, + y: 7, + x: 0, + }, + [timeseriesConfig.id]: { + cols: 6, + rows: 7, + y: 7, + x: 6, + }, +}; + +export const widgets: IWidget[] = [ + { + ...tableConfig, + }, + { + ...proportionalConfig, + }, + { + ...kpiConfig, + }, + { + ...timeseriesConfig, + }, +]; +`, + "overview/hero/data/kpi-datasources.ts": `// © 2022 SolarWinds Worldwide, LLC. All rights reserved. +// +// Permission is hereby granted, free of charge, to any person obtaining a copy +// of this software and associated documentation files (the "Software"), to +// deal in the Software without restriction, including without limitation the +// rights to use, copy, modify, merge, publish, distribute, sublicense, and/or +// sell copies of the Software, and to permit persons to whom the Software is +// furnished to do so, subject to the following conditions: +// +// The above copyright notice and this permission notice shall be included in +// all copies or substantial portions of the Software. +// +// THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR +// IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, +// FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE +// AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER +// LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, +// OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN +// THE SOFTWARE. + +import { HttpClient, HttpErrorResponse } from "@angular/common/http"; +import { Injectable, OnDestroy } from "@angular/core"; +import { BehaviorSubject } from "rxjs"; +import { finalize } from "rxjs/operators"; + +import { DataSourceService, IFilteringOutputs } from "@nova-ui/bits"; +import { IKpiData } from "@nova-ui/dashboards"; + +import { GOOGLE_BOOKS_URL } from "./table/constants"; + +@Injectable() +export class HarryPotterAverageRatingDataSource + extends DataSourceService + implements OnDestroy +{ + public static providerId = "HarryPotterAverageRatingDataSource"; + + public busy = new BehaviorSubject(false); + + constructor(private http: HttpClient) { + super(); + } + + public async getFilteredData(): Promise { + this.busy.next(true); + return new Promise((resolve) => { + // *** Make a resource request to an external API (if needed) + this.http + .get(\`\${GOOGLE_BOOKS_URL}/5MQFrgEACAAJ\`) + .pipe(finalize(() => this.busy.next(false))) + .subscribe({ + next: (data: any) => { + resolve({ + result: { + value: data.volumeInfo.averageRating, + }, + }); + }, + error: (error: HttpErrorResponse) => { + resolve({ + result: null, + error: { + type: error.status, + }, + }); + }, + }); + }); + } + + public ngOnDestroy(): void { + this.outputsSubject.complete(); + } +} + +@Injectable() +export class HarryPotterRatingsCountDataSource + extends DataSourceService + implements OnDestroy +{ + public static providerId = "HarryPotterRatingsCountDataSource"; + + public busy = new BehaviorSubject(false); + + constructor(private http: HttpClient) { + super(); + } + + public async getFilteredData(): Promise { + this.busy.next(true); + return new Promise((resolve) => { + this.http + .get(\`\${GOOGLE_BOOKS_URL}/5MQFrgEACAAJ\`) + .pipe(finalize(() => this.busy.next(false))) + .subscribe({ + next: (data: any) => { + resolve({ + result: { + value: data.volumeInfo.ratingsCount, + }, + }); + }, + error: (error: HttpErrorResponse) => { + resolve({ + result: null, + error: { + type: error.status, + }, + }); + }, + }); + }); + } + + public ngOnDestroy(): void { + this.outputsSubject.complete(); + } +} +`, + "overview/hero/data/proportional-datasources.ts": `// © 2022 SolarWinds Worldwide, LLC. All rights reserved. +// +// Permission is hereby granted, free of charge, to any person obtaining a copy +// of this software and associated documentation files (the "Software"), to +// deal in the Software without restriction, including without limitation the +// rights to use, copy, modify, merge, publish, distribute, sublicense, and/or +// sell copies of the Software, and to permit persons to whom the Software is +// furnished to do so, subject to the following conditions: +// +// The above copyright notice and this permission notice shall be included in +// all copies or substantial portions of the Software. +// +// THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR +// IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, +// FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE +// AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER +// LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, +// OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN +// THE SOFTWARE. + +import { Injectable, OnDestroy } from "@angular/core"; +import { BehaviorSubject } from "rxjs"; + +import { + DataSourceService, + IDataSource, + IFilteringOutputs, +} from "@nova-ui/bits"; + +import { + getMockBeerReviewCountsByCity, + getMockBeerReviewCountsByCity2, + IProportionalWidgetData, +} from "./widget-data"; + +@Injectable() +export class BeerReviewCountsByCityMockDataSource + extends DataSourceService + implements IDataSource, OnDestroy +{ + // This is the ID we'll use to identify the provider + public static providerId = "BeerReviewCountsByCityMockDataSource"; + public busy = new BehaviorSubject(false); + + public async getFilteredData(): Promise { + this.busy.next(true); + return new Promise((resolve) => { + setTimeout(() => { + this.outputsSubject.next({ + result: getMockBeerReviewCountsByCity(), + }); + this.busy.next(false); + }, 300); + }); + } + + public ngOnDestroy(): void { + this.outputsSubject.complete(); + } +} + +@Injectable() +export class BeerReviewCountsByCityMockDataSource2 + extends DataSourceService + implements IDataSource, OnDestroy +{ + // This is the ID we'll use to identify the provider + public static providerId = "BeerReviewCountsByCityMockDataSource2"; + public busy = new BehaviorSubject(false); + + public async getFilteredData(): Promise { + this.busy.next(true); + return new Promise((resolve) => { + setTimeout(() => { + this.outputsSubject.next({ + result: getMockBeerReviewCountsByCity2(), + }); + this.busy.next(false); + }, 1500); + }); + } + + public ngOnDestroy(): void { + this.outputsSubject.complete(); + } +} +`, + "overview/hero/data/table/beer-data-source.ts": `// © 2022 SolarWinds Worldwide, LLC. All rights reserved. +// +// Permission is hereby granted, free of charge, to any person obtaining a copy +// of this software and associated documentation files (the "Software"), to +// deal in the Software without restriction, including without limitation the +// rights to use, copy, modify, merge, publish, distribute, sublicense, and/or +// sell copies of the Software, and to permit persons to whom the Software is +// furnished to do so, subject to the following conditions: +// +// The above copyright notice and this permission notice shall be included in +// all copies or substantial portions of the Software. +// +// THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR +// IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, +// FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE +// AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER +// LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, +// OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN +// THE SOFTWARE. + +import { ListRange } from "@angular/cdk/collections"; +import { Injectable } from "@angular/core"; +import isEqual from "lodash/isEqual"; +import orderBy from "lodash/orderBy"; +import { BehaviorSubject } from "rxjs"; + +import { + DataSourceService, + IDataField, + INovaFilteringOutputs, + INovaFilters, + ISorterFilter, + LoggerService, +} from "@nova-ui/bits"; + +import { IBrewDatasourceResponse, IBrewInfo } from "../types"; +import { BREW_API_URL } from "./constants"; + +@Injectable() +export class BeerDataSource extends DataSourceService { + public static providerId = "BeerDataSource"; + + private cache = Array.from({ length: 0 }); + private lastSortValue?: ISorterFilter; + private lastVirtualScroll?: ListRange; + private totalItems: number = 325; + + public page: number = 1; + public busy = new BehaviorSubject(false); + + public dataFields: Array = [ + { id: "id", label: "No", dataType: "number" }, + { id: "name", label: "Name", dataType: "string" }, + { id: "tagline", label: "Tagline", dataType: "string" }, + { id: "first_brewed", label: "First Brewed", dataType: "string" }, + { id: "description", label: "Description", dataType: "string" }, + { id: "brewers_tips", label: "Brewer's Tips", dataType: "string" }, + ]; + + constructor(private logger: LoggerService) { + super(); + } + + public async getFilteredData( + filters: INovaFilters + ): Promise { + const start = filters.virtualScroll?.value?.start ?? 0; + const end = filters.virtualScroll?.value?.end ?? 0; + const delta = end - start; + + // This condition handles sorting. We want to sort columns without fetching another chunk of data. + // Since the data is being fetched when scrolled, we compare virtual scroll indexes here in the condition as well. + if (filters.sorter?.value) { + if ( + !isEqual(this.lastSortValue, filters.sorter.value) && + isEqual(this.lastVirtualScroll, filters.virtualScroll?.value) + ) { + const totalPages = Math.ceil( + delta ? this.totalItems / delta : 1 + ); + const itemsPerPage: number = Math.max( + delta < 80 ? delta : 80, + 1 + ); + let response: Array | null = null; + let map: IBrewDatasourceResponse; + + if (filters.sorter?.value?.direction === "desc") { + this.cache = []; + for (let i = 0; i < this.page; ++i) { + response = await ( + await fetch( + \`\${BREW_API_URL}/?page=\${ + totalPages - i || 1 + }&per_page=\${itemsPerPage}\` + ) + ).json(); + + // since the last page contains only 5 items we need to fetch another page to give virtual scroll enough space to work + if (response && response.length < itemsPerPage) { + this.page++; + } + map = { + brewInfo: response?.map((result: IBrewInfo) => ({ + id: result.id, + name: result.name, + tagline: result.tagline, + first_brewed: result.first_brewed, + description: result.description, + brewers_tips: result.brewers_tips, + })), + total: response?.length, + } as IBrewDatasourceResponse; + this.cache = + totalPages - i !== 0 + ? this.cache.concat(map.brewInfo) + : this.cache; + } + } + + if (filters.sorter?.value?.direction === "asc") { + this.cache = []; + for (let i = 0; i < this.page; i++) { + response = await ( + await fetch( + \`\${BREW_API_URL}/?page=\${ + i + 1 + }&per_page=\${itemsPerPage}\` + ) + ).json(); + map = { + brewInfo: response?.map((result: IBrewInfo) => ({ + id: result.id, + name: result.name, + tagline: result.tagline, + first_brewed: result.first_brewed, + description: result.description, + brewers_tips: result.brewers_tips, + })), + total: response?.length, + } as IBrewDatasourceResponse; + this.cache = this.cache.concat(map.brewInfo); + } + } + + this.lastSortValue = filters.sorter?.value; + this.lastVirtualScroll = filters.virtualScroll?.value; + + return { + repeat: { itemsSource: this.sortData(this.cache, filters) }, + paginator: { total: this.totalItems }, + dataFields: this.dataFields, + }; + } + } + + this.busy.next(true); + return new Promise((resolve) => { + setTimeout(() => { + this.getData(start, end, filters).then( + (response: INovaFilteringOutputs) => { + if (!response) { + return; + } + + this.cache = this.cache.concat(response.brewInfo); + + this.dataSubject.next(this.cache); + resolve({ + repeat: { + itemsSource: this.sortData(this.cache, filters), + }, + paginator: { total: this.totalItems }, + dataFields: this.dataFields, + }); + + this.lastSortValue = filters.sorter?.value; + this.lastVirtualScroll = filters.virtualScroll?.value; + this.busy.next(false); + } + ); + }, 500); + }); + } + + public async getData( + start: number = 0, + end: number = 20, + filters: INovaFilters + ): Promise { + const delta = end - start; + const totalPages = Math.ceil(delta ? this.totalItems / delta : 1); + let response: Array | null = null; + // The api.punk.com is able to return only 80 items per page + const itemsPerPage: number = Math.max(delta < 80 ? delta : 80, 1); + + if (filters.sorter?.value?.direction === "asc") { + response = await ( + await fetch( + \`\${BREW_API_URL}/?page=\${this.page}&per_page=\${itemsPerPage}\` + ) + ).json(); + } + + if (filters.sorter?.value?.direction === "desc") { + response = await ( + await fetch( + \`\${BREW_API_URL}/?page=\${ + totalPages - this.page + }&per_page=\${itemsPerPage}\` + ) + ).json(); + } + + if (!filters.sorter) { + response = await ( + await fetch( + \`\${BREW_API_URL}/?page=\${this.page}&per_page=\${itemsPerPage}\` + ) + ).json(); + } + return { + brewInfo: response?.map((result: IBrewInfo, i: number) => ({ + id: result.id, + name: result.name, + tagline: result.tagline, + first_brewed: result.first_brewed, + description: result.description, + brewers_tips: result.brewers_tips, + })), + total: response?.length, + } as IBrewDatasourceResponse; + } + + private sortData(data: IBrewInfo[], filters: INovaFilters) { + return orderBy( + data, + filters.sorter?.value?.sortBy, + filters.sorter?.value?.direction as "desc" | "asc" + ); + } +} +`, + "overview/hero/data/table/constants.ts": `// © 2022 SolarWinds Worldwide, LLC. All rights reserved. +// +// Permission is hereby granted, free of charge, to any person obtaining a copy +// of this software and associated documentation files (the "Software"), to +// deal in the Software without restriction, including without limitation the +// rights to use, copy, modify, merge, publish, distribute, sublicense, and/or +// sell copies of the Software, and to permit persons to whom the Software is +// furnished to do so, subject to the following conditions: +// +// The above copyright notice and this permission notice shall be included in +// all copies or substantial portions of the Software. +// +// THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR +// IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, +// FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE +// AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER +// LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, +// OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN +// THE SOFTWARE. + +export const corsProxy = "https://cors-anywhere.herokuapp.com"; +export const RANDOMUSER_API_URL = "https://randomuser.me"; +export const BREW_API_URL = "https://api.punkapi.com/v2/beers"; +export const GOOGLE_BOOKS_URL = "https://www.googleapis.com/books/v1/volumes"; +export const apiRoute = "api/1.3"; +export const responseError = \`Error responding from server. Please visit \${RANDOMUSER_API_URL} and \${corsProxy} to see if they're available\`; +`, + "overview/hero/data/table/random-user-data-source.ts": `// © 2022 SolarWinds Worldwide, LLC. All rights reserved. +// +// Permission is hereby granted, free of charge, to any person obtaining a copy +// of this software and associated documentation files (the "Software"), to +// deal in the Software without restriction, including without limitation the +// rights to use, copy, modify, merge, publish, distribute, sublicense, and/or +// sell copies of the Software, and to permit persons to whom the Software is +// furnished to do so, subject to the following conditions: +// +// The above copyright notice and this permission notice shall be included in +// all copies or substantial portions of the Software. +// +// THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR +// IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, +// FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE +// AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER +// LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, +// OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN +// THE SOFTWARE. + +import { ListRange } from "@angular/cdk/collections"; +import { Injectable } from "@angular/core"; +import isEqual from "lodash/isEqual"; +import orderBy from "lodash/orderBy"; +import { BehaviorSubject } from "rxjs"; + +import { + DataSourceService, + IDataField, + INovaFilteringOutputs, + INovaFilters, + ISorterFilter, + LoggerService, +} from "@nova-ui/bits"; + +import { + IRandomUserResponse, + IRandomUserResults, + IRandomUserTableModel, + UsersQueryResponse, +} from "../types"; +import { + apiRoute, + corsProxy, + RANDOMUSER_API_URL, + responseError, +} from "./constants"; + +@Injectable() +export class RandomUserDataSource extends DataSourceService { + public static providerId = "RandomUserDataSource"; + + private readonly seed = "sw"; + + private cache = Array.from({ length: 0 }); + private lastSortValue?: ISorterFilter; + private lastVirtualScroll?: ListRange; + + public page: number = 1; + public busy = new BehaviorSubject(false); + + public dataFields: Array = [ + { id: "no", label: $localize\`No\`, dataType: "number" }, + { id: "nameTitle", label: $localize\`Title\`, dataType: "string" }, + { id: "nameFirst", label: $localize\`First\`, dataType: "string" }, + { id: "nameLast", label: $localize\`Last\`, dataType: "string" }, + { id: "gender", label: $localize\`Gender\`, dataType: "string" }, + { id: "country", label: $localize\`Country\`, dataType: "string" }, + { id: "city", label: $localize\`City\`, dataType: "string" }, + { id: "postcode", label: $localize\`Postcode\`, dataType: "number" }, + { id: "email", label: $localize\`E-Mail\`, dataType: "string" }, + { id: "cell", label: $localize\`Cell\`, dataType: "string" }, + ]; + + constructor(private logger: LoggerService) { + super(); + } + + public async getFilteredData( + filters: INovaFilters + ): Promise { + // This condition handles sorting. We want to sort columns without fetching another chunk of data. + // Since the data is being fetched when scrolled, we compare virtual scroll indexes here in the condition as well. + if (filters.sorter?.value) { + if ( + !isEqual(this.lastSortValue, filters.sorter.value) && + isEqual(this.lastVirtualScroll, filters.virtualScroll?.value) + ) { + this.lastSortValue = filters.sorter?.value; + this.lastVirtualScroll = filters.virtualScroll?.value; + + return { + repeat: { itemsSource: this.sortData(this.cache, filters) }, + paginator: { total: 200 }, + dataFields: this.dataFields, + }; + } + } + this.busy.next(true); + + const virtualScrollFilter = + filters.virtualScroll && filters.virtualScroll.value; + const start = virtualScrollFilter + ? filters.virtualScroll?.value.start + : 0; + const end = virtualScrollFilter ? filters.virtualScroll?.value.end : 0; + + // We're returning Promise with setTimeout here to make the response from the server longer, as the API being used sends responses + // almost immediately. We need it longer to be able the show the spinner component on data load + return new Promise((resolve) => { + setTimeout(() => { + this.getData(start, end).then( + (response: INovaFilteringOutputs | undefined) => { + if (!response) { + return; + } + + this.cache = this.cache.concat(response.users); + + this.dataSubject.next(this.cache); + resolve({ + repeat: { + itemsSource: this.sortData(this.cache, filters), + }, + // This API can return thousands of results, however doesn't return the max number of results, + // so we set the max number of result manually here. + paginator: { total: 200 }, + dataFields: this.dataFields, + }); + + this.lastSortValue = filters.sorter?.value; + this.lastVirtualScroll = filters.virtualScroll?.value; + this.busy.next(false); + } + ); + }, 300); + }); + } + + public async getData( + start: number = 0, + end: number = 20 + ): Promise { + let response: IRandomUserResponse | null = null; + try { + response = await ( + await fetch( + \`\${corsProxy}/\${RANDOMUSER_API_URL}/\${apiRoute}/?page=\${ + this.page + }&results=\${end - start}&seed=\${this.seed}\` + ) + ).json(); + return { + users: response?.results.map( + (result: IRandomUserResults, i: number) => ({ + no: this.cache.length + i + 1, + nameTitle: result.name.title, + nameFirst: result.name.first, + nameLast: result.name.last, + gender: result.gender, + country: result.location.country, + city: result.location.city, + postcode: result.location.postcode, + email: result.email, + cell: result.cell, + }) + ), + total: response?.results.length, + start: start, + } as UsersQueryResponse; + } catch (e) { + this.logger.error(responseError); + } + } + + private sortData(data: IRandomUserTableModel[], filters: INovaFilters) { + return orderBy( + data, + filters.sorter?.value?.sortBy, + filters.sorter?.value?.direction as "desc" | "asc" + ); + } +} +`, + "overview/hero/data/table/types.ts": `// © 2022 SolarWinds Worldwide, LLC. All rights reserved. +// +// Permission is hereby granted, free of charge, to any person obtaining a copy +// of this software and associated documentation files (the "Software"), to +// deal in the Software without restriction, including without limitation the +// rights to use, copy, modify, merge, publish, distribute, sublicense, and/or +// sell copies of the Software, and to permit persons to whom the Software is +// furnished to do so, subject to the following conditions: +// +// The above copyright notice and this permission notice shall be included in +// all copies or substantial portions of the Software. +// +// THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR +// IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, +// FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE +// AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER +// LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, +// OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN +// THE SOFTWARE. + +import { IDataField, INovaFilteringOutputs } from "@nova-ui/bits"; +export interface BasicTableModel { + position: number; + name: string; + features: any; + status: string; + checks: any; + "cpu-load": number; + firstUrl: string; + firstUrlLabel: string; + secondUrl: string; + secondUrlLabel: string; +} + +export interface ITableDataSourceOutput extends INovaFilteringOutputs { + dataFields: IDataField[]; +} +`, + "overview/hero/data/timeseries-data-sources.ts": `// © 2022 SolarWinds Worldwide, LLC. All rights reserved. +// +// Permission is hereby granted, free of charge, to any person obtaining a copy +// of this software and associated documentation files (the "Software"), to +// deal in the Software without restriction, including without limitation the +// rights to use, copy, modify, merge, publish, distribute, sublicense, and/or +// sell copies of the Software, and to permit persons to whom the Software is +// furnished to do so, subject to the following conditions: +// +// The above copyright notice and this permission notice shall be included in +// all copies or substantial portions of the Software. +// +// THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR +// IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, +// FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE +// AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER +// LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, +// OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN +// THE SOFTWARE. + +import { Injectable } from "@angular/core"; +import { Moment } from "moment/moment"; +import { BehaviorSubject } from "rxjs"; + +import { DataSourceService, IDataSource, INovaFilters } from "@nova-ui/bits"; +import { + ITimeseriesOutput, + ITimeseriesWidgetData, + ITimeseriesWidgetSeriesData, +} from "@nova-ui/dashboards"; + +import { + BEER_VS_READING_DATA, + LOUNGING_VS_ULTIMATE_FRISBEE_DATA, +} from "./widget-data"; + +@Injectable() +export class BeerVsReadingMockDataSource + extends DataSourceService + implements IDataSource +{ + public static providerId = "BeerVsReadingMockDataSource"; + + public busy = new BehaviorSubject(false); + + constructor() { + super(); + } + + public async getFilteredData( + filters: INovaFilters + ): Promise { + this.busy.next(true); + const result = await delay( + { series: getData(filters, BEER_VS_READING_DATA) }, + 1000 + ); + this.busy.next(false); + return result; + } +} + +@Injectable() +export class LoungingVsFrisbeeGolfMockDataSource + extends DataSourceService + implements IDataSource +{ + public static providerId = "LoungingVsFrisbeeGolfMockDataSource"; + + public busy = new BehaviorSubject(false); + + constructor() { + super(); + } + + public async getFilteredData( + filters: INovaFilters + ): Promise { + this.busy.next(true); + const result = await delay( + { series: getData(filters, LOUNGING_VS_ULTIMATE_FRISBEE_DATA) }, + 1000 + ); + this.busy.next(false); + return result; + } +} + +function getData( + filters: INovaFilters, + data: ITimeseriesWidgetData[] +): ITimeseriesWidgetData[] { + const timeframeFilter = filters.timeframe; + let filteredData = data; + // TIME FRAME PICKER FILTERING + if (timeframeFilter) { + filteredData = filteredData.map((item: ITimeseriesWidgetData) => ({ + id: item.id, + name: item.name, + description: item.description, + data: item.data.filter((seriesData: ITimeseriesWidgetSeriesData) => + filterDates( + seriesData.x, + timeframeFilter.value.startDatetime, + timeframeFilter.value.endDatetime + ) + ), + })); + } + + return filteredData; +} + +function filterDates(dateToCheck: Moment, startDate: Moment, endDate: Moment) { + return ( + dateToCheck.isBetween(startDate, endDate) || + dateToCheck.isSame(startDate) || + dateToCheck.isSame(endDate) + ); +} + +async function delay( + value: ITimeseriesOutput, + ms: number +): Promise { + return new Promise((resolve) => setTimeout(() => resolve(value), ms)); +} +`, + "overview/hero/data/types.ts": `// © 2022 SolarWinds Worldwide, LLC. All rights reserved. +// +// Permission is hereby granted, free of charge, to any person obtaining a copy +// of this software and associated documentation files (the "Software"), to +// deal in the Software without restriction, including without limitation the +// rights to use, copy, modify, merge, publish, distribute, sublicense, and/or +// sell copies of the Software, and to permit persons to whom the Software is +// furnished to do so, subject to the following conditions: +// +// The above copyright notice and this permission notice shall be included in +// all copies or substantial portions of the Software. +// +// THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR +// IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, +// FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE +// AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER +// LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, +// OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN +// THE SOFTWARE. + +export interface UsersQueryResponse { + users: IRandomUserTableModel[]; + total: number; + start: number; +} + +export interface IRandomUserResponse { + info: Array; + results: Array; +} + +export interface IRandomUserInfo { + page: number; + results: number; + seed: string; + version: string; +} + +export interface IRandomUserResults { + cell: string; + dob: { + age: number; + date: string; + }; + email: string; + gender: string; + id: any; + location: IRandomUserLocation; + login: { + md5: string; + password: string; + salt: string; + sha1: string; + sha256: string; + username: string; + uuid: string; + }; + name: { + title: string; + first: string; + last: string; + }; + nat: string; + phone: string; + picture: { + large: string; + medium: string; + thumbnail: string; + }; + registered: { + date: string; + age: number; + }; +} + +export interface IRandomUserTableModel { + no: number; + nameTitle: string; + nameFirst: string; + nameLast: string; + gender: string; + country: string; + city: string; + postcode: number; + email: string; + cell: string; +} + +export interface IRandomUserLocation { + city: string; + coordinates: { latitude: string; longitude: string }; + country: string; + postcode: number; + state: string; + street: { number: number; name: string }; + timezone: any; +} + +export interface IBrewInfo { + id: number; + name: string; + tagline: string; + first_brewed: string; + description: string; + brewers_tips: string; +} + +export interface IBrewDatasourceResponse { + brewInfo: IBrewInfo[]; + total: number; +} +`, + "overview/hero/data/widget-data.ts": `// © 2022 SolarWinds Worldwide, LLC. All rights reserved. +// +// Permission is hereby granted, free of charge, to any person obtaining a copy +// of this software and associated documentation files (the "Software"), to +// deal in the Software without restriction, including without limitation the +// rights to use, copy, modify, merge, publish, distribute, sublicense, and/or +// sell copies of the Software, and to permit persons to whom the Software is +// furnished to do so, subject to the following conditions: +// +// The above copyright notice and this permission notice shall be included in +// all copies or substantial portions of the Software. +// +// THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR +// IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, +// FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE +// AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER +// LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, +// OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN +// THE SOFTWARE. + +import moment from "moment/moment"; + +import { ITimeseriesWidgetData } from "@nova-ui/dashboards"; + +import { BasicTableModel } from "./table/types"; + +export interface IProportionalWidgetData { + id: string; + name: string; + data: number[]; + link: string; + value: string; +} + +export function getMockBeerReviewCountsByCity(): IProportionalWidgetData[] { + return [ + { + id: "Brno", + name: "Brno", + data: [Math.round(Math.random() * 100000)], + link: "https://en.wikipedia.org/wiki/Brno", + value: "Brno", + }, + { + id: "kyiv", + name: "Kyiv", + data: [Math.round(Math.random() * 100000)], + link: "https://en.wikipedia.org/wiki/Kyiv", + value: "Kyiv", + }, + { + id: "austin", + name: "Austin", + data: [Math.round(Math.random() * 100000)], + link: "https://en.wikipedia.org/wiki/Austin", + value: "Austin", + }, + { + id: "lisbon", + name: "Lisbon", + data: [Math.round(Math.random() * 100000)], + link: "https://en.wikipedia.org/wiki/Lisbon", + value: "Lisbon", + }, + { + id: "sydney", + name: "Sydney", + data: [Math.round(Math.random() * 100000)], + link: "https://en.wikipedia.org/wiki/Sydney", + value: "Sydney", + }, + { + id: "nur-sultan", + name: "Nur-Sultan", + data: [Math.round(Math.random() * 100000)], + link: "https://en.wikipedia.org/wiki/Nur-Sultan", + value: "Nur-Sultan", + }, + ].sort((a, b) => a.data[0] - b.data[0]); +} + +export function getMockBeerReviewCountsByCity2(): IProportionalWidgetData[] { + return [ + { + id: "london", + name: "London", + data: [Math.round(Math.random() * 100000)], + link: "https://en.wikipedia.org/wiki/London", + value: "London", + }, + { + id: "paris", + name: "Paris", + data: [Math.round(Math.random() * 100000)], + link: "https://en.wikipedia.org/wiki/Paris", + value: "Paris", + }, + { + id: "rio", + name: "Rio", + data: [Math.round(Math.random() * 100000)], + link: "https://en.wikipedia.org/wiki/Rio_de_Janeiro", + value: "Rio", + }, + ].sort((a, b) => a.data[0] - b.data[0]); +} + +export const BEER_VS_READING_DATA: ITimeseriesWidgetData[] = [ + { + id: "series-1", + name: "Beer Tasting", + description: "Havin' some suds", + data: [ + { x: moment().subtract(10, "day"), y: 30 }, + { x: moment().subtract(9, "day"), y: 35 }, + { x: moment().subtract(8, "day"), y: 33 }, + { x: moment().subtract(7, "day"), y: 40 }, + { x: moment().subtract(6, "day"), y: 35 }, + { x: moment().subtract(5, "day"), y: 30 }, + { x: moment().subtract(4, "day"), y: 35 }, + { x: moment().subtract(3, "day"), y: 15 }, + { x: moment().subtract(2, "day"), y: 30 }, + { x: moment().subtract(1, "day"), y: 35 }, + { x: moment().subtract(24, "hour"), y: 34 }, + { x: moment().subtract(15, "hour"), y: 33 }, + { x: moment().subtract(10, "hour"), y: 35 }, + { x: moment().subtract(5, "hour"), y: 36 }, + { x: moment().subtract(1, "hour"), y: 34 }, + { x: moment().subtract(50, "minute"), y: 33 }, + { x: moment().subtract(40, "minute"), y: 30 }, + { x: moment().subtract(30, "minute"), y: 32 }, + { x: moment().subtract(20, "minute"), y: 31 }, + { x: moment().subtract(10, "minute"), y: 34 }, + ], + }, + { + id: "series-2", + name: "Reading", + description: "Hittin' the books", + data: [ + { x: moment().subtract(10, "day"), y: 60 }, + { x: moment().subtract(9, "day"), y: 64 }, + { x: moment().subtract(8, "day"), y: 70 }, + { x: moment().subtract(7, "day"), y: 55 }, + { x: moment().subtract(6, "day"), y: 55 }, + { x: moment().subtract(5, "day"), y: 45 }, + { x: moment().subtract(4, "day"), y: 10 }, + { x: moment().subtract(3, "day"), y: 65 }, + { x: moment().subtract(2, "day"), y: 35 }, + { x: moment().subtract(1, "day"), y: 60 }, + { x: moment().subtract(24, "hour"), y: 61 }, + { x: moment().subtract(15, "hour"), y: 65 }, + { x: moment().subtract(10, "hour"), y: 63 }, + { x: moment().subtract(5, "hour"), y: 58 }, + { x: moment().subtract(1, "hour"), y: 64 }, + { x: moment().subtract(50, "minute"), y: 63 }, + { x: moment().subtract(40, "minute"), y: 60 }, + { x: moment().subtract(30, "minute"), y: 62 }, + { x: moment().subtract(20, "minute"), y: 61 }, + { x: moment().subtract(10, "minute"), y: 62 }, + ], + }, +]; + +export const LOUNGING_VS_ULTIMATE_FRISBEE_DATA: ITimeseriesWidgetData[] = [ + { + id: "series-a", + name: "Lounging", + description: "Shootin' the Breeze", + data: [ + { x: moment().subtract(10, "day"), y: 10 }, + { x: moment().subtract(9, "day"), y: 15 }, + { x: moment().subtract(8, "day"), y: 13 }, + { x: moment().subtract(7, "day"), y: 20 }, + { x: moment().subtract(6, "day"), y: 15 }, + { x: moment().subtract(5, "day"), y: 10 }, + { x: moment().subtract(4, "day"), y: 15 }, + { x: moment().subtract(3, "day"), y: 5 }, + { x: moment().subtract(2, "day"), y: 10 }, + { x: moment().subtract(1, "day"), y: 15 }, + { x: moment().subtract(24, "hour"), y: 14 }, + { x: moment().subtract(15, "hour"), y: 13 }, + { x: moment().subtract(10, "hour"), y: 15 }, + { x: moment().subtract(5, "hour"), y: 16 }, + { x: moment().subtract(1, "hour"), y: 14 }, + { x: moment().subtract(50, "minute"), y: 13 }, + { x: moment().subtract(40, "minute"), y: 10 }, + { x: moment().subtract(30, "minute"), y: 12 }, + { x: moment().subtract(20, "minute"), y: 11 }, + { x: moment().subtract(10, "minute"), y: 14 }, + ], + }, + { + id: "series-b", + name: "Frisbee Golfing", + description: "Golfin' with a disc", + data: [ + { x: moment().subtract(10, "day"), y: 80 }, + { x: moment().subtract(9, "day"), y: 84 }, + { x: moment().subtract(8, "day"), y: 80 }, + { x: moment().subtract(7, "day"), y: 75 }, + { x: moment().subtract(6, "day"), y: 95 }, + { x: moment().subtract(5, "day"), y: 85 }, + { x: moment().subtract(4, "day"), y: 80 }, + { x: moment().subtract(3, "day"), y: 85 }, + { x: moment().subtract(2, "day"), y: 85 }, + { x: moment().subtract(1, "day"), y: 80 }, + { x: moment().subtract(24, "hour"), y: 81 }, + { x: moment().subtract(15, "hour"), y: 85 }, + { x: moment().subtract(10, "hour"), y: 83 }, + { x: moment().subtract(5, "hour"), y: 88 }, + { x: moment().subtract(1, "hour"), y: 84 }, + { x: moment().subtract(50, "minute"), y: 83 }, + { x: moment().subtract(40, "minute"), y: 80 }, + { x: moment().subtract(30, "minute"), y: 82 }, + { x: moment().subtract(20, "minute"), y: 81 }, + { x: moment().subtract(10, "minute"), y: 82 }, + ], + }, +]; + +export const TABLE_DATA: BasicTableModel[] = [ + { + position: 1, + name: "FOCUS-SVR-02258", + features: ["remote-access-vpn-tunnel", "patch-manager01"], + status: "Active", + checks: { + icon: "status_up", + num: 25, + }, + "cpu-load": 86, + firstUrl: "https://en.wikipedia.org/wiki/Brno", + firstUrlLabel: "Brno", + secondUrl: "https://en.wikipedia.org/wiki/VMware_Workstation", + secondUrlLabel: "Workstation", + }, + { + position: 2, + name: "FOCUS-SVR-03312", + features: ["tools", "database", "orion-ape-backup"], + status: "Active", + checks: { + icon: "status_critical", + num: 25, + }, + "cpu-load": 47, + firstUrl: "https://en.wikipedia.org/wiki/Brno", + firstUrlLabel: "Brno", + secondUrl: "https://en.wikipedia.org/wiki/VMware_Workstation", + secondUrlLabel: "Workstation", + }, + { + position: 3, + name: "FOCUS-SVR-02258", + features: [ + "remote-access-vpn-tunnel", + "database", + "orion-ape-backup", + "patch-manager01", + ], + status: "Active", + checks: { + icon: "status_down", + num: 25, + }, + "cpu-load": 53, + firstUrl: "https://en.wikipedia.org/wiki/Kyiv", + firstUrlLabel: "Kyiv", + secondUrl: "https://en.wikipedia.org/wiki/VMware_Workstation", + secondUrlLabel: "Workstation", + }, + { + position: 4, + name: "Man-LT-JYJ4AD5", + features: [ + "remote-access-vpn-tunnel", + "tools", + "database", + "orion-ape-backup", + ], + status: "Active", + checks: { + icon: "status_up", + num: 25, + }, + "cpu-load": 32, + firstUrl: "https://en.wikipedia.org/wiki/Kyiv", + firstUrlLabel: "Kyiv", + secondUrl: "https://en.wikipedia.org/wiki/VirtualBox", + secondUrlLabel: "VirtualBox", + }, + { + position: 5, + name: "Man-LT-JYJ425", + features: [ + "remote-access-vpn-tunnel", + "tools", + "database", + "orion-ape-backup", + ], + status: "Active", + checks: { + icon: "status_up", + num: 25, + }, + "cpu-load": 22, + firstUrl: "https://en.wikipedia.org/wiki/Kyiv", + firstUrlLabel: "Kyiv", + secondUrl: "https://en.wikipedia.org/wiki/VirtualBox", + secondUrlLabel: "VirtualBox", + }, + { + position: 6, + name: "Man-LT-JYJ4333", + features: [ + "remote-access-vpn-tunnel", + "tools", + "database", + "orion-ape-backup", + ], + status: "Active", + checks: { + icon: "status_up", + num: 25, + }, + "cpu-load": 12, + firstUrl: "https://en.wikipedia.org/wiki/Kyiv", + firstUrlLabel: "Kyiv", + secondUrl: "https://en.wikipedia.org/wiki/VirtualBox", + secondUrlLabel: "VirtualBox", + }, + { + position: 7, + name: "FOCUS-SVR-02258", + features: ["remote-access-vpn-tunnel", "patch-manager01"], + status: "Active", + checks: { + icon: "status_up", + num: 25, + }, + "cpu-load": 86, + firstUrl: "https://en.wikipedia.org/wiki/Austin", + firstUrlLabel: "Austin", + secondUrl: "https://en.wikipedia.org/wiki/VMware_Workstation", + secondUrlLabel: "Workstation", + }, + { + position: 8, + name: "Man-LT-JYJ4AD5", + features: [ + "remote-access-vpn-tunnel", + "tools", + "database", + "orion-ape-backup", + "patch-manager01", + ], + status: "Active", + checks: { + icon: "status_inactive", + num: 25, + }, + "cpu-load": 35, + firstUrl: "https://en.wikipedia.org/wiki/Austin", + firstUrlLabel: "Austin", + secondUrl: "https://en.wikipedia.org/wiki/VMware_Workstation", + secondUrlLabel: "Workstation", + }, + { + position: 9, + name: "Man-LT-JYJ4AD5", + features: [ + "remote-access-vpn-tunnel", + "tools", + "database", + "patch-manager01", + ], + status: "Active", + checks: { + icon: "status_up", + num: 25, + }, + "cpu-load": 32, + firstUrl: "https://en.wikipedia.org/wiki/Brno", + firstUrlLabel: "Brno", + secondUrl: "https://en.wikipedia.org/wiki/VMware_Workstation", + secondUrlLabel: "Workstation", + }, + { + position: 10, + name: "Man-LT-JYJ4AD5", + features: [ + "remote-access-vpn-tunnel", + "database", + "orion-ape-backup", + "patch-manager01", + ], + status: "Active", + checks: { + icon: "status_up", + num: 25, + }, + "cpu-load": 64, + firstUrl: "https://en.wikipedia.org/wiki/Kyiv", + firstUrlLabel: "Kyiv", + secondUrl: "https://en.wikipedia.org/wiki/VMware_Workstation", + secondUrlLabel: "Workstation", + }, + { + position: 11, + name: "Man-LT-111", + features: [], + status: "Active", + checks: { + icon: "status_external", + num: 25, + }, + "cpu-load": 55, + firstUrl: "https://en.wikipedia.org/wiki/Brno", + firstUrlLabel: "Brno", + secondUrl: "https://en.wikipedia.org/wiki/VMware_Workstation", + secondUrlLabel: "Workstation", + }, + { + position: 12, + name: "Man-LT-2222", + features: [ + "remote-access-vpn-tunnel", + "tools", + "database", + "orion-ape-backup", + "patch-manager01", + ], + status: "Active", + checks: { + icon: "status_inactive", + num: 25, + }, + "cpu-load": 34, + firstUrl: "https://en.wikipedia.org/wiki/Brno", + firstUrlLabel: "Brno", + secondUrl: "https://en.wikipedia.org/wiki/VMware_Workstation", + secondUrlLabel: "Workstation", + }, + { + position: 13, + name: "Man-LT-333333", + features: [ + "remote-access-vpn-tunnel", + "tools", + "database", + "patch-manager01", + ], + status: "Active", + checks: { + icon: "status_up", + num: 25, + }, + "cpu-load": 56, + firstUrl: "https://en.wikipedia.org/wiki/Kyiv", + firstUrlLabel: "Kyiv", + secondUrl: "https://en.wikipedia.org/wiki/VMware_Workstation", + secondUrlLabel: "Workstation", + }, + { + position: 14, + name: "Man-LT-444444", + features: [ + "remote-access-vpn-tunnel", + "database", + "orion-ape-backup", + "patch-manager01", + ], + status: "Active", + checks: { + icon: "status_up", + num: 25, + }, + "cpu-load": 26, + firstUrl: "https://en.wikipedia.org/wiki/Kyiv", + firstUrlLabel: "Kyiv", + secondUrl: "https://en.wikipedia.org/wiki/VMware_Workstation", + secondUrlLabel: "Workstation", + }, + { + position: 15, + name: "Man-LT-555555", + features: [ + "remote-access-vpn-tunnel", + "database", + "orion-ape-backup", + "patch-manager01", + ], + status: "Active", + checks: { + icon: "status_up", + num: 25, + }, + "cpu-load": 76, + firstUrl: "https://en.wikipedia.org/wiki/Austin", + firstUrlLabel: "Austin", + secondUrl: "https://en.wikipedia.org/wiki/VMware_Workstation", + secondUrlLabel: "Workstation", + }, + { + position: 16, + name: "FOCUS-SVR-02258", + features: ["remote-access-vpn-tunnel", "patch-manager01"], + status: "Active", + checks: { + icon: "status_up", + num: 25, + }, + "cpu-load": 86, + firstUrl: "https://en.wikipedia.org/wiki/Brno", + firstUrlLabel: "Brno", + secondUrl: "https://en.wikipedia.org/wiki/VMware_Workstation", + secondUrlLabel: "Workstation", + }, + { + position: 17, + name: "FOCUS-SVR-03312", + features: ["tools", "database", "orion-ape-backup"], + status: "Active", + checks: { + icon: "status_critical", + num: 25, + }, + "cpu-load": 47, + firstUrl: "https://en.wikipedia.org/wiki/Brno", + firstUrlLabel: "Brno", + secondUrl: "https://en.wikipedia.org/wiki/VMware_Workstation", + secondUrlLabel: "Workstation", + }, + { + position: 18, + name: "FOCUS-SVR-02258", + features: [ + "remote-access-vpn-tunnel", + "database", + "orion-ape-backup", + "patch-manager01", + ], + status: "Active", + checks: { + icon: "status_down", + num: 25, + }, + "cpu-load": 53, + firstUrl: "https://en.wikipedia.org/wiki/Kyiv", + firstUrlLabel: "Kyiv", + secondUrl: "https://en.wikipedia.org/wiki/VMware_Workstation", + secondUrlLabel: "Workstation", + }, + { + position: 19, + name: "Man-LT-JYJ4AD5", + features: [ + "remote-access-vpn-tunnel", + "tools", + "database", + "orion-ape-backup", + ], + status: "Active", + checks: { + icon: "status_up", + num: 25, + }, + "cpu-load": 32, + firstUrl: "https://en.wikipedia.org/wiki/Kyiv", + firstUrlLabel: "Kyiv", + secondUrl: "https://en.wikipedia.org/wiki/VirtualBox", + secondUrlLabel: "VirtualBox", + }, + { + position: 20, + name: "Man-LT-JYJ425", + features: [ + "remote-access-vpn-tunnel", + "tools", + "database", + "orion-ape-backup", + ], + status: "Active", + checks: { + icon: "status_up", + num: 25, + }, + "cpu-load": 22, + firstUrl: "https://en.wikipedia.org/wiki/Kyiv", + firstUrlLabel: "Kyiv", + secondUrl: "https://en.wikipedia.org/wiki/VirtualBox", + secondUrlLabel: "VirtualBox", + }, + { + position: 21, + name: "Man-LT-JYJ4333", + features: [ + "remote-access-vpn-tunnel", + "tools", + "database", + "orion-ape-backup", + ], + status: "Active", + checks: { + icon: "status_up", + num: 25, + }, + "cpu-load": 12, + firstUrl: "https://en.wikipedia.org/wiki/Kyiv", + firstUrlLabel: "Kyiv", + secondUrl: "https://en.wikipedia.org/wiki/VirtualBox", + secondUrlLabel: "VirtualBox", + }, + { + position: 22, + name: "FOCUS-SVR-02258", + features: ["remote-access-vpn-tunnel", "patch-manager01"], + status: "Active", + checks: { + icon: "status_up", + num: 25, + }, + "cpu-load": 86, + firstUrl: "https://en.wikipedia.org/wiki/Austin", + firstUrlLabel: "Austin", + secondUrl: "https://en.wikipedia.org/wiki/VMware_Workstation", + secondUrlLabel: "Workstation", + }, + { + position: 23, + name: "Man-LT-JYJ4AD5", + features: [ + "remote-access-vpn-tunnel", + "tools", + "database", + "orion-ape-backup", + "patch-manager01", + ], + status: "Active", + checks: { + icon: "status_inactive", + num: 25, + }, + "cpu-load": 35, + firstUrl: "https://en.wikipedia.org/wiki/Austin", + firstUrlLabel: "Austin", + secondUrl: "https://en.wikipedia.org/wiki/VMware_Workstation", + secondUrlLabel: "Workstation", + }, + { + position: 24, + name: "Man-LT-JYJ4AD5", + features: [ + "remote-access-vpn-tunnel", + "tools", + "database", + "patch-manager01", + ], + status: "Active", + checks: { + icon: "status_up", + num: 25, + }, + "cpu-load": 32, + firstUrl: "https://en.wikipedia.org/wiki/Brno", + firstUrlLabel: "Brno", + secondUrl: "https://en.wikipedia.org/wiki/VMware_Workstation", + secondUrlLabel: "Workstation", + }, + { + position: 25, + name: "Man-LT-JYJ4AD5", + features: [ + "remote-access-vpn-tunnel", + "database", + "orion-ape-backup", + "patch-manager01", + ], + status: "Active", + checks: { + icon: "status_up", + num: 25, + }, + "cpu-load": 64, + firstUrl: "https://en.wikipedia.org/wiki/Kyiv", + firstUrlLabel: "Kyiv", + secondUrl: "https://en.wikipedia.org/wiki/VMware_Workstation", + secondUrlLabel: "Workstation", + }, + { + position: 26, + name: "Man-LT-111", + features: [], + status: "Active", + checks: { + icon: "status_external", + num: 25, + }, + "cpu-load": 55, + firstUrl: "https://en.wikipedia.org/wiki/Brno", + firstUrlLabel: "Brno", + secondUrl: "https://en.wikipedia.org/wiki/VMware_Workstation", + secondUrlLabel: "Workstation", + }, + { + position: 27, + name: "Man-LT-2222", + features: [ + "remote-access-vpn-tunnel", + "tools", + "database", + "orion-ape-backup", + "patch-manager01", + ], + status: "Active", + checks: { + icon: "status_inactive", + num: 25, + }, + "cpu-load": 34, + firstUrl: "https://en.wikipedia.org/wiki/Brno", + firstUrlLabel: "Brno", + secondUrl: "https://en.wikipedia.org/wiki/VMware_Workstation", + secondUrlLabel: "Workstation", + }, + { + position: 28, + name: "Man-LT-333333", + features: [ + "remote-access-vpn-tunnel", + "tools", + "database", + "patch-manager01", + ], + status: "Active", + checks: { + icon: "status_up", + num: 25, + }, + "cpu-load": 56, + firstUrl: "https://en.wikipedia.org/wiki/Kyiv", + firstUrlLabel: "Kyiv", + secondUrl: "https://en.wikipedia.org/wiki/VMware_Workstation", + secondUrlLabel: "Workstation", + }, + { + position: 29, + name: "Man-LT-444444", + features: [ + "remote-access-vpn-tunnel", + "database", + "orion-ape-backup", + "patch-manager01", + ], + status: "Active", + checks: { + icon: "status_up", + num: 25, + }, + "cpu-load": 26, + firstUrl: "https://en.wikipedia.org/wiki/Kyiv", + firstUrlLabel: "Kyiv", + secondUrl: "https://en.wikipedia.org/wiki/VMware_Workstation", + secondUrlLabel: "Workstation", + }, + { + position: 30, + name: "Man-LT-555555", + features: [ + "remote-access-vpn-tunnel", + "database", + "orion-ape-backup", + "patch-manager01", + ], + status: "Active", + checks: { + icon: "status_up", + num: 25, + }, + "cpu-load": 76, + firstUrl: "https://en.wikipedia.org/wiki/Austin", + firstUrlLabel: "Austin", + secondUrl: "https://en.wikipedia.org/wiki/VMware_Workstation", + secondUrlLabel: "Workstation", + }, + { + position: 31, + name: "FOCUS-SVR-02258", + features: ["remote-access-vpn-tunnel", "patch-manager01"], + status: "Active", + checks: { + icon: "status_up", + num: 25, + }, + "cpu-load": 86, + firstUrl: "https://en.wikipedia.org/wiki/Brno", + firstUrlLabel: "Brno", + secondUrl: "https://en.wikipedia.org/wiki/VMware_Workstation", + secondUrlLabel: "Workstation", + }, + { + position: 32, + name: "FOCUS-SVR-03312", + features: ["tools", "database", "orion-ape-backup"], + status: "Active", + checks: { + icon: "status_critical", + num: 25, + }, + "cpu-load": 47, + firstUrl: "https://en.wikipedia.org/wiki/Brno", + firstUrlLabel: "Brno", + secondUrl: "https://en.wikipedia.org/wiki/VMware_Workstation", + secondUrlLabel: "Workstation", + }, + { + position: 33, + name: "FOCUS-SVR-02258", + features: [ + "remote-access-vpn-tunnel", + "database", + "orion-ape-backup", + "patch-manager01", + ], + status: "Active", + checks: { + icon: "status_down", + num: 25, + }, + "cpu-load": 53, + firstUrl: "https://en.wikipedia.org/wiki/Kyiv", + firstUrlLabel: "Kyiv", + secondUrl: "https://en.wikipedia.org/wiki/VMware_Workstation", + secondUrlLabel: "Workstation", + }, + { + position: 34, + name: "Man-LT-JYJ4AD5", + features: [ + "remote-access-vpn-tunnel", + "tools", + "database", + "orion-ape-backup", + ], + status: "Active", + checks: { + icon: "status_up", + num: 25, + }, + "cpu-load": 32, + firstUrl: "https://en.wikipedia.org/wiki/Kyiv", + firstUrlLabel: "Kyiv", + secondUrl: "https://en.wikipedia.org/wiki/VirtualBox", + secondUrlLabel: "VirtualBox", + }, + { + position: 35, + name: "Man-LT-JYJ425", + features: [ + "remote-access-vpn-tunnel", + "tools", + "database", + "orion-ape-backup", + ], + status: "Active", + checks: { + icon: "status_up", + num: 25, + }, + "cpu-load": 22, + firstUrl: "https://en.wikipedia.org/wiki/Kyiv", + firstUrlLabel: "Kyiv", + secondUrl: "https://en.wikipedia.org/wiki/VirtualBox", + secondUrlLabel: "VirtualBox", + }, + { + position: 36, + name: "Man-LT-JYJ4333", + features: [ + "remote-access-vpn-tunnel", + "tools", + "database", + "orion-ape-backup", + ], + status: "Active", + checks: { + icon: "status_up", + num: 25, + }, + "cpu-load": 12, + firstUrl: "https://en.wikipedia.org/wiki/Kyiv", + firstUrlLabel: "Kyiv", + secondUrl: "https://en.wikipedia.org/wiki/VirtualBox", + secondUrlLabel: "VirtualBox", + }, + { + position: 37, + name: "FOCUS-SVR-02258", + features: ["remote-access-vpn-tunnel", "patch-manager01"], + status: "Active", + checks: { + icon: "status_up", + num: 25, + }, + "cpu-load": 86, + firstUrl: "https://en.wikipedia.org/wiki/Austin", + firstUrlLabel: "Austin", + secondUrl: "https://en.wikipedia.org/wiki/VMware_Workstation", + secondUrlLabel: "Workstation", + }, + { + position: 38, + name: "Man-LT-JYJ4AD5", + features: [ + "remote-access-vpn-tunnel", + "tools", + "database", + "orion-ape-backup", + "patch-manager01", + ], + status: "Active", + checks: { + icon: "status_inactive", + num: 25, + }, + "cpu-load": 35, + firstUrl: "https://en.wikipedia.org/wiki/Austin", + firstUrlLabel: "Austin", + secondUrl: "https://en.wikipedia.org/wiki/VMware_Workstation", + secondUrlLabel: "Workstation", + }, + { + position: 39, + name: "Man-LT-JYJ4AD5", + features: [ + "remote-access-vpn-tunnel", + "tools", + "database", + "patch-manager01", + ], + status: "Active", + checks: { + icon: "status_up", + num: 25, + }, + "cpu-load": 32, + firstUrl: "https://en.wikipedia.org/wiki/Brno", + firstUrlLabel: "Brno", + secondUrl: "https://en.wikipedia.org/wiki/VMware_Workstation", + secondUrlLabel: "Workstation", + }, + { + position: 40, + name: "Man-LT-JYJ4AD5", + features: [ + "remote-access-vpn-tunnel", + "database", + "orion-ape-backup", + "patch-manager01", + ], + status: "Active", + checks: { + icon: "status_up", + num: 25, + }, + "cpu-load": 64, + firstUrl: "https://en.wikipedia.org/wiki/Kyiv", + firstUrlLabel: "Kyiv", + secondUrl: "https://en.wikipedia.org/wiki/VMware_Workstation", + secondUrlLabel: "Workstation", + }, + { + position: 41, + name: "Man-LT-111", + features: [], + status: "Active", + checks: { + icon: "status_external", + num: 25, + }, + "cpu-load": 55, + firstUrl: "https://en.wikipedia.org/wiki/Brno", + firstUrlLabel: "Brno", + secondUrl: "https://en.wikipedia.org/wiki/VMware_Workstation", + secondUrlLabel: "Workstation", + }, + { + position: 42, + name: "Man-LT-2222", + features: [ + "remote-access-vpn-tunnel", + "tools", + "database", + "orion-ape-backup", + "patch-manager01", + ], + status: "Active", + checks: { + icon: "status_inactive", + num: 25, + }, + "cpu-load": 34, + firstUrl: "https://en.wikipedia.org/wiki/Brno", + firstUrlLabel: "Brno", + secondUrl: "https://en.wikipedia.org/wiki/VMware_Workstation", + secondUrlLabel: "Workstation", + }, + { + position: 43, + name: "Man-LT-333333", + features: [ + "remote-access-vpn-tunnel", + "tools", + "database", + "patch-manager01", + ], + status: "Active", + checks: { + icon: "status_up", + num: 25, + }, + "cpu-load": 56, + firstUrl: "https://en.wikipedia.org/wiki/Kyiv", + firstUrlLabel: "Kyiv", + secondUrl: "https://en.wikipedia.org/wiki/VMware_Workstation", + secondUrlLabel: "Workstation", + }, + { + position: 44, + name: "Man-LT-444444", + features: [ + "remote-access-vpn-tunnel", + "database", + "orion-ape-backup", + "patch-manager01", + ], + status: "Active", + checks: { + icon: "status_up", + num: 25, + }, + "cpu-load": 26, + firstUrl: "https://en.wikipedia.org/wiki/Kyiv", + firstUrlLabel: "Kyiv", + secondUrl: "https://en.wikipedia.org/wiki/VMware_Workstation", + secondUrlLabel: "Workstation", + }, + { + position: 45, + name: "Man-LT-555555", + features: [ + "remote-access-vpn-tunnel", + "database", + "orion-ape-backup", + "patch-manager01", + ], + status: "Active", + checks: { + icon: "status_up", + num: 25, + }, + "cpu-load": 76, + firstUrl: "https://en.wikipedia.org/wiki/Austin", + firstUrlLabel: "Austin", + secondUrl: "https://en.wikipedia.org/wiki/VMware_Workstation", + secondUrlLabel: "Workstation", + }, +]; +`, + "overview/hero/widget-configs/kpi.ts": `// © 2022 SolarWinds Worldwide, LLC. All rights reserved. +// +// Permission is hereby granted, free of charge, to any person obtaining a copy +// of this software and associated documentation files (the "Software"), to +// deal in the Software without restriction, including without limitation the +// rights to use, copy, modify, merge, publish, distribute, sublicense, and/or +// sell copies of the Software, and to permit persons to whom the Software is +// furnished to do so, subject to the following conditions: +// +// The above copyright notice and this permission notice shall be included in +// all copies or substantial portions of the Software. +// +// THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR +// IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, +// FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE +// AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER +// LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, +// OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN +// THE SOFTWARE. + +import { + DEFAULT_PIZZAGNA_ROOT, + IProviderConfiguration, + IRefresherProperties, + IWidget, + KpiComponent, + NOVA_KPI_DATASOURCE_ADAPTER, + PizzagnaLayer, + WellKnownProviders, +} from "@nova-ui/dashboards"; + +import { + HarryPotterAverageRatingDataSource, + HarryPotterRatingsCountDataSource, +} from "../data/kpi-datasources"; + +export const kpiConfig: IWidget = { + id: "kpiWidgetId", + type: "kpi", + pizzagna: { + [PizzagnaLayer.Configuration]: { + [DEFAULT_PIZZAGNA_ROOT]: { + providers: { + [WellKnownProviders.Refresher]: { + properties: { + // Configuring the refresher interval so that our data source is invoked every ten minutes + interval: 60 * 10, + enabled: true, + } as IRefresherProperties, + } as Partial, + }, + }, + header: { + properties: { + title: "Harry Potter and the Sorcerer's Stone", + subtitle: "By J. K. Rowling", + }, + }, + tiles: { + properties: { + nodes: ["kpi1", "kpi2"], + }, + }, + kpi1: { + id: "kpi1", + componentType: KpiComponent.lateLoadKey, + properties: { + widgetData: { + label: "Average Rating", + backgroundColor: "var(--nui-color-chart-three)", + units: "out of 5 Stars", + }, + }, + providers: { + [WellKnownProviders.DataSource]: { + // Setting the data source providerId for the tile with id "kpi1" + providerId: + HarryPotterAverageRatingDataSource.providerId, + } as IProviderConfiguration, + [WellKnownProviders.Adapter]: { + providerId: NOVA_KPI_DATASOURCE_ADAPTER, + properties: { + componentId: "kpi1", + propertyPath: "widgetData", + }, + } as IProviderConfiguration, + }, + }, + kpi2: { + id: "kpi2", + componentType: KpiComponent.lateLoadKey, + properties: { + widgetData: { + label: "Reader Feedback", + units: "Ratings", + }, + }, + providers: { + [WellKnownProviders.DataSource]: { + // Setting the data source providerId for the tile with id "kpi" + providerId: + HarryPotterRatingsCountDataSource.providerId, + } as IProviderConfiguration, + [WellKnownProviders.Adapter]: { + providerId: NOVA_KPI_DATASOURCE_ADAPTER, + properties: { + componentId: "kpi2", + propertyPath: "widgetData", + }, + } as IProviderConfiguration, + }, + }, + }, + }, +}; +`, + "overview/hero/widget-configs/proportional.ts": `// © 2022 SolarWinds Worldwide, LLC. All rights reserved. +// +// Permission is hereby granted, free of charge, to any person obtaining a copy +// of this software and associated documentation files (the "Software"), to +// deal in the Software without restriction, including without limitation the +// rights to use, copy, modify, merge, publish, distribute, sublicense, and/or +// sell copies of the Software, and to permit persons to whom the Software is +// furnished to do so, subject to the following conditions: +// +// The above copyright notice and this permission notice shall be included in +// all copies or substantial portions of the Software. +// +// THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR +// IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, +// FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE +// AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER +// LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, +// OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN +// THE SOFTWARE. + +import { + DEFAULT_PIZZAGNA_ROOT, + IProportionalWidgetChartOptions, + IProviderConfiguration, + IWidget, + LegendPlacement, + PizzagnaLayer, + ProportionalWidgetChartTypes, + WellKnownProviders, +} from "@nova-ui/dashboards"; + +import { BeerReviewCountsByCityMockDataSource } from "../data/proportional-datasources"; + +export const proportionalConfig: IWidget = { + id: "proportionalWidgetId", + type: "proportional", + pizzagna: { + [PizzagnaLayer.Configuration]: { + [DEFAULT_PIZZAGNA_ROOT]: { + providers: { + [WellKnownProviders.Refresher]: { + properties: { + interval: 0, + }, + }, + }, + }, + header: { + properties: { + title: "Beer Review Tally by City", + subtitle: "These People Love Beer", + }, + }, + chart: { + providers: { + [WellKnownProviders.DataSource]: { + providerId: + BeerReviewCountsByCityMockDataSource.providerId, + } as IProviderConfiguration, + }, + properties: { + configuration: { + chartOptions: { + type: ProportionalWidgetChartTypes.DonutChart, + legendPlacement: LegendPlacement.Right, + } as IProportionalWidgetChartOptions, + }, + }, + }, + }, + }, +}; +`, + "overview/hero/widget-configs/risk-score.ts": `// © 2023 SolarWinds Worldwide, LLC. All rights reserved. +// +// Permission is hereby granted, free of charge, to any person obtaining a copy +// of this software and associated documentation files (the "Software"), to +// deal in the Software without restriction, including without limitation the +// rights to use, copy, modify, merge, publish, distribute, sublicense, and/or +// sell copies of the Software, and to permit persons to whom the Software is +// furnished to do so, subject to the following conditions: +// +// The above copyright notice and this permission notice shall be included in +// all copies or substantial portions of the Software. +// +// THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR +// IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, +// FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE +// AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER +// LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, +// OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN +// THE SOFTWARE. + +import { + DEFAULT_PIZZAGNA_ROOT, + IProviderConfiguration, + IRefresherProperties, + IWidget, + RiskScoreTileComponent, + NOVA_KPI_DATASOURCE_ADAPTER, + PizzagnaLayer, + WellKnownProviders, +} from "@nova-ui/dashboards"; + +import { HarryPotterAverageRatingDataSource } from "../data/kpi-datasources"; + +export const riskScoreConfig: IWidget = { + id: "riskScoreWidgetId", + type: "risk-score", + pizzagna: { + [PizzagnaLayer.Configuration]: { + [DEFAULT_PIZZAGNA_ROOT]: { + providers: { + [WellKnownProviders.Refresher]: { + properties: { + // Configuring the refresher interval so that our data source is invoked every ten minutes + interval: 60 * 10, + enabled: true, + } as IRefresherProperties, + } as Partial, + }, + }, + header: { + properties: { + title: "Harry Potter and the Sorcerer's Stone", + subtitle: "By J. K. Rowling", + }, + }, + tiles: { + properties: { + nodes: ["riskScore1"], + }, + }, + riskScore1: { + id: "riskScore1", + componentType: RiskScoreTileComponent.lateLoadKey, + properties: { + widgetData: { + minValue: 0, + maxValue: 5, + useStaticLabel: false, + staticLabel: undefined, + label: \`Average Rating\`, + description: \`Harry Potter and the Sorcerer's Stone By J. K. Rowling Average Rating Risk Score\`, + }, + }, + providers: { + [WellKnownProviders.DataSource]: { + // Setting the data source providerId for the tile with id "riskScore1" + providerId: + HarryPotterAverageRatingDataSource.providerId, + } as IProviderConfiguration, + [WellKnownProviders.Adapter]: { + providerId: NOVA_KPI_DATASOURCE_ADAPTER, + properties: { + componentId: "riskScore1", + propertyPath: "widgetData", + }, + } as IProviderConfiguration, + }, + }, + }, + }, +}; +`, + "overview/hero/widget-configs/table.ts": `// © 2022 SolarWinds Worldwide, LLC. All rights reserved. +// +// Permission is hereby granted, free of charge, to any person obtaining a copy +// of this software and associated documentation files (the "Software"), to +// deal in the Software without restriction, including without limitation the +// rights to use, copy, modify, merge, publish, distribute, sublicense, and/or +// sell copies of the Software, and to permit persons to whom the Software is +// furnished to do so, subject to the following conditions: +// +// The above copyright notice and this permission notice shall be included in +// all copies or substantial portions of the Software. +// +// THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR +// IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, +// FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE +// AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER +// LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, +// OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN +// THE SOFTWARE. + +import { + ITableWidgetColumnConfig, + ITableWidgetSorterConfig, + IWidget, + PizzagnaLayer, + RawFormatterComponent, + WellKnownProviders, +} from "@nova-ui/dashboards"; + +import { BeerDataSource } from "../data/table/beer-data-source"; + +export const tableConfig: IWidget = { + id: "tableWidgetId", + type: "table", + pizzagna: { + [PizzagnaLayer.Configuration]: { + header: { + properties: { + title: "Stupendous Suds", + subtitle: "Try These Brilliant Brews", + }, + }, + table: { + providers: { + [WellKnownProviders.DataSource]: { + providerId: BeerDataSource.providerId, + }, + }, + properties: { + configuration: { + columns: [ + { + id: "column1", + label: "Beer Name", + isActive: true, + width: 185, + formatter: { + componentType: + RawFormatterComponent.lateLoadKey, + properties: { + dataFieldIds: { + value: "name", + }, + }, + }, + }, + { + id: "column2", + label: "Tagline", + isActive: true, + width: 250, + formatter: { + componentType: + RawFormatterComponent.lateLoadKey, + properties: { + dataFieldIds: { + value: "tagline", + }, + }, + }, + }, + { + id: "column3", + label: "First Brewed", + isActive: true, + formatter: { + componentType: + RawFormatterComponent.lateLoadKey, + properties: { + dataFieldIds: { + value: "first_brewed", + }, + }, + }, + }, + { + id: "column4", + label: "Description", + isActive: true, + width: 275, + formatter: { + componentType: + RawFormatterComponent.lateLoadKey, + properties: { + dataFieldIds: { + value: "description", + }, + }, + }, + }, + ] as ITableWidgetColumnConfig[], + sorterConfiguration: { + descendantSorting: false, + sortBy: "", + } as ITableWidgetSorterConfig, + hasVirtualScroll: true, + }, + }, + }, + }, + }, +}; +`, + "overview/hero/widget-configs/timeseries.ts": `// © 2022 SolarWinds Worldwide, LLC. All rights reserved. +// +// Permission is hereby granted, free of charge, to any person obtaining a copy +// of this software and associated documentation files (the "Software"), to +// deal in the Software without restriction, including without limitation the +// rights to use, copy, modify, merge, publish, distribute, sublicense, and/or +// sell copies of the Software, and to permit persons to whom the Software is +// furnished to do so, subject to the following conditions: +// +// The above copyright notice and this permission notice shall be included in +// all copies or substantial portions of the Software. +// +// THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR +// IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, +// FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE +// AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER +// LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, +// OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN +// THE SOFTWARE. + +import moment from "moment/moment"; + +import { + DEFAULT_PIZZAGNA_ROOT, + IProviderConfiguration, + ISerializableTimeframe, + ITimeseriesItemConfiguration, + IWidget, + LegendPlacement, + WellKnownProviders, +} from "@nova-ui/dashboards"; + +import { BeerVsReadingMockDataSource } from "../data/timeseries-data-sources"; + +export const timeseriesConfig: IWidget = { + id: "timeseriesWidgetId", + type: "timeseries", + pizzagna: { + configuration: { + [DEFAULT_PIZZAGNA_ROOT]: { + providers: { + [WellKnownProviders.DataSource]: { + providerId: BeerVsReadingMockDataSource.providerId, + } as IProviderConfiguration, + }, + }, + header: { + properties: { + title: "Primary Leisure Activity Over Time", + subtitle: "Survey of 1000 Solarians", + }, + }, + chart: { + providers: { + [WellKnownProviders.Adapter]: { + properties: { + series: [ + { + id: "series-1", + label: "Beer Tasting", + selectedSeriesId: "series-1", + }, + { + id: "series-2", + label: "Reading", + selectedSeriesId: "series-2", + }, + ] as ITimeseriesItemConfiguration[], + }, + } as Partial, + }, + properties: { + configuration: { + legendPlacement: LegendPlacement.Right, + enableZoom: true, + leftAxisLabel: "Solarians (%)", + }, + }, + }, + timeframeSelection: { + properties: { + timeframe: { + selectedPresetId: "last7Days", + } as ISerializableTimeframe, + minDate: moment().subtract(10, "days").format(), + maxDate: moment().format(), + }, + }, + }, + }, +}; +`, + "overview/overview-docs.component.ts": `// © 2022 SolarWinds Worldwide, LLC. All rights reserved. +// +// Permission is hereby granted, free of charge, to any person obtaining a copy +// of this software and associated documentation files (the "Software"), to +// deal in the Software without restriction, including without limitation the +// rights to use, copy, modify, merge, publish, distribute, sublicense, and/or +// sell copies of the Software, and to permit persons to whom the Software is +// furnished to do so, subject to the following conditions: +// +// The above copyright notice and this permission notice shall be included in +// all copies or substantial portions of the Software. +// +// THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR +// IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, +// FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE +// AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER +// LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, +// OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN +// THE SOFTWARE. + +import { Component } from "@angular/core"; + +@Component({ + selector: "nui-dashboard-overview-docs", + templateUrl: "./overview-docs.component.html", + standalone: false, +}) +export class OverviewDocsComponent {} +`, + "overview/overview.module.ts": `// © 2022 SolarWinds Worldwide, LLC. All rights reserved. +// +// Permission is hereby granted, free of charge, to any person obtaining a copy +// of this software and associated documentation files (the "Software"), to +// deal in the Software without restriction, including without limitation the +// rights to use, copy, modify, merge, publish, distribute, sublicense, and/or +// sell copies of the Software, and to permit persons to whom the Software is +// furnished to do so, subject to the following conditions: +// +// The above copyright notice and this permission notice shall be included in +// all copies or substantial portions of the Software. +// +// THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR +// IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, +// FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE +// AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER +// LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, +// OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN +// THE SOFTWARE. + +import { CommonModule } from "@angular/common"; +import { NgModule } from "@angular/core"; +import { RouterModule } from "@angular/router"; + +import { + NuiBusyModule, + NuiButtonModule, + NuiDocsModule, + NuiIconModule, + NuiMessageModule, + NuiSwitchModule, +} from "@nova-ui/bits"; +import { + ConfiguratorHeadingService, + IFormatterDefinition, + LinkFormatterComponent, + NuiDashboardsModule, + WellKnownPathKey, + WidgetTypesService, +} from "@nova-ui/dashboards"; + +import { HeroDashboardComponent } from "./hero/dashboard/hero-dashboard.component"; +import { + HarryPotterAverageRatingDataSource, + HarryPotterRatingsCountDataSource, +} from "./hero/data/kpi-datasources"; +import { + BeerReviewCountsByCityMockDataSource, + BeerReviewCountsByCityMockDataSource2, +} from "./hero/data/proportional-datasources"; +import { BeerDataSource } from "./hero/data/table/beer-data-source"; +import { RandomUserDataSource } from "./hero/data/table/random-user-data-source"; +import { + BeerVsReadingMockDataSource, + LoungingVsFrisbeeGolfMockDataSource, +} from "./hero/data/timeseries-data-sources"; +import { OverviewDocsComponent } from "./overview-docs.component"; + +const routes = [ + { + path: "", + component: OverviewDocsComponent, + data: { + srlc: { + hideIndicator: true, + }, + showThemeSwitcher: true, + }, + }, + { + path: "hero", + component: HeroDashboardComponent, + data: { + srlc: { + hideIndicator: true, + }, + }, + }, +]; + +@NgModule({ + imports: [ + CommonModule, + NuiDashboardsModule, + NuiBusyModule, + NuiButtonModule, + NuiDocsModule, + NuiMessageModule, + NuiSwitchModule, + NuiIconModule, + RouterModule.forChild(routes), + ], + declarations: [OverviewDocsComponent, HeroDashboardComponent], + providers: [ConfiguratorHeadingService], +}) +export default class OverviewModule { + constructor(private widgetTypesService: WidgetTypesService) { + this.setupDataSourceProviders(); + this.setupProportionalLegendFormatters(); + } + + private setupDataSourceProviders() { + this.setDataSourceProviders("table", [ + RandomUserDataSource.providerId, + BeerDataSource.providerId, + ]); + this.setDataSourceProviders("kpi", [ + HarryPotterAverageRatingDataSource.providerId, + HarryPotterRatingsCountDataSource.providerId, + ]); + this.setDataSourceProviders("risk-score", [ + HarryPotterAverageRatingDataSource.providerId, + HarryPotterRatingsCountDataSource.providerId, + ]); + this.setDataSourceProviders("proportional", [ + BeerReviewCountsByCityMockDataSource.providerId, + BeerReviewCountsByCityMockDataSource2.providerId, + ]); + this.setDataSourceProviders("timeseries", [ + BeerVsReadingMockDataSource.providerId, + LoungingVsFrisbeeGolfMockDataSource.providerId, + ]); + } + + private setDataSourceProviders(type: string, providers: string[]) { + const widgetTemplate = this.widgetTypesService.getWidgetType(type, 1); + this.widgetTypesService.setNode( + widgetTemplate, + "configurator", + WellKnownPathKey.DataSourceProviders, + providers + ); + } + + private setupProportionalLegendFormatters() { + const formatters: IFormatterDefinition[] = [ + { + componentType: LinkFormatterComponent.lateLoadKey, + label: $localize\`Link\`, + dataTypes: { + value: "label", + link: "link", + }, + }, + ]; + + const widgetTemplate = this.widgetTypesService.getWidgetType( + "proportional", + 1 + ); + this.widgetTypesService.setNode( + widgetTemplate, + "configurator", + WellKnownPathKey.Formatters, + formatters + ); + } +} +`, + "tutorials/customization/configurator-section/custom-configurator-section/custom-configurator-section.example.component.ts": `// © 2022 SolarWinds Worldwide, LLC. All rights reserved. +// +// Permission is hereby granted, free of charge, to any person obtaining a copy +// of this software and associated documentation files (the "Software"), to +// deal in the Software without restriction, including without limitation the +// rights to use, copy, modify, merge, publish, distribute, sublicense, and/or +// sell copies of the Software, and to permit persons to whom the Software is +// furnished to do so, subject to the following conditions: +// +// The above copyright notice and this permission notice shall be included in +// all copies or substantial portions of the Software. +// +// THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR +// IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, +// FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE +// AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER +// LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, +// OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN +// THE SOFTWARE. + +import { HttpClient, HttpErrorResponse } from "@angular/common/http"; +import { + ChangeDetectionStrategy, + ChangeDetectorRef, + Component, + EventEmitter, + Injectable, + Input, + OnChanges, + OnDestroy, + OnInit, + Output, + SimpleChanges, +} from "@angular/core"; +import { FormBuilder, FormGroup, Validators } from "@angular/forms"; +import { GridsterConfig, GridsterItem } from "angular-gridster2"; +// eslint-disable-next-line import/no-deprecated +import { BehaviorSubject, combineLatest, Observable } from "rxjs"; +// eslint-disable-next-line import/no-deprecated +import { finalize, map, startWith } from "rxjs/operators"; + +import { DataSourceService, IFilteringOutputs } from "@nova-ui/bits"; +import { + ComponentRegistryService, + DATA_SOURCE, + DEFAULT_PIZZAGNA_ROOT, + IDashboard, + IHasChangeDetector, + IHasForm, + IKpiData, + IProviderConfiguration, + IRefresherProperties, + IWidget, + IWidgets, + KpiComponent, + NOVA_KPI_DATASOURCE_ADAPTER, + PizzagnaLayer, + ProviderRegistryService, + WellKnownPathKey, + WellKnownProviders, + WidgetTypesService, +} from "@nova-ui/dashboards"; + +/** + * A custom version of the KpiDescriptionConfigurationComponent provided by the dashboards framework. + * --- + * For this example, the existing background color selection functionality has been replaced by custom + * template content. + */ +@Component({ + selector: "custom-kpi-description-configuration", + template: \` + + +
+
+ + + +
+ + +
+
+ Custom Content +
+
+ The default version of this configurator section + displays a background color selector here. +
+
+ + +
+ + + +
+
+
+ \`, + styleUrls: ["./custom-configurator-section.example.component.less"], + changeDetection: ChangeDetectionStrategy.OnPush, + standalone: false, +}) +// Remember to declare this class in the parent module +export class CustomKpiDescriptionConfigurationComponent + implements OnInit, OnChanges, IHasChangeDetector, IHasForm +{ + // Ensure that the lateLoadKey value matches class name + public static lateLoadKey = "CustomKpiDescriptionConfigurationComponent"; + + @Input() componentId: string; + @Input() configurableUnits: boolean; + + @Input() label: string = ""; + @Input() units: string = ""; + + @Output() formReady = new EventEmitter(); + + public form: FormGroup; + public subtitle$: Observable; + + constructor( + public changeDetector: ChangeDetectorRef, + private formBuilder: FormBuilder + ) {} + + public ngOnInit(): void { + this.form = this.formBuilder.group({ + label: [this.label, [Validators.required]], + }); + + if (this.configurableUnits) { + this.form.addControl("units", this.formBuilder.control(this.units)); + } + + const label = this.form.get("label"); + // eslint-disable-next-line import/no-deprecated + const labelValue = label?.valueChanges.pipe(startWith(label?.value)); + + // eslint-disable-next-line import/no-deprecated + this.subtitle$ = combineLatest([ + labelValue?.pipe(map((t) => t || $localize\`no label\`)), + ]).pipe(map((labels) => labels.join(", "))); + + this.formReady.emit(this.form); + } + + public ngOnChanges(changes: SimpleChanges): void { + if (changes.label) { + this.form.patchValue({ label: changes.label.currentValue }); + } + if (changes.units) { + this.form.patchValue({ units: changes.units.currentValue }); + } + } +} + +/** + * A simple KPI data source to retrieve the average rating of Harry Potter and the Sorcerer's Stone (book) via googleapis + */ +@Injectable() +export class AverageRatingKpiDataSource + extends DataSourceService + implements OnDestroy +{ + // This is the ID we'll use to identify the provider + public static providerId = "AverageRatingKpiDataSource"; + + // Use this subject to communicate the data source's busy state + public busy = new BehaviorSubject(false); + + constructor(private http: HttpClient) { + super(); + } + + // In this example, getFilteredData is invoked every 10 minutes (Take a look at the refresher + // provider definition in the widget configuration below to see how the interval is set) + public async getFilteredData(): Promise { + this.busy.next(true); + return new Promise((resolve) => { + // *** Make a resource request to an external API (if needed) + this.http + .get("https://www.googleapis.com/books/v1/volumes/5MQFrgEACAAJ") + .pipe(finalize(() => this.busy.next(false))) + .subscribe({ + next: (data: any) => { + resolve({ + result: { + value: data.volumeInfo.averageRating, + }, + }); + }, + error: (error: HttpErrorResponse) => { + resolve({ + result: null, + error: { + type: error.status, + }, + }); + }, + }); + }); + } + + public ngOnDestroy(): void { + this.outputsSubject.complete(); + } +} + +/** + * A simple KPI data source to retrieve the ratings count of Harry Potter and the Sorcerer's Stone (book) via googleapis + */ +@Injectable() +export class RatingsCountKpiDataSource + extends DataSourceService + implements OnDestroy +{ + public static providerId = "RatingsCountKpiDataSource"; + + // Use this subject to communicate the data source's busy state + public busy = new BehaviorSubject(false); + + constructor(private http: HttpClient) { + super(); + } + + public async getFilteredData(): Promise { + this.busy.next(true); + return new Promise((resolve) => { + this.http + .get("https://www.googleapis.com/books/v1/volumes/5MQFrgEACAAJ") + .pipe(finalize(() => this.busy.next(false))) + .subscribe({ + next: (data: any) => { + resolve({ + result: { + value: data.volumeInfo.ratingsCount, + }, + }); + }, + error: (error: HttpErrorResponse) => { + resolve({ + result: null, + error: { + type: error.status, + }, + }); + }, + }); + }); + } + + public ngOnDestroy(): void { + this.outputsSubject.complete(); + } +} + +/** + * A component that instantiates the dashboard + */ +@Component({ + selector: "custom-configurator-section-example", + templateUrl: "./custom-configurator-section.example.component.html", + styleUrls: ["./custom-configurator-section.example.component.less"], + standalone: false, +}) +export class CustomConfiguratorSectionExampleComponent implements OnInit { + // This variable will hold all the data needed to define the layout and behavior of the widgets. + // Pass this to the dashboard component's dashboard input in the template. + public dashboard: IDashboard | undefined; + + // Angular gridster requires a configuration object even if it's empty. + // Pass this to the dashboard component's gridsterConfig input in the template. + public gridsterConfig: GridsterConfig = {}; + + // Boolean which dashboard takes in as an input if its true it allows you to move widgets around. + public editMode: boolean = false; + + constructor( + // WidgetTypesService provides the widget's necessary structure information + private widgetTypesService: WidgetTypesService, + + // In general, the ProviderRegistryService is used for making entities available for injection into dynamically loaded components. + private providerRegistry: ProviderRegistryService, + + // Inject the ComponentRegistryService to make our custom component available for late loading by the dashboards framework + private componentRegistry: ComponentRegistryService, + + private changeDetectorRef: ChangeDetectorRef + ) {} + + public ngOnInit(): void { + // Grab the widget's default template which will be needed as a parameter for setNode. + const widgetTemplate = this.widgetTypesService.getWidgetType("kpi", 1); + + // Replace the default KPI description configuration component with our custom one. + // Note: This could also be done in the parent module's constructor to give + // multiple dashboards access to the same custom configurator section. + this.widgetTypesService.setNode( + widgetTemplate, + "configurator", + WellKnownPathKey.TileDescriptionConfigComponentType, + CustomKpiDescriptionConfigurationComponent.lateLoadKey + ); + + // Register the custom configurator section with the component registry to make it available + // for late loading by the dashboards framework. + this.componentRegistry.registerByLateLoadKey( + CustomKpiDescriptionConfigurationComponent + ); + + // Register our data sources as dropdown options in the widget editor/configurator + // Note: This could also be done in the parent module's constructor so that + // multiple dashboards could have access to the same widget template modification. + this.widgetTypesService.setNode( + // This is the template we grabbed above with getWidgetType + widgetTemplate, + // We are setting the editor/configurator part of the widget template + "configurator", + // This indicates which node you are changing and we want to change + // the data source providers available for selection in the editor. + WellKnownPathKey.DataSourceProviders, + // We are setting the data sources available for selection in the editor + [ + AverageRatingKpiDataSource.providerId, + RatingsCountKpiDataSource.providerId, + ] + ); + + // Register the data sources available for injection into the KPI tiles. + // Note: Each tile of a KPI widget is assigned its own instance of a data source + this.providerRegistry.setProviders({ + [AverageRatingKpiDataSource.providerId]: { + provide: DATA_SOURCE, + useClass: AverageRatingKpiDataSource, + // Any dependencies that need to be injected into the provider must be listed here + deps: [HttpClient], + }, + [RatingsCountKpiDataSource.providerId]: { + provide: DATA_SOURCE, + useClass: RatingsCountKpiDataSource, + deps: [HttpClient], + }, + }); + + this.initializeDashboard(); + } + + public initializeDashboard(): void { + // We're using a static configuration object for this example (see widgetConfig at the bottom of the file), + // but this is where the widget's configuration could potentially be populated from a database + const kpiWidget = widgetConfig; + const widgetIndex: IWidgets = { + // Complete the KPI widget with information coming from its type definition + [kpiWidget.id]: + this.widgetTypesService.mergeWithWidgetType(kpiWidget), + }; + + // Setting the widget dimensions and position (this is for gridster) + const positions: Record = { + [kpiWidget.id]: { + cols: 4, + rows: 6, + y: 0, + x: 0, + }, + }; + + // Finally, assigning the variables we created above to the dashboard + this.dashboard = { + positions, + widgets: widgetIndex, + }; + } + + /** Used for restoring widgets state */ + public reInitializeDashboard(): void { + // destroys the components and their providers so the dashboard can re init data + this.dashboard = undefined; + this.changeDetectorRef.detectChanges(); + + this.initializeDashboard(); + } +} + +const widgetConfig: IWidget = { + id: "widget1", + type: "kpi", + pizzagna: { + [PizzagnaLayer.Configuration]: { + [DEFAULT_PIZZAGNA_ROOT]: { + providers: { + [WellKnownProviders.Refresher]: { + properties: { + // Configuring the refresher interval so that our data source is invoked every ten minutes + interval: 60 * 10, + enabled: true, + } as IRefresherProperties, + } as Partial, + }, + }, + header: { + properties: { + title: "Harry Potter and the Sorcerer's Stone", + subtitle: "By J. K. Rowling", + }, + }, + tiles: { + properties: { + nodes: ["kpi1"], + }, + }, + kpi1: { + id: "kpi1", + componentType: KpiComponent.lateLoadKey, + properties: { + widgetData: { + units: "out of 5 Stars", + label: "Average Rating", + }, + }, + providers: { + [WellKnownProviders.DataSource]: { + // Setting the data source providerId for the tile with id "kpi1" + providerId: AverageRatingKpiDataSource.providerId, + } as IProviderConfiguration, + [WellKnownProviders.Adapter]: { + providerId: NOVA_KPI_DATASOURCE_ADAPTER, + properties: { + componentId: "kpi1", + propertyPath: "widgetData", + }, + } as IProviderConfiguration, + }, + }, + }, + }, +}; +`, + "tutorials/customization/configurator-section/custom-configurator-section-docs.component.ts": `// © 2022 SolarWinds Worldwide, LLC. All rights reserved. +// +// Permission is hereby granted, free of charge, to any person obtaining a copy +// of this software and associated documentation files (the "Software"), to +// deal in the Software without restriction, including without limitation the +// rights to use, copy, modify, merge, publish, distribute, sublicense, and/or +// sell copies of the Software, and to permit persons to whom the Software is +// furnished to do so, subject to the following conditions: +// +// The above copyright notice and this permission notice shall be included in +// all copies or substantial portions of the Software. +// +// THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR +// IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, +// FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE +// AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER +// LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, +// OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN +// THE SOFTWARE. + +import { Component } from "@angular/core"; + +@Component({ + selector: "custom-configurator-section-docs", + templateUrl: "./custom-configurator-section-docs.component.html", + standalone: false, +}) +export class CustomConfiguratorSectionDocsComponent {} +`, + "tutorials/customization/configurator-section/custom-configurator-section.module.ts": `// © 2022 SolarWinds Worldwide, LLC. All rights reserved. +// +// Permission is hereby granted, free of charge, to any person obtaining a copy +// of this software and associated documentation files (the "Software"), to +// deal in the Software without restriction, including without limitation the +// rights to use, copy, modify, merge, publish, distribute, sublicense, and/or +// sell copies of the Software, and to permit persons to whom the Software is +// furnished to do so, subject to the following conditions: +// +// The above copyright notice and this permission notice shall be included in +// all copies or substantial portions of the Software. +// +// THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR +// IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, +// FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE +// AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER +// LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, +// OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN +// THE SOFTWARE. + +import { CommonModule } from "@angular/common"; +import { HttpClientModule } from "@angular/common/http"; +import { NgModule } from "@angular/core"; +import { ReactiveFormsModule } from "@angular/forms"; +import { RouterModule } from "@angular/router"; + +import { + NuiButtonModule, + NuiDocsModule, + NuiFormFieldModule, + NuiIconModule, + NuiMessageModule, + NuiSwitchModule, + NuiTextboxModule, + DEMO_PATH_TOKEN, +} from "@nova-ui/bits"; +import { + NuiDashboardConfiguratorModule, + NuiDashboardsModule, +} from "@nova-ui/dashboards"; + +import { + CustomConfiguratorSectionExampleComponent, + CustomKpiDescriptionConfigurationComponent, +} from "./custom-configurator-section/custom-configurator-section.example.component"; +import { CustomConfiguratorSectionDocsComponent } from "./custom-configurator-section-docs.component"; +import { getDemoFiles } from "../../../../../demo-files-factory"; + +const routes = [ + { + path: "", + component: CustomConfiguratorSectionDocsComponent, + data: { + srlc: { + hideIndicator: true, + }, + showThemeSwitcher: true, + }, + }, + { + path: "example", + component: CustomConfiguratorSectionExampleComponent, + data: { + srlc: { + hideIndicator: true, + }, + }, + }, +]; + +@NgModule({ + imports: [ + CommonModule, + ReactiveFormsModule, + HttpClientModule, + NuiDashboardsModule, + NuiDashboardConfiguratorModule, + NuiDocsModule, + NuiFormFieldModule, + NuiIconModule, + NuiMessageModule, + NuiSwitchModule, + NuiTextboxModule, + NuiButtonModule, + RouterModule.forChild(routes), + ], + declarations: [ + CustomConfiguratorSectionDocsComponent, + CustomKpiDescriptionConfigurationComponent, + CustomConfiguratorSectionExampleComponent, + ], + providers: [ + { + provide: DEMO_PATH_TOKEN, + useValue: getDemoFiles("configurator-section"), + }, + ], +}) +export default class CustomConfiguratorSectionModule {} +`, + "tutorials/customization/customization.module.ts": `// © 2022 SolarWinds Worldwide, LLC. All rights reserved. +// +// Permission is hereby granted, free of charge, to any person obtaining a copy +// of this software and associated documentation files (the "Software"), to +// deal in the Software without restriction, including without limitation the +// rights to use, copy, modify, merge, publish, distribute, sublicense, and/or +// sell copies of the Software, and to permit persons to whom the Software is +// furnished to do so, subject to the following conditions: +// +// The above copyright notice and this permission notice shall be included in +// all copies or substantial portions of the Software. +// +// THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR +// IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, +// FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE +// AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER +// LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, +// OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN +// THE SOFTWARE. + +import { NgModule, Type } from "@angular/core"; +import { RouterModule, Routes } from "@angular/router"; + +import { ConfiguratorHeadingService } from "@nova-ui/dashboards"; + +enum CustomizationModuleRoute { + ConfiguratorSection = "configurator-section", + Widget = "widget", + Formatter = "formatter", + DataSourceConfigurator = "data-source-configurator", +} + +const routes: Routes = [ + { + path: CustomizationModuleRoute.ConfiguratorSection, + loadChildren: async () => + import( + "./configurator-section/custom-configurator-section.module" + ) as object as Promise>, + }, + { + path: CustomizationModuleRoute.Widget, + loadChildren: async () => + import("./widget/custom-widget.module") as object as Promise< + Type + >, + }, + { + path: CustomizationModuleRoute.Formatter, + loadChildren: async () => + import("./formatter/custom-formatter.module") as object as Promise< + Type + >, + }, + { + path: CustomizationModuleRoute.DataSourceConfigurator, + loadChildren: async () => + import( + "./data-source-configurator/custom-data-source-configurator.module" + ) as object as Promise>, + }, +]; + +@NgModule({ + imports: [RouterModule.forChild(routes)], + providers: [ConfiguratorHeadingService], +}) +export default class CustomizationModule {} +`, + "tutorials/customization/data-source-configurator/custom-data-source-configurator-docs.component.ts": `// © 2022 SolarWinds Worldwide, LLC. All rights reserved. +// +// Permission is hereby granted, free of charge, to any person obtaining a copy +// of this software and associated documentation files (the "Software"), to +// deal in the Software without restriction, including without limitation the +// rights to use, copy, modify, merge, publish, distribute, sublicense, and/or +// sell copies of the Software, and to permit persons to whom the Software is +// furnished to do so, subject to the following conditions: +// +// The above copyright notice and this permission notice shall be included in +// all copies or substantial portions of the Software. +// +// THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR +// IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, +// FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE +// AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER +// LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, +// OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN +// THE SOFTWARE. + +import { Component } from "@angular/core"; + +@Component({ + selector: "nui-custom-data-source-configurator-docs", + templateUrl: "./custom-data-source-configurator-docs.component.html", + standalone: false, +}) +export class CustomDataSourceConfiguratorDocComponent {} +`, + "tutorials/customization/data-source-configurator/custom-data-source-configurator.module.ts": `// © 2022 SolarWinds Worldwide, LLC. All rights reserved. +// +// Permission is hereby granted, free of charge, to any person obtaining a copy +// of this software and associated documentation files (the "Software"), to +// deal in the Software without restriction, including without limitation the +// rights to use, copy, modify, merge, publish, distribute, sublicense, and/or +// sell copies of the Software, and to permit persons to whom the Software is +// furnished to do so, subject to the following conditions: +// +// The above copyright notice and this permission notice shall be included in +// all copies or substantial portions of the Software. +// +// THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR +// IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, +// FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE +// AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER +// LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, +// OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN +// THE SOFTWARE. + +import { NgModule } from "@angular/core"; +import { ReactiveFormsModule } from "@angular/forms"; +import { RouterModule, Routes } from "@angular/router"; + +// eslint-disable-next-line max-len +import { + NuiButtonModule, + NuiDocsModule, + NuiFormFieldModule, + NuiIconModule, + NuiMessageModule, + NuiSelectV2Module, + NuiSwitchModule, + NuiTextboxModule, + NuiValidationMessageModule, + DEMO_PATH_TOKEN, +} from "@nova-ui/bits"; +import { + NuiDashboardConfiguratorModule, + NuiDashboardsModule, +} from "@nova-ui/dashboards"; + +import { CustomDataSourceConfiguratorDocComponent } from "./custom-data-source-configurator-docs.component"; +import { + CustomDataSourceConfiguratorExampleComponent, + HarryPotterDataSourceConfiguratorComponent, +} from "./example/custom-data-source-configurator-example.component"; +import { getDemoFiles } from "../../../../../demo-files-factory"; + +const routes: Routes = [ + { + path: "", + component: CustomDataSourceConfiguratorDocComponent, + data: { + srlc: { + hideIndicator: true, + }, + showThemeSwitcher: true, + }, + }, +]; + +@NgModule({ + imports: [ + RouterModule.forChild(routes), + NuiDocsModule, + NuiButtonModule, + NuiMessageModule, + NuiDashboardConfiguratorModule, + NuiDashboardsModule, + NuiFormFieldModule, + NuiTextboxModule, + NuiSwitchModule, + NuiSelectV2Module, + NuiValidationMessageModule, + NuiIconModule, + ReactiveFormsModule, + ], + declarations: [ + CustomDataSourceConfiguratorDocComponent, + CustomDataSourceConfiguratorExampleComponent, + HarryPotterDataSourceConfiguratorComponent, + ], + providers: [ + { + provide: DEMO_PATH_TOKEN, + useValue: getDemoFiles("data-source-configurator"), + }, + ], +}) +export default class CustomDataSourceConfiguratorModuleRoute {} +`, + "tutorials/customization/data-source-configurator/example/custom-data-source-configurator-example.component.ts": `// © 2022 SolarWinds Worldwide, LLC. All rights reserved. +// +// Permission is hereby granted, free of charge, to any person obtaining a copy +// of this software and associated documentation files (the "Software"), to +// deal in the Software without restriction, including without limitation the +// rights to use, copy, modify, merge, publish, distribute, sublicense, and/or +// sell copies of the Software, and to permit persons to whom the Software is +// furnished to do so, subject to the following conditions: +// +// The above copyright notice and this permission notice shall be included in +// all copies or substantial portions of the Software. +// +// THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR +// IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, +// FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE +// AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER +// LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, +// OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN +// THE SOFTWARE. + +import { HttpClient, HttpErrorResponse } from "@angular/common/http"; +import { + ChangeDetectorRef, + Component, + Inject, + Injectable, + Injector, + OnDestroy, + OnInit, +} from "@angular/core"; +import { FormBuilder, Validators } from "@angular/forms"; +import { GridsterConfig, GridsterItem } from "angular-gridster2"; +import { BehaviorSubject } from "rxjs"; +import { finalize } from "rxjs/operators"; + +import { + DataSourceService, + EventBus, + IEvent, + IFilteringOutputs, + LoggerService, +} from "@nova-ui/bits"; +import { + ComponentRegistryService, + ConfiguratorHeadingService, + DataSourceConfigurationV2Component, + DATA_SOURCE, + DEFAULT_PIZZAGNA_ROOT, + IConfigurable, + IDashboard, + IKpiData, + IProperties, + IProviderConfiguration, + IRefresherProperties, + IWidget, + IWidgets, + KpiComponent, + NOVA_KPI_DATASOURCE_ADAPTER, + PizzagnaLayer, + PIZZAGNA_EVENT_BUS, + ProviderRegistryService, + WellKnownPathKey, + WellKnownProviders, + WidgetTypesService, +} from "@nova-ui/dashboards"; + +/** + * This component will serve as the data source accordion in the configurator. + */ +@Component({ + selector: "harry-potter-data-source-configurator", + styleUrls: ["./custom-data-source-configurator-example.component.less"], + template: \` + +
+ +
+ Data Source +
+ Harry Potter Books +
+
+
+
+ + + + {{ book.title }} + + + +
+
+ + + + {{ metric.label }} + + + +
+
+ \`, + standalone: false, +}) +@Injectable() +export class HarryPotterDataSourceConfiguratorComponent + extends DataSourceConfigurationV2Component + implements OnInit +{ + // This lateLoadKey allows the component to be able to be registered by the componentRegistry + public static lateLoadKey = "HarryPotterDataSourceConfiguratorComponent"; + + // Array of books that will populate the book select + public books = [ + { + id: "5MQFrgEACAAJ", + title: $localize\`Harry Potter and the Sorcerer's Stone\`, + }, + { + id: "5iTebBW-w7QC", + title: $localize\`Harry Potter and the Chamber of Secrets\`, + }, + ]; + + // Array of metrics that will populate the metric select + public metrics = [ + { + id: "averageRating", + label: $localize\`Average Rating\`, + }, + { + id: "ratingsCount", + label: $localize\`Ratings Count\`, + }, + ]; + + // These need to be injected because DataSourceConfigurationV2Component uses them + constructor( + changeDetector: ChangeDetectorRef, + configuratorHeading: ConfiguratorHeadingService, + formBuilder: FormBuilder, + providerRegistryService: ProviderRegistryService, + @Inject(PIZZAGNA_EVENT_BUS) eventBus: EventBus, + injector: Injector, + logger: LoggerService + ) { + super( + changeDetector, + configuratorHeading, + formBuilder, + providerRegistryService, + eventBus, + injector, + logger + ); + } + + // Overriding 'ngOnInit' to add custom controls to the 'properties' form group + public ngOnInit(): void { + super.ngOnInit(); + + // Overriding the 'properties' control on the form to create a form group that accommodates our custom properties + this.form.setControl( + "properties", + this.formBuilder.group({ + bookId: [this.properties?.bookId ?? "", Validators.required], + metric: [this.properties?.metric ?? "", Validators.required], + }) + ); + // The default data source control has a required validator we're removing that validator here since we aren't using it. + this.form.setControl("dataSource", this.formBuilder.control(null)); + // Here we set the providerId to our only data source so when a new tile gets created it will default to it. + this.form.get("providerId")?.setValue(AcmeKpiDataSource.providerId); + // Here we subscribe to the form and if there are any changes we invoke the data source + this.form.valueChanges.subscribe((value) => { + if (!value.providerId) { + return; + } + this.invokeDataSource(value); + }); + } +} + +/** + * A simple KPI data source to retrieve the average rating of Harry Potter and the Sorcerer's Stone (book) via googleapis + */ +@Injectable() +export class AcmeKpiDataSource + extends DataSourceService + implements OnDestroy, IConfigurable +{ + // This is the ID we'll use to identify the provider + public static providerId = "AcmeKpiDataSource"; + + // Use this subject to communicate the data source's busy state + public busy = new BehaviorSubject(false); + + public properties: IProperties; + + constructor(private http: HttpClient) { + super(); + } + + // This function MUST be implemented in order to receive property updates from our configurator + public updateConfiguration(properties: IProperties): void { + // Saving the properties because we will need it for this data source. + this.properties = properties; + } + + // In this example, getFilteredData is invoked every 10 minutes (Take a look at the refresher + // provider definition in the widget configuration below to see how the interval is set) + public async getFilteredData(): Promise { + // For loading indicator to show + this.busy.next(true); + return new Promise((resolve) => { + // *** Make a resource request to an external API (if needed) + this.http + .get( + \`https://www.googleapis.com/books/v1/volumes/\${this.properties?.bookId}\` + ) + // For loading indicator to be hidden + .pipe(finalize(() => this.busy.next(false))) + .subscribe({ + next: (data: any) => { + resolve({ + result: { + value: data.volumeInfo[this.properties?.metric], + }, + }); + }, + error: (error: HttpErrorResponse) => { + resolve({ + result: null, + error: { + type: error.status, + }, + }); + }, + }); + }); + } + + public ngOnDestroy(): void { + this.outputsSubject.complete(); + } +} + +/** + * A component that instantiates the dashboard + */ +@Component({ + selector: "custom-data-source-configurator-example", + templateUrl: "./custom-data-source-configurator-example.component.html", + styleUrls: ["./custom-data-source-configurator-example.component.less"], + standalone: false, +}) +export class CustomDataSourceConfiguratorExampleComponent implements OnInit { + // This variable will hold all the data needed to define the layout and behavior of the widgets. + // Pass this to the dashboard component's dashboard input in the template. + public dashboard: IDashboard; + + // Angular gridster requires a configuration object even if it's empty. + // Pass this to the dashboard component's gridsterConfig input in the template. + public gridsterConfig: GridsterConfig = {}; + + // Boolean which dashboard takes in as an input if its true it allows you to move widgets around. + public editMode: boolean = false; + + constructor( + // WidgetTypesService provides the widget's necessary structure information + private widgetTypesService: WidgetTypesService, + + // In general, the ProviderRegistryService is used for making entities available for injection into dynamically loaded components. + private providerRegistry: ProviderRegistryService, + + // Inject the ComponentRegistryService to make our custom component available for late loading by the dashboards framework + private componentRegistry: ComponentRegistryService + ) {} + + public ngOnInit(): void { + // Registering the new data source configurator so it can be used. + this.componentRegistry.registerByLateLoadKey( + HarryPotterDataSourceConfiguratorComponent + ); + // Registering the data source for injection into the KPI tile. + // Note: Each tile of a KPI widget is assigned its own instance of the data source + this.providerRegistry.setProviders({ + [AcmeKpiDataSource.providerId]: { + provide: DATA_SOURCE, + useClass: AcmeKpiDataSource, + // Any dependencies that need to be injected into the provider must be listed here + deps: [HttpClient], + }, + }); + + const kpiWidgetTemplate = this.widgetTypesService.getWidgetType( + "kpi", + 1 + ); + + this.widgetTypesService.setNode( + // This is the template we grabbed above with getWidgetType + kpiWidgetTemplate, + // We are setting the editor/configurator part of the widget template + "configurator", + // This is the path to go to the data source config component type. + WellKnownPathKey.DataSourceConfigComponentType, + // We are changing it to use the component we just created above instead of the default. + HarryPotterDataSourceConfiguratorComponent.lateLoadKey + ); + + // We're using a static configuration object for this example, but this is where + // the widget's configuration could potentially be populated from a database + const kpiWidget = widgetConfig; + const widgetIndex: IWidgets = { + // Complete the KPI widget with information coming from its type definition + [kpiWidget.id]: + this.widgetTypesService.mergeWithWidgetType(kpiWidget), + }; + + // Setting the widget dimensions and position (this is for gridster) + const positions: Record = { + [kpiWidget.id]: { + cols: 4, + rows: 6, + y: 0, + x: 0, + }, + }; + + // Finally, assigning the variables we created above to the dashboard + this.dashboard = { + positions, + widgets: widgetIndex, + }; + } +} + +const widgetConfig: IWidget = { + id: "widget1", + type: "kpi", + pizzagna: { + [PizzagnaLayer.Configuration]: { + [DEFAULT_PIZZAGNA_ROOT]: { + providers: { + [WellKnownProviders.Refresher]: { + properties: { + // Configuring the refresher interval so that our data source is invoked every ten minutes + interval: 60 * 10, + enabled: true, + } as IRefresherProperties, + } as Partial, + }, + }, + header: { + properties: { + title: "Harry Potter and the Sorcerer's Stone", + subtitle: "By J. K. Rowling", + }, + }, + tiles: { + properties: { + nodes: ["kpi1"], + }, + }, + kpi1: { + id: "kpi1", + componentType: KpiComponent.lateLoadKey, + properties: { + widgetData: { + units: "out of 5 Stars", + label: "Average Rating", + }, + }, + providers: { + [WellKnownProviders.DataSource]: { + // Setting the data source providerId for the tile with id "kpi1" + providerId: AcmeKpiDataSource.providerId, + properties: { + bookId: "5MQFrgEACAAJ", + metric: "averageRating", + }, + } as IProviderConfiguration, + [WellKnownProviders.Adapter]: { + providerId: NOVA_KPI_DATASOURCE_ADAPTER, + properties: { + componentId: "kpi1", + propertyPath: "widgetData", + }, + } as IProviderConfiguration, + }, + }, + }, + }, +}; +`, + "tutorials/customization/formatter/custom-formatter.module.ts": `// © 2022 SolarWinds Worldwide, LLC. All rights reserved. +// +// Permission is hereby granted, free of charge, to any person obtaining a copy +// of this software and associated documentation files (the "Software"), to +// deal in the Software without restriction, including without limitation the +// rights to use, copy, modify, merge, publish, distribute, sublicense, and/or +// sell copies of the Software, and to permit persons to whom the Software is +// furnished to do so, subject to the following conditions: +// +// The above copyright notice and this permission notice shall be included in +// all copies or substantial portions of the Software. +// +// THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR +// IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, +// FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE +// AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER +// LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, +// OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN +// THE SOFTWARE. + +import { NgModule } from "@angular/core"; +import { ReactiveFormsModule } from "@angular/forms"; +import { RouterModule, Routes } from "@angular/router"; + +// eslint-disable-next-line max-len +import { + NuiButtonModule, + NuiDocsModule, + NuiFormFieldModule, + NuiIconModule, + NuiMessageModule, + NuiSelectV2Module, + NuiSwitchModule, + NuiTextboxModule, + NuiValidationMessageModule, + DEMO_PATH_TOKEN, +} from "@nova-ui/bits"; +import { NuiDashboardsModule } from "@nova-ui/dashboards"; + +import { CustomDonutContentFormatterDocComponent } from "./donut-content-formatter-example/custom-donut-content-formatter-docs.component"; +import { + CustomDonutContentFormatterComponent, + CustomDonutContentFormatterConfiguratorComponent, + CustomDonutContentFormatterExampleComponent, +} from "./donut-content-formatter-example/custom-donut-content-formatter-example.component"; +import { CustomFormatterDocComponent } from "./formatter-example/custom-formatter-docs.component"; +import { + CustomFormatterComponent, + CustomFormatterConfiguratorComponent, + CustomFormatterExampleComponent, +} from "./formatter-example/custom-formatter-example.component"; +import { getDemoFiles } from "../../../../../demo-files-factory"; + +const routes: Routes = [ + { + path: "table-formatter", + component: CustomFormatterDocComponent, + data: { + srlc: { + hideIndicator: true, + }, + showThemeSwitcher: true, + }, + }, + { + path: "donut-content-formatter", + component: CustomDonutContentFormatterDocComponent, + data: { + srlc: { + hideIndicator: true, + }, + showThemeSwitcher: true, + }, + }, +]; + +@NgModule({ + imports: [ + RouterModule.forChild(routes), + NuiDocsModule, + NuiButtonModule, + NuiMessageModule, + NuiDashboardsModule, + NuiFormFieldModule, + NuiTextboxModule, + NuiSwitchModule, + NuiSelectV2Module, + NuiValidationMessageModule, + NuiIconModule, + ReactiveFormsModule, + ], + declarations: [ + CustomDonutContentFormatterComponent, + CustomDonutContentFormatterExampleComponent, + CustomDonutContentFormatterConfiguratorComponent, + CustomDonutContentFormatterDocComponent, + CustomFormatterDocComponent, + CustomFormatterExampleComponent, + CustomFormatterConfiguratorComponent, + CustomFormatterComponent, + ], + providers: [ + { + provide: DEMO_PATH_TOKEN, + useValue: getDemoFiles("formatter"), + }, + ], +}) +export default class CustomFormatterModuleRoute {} +`, + "tutorials/customization/formatter/donut-content-formatter-example/custom-donut-content-formatter-docs.component.ts": `// © 2022 SolarWinds Worldwide, LLC. All rights reserved. +// +// Permission is hereby granted, free of charge, to any person obtaining a copy +// of this software and associated documentation files (the "Software"), to +// deal in the Software without restriction, including without limitation the +// rights to use, copy, modify, merge, publish, distribute, sublicense, and/or +// sell copies of the Software, and to permit persons to whom the Software is +// furnished to do so, subject to the following conditions: +// +// The above copyright notice and this permission notice shall be included in +// all copies or substantial portions of the Software. +// +// THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR +// IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, +// FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE +// AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER +// LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, +// OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN +// THE SOFTWARE. + +import { Component } from "@angular/core"; + +@Component({ + selector: "nui-custom-donut-content-formatter-docs", + templateUrl: "./custom-donut-content-formatter-docs.component.html", + standalone: false, +}) +export class CustomDonutContentFormatterDocComponent {} +`, + "tutorials/customization/formatter/donut-content-formatter-example/custom-donut-content-formatter-example.component.ts": `// © 2022 SolarWinds Worldwide, LLC. All rights reserved. +// +// Permission is hereby granted, free of charge, to any person obtaining a copy +// of this software and associated documentation files (the "Software"), to +// deal in the Software without restriction, including without limitation the +// rights to use, copy, modify, merge, publish, distribute, sublicense, and/or +// sell copies of the Software, and to permit persons to whom the Software is +// furnished to do so, subject to the following conditions: +// +// The above copyright notice and this permission notice shall be included in +// all copies or substantial portions of the Software. +// +// THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR +// IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, +// FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE +// AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER +// LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, +// OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN +// THE SOFTWARE. + +import { HttpClient } from "@angular/common/http"; +import { + ChangeDetectorRef, + Component, + Injectable, + Input, + OnChanges, + OnDestroy, + OnInit, + SimpleChanges, +} from "@angular/core"; +import { FormBuilder, FormGroup, Validators } from "@angular/forms"; +import { GridsterConfig, GridsterItem } from "angular-gridster2"; +import { Subject } from "rxjs"; +import { takeUntil, tap } from "rxjs/operators"; + +import { + DataSourceService, + IconService, + IDataSource, + IFilteringOutputs, + LoggerService, +} from "@nova-ui/bits"; +import { + ChartAssist, + IAccessors, + IChartAssistEvent, + IChartAssistSeries, +} from "@nova-ui/charts"; +import { + ComponentRegistryService, + ConfiguratorHeadingService, + DATA_SOURCE, + DonutChartFormatterConfiguratorComponent, + DonutContentPercentageConfigurationComponent, + DonutContentPercentageFormatterComponent, + DonutContentSumFormatterComponent, + IDashboard, + IFormatterDefinition, + IHasChangeDetector, + IProperties, + IProportionalWidgetChartOptions, + IProportionalWidgetConfig, + IProviderConfiguration, + IWidget, + IWidgets, + LegendPlacement, + PizzagnaLayer, + ProportionalWidgetChartTypes, + ProviderRegistryService, + WellKnownPathKey, + WellKnownProviders, + WidgetTypesService, +} from "@nova-ui/dashboards"; + +export enum Units { + Days = "Day(s)", + Weeks = "Week(s)", + Hours = "Hour(s)", +} + +@Component({ + selector: "custom-donut-content-formatter", + host: { class: "d-flex flex-column align-items-center" }, + template: \` +
+ + {{ chartMetric || properties?.currentMetric || data[0].id }} +
+
+ {{ chartContent }} +
+
+ {{ units }} +
+
\`, + styleUrls: ["./custom-donut-content-formatter-example.component.less"], + standalone: false, +}) +export class CustomDonutContentFormatterComponent + implements IHasChangeDetector, OnInit, OnChanges +{ + public static lateLoadKey = "CustomDonutContentFormatterComponent"; + + // Used to emphasize the chart series when user interacts either with the chart legend, or chart segments. + public emphasizedSeriesData: IChartAssistSeries | undefined; + + // Current raw value of the metric to display + public currentMetricData: number; + + // Metric value rendered inside the template, when user selects a metric, and gets automatically recalculated depending on selected units + public chartContent: number; + + // Metric value rendered inside the template, when user interacts with either chart legend, or chart segments + public chartMetric: number; + + // Units which user can select from the configuration + public units: Units = Units.Days; + + private readonly destroy$ = new Subject(); + + constructor(public changeDetector: ChangeDetectorRef) {} + + // The data we receive from the chart, including metrics names and their values + @Input() data: IChartAssistSeries[]; + + // We use this chart assist instance to subscribe to the events triggered when an interaction with the chart occurs + @Input() chartAssist: ChartAssist; + + // These are the current properties from pizzagna. Used to use data set at the configuration layer + @Input() properties: IProperties; + + public ngOnChanges(changes: SimpleChanges): void { + if (changes.properties || !this.properties) { + // If current metric is not in the list of metrics any more we fall back to the very first one from the list we get from the datasource + this.currentMetricData = + this.data.find( + (item) => item.id === this.properties?.currentMetric + )?.data[0] || this.data[0].data[0]; + + // We either take the selected value, or fall back to the preselected default one + this.units = this.properties?.units || this.units; + } + + this.setContentValue(); + } + + public ngOnInit(): void { + // Here 'chartAssistSubject' is the entity that emits events every time user interacts with either chart legend, or chart segments. + // Subscribing to properly react on these kind of events + this.chartAssist.chartAssistSubject + .pipe( + tap( + (data: IChartAssistEvent) => + (this.emphasizedSeriesData = this.data.find( + (item) => item.id === data.payload.seriesId + )) + ), + tap(() => this.setContentValue()), + tap(() => this.setMetricValue()), + takeUntil(this.destroy$) + ) + .subscribe(); + } + + public getConvertedData(emphData: number): number { + // Recalculating data depending on the units user selected from the configuration view + switch (this.units) { + case Units.Weeks: + return this.emphasizedSeriesData + ? this.convertToWeeks(emphData) + : this.convertToWeeks(this.currentMetricData); + + case Units.Hours: + return this.emphasizedSeriesData + ? this.convertToHours(emphData) + : this.convertToHours(this.currentMetricData); + + default: + return this.emphasizedSeriesData + ? emphData + : this.currentMetricData; + } + } + + public setContentValue(): void { + this.chartContent = this.getConvertedData( + this.emphasizedSeriesData?.data[0] + ); + } + + public setMetricValue(): void { + this.chartMetric = this.emphasizedSeriesData + ? this.data.find( + (item) => + this.getConvertedData(item.data[0]) === + this.getConvertedData(this.emphasizedSeriesData?.data[0]) + )?.id + : // if metric was not initially selected we fall back to the very first one + this.properties?.currentMetric || this.data[0].id; + } + + private convertToWeeks(days: number | undefined): number { + return days ? Number((days / 7).toFixed(2)) : 0; + } + + private convertToHours(days: number | undefined): number { + return days ? Number((days * 24).toFixed(2)) : 0; + } +} + +@Component({ + selector: "custom-donut-content-formatter-configurator", + styleUrls: ["./custom-donut-content-formatter-example.component.less"], + template: \` +
+
+ + + + {{ itemValue?.name }} + + + + This field is required + + +
+
+ + + + {{ itemValue }} + + + + This field is required + + +
+
+ \`, + standalone: false, +}) +export class CustomDonutContentFormatterConfiguratorComponent + extends DonutChartFormatterConfiguratorComponent + implements OnChanges, OnInit, IHasChangeDetector +{ + public static lateLoadKey = "CustomFormatterConfiguratorComponent"; + + constructor( + changeDetector: ChangeDetectorRef, + formBuilder: FormBuilder, + logger: LoggerService, + public iconService: IconService, + public configuratorHeading: ConfiguratorHeadingService + ) { + super(changeDetector, formBuilder, logger); + } + + public availableUnits: Units[] = [Units.Days, Units.Hours, Units.Weeks]; + + protected addCustomFormControls(form: FormGroup): void { + form.addControl( + "units", + this.formBuilder.control(Units.Days, Validators.required) + ); + } +} + +/** + * A component that instantiates the dashboard + */ +@Component({ + selector: "custom-donut-content-formatter-example", + templateUrl: "./custom-donut-content-formatter-example.component.html", + styleUrls: ["./custom-donut-content-formatter-example.component.less"], + standalone: false, +}) +export class CustomDonutContentFormatterExampleComponent implements OnInit { + public editMode: boolean = false; + // This variable will hold all the data needed to define the layout and behavior of the widgets. + // Pass this to the dashboard component's dashboard input in the template. + public dashboard: IDashboard | undefined; + + // Angular gridster requires a configuration object even if it's empty. + // Pass this to the dashboard component's gridsterConfig input in the template. + public gridsterConfig: GridsterConfig = {}; + + constructor( + // WidgetTypesService provides the widget's necessary structure information + private widgetTypesService: WidgetTypesService, + // In general, the ProviderRegistryService is used for making entities available for injection into dynamically loaded components. + private providerRegistry: ProviderRegistryService, + // Inject the ComponentRegistryService to make our custom component available for late loading by the dashboards framework + private componentRegistry: ComponentRegistryService, + private changeDetectorRef: ChangeDetectorRef + ) { + // Register the custom configurator component with the component registry to make it available + // for late loading by the dashboard framework. + this.componentRegistry.registerByLateLoadKey( + CustomDonutContentFormatterConfiguratorComponent + ); + // Register the custom formatter component with the component registry to make it available + // for late loading by the dashboard framework. + this.componentRegistry.registerByLateLoadKey( + CustomDonutContentFormatterComponent + ); + + // Grab the widget's default template which will be needed as a parameter for setNode below. + const proportional = this.widgetTypesService.getWidgetType( + "proportional", + 1 + ); + + const donutFormatters: IFormatterDefinition[] = [ + { + componentType: DonutContentSumFormatterComponent.lateLoadKey, + label: $localize\`Sum\`, + } as IFormatterDefinition, + { + componentType: + DonutContentPercentageFormatterComponent.lateLoadKey, + label: $localize\`Percentage\`, + configurationComponent: + DonutContentPercentageConfigurationComponent.lateLoadKey, + } as IFormatterDefinition, + { + componentType: CustomDonutContentFormatterComponent.lateLoadKey, + label: $localize\`Custom\`, + // This is a custom configurator that will pop up below the formatter once it gets selected + configurationComponent: + CustomDonutContentFormatterConfiguratorComponent.lateLoadKey, + } as IFormatterDefinition, + ]; + + this.widgetTypesService.setNode( + // This is the template we grabbed above with getWidgetType + proportional, + // We are setting the editor/configurator part of the widget template + "configurator", + // This indicates which node you are changing and we want to change the formatters available for selection in the editor. + WellKnownPathKey.Formatters, + // We are setting the available formatters with the array we created above. + donutFormatters + ); + + // This sets the donut chart's datasource to have the StatusesExampleDatasource so the drop down is filled similar to the line above. + this.widgetTypesService.setNode( + proportional, + "configurator", + WellKnownPathKey.DataSourceProviders, + [StatusesExampleDatasource.providerId] + ); + + // Registering the data source for injection into the widget. + this.providerRegistry.setProviders({ + [StatusesExampleDatasource.providerId]: { + provide: DATA_SOURCE, + useClass: StatusesExampleDatasource, + // Any dependencies that need to be injected into the provider must be listed here + deps: [], + }, + }); + } + + public ngOnInit(): void { + this.initializeDashboard(); + } + + /** Used for restoring widgets state */ + public reInitializeDashboard(): void { + // destroys the components and their providers so the dashboard can re init data + this.dashboard = undefined; + this.changeDetectorRef.detectChanges(); + + this.initializeDashboard(); + } + + public initializeDashboard(): void { + // We're using a static configuration object for this example, but this is where + // the widget's configuration could potentially be populated from a database + const proportionalWidget = widgetConfig; + const widgetIndex: IWidgets = { + // Enhance the widget with information coming from it's type definition + [proportionalWidget.id]: + this.widgetTypesService.mergeWithWidgetType(proportionalWidget), + }; + + // Setting the widget dimensions and position (this is for gridster) + const positions: Record = { + [proportionalWidget.id]: { + cols: 12, + rows: 6, + y: 0, + x: 0, + }, + }; + + // Finally, assigning the variables we created above to the dashboard + this.dashboard = { + positions, + widgets: widgetIndex, + }; + } +} + +export interface IStatusesWidgetData { + id: string; + name: string; + data: number[]; +} + +export const randomStatusesWidgetData: IStatusesWidgetData[] = [ + { + id: "Down", + name: "Down", + data: [Math.round(Math.random() * 100)], + }, + { + id: "Critical", + name: "Critical", + data: [Math.round(Math.random() * 100)], + }, + { + id: "Warning", + name: "Warning", + data: [Math.round(Math.random() * 100)], + }, + { + id: "Unknown", + name: "Unknown", + data: [Math.round(Math.random() * 100)], + }, + { + id: "Up", + name: "Up", + data: [Math.round(Math.random() * 100)], + }, + { + id: "Unmanaged", + name: "Unmanaged", + data: [Math.round(Math.random() * 100)], + }, +]; + +@Injectable() +export class StatusesExampleDatasource + extends DataSourceService + implements IDataSource, OnDestroy +{ + public static providerId = "StatusesExampleDatasource"; + + public busy = new Subject(); + + constructor(private http: HttpClient) { + super(); + } + + public async getFilteredData(): Promise { + this.busy.next(true); + + return new Promise((resolve) => { + setTimeout(() => { + resolve({ + result: randomStatusesWidgetData, + }); + this.busy.next(false); + }, 1000); + }); + } + + public ngOnDestroy(): void { + this.outputsSubject.complete(); + } +} + +export const widgetConfig: IWidget = { + id: "proportionalWidgetId", + type: "proportional", + pizzagna: { + [PizzagnaLayer.Configuration]: { + header: { + properties: { + title: "Proportional Widget!", + subtitle: "Proportional widget with legend formatters", + }, + }, + chart: { + providers: { + [WellKnownProviders.DataSource]: { + providerId: StatusesExampleDatasource.providerId, + } as IProviderConfiguration, + }, + properties: { + configuration: { + interactive: true, + chartOptions: { + type: ProportionalWidgetChartTypes.DonutChart, + legendPlacement: LegendPlacement.Right, + contentFormatter: { + componentType: + CustomDonutContentFormatterComponent.lateLoadKey, + properties: { + // here you can set the default value for the metric you receive. If not set the first one from the list will be taken + currentMetric: "Down", + // here you set the default value for your custom controls. If not set the first one from the list will be taken + units: Units.Weeks, + }, + }, + } as IProportionalWidgetChartOptions, + chartColors: [ + "var(--nui-color-chart-eight)", + "var(--nui-color-chart-nine)", + "var(--nui-color-chart-ten)", + ], + } as IProportionalWidgetConfig, + }, + }, + }, + }, +}; +`, + "tutorials/customization/formatter/formatter-example/custom-formatter-docs.component.ts": `// © 2022 SolarWinds Worldwide, LLC. All rights reserved. +// +// Permission is hereby granted, free of charge, to any person obtaining a copy +// of this software and associated documentation files (the "Software"), to +// deal in the Software without restriction, including without limitation the +// rights to use, copy, modify, merge, publish, distribute, sublicense, and/or +// sell copies of the Software, and to permit persons to whom the Software is +// furnished to do so, subject to the following conditions: +// +// The above copyright notice and this permission notice shall be included in +// all copies or substantial portions of the Software. +// +// THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR +// IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, +// FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE +// AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER +// LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, +// OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN +// THE SOFTWARE. + +import { Component } from "@angular/core"; + +@Component({ + selector: "nui-custom-formatter-docs", + templateUrl: "./custom-formatter-docs.component.html", + standalone: false, +}) +export class CustomFormatterDocComponent {} +`, + "tutorials/customization/formatter/formatter-example/custom-formatter-example.component.ts": `// © 2022 SolarWinds Worldwide, LLC. All rights reserved. +// +// Permission is hereby granted, free of charge, to any person obtaining a copy +// of this software and associated documentation files (the "Software"), to +// deal in the Software without restriction, including without limitation the +// rights to use, copy, modify, merge, publish, distribute, sublicense, and/or +// sell copies of the Software, and to permit persons to whom the Software is +// furnished to do so, subject to the following conditions: +// +// The above copyright notice and this permission notice shall be included in +// all copies or substantial portions of the Software. +// +// THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR +// IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, +// FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE +// AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER +// LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, +// OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN +// THE SOFTWARE. + +import { ListRange } from "@angular/cdk/collections"; +import { ChangeDetectorRef, Component, Input, OnInit } from "@angular/core"; +import { FormBuilder, FormGroup, Validators } from "@angular/forms"; +import { GridsterConfig, GridsterItem } from "angular-gridster2"; +import isEqual from "lodash/isEqual"; +import orderBy from "lodash/orderBy"; +import { BehaviorSubject } from "rxjs"; + +import { + DataSourceService, + IconService, + IDataField, + INovaFilteringOutputs, + INovaFilters, + ISorterFilter, + LoggerService, +} from "@nova-ui/bits"; +import { + ComponentRegistryService, + ConfiguratorHeadingService, + DATA_SOURCE, + FormatterConfiguratorComponent, + IDashboard, + IDataSourceOutput, + IFormatterDefinition, + IHasChangeDetector, + ITableWidgetColumnConfig, + ITableWidgetSorterConfig, + IWidget, + IWidgets, + PizzagnaLayer, + ProviderRegistryService, + RawFormatterComponent, + TableFormatterRegistryService, + WellKnownPathKey, + WellKnownProviders, + WidgetTypesService, +} from "@nova-ui/dashboards"; + +export const BREW_API_URL = "https://api.punkapi.com/v2/beers"; + +@Component({ + selector: "custom-formatter", + host: { class: "d-flex" }, + template: \` +
+
+ +
+
+ {{ data.value }} +
+
+ \`, + styleUrls: ["./custom-formatter-example.component.less"], + standalone: false, +}) +export class CustomFormatterComponent implements IHasChangeDetector { + public static lateLoadKey = "CustomFormatterComponent"; + + constructor(public changeDetector: ChangeDetectorRef) {} + + @Input() public data: any; + @Input() public icon: string; + @Input() public threshold: string; + + public isAboveThreshold(): boolean { + return parseFloat(this.threshold) <= this.data.value; + } +} + +@Component({ + selector: "custom-formatter-configurator", + styleUrls: ["./custom-formatter-example.component.less"], + template: \` +
+
+ + + + {{ item.label }} + + + + This field is required + + +
+
+ + + + + + + + This field is required + + +
+
+ + + + + This field is required + + +
+
+ +
+
+ +
+ + +
+ + + Select Item + +
+ \`, + standalone: false, +}) +export class CustomFormatterConfiguratorComponent + extends FormatterConfiguratorComponent + implements OnInit, IHasChangeDetector +{ + public static lateLoadKey = "CustomFormatterConfiguratorComponent"; + + constructor( + changeDetector: ChangeDetectorRef, + configuratorHeading: ConfiguratorHeadingService, + formBuilder: FormBuilder, + logger: LoggerService, + public iconService: IconService + ) { + super(changeDetector, configuratorHeading, formBuilder, logger); + } + + public formatterFormGroup: FormGroup; + // This array is where the icon names will be stored + public options: string[] = []; + + public ngOnInit(): void { + for (const icon of this.iconService.icons) { + if (icon.category === "severity") { + this.options.push(icon.name); + } + } + } + + protected addCustomFormControls(form: FormGroup): void { + form.addControl( + "icon", + this.formBuilder.control("", Validators.required) + ); + form.addControl( + "threshold", + this.formBuilder.control(null, Validators.required) + ); + } +} + +/** + * A component that instantiates the dashboard + */ +@Component({ + selector: "custom-formatter-example", + templateUrl: "./custom-formatter-example.component.html", + styleUrls: ["./custom-formatter-example.component.less"], + standalone: false, +}) +export class CustomFormatterExampleComponent implements OnInit { + public editMode: boolean = false; + // This variable will hold all the data needed to define the layout and behavior of the widgets. + // Pass this to the dashboard component's dashboard input in the template. + public dashboard: IDashboard | undefined; + + // Angular gridster requires a configuration object even if it's empty. + // Pass this to the dashboard component's gridsterConfig input in the template. + public gridsterConfig: GridsterConfig = {}; + + constructor( + // WidgetTypesService provides the widget's necessary structure information + private widgetTypesService: WidgetTypesService, + // In general, the ProviderRegistryService is used for making entities available for injection into dynamically loaded components. + private providerRegistry: ProviderRegistryService, + // Inject the ComponentRegistryService to make our custom component available for late loading by the dashboards framework + private componentRegistry: ComponentRegistryService, + private tableFormatterRegistryService: TableFormatterRegistryService, + private changeDetectorRef: ChangeDetectorRef + ) { + // Register the custom configurator component with the component registry to make it available + // for late loading by the dashboard framework. + this.componentRegistry.registerByLateLoadKey( + CustomFormatterConfiguratorComponent + ); + // Register the custom formatter component with the component registry to make it available + // for late loading by the dashboard framework. + this.componentRegistry.registerByLateLoadKey(CustomFormatterComponent); + + // Grab the widget's default template which will be needed as a parameter for setNode below. + const table = this.widgetTypesService.getWidgetType("table", 1); + + const tableFormatters: IFormatterDefinition[] = [ + { + // This will be the component that will format the data + componentType: RawFormatterComponent.lateLoadKey, + // This is the label for what the formatter is selected in the drop down + label: $localize\`:table formatter|:No formatter\`, + // This says what datatype the formatter supports. If the value node is null, it accepts any data type. + dataTypes: { + // @ts-ignore: Ignoring compiler error to keep the same flow + value: null, + }, + }, + { + componentType: CustomFormatterComponent.lateLoadKey, + label: $localize\`:table formatter|:Custom formatter\`, + // This is a custom configurator that will pop up below the formatter once it gets selected + configurationComponent: + CustomFormatterConfiguratorComponent.lateLoadKey, + // This says what data types the formatter supports. + // In this case, it supports abv values only. + // If you look below in the table data source you'll see where we define our column's data types. + dataTypes: { + value: ["abv"], + }, + }, + ]; + + // Registering the formatters + this.tableFormatterRegistryService.addItems(tableFormatters); + + // This sets the table's datasource to have the BeerDataSource so the drop down is filled similar to the line above. + this.widgetTypesService.setNode( + table, + "configurator", + WellKnownPathKey.DataSourceProviders, + [BeerDataSource.providerId] + ); + + // Registering the data source for injection into the widget. + this.providerRegistry.setProviders({ + [BeerDataSource.providerId]: { + provide: DATA_SOURCE, + useClass: BeerDataSource, + // Any dependencies that need to be injected into the provider must be listed here + deps: [], + }, + }); + } + + public ngOnInit(): void { + this.initializeDashboard(); + } + + /** Used for restoring widgets state */ + public reInitializeDashboard(): void { + // destroys the components and their providers so the dashboard can re init data + this.dashboard = undefined; + this.changeDetectorRef.detectChanges(); + + this.initializeDashboard(); + } + + public initializeDashboard(): void { + // We're using a static configuration object for this example, but this is where + // the widget's configuration could potentially be populated from a database + const tableWidget = widgetConfig; + const widgetIndex: IWidgets = { + // Enhance the widget with information coming from it's type definition + [tableWidget.id]: + this.widgetTypesService.mergeWithWidgetType(tableWidget), + }; + + // Setting the widget dimensions and position (this is for gridster) + const positions: Record = { + [tableWidget.id]: { + cols: 12, + rows: 6, + y: 0, + x: 0, + }, + }; + + // Finally, assigning the variables we created above to the dashboard + this.dashboard = { + positions, + widgets: widgetIndex, + }; + } +} + +export interface IBrewInfo { + id: number; + name: string; + tagline: string; + first_brewed: string; + description: string; + brewers_tips: string; + abv: number; +} + +export interface IBrewDatasourceResponse { + brewInfo: IBrewInfo[]; + total: number; +} + +export class BeerDataSource extends DataSourceService { + public static providerId = "BeerDataSource"; + + private cache = Array.from({ length: 0 }); + private lastSortValue?: ISorterFilter; + private lastVirtualScroll?: ListRange; + // For simplicity, the totalItems value is hard-coded here, but in a real-world scenario the value would likely be retrieved via an async backend call + private totalItems: number = 325; + + public page: number = 1; + public busy = new BehaviorSubject(false); + + public dataFields: Array = [ + { id: "id", label: "No", dataType: "number" }, + { id: "name", label: "Name", dataType: "string" }, + { id: "tagline", label: "Tagline", dataType: "string" }, + { id: "first_brewed", label: "First Brewed", dataType: "string" }, + { id: "description", label: "Description", dataType: "string" }, + { id: "brewers_tips", label: "Brewer's Tips", dataType: "string" }, + // We are giving this field a custom data type of 'abv' so the dropdown in the custom formatter configurator can use it to filter out other data types + { id: "abv", label: "Alcohol By Volume", dataType: "abv" }, + ]; + + constructor(private logger: LoggerService) { + super(); + } + + public async getFilteredData( + filters: INovaFilters + ): Promise> { + const start = filters.virtualScroll?.value?.start ?? 0; + const end = filters.virtualScroll?.value?.end ?? 0; + const delta = end - start; + + // Note: We should start with a clean cache every time first page is requested + if (start === 0) { + this.cache = []; + } + + // This condition handles sorting. We want to sort columns without fetching another chunk of data. + // Since the data is being fetched when scrolled, we compare virtual scroll indexes here in the condition as well. + if (filters.sorter?.value) { + if ( + !isEqual(this.lastSortValue, filters.sorter.value) && + filters.virtualScroll?.value.start === 0 && + !!this.lastVirtualScroll + ) { + const totalPages = Math.ceil( + delta ? this.totalItems / delta : 1 + ); + const itemsPerPage: number = Math.max( + delta < 80 ? delta : 80, + 1 + ); + let response: Array | null = null; + let map: IBrewDatasourceResponse; + + if (filters.sorter?.value?.direction === "desc") { + this.cache = []; + for (let i = 0; i < this.page; ++i) { + response = await ( + await fetch( + \`\${BREW_API_URL}/?page=\${ + totalPages - i || 1 + }&per_page=\${itemsPerPage}\` + ) + ).json(); + + // since the last page contains only 5 items we need to fetch another page to give virtual scroll enough space to work + if (response && response.length < itemsPerPage) { + this.page++; + } + map = { + brewInfo: response?.map((result: IBrewInfo) => ({ + id: result.id, + name: result.name, + tagline: result.tagline, + first_brewed: result.first_brewed, + description: result.description, + brewers_tips: result.brewers_tips, + })), + total: response?.length, + } as IBrewDatasourceResponse; + this.cache = + totalPages - i !== 0 + ? this.cache.concat(map.brewInfo) + : this.cache; + } + } + + if (filters.sorter?.value?.direction === "asc") { + this.cache = []; + for (let i = 0; i < this.page; i++) { + response = await ( + await fetch( + \`\${BREW_API_URL}/?page=\${ + i + 1 + }&per_page=\${itemsPerPage}\` + ) + ).json(); + map = { + brewInfo: response?.map((result: IBrewInfo) => ({ + id: result.id, + name: result.name, + tagline: result.tagline, + first_brewed: result.first_brewed, + description: result.description, + brewers_tips: result.brewers_tips, + })), + total: response?.length, + } as IBrewDatasourceResponse; + this.cache = this.cache.concat(map.brewInfo); + } + } + + this.lastSortValue = filters.sorter?.value; + this.lastVirtualScroll = filters.virtualScroll?.value; + + return { + result: { + repeat: { + itemsSource: this.sortData(this.cache, filters), + }, + paginator: { total: this.totalItems }, + dataFields: this.dataFields, + }, + }; + } + } + + this.busy.next(true); + return new Promise((resolve) => { + setTimeout(() => { + this.getData(start, end, filters).then( + (response: INovaFilteringOutputs) => { + if (!response) { + return; + } + + this.cache = this.cache.concat(response.brewInfo); + + this.dataSubject.next(this.cache); + resolve({ + result: { + repeat: { + itemsSource: this.sortData( + this.cache, + filters + ), + }, + paginator: { total: this.totalItems }, + dataFields: this.dataFields, + }, + }); + + this.lastSortValue = filters.sorter?.value; + this.lastVirtualScroll = filters.virtualScroll?.value; + this.busy.next(false); + } + ); + }, 500); + }); + } + + public async getData( + start: number = 0, + end: number = 20, + filters: INovaFilters + ): Promise { + const delta = end - start; + const totalPages = Math.ceil(delta ? this.totalItems / delta : 1); + let response: Array | null = null; + // The api.punk.com is able to return only 80 items per page + const itemsPerPage: number = Math.max(delta < 80 ? delta : 80, 1); + + if (filters.sorter?.value?.direction === "asc") { + response = await ( + await fetch( + \`\${BREW_API_URL}/?page=\${this.page}&per_page=\${itemsPerPage}\` + ) + ).json(); + } + + if (filters.sorter?.value?.direction === "desc") { + response = await ( + await fetch( + \`\${BREW_API_URL}/?page=\${ + totalPages - this.page + }&per_page=\${itemsPerPage}\` + ) + ).json(); + } + + if (!filters.sorter) { + response = await ( + await fetch( + \`\${BREW_API_URL}/?page=\${this.page}&per_page=\${itemsPerPage}\` + ) + ).json(); + } + return { + brewInfo: response?.map((result: IBrewInfo, i: number) => ({ + id: result.id, + abv: result.abv, + name: result.name, + tagline: result.tagline, + first_brewed: result.first_brewed, + description: result.description, + brewers_tips: result.brewers_tips, + })), + total: response?.length, + } as IBrewDatasourceResponse; + } + + private sortData(data: IBrewInfo[], filters: INovaFilters) { + return orderBy( + data, + filters.sorter?.value?.sortBy, + filters.sorter?.value?.direction as "desc" | "asc" + ); + } +} + +export const widgetConfig: IWidget = { + id: "tableWidgetId", + type: "table", + pizzagna: { + [PizzagnaLayer.Configuration]: { + header: { + properties: { + title: "Stupendous Suds", + subtitle: "Try These Brilliant Brews", + }, + }, + table: { + providers: { + [WellKnownProviders.DataSource]: { + providerId: BeerDataSource.providerId, + }, + }, + properties: { + configuration: { + columns: [ + { + id: "column1", + label: "Beer Name", + isActive: true, + width: 185, + formatter: { + componentType: + RawFormatterComponent.lateLoadKey, + properties: { + dataFieldIds: { + value: "name", + }, + }, + }, + }, + { + id: "column2", + label: "Tagline", + isActive: true, + width: 250, + formatter: { + componentType: + RawFormatterComponent.lateLoadKey, + properties: { + dataFieldIds: { + value: "tagline", + }, + }, + }, + }, + { + id: "column3", + label: "Alcohol By Volume", + isActive: true, + width: 150, + formatter: { + componentType: + CustomFormatterComponent.lateLoadKey, + properties: { + dataFieldIds: { + value: "abv", + }, + icon: "severity_error", + threshold: "5", + }, + }, + }, + { + id: "column4", + label: "Description", + isActive: true, + formatter: { + componentType: + RawFormatterComponent.lateLoadKey, + properties: { + dataFieldIds: { + value: "description", + }, + }, + }, + }, + ] as ITableWidgetColumnConfig[], + sorterConfiguration: { + descendantSorting: false, + sortBy: "", + } as ITableWidgetSorterConfig, + hasVirtualScroll: true, + }, + }, + }, + }, + }, +}; +`, + "tutorials/customization/widget/custom-widget-docs.component.ts": `// © 2022 SolarWinds Worldwide, LLC. All rights reserved. +// +// Permission is hereby granted, free of charge, to any person obtaining a copy +// of this software and associated documentation files (the "Software"), to +// deal in the Software without restriction, including without limitation the +// rights to use, copy, modify, merge, publish, distribute, sublicense, and/or +// sell copies of the Software, and to permit persons to whom the Software is +// furnished to do so, subject to the following conditions: +// +// The above copyright notice and this permission notice shall be included in +// all copies or substantial portions of the Software. +// +// THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR +// IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, +// FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE +// AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER +// LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, +// OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN +// THE SOFTWARE. + +import { Component } from "@angular/core"; + +@Component({ + selector: "custom-widget-docs", + templateUrl: "./custom-widget-docs.component.html", + standalone: false, +}) +export class CustomWidgetDocsComponent {} +`, + "tutorials/customization/widget/custom-widget.component.ts": `// © 2022 SolarWinds Worldwide, LLC. All rights reserved. +// +// Permission is hereby granted, free of charge, to any person obtaining a copy +// of this software and associated documentation files (the "Software"), to +// deal in the Software without restriction, including without limitation the +// rights to use, copy, modify, merge, publish, distribute, sublicense, and/or +// sell copies of the Software, and to permit persons to whom the Software is +// furnished to do so, subject to the following conditions: +// +// The above copyright notice and this permission notice shall be included in +// all copies or substantial portions of the Software. +// +// THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR +// IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, +// FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE +// AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER +// LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, +// OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN +// THE SOFTWARE. + +import { + ChangeDetectionStrategy, + ChangeDetectorRef, + Component, + EventEmitter, + HostBinding, + Input, + OnChanges, + OnInit, + Output, + SimpleChanges, +} from "@angular/core"; +import { FormBuilder, FormGroup, Validators } from "@angular/forms"; +import { GridsterConfig, GridsterItem } from "angular-gridster2"; + +import { IMenuItem } from "@nova-ui/bits"; +import { + ComponentRegistryService, + ConfiguratorHeadingService, + DEFAULT_PIZZAGNA_ROOT, + EVENT_PROXY, + FormStackComponent, + IConverterFormPartsProperties, + IDashboard, + IHasChangeDetector, + IHasForm, + IProviderConfiguration, + IWidget, + IWidgets, + IWidgetTypeDefinition, + NOVA_GENERIC_CONVERTER, + NOVA_TITLE_AND_DESCRIPTION_CONVERTER, + PizzagnaLayer, + refresher, + StackComponent, + TitleAndDescriptionConfigurationComponent, + WellKnownPathKey, + WellKnownProviders, + widgetBodyContentNodes, + WidgetConfiguratorSectionComponent, + WidgetTypesService, + WIDGET_BODY, + WIDGET_HEADER, + WIDGET_LOADING, +} from "@nova-ui/dashboards"; + +// The custom widget type name we'll use +const CUSTOM_WIDGET_TYPENAME = "example-custom-widget"; +// The path key we'll use for image selection in the configurator definition +const IMAGE_SELECTION_CONFIGURATOR_PATH_KEY = "imageSelection"; + +@Component({ + selector: "custom-widget-body", + // A simple template for our custom widget + template: \`\`, + styleUrls: ["./custom-widget.component.less"], + changeDetection: ChangeDetectionStrategy.OnPush, + standalone: false, +}) +// Remember to declare this class in the parent module +export class CustomWidgetBodyContentComponent implements IHasChangeDetector { + // Ensure that the lateLoadKey value matches class name + public static lateLoadKey = "CustomWidgetBodyContentComponent"; + + // Optionally, providing an input for styling of the host element + @Input() @HostBinding("class") public elementClass = ""; + + // We'll map this input with the configurator form using the NOVA_GENERIC_CONVERTER. + // See the customWidget definition at the bottom of the file. + @Input() public imageSource: string; + + // Injecting the ChangeDetectorRef to implement IHasChangeDetector. + // This allows the dashboard framework to reliably propagate component property changes to the DOM. + constructor(public changeDetector: ChangeDetectorRef) {} +} + +/** + * A custom configurator section component for selecting the image source for the custom widget + */ +@Component({ + selector: "custom-configurator-section", + template: \` + + + + +
+ + +
+ Image Selection +
+ {{ imageDisplayValue }} +
+
+
+
+ + + + + {{ item.title }} + + + +
+
+ \`, + styleUrls: ["./custom-widget.component.less"], + changeDetection: ChangeDetectionStrategy.OnPush, + standalone: false, +}) +// Remember to declare this class in the parent module +export class CustomConfiguratorSectionComponent + implements OnInit, OnChanges, IHasChangeDetector, IHasForm +{ + // Ensure that the lateLoadKey value matches the class name + public static lateLoadKey = "CustomConfiguratorSectionComponent"; + + /** + * This input serves as the itemsSource a user can select an image from. + */ + @Input() imageItems: IMenuItem[] = []; + /** + * This property holds the currently selected image source string. + */ + @Input() imageSource: string; + + /** + * An output for emitting formReady to allow the immediate parent formGroup component to register us as a form control + * in the larger form. In this case, the immediate parent would be the WidgetConfiguratorSectionComponent as specified + * in the customWidget configurator definition at the bottom of this file. + */ + @Output() formReady = new EventEmitter(); + + public form: FormGroup; + public imageDisplayValue: string; + + constructor( + public changeDetector: ChangeDetectorRef, + private formBuilder: FormBuilder, + public configuratorHeading: ConfiguratorHeadingService + ) {} + + public ngOnInit(): void { + // Initializing the form + this.form = this.formBuilder.group({ + // Note: When using the NOVA_GENERIC_CONVERTER, the form control name, in this case 'imageSource', must match the input name on + // this component as well as that of the corresponding property on the custom widget body component. + imageSource: [{}, [Validators.required]], + }); + + // Emitting the formReady as described above. + this.formReady.emit(this.form); + } + + public ngOnChanges(changes: SimpleChanges): void { + if (changes.imageSource && !changes.imageSource.isFirstChange()) { + const previousValue: string = changes.imageSource.previousValue; + if (previousValue !== this.imageSource) { + // Setting the display value according to the current imageSource value + this.imageDisplayValue = this.imageItems.find( + (item: IMenuItem) => item.url === this.imageSource + )?.title; + + // Updating the form when the imageSource input gets updated + this.form.get("imageSource")?.setValue(this.imageSource); + } + } + } + + public onChanged(newValue: string): void { + // Keeping the display value updated as the user changes the dropdown selection + this.imageDisplayValue = this.imageItems.find( + (item: IMenuItem) => item.url === newValue + )?.title; + } +} + +/** + * The component that instantiates the dashboard + */ +@Component({ + selector: "custom-widget", + templateUrl: "./custom-widget.component.html", + styleUrls: ["./custom-widget.component.less"], + standalone: false, +}) +export class CustomWidgetComponent implements OnInit { + // This variable will hold all the data needed to define the layout and behavior of the widgets. + // Pass this to the dashboard component's dashboard input in the template. + public dashboard: IDashboard | undefined; + + // Angular gridster requires a configuration object even if it's empty. + // Pass this to the dashboard component's gridsterConfig input in the template. + public gridsterConfig: GridsterConfig = {}; + + // Boolean which dashboard takes in as an input if its true it allows you to move widgets around. + public editMode: boolean = false; + + constructor( + // WidgetTypesService provides the widget's necessary structure information + private widgetTypesService: WidgetTypesService, + + // Inject the ComponentRegistryService to make our custom component available for late loading by the dashboards framework + private componentRegistry: ComponentRegistryService, + private changeDetectorRef: ChangeDetectorRef + ) {} + + public ngOnInit(): void { + // Register the custom widget type and custom components + // Note: This could also be done in the parent module's constructor so that + // multiple dashboards could have access to the same registrations. + this.prepareNovaDashboards(); + + // Register some image items as dropdown options in the widget editor/configurator + // Note: This could also be done in the parent module's constructor so that + // multiple dashboards could have access to the same dropdown options. + this.registerImageOptions(); + + // Initialize our current instance of a dashboard with an instance of our custom widget + this.initializeDashboard(); + } + + /** Used for restoring widgets state */ + public reInitializeDashboard(): void { + // destroys the components and their providers so the dashboard can re init data + this.dashboard = undefined; + this.changeDetectorRef.detectChanges(); + + this.initializeDashboard(); + } + + public initializeDashboard(): void { + // We're using a static configuration object for this example (see widgetConfig at the bottom of the file), + // but this is where the widget's configuration could potentially be populated from a database + const widget = widgetConfig; + + // Create an index of widgets complete with structure and configuration to assign to the dashboard + const widgets: IWidgets = { + // Complete the custom widget with structure information coming from its type definition + [widget.id]: this.widgetTypesService.mergeWithWidgetType(widget), + }; + + // Setting the widget dimensions and position (this is for gridster) + const positions: Record = { + [widget.id]: { + cols: 4, + rows: 11, + y: 0, + x: 0, + }, + }; + + // Finally, assigning the variables we created above to the dashboard + this.dashboard = { positions, widgets }; + } + + private prepareNovaDashboards() { + // Register the custom widget type + this.widgetTypesService.registerWidgetType( + CUSTOM_WIDGET_TYPENAME, + 1, + customWidget + ); + + // Register the custom widget body component with the component registry to make it available + // for late loading by the dashboard framework. + this.componentRegistry.registerByLateLoadKey( + CustomWidgetBodyContentComponent + ); + + // Register the custom configurator section with the component registry to make it available + // for late loading by the dashboard framework. + this.componentRegistry.registerByLateLoadKey( + CustomConfiguratorSectionComponent + ); + } + + private registerImageOptions() { + // Grab the widget's default template which will be needed as a parameter for setNode below. + const widgetTemplate = this.widgetTypesService.getWidgetType( + CUSTOM_WIDGET_TYPENAME, + 1 + ); + + // Register some image items as dropdown options in the widget editor/configurator + this.widgetTypesService.setNode( + // This is the template we grabbed above with getWidgetType + widgetTemplate, + // We are setting the editor/configurator part of the widget template + "configurator", + // This indicates which node you are changing and we want to change the image items available for selection in the editor. + // For reference, see the 'paths' property of the custom widget's IWidgetTypeDefinition at the bottom of this file. + IMAGE_SELECTION_CONFIGURATOR_PATH_KEY, + // We are setting the image items available for selection in the editor. 'imageItems' is defined + // at the bottom of this file. + imageItems + ); + } +} + +/*************************************************************************************************** + * This is the type definition of our custom widget + ***************************************************************************************************/ +const customWidget: IWidgetTypeDefinition = { + /*************************************************************************************************** + * Paths to important settings in this type definition + ***************************************************************************************************/ + paths: { + widget: { + [WellKnownPathKey.Root]: DEFAULT_PIZZAGNA_ROOT, + }, + configurator: { + [WellKnownPathKey.Root]: DEFAULT_PIZZAGNA_ROOT, + // for the custom configuration component, this is the path for the list of image items available for selection + [IMAGE_SELECTION_CONFIGURATOR_PATH_KEY]: + "imageSelection.properties.imageItems", + }, + }, + /*************************************************************************************************** + * Widget section describes the structural part of the custom widget + ***************************************************************************************************/ + widget: { + [PizzagnaLayer.Structure]: { + [DEFAULT_PIZZAGNA_ROOT]: { + id: DEFAULT_PIZZAGNA_ROOT, + // base layout of the widget - all components referenced herein will be stacked in a column + componentType: StackComponent.lateLoadKey, + providers: { + // When enabled, this provider emits the REFRESH event on the pizzagna event bus every X seconds + [WellKnownProviders.Refresher]: refresher(), + // event proxy manages the transmission of events between widget and dashboard such as the WIDGET_EDIT and WIDGET_REMOVE events + [WellKnownProviders.EventProxy]: EVENT_PROXY, + }, + properties: { + // these values reference child components in the widget structure defined below + nodes: ["header", "loading", "body"], + }, + }, + // standard widget header + header: WIDGET_HEADER, + // this is the loading bar below the header + loading: WIDGET_LOADING, + // the body node + body: WIDGET_BODY, + + // retrieving the definitions for the body content nodes. the argument corresponds to the main content node key + ...widgetBodyContentNodes("mainContent"), + + // the component that supplies the content of our custom widget + mainContent: { + id: "mainContent", + componentType: CustomWidgetBodyContentComponent.lateLoadKey, + properties: { + elementClass: "d-flex w-100 justify-content-center", + }, + }, + }, + [PizzagnaLayer.Configuration]: { + [DEFAULT_PIZZAGNA_ROOT]: { + id: DEFAULT_PIZZAGNA_ROOT, + providers: { + // default refresher configuration + [WellKnownProviders.Refresher]: refresher(false, 60), + }, + }, + // default header configuration + header: { + properties: { + title: $localize\`Empty Custom Widget\`, + }, + }, + }, + }, + /*************************************************************************************************** + * Configurator section describes the form that's used to configure the widget + ***************************************************************************************************/ + configurator: { + [PizzagnaLayer.Structure]: { + [DEFAULT_PIZZAGNA_ROOT]: { + id: DEFAULT_PIZZAGNA_ROOT, + // base layout of the configurator - all form components referenced herein will be stacked in a column + componentType: FormStackComponent.lateLoadKey, + properties: { + elementClass: + "flex-grow-1 overflow-auto nui-scroll-shadows", + // these values reference child components laid out in this form (defined below) + nodes: ["presentation", "customConfig"], + }, + }, + // /presentation + presentation: { + id: "presentation", + componentType: WidgetConfiguratorSectionComponent.lateLoadKey, + properties: { + headerText: $localize\`Presentation\`, + nodes: ["titleAndDescription"], + }, + }, + // /presentation/titleAndDescription + titleAndDescription: { + id: "titleAndDescription", + componentType: + TitleAndDescriptionConfigurationComponent.lateLoadKey, + providers: { + converter: { + providerId: NOVA_TITLE_AND_DESCRIPTION_CONVERTER, + } as IProviderConfiguration, + }, + }, + // /customConfig + customConfig: { + id: "customConfig", + componentType: WidgetConfiguratorSectionComponent.lateLoadKey, + properties: { + headerText: $localize\`Custom Widget Configuration\`, + nodes: ["imageSelection"], + }, + }, + // /customConfig/imageSelection + imageSelection: { + id: "imageSelection", + // Here's where we set the configurator to use our custom configurator section + componentType: CustomConfiguratorSectionComponent.lateLoadKey, + properties: { + // This corresponds to the 'imageItems' input on the custom configurator section component + // which defines the list of image items to pick from. The empty value shown here is overridden + // in the 'registerImageOptions' method above. + imageItems: [] as IMenuItem[], + }, + providers: { + // Using the generic converter to map the selected image source between the widget and the form + [WellKnownProviders.Converter]: { + providerId: NOVA_GENERIC_CONVERTER, + properties: { + formParts: [ + { + // Setting up the generic converter to update the 'imageSource' property of the custom widget 'mainContent' component + previewPath: "mainContent.properties", + // Note: To use the NOVA_GENERIC_CONVERTER, the linked properties must have the same name between the configurator + // section component and the widget 'mainContent' component. Additionally, the property name must match the formControl + // name used in the configurator section component. In this case, the common name among all three is 'imageSource'. + keys: ["imageSource"], + }, + ] as IConverterFormPartsProperties[], + }, + } as IProviderConfiguration, + }, + }, + }, + }, +}; + +// For this example, we're using static items for the image selection dropdown. In a more realistic scenario, +// the items available for selection might come from a backend database. +const imageItems = [ + { + title: "Harry Potter Book Cover", + url: "https://imgc.allpostersimages.com/img/print/u-g-F8PQ9I0.jpg?w=550&h=550&p=0", + }, + { + title: "Harry Potter Movie Poster", + url: "https://images-na.ssl-images-amazon.com/images/I/81gpmMdKOHL._AC_SY741_.jpg", + }, +] as IMenuItem[]; + +// We're using a static configuration object for this example. In a more realistic scenario, +// a widget's configuration would likely be stored in a database. +const widgetConfig: IWidget = { + id: "widget1", + // This custom type is registered in the 'prepareNovaDashboards' method above. + type: CUSTOM_WIDGET_TYPENAME, + pizzagna: { + [PizzagnaLayer.Configuration]: { + header: { + // Setting the initial property values for the WidgetHeaderComponent + properties: { + title: "Harry Potter and the Sorcerer's Stone", + subtitle: "By J. K. Rowling", + }, + }, + mainContent: { + properties: { + // Setting the initial value for the 'imageSource' property on our custom widget body + imageSource: imageItems[0].url, + }, + }, + }, + }, +}; +`, + "tutorials/customization/widget/custom-widget.module.ts": `// © 2022 SolarWinds Worldwide, LLC. All rights reserved. +// +// Permission is hereby granted, free of charge, to any person obtaining a copy +// of this software and associated documentation files (the "Software"), to +// deal in the Software without restriction, including without limitation the +// rights to use, copy, modify, merge, publish, distribute, sublicense, and/or +// sell copies of the Software, and to permit persons to whom the Software is +// furnished to do so, subject to the following conditions: +// +// The above copyright notice and this permission notice shall be included in +// all copies or substantial portions of the Software. +// +// THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR +// IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, +// FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE +// AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER +// LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, +// OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN +// THE SOFTWARE. + +import { CommonModule } from "@angular/common"; +import { HttpClientModule } from "@angular/common/http"; +import { NgModule } from "@angular/core"; +import { ReactiveFormsModule } from "@angular/forms"; +import { RouterModule } from "@angular/router"; + +import { + NuiButtonModule, + NuiDocsModule, + NuiFormFieldModule, + NuiIconModule, + NuiImageModule, + NuiMessageModule, + NuiSelectV2Module, + NuiSwitchModule, + DEMO_PATH_TOKEN, +} from "@nova-ui/bits"; +import { + NuiDashboardConfiguratorModule, + NuiDashboardsModule, +} from "@nova-ui/dashboards"; + +import { CustomWidgetDocsComponent } from "./custom-widget-docs.component"; +import { + CustomConfiguratorSectionComponent, + CustomWidgetBodyContentComponent, + CustomWidgetComponent, +} from "./custom-widget.component"; +import { getDemoFiles } from "../../../../../demo-files-factory"; + +const routes = [ + { + path: "", + component: CustomWidgetDocsComponent, + data: { + srlc: { + hideIndicator: true, + }, + showThemeSwitcher: true, + }, + }, + { + path: "example", + component: CustomWidgetComponent, + data: { + srlc: { + hideIndicator: true, + }, + }, + }, +]; + +@NgModule({ + imports: [ + CommonModule, + ReactiveFormsModule, + HttpClientModule, + NuiDashboardsModule, + NuiDashboardConfiguratorModule, + NuiDocsModule, + NuiFormFieldModule, + NuiIconModule, + NuiImageModule, + NuiMessageModule, + NuiSelectV2Module, + NuiSwitchModule, + NuiButtonModule, + RouterModule.forChild(routes), + ], + declarations: [ + CustomWidgetDocsComponent, + CustomConfiguratorSectionComponent, + CustomWidgetBodyContentComponent, + CustomWidgetComponent, + ], + providers: [ + { + provide: DEMO_PATH_TOKEN, + useValue: getDemoFiles("widget"), + }, + ], +}) +export default class CustomWidgetModule {} +`, + "tutorials/data-source-setup/data-source-setup-docs.component.ts": `// © 2022 SolarWinds Worldwide, LLC. All rights reserved. +// +// Permission is hereby granted, free of charge, to any person obtaining a copy +// of this software and associated documentation files (the "Software"), to +// deal in the Software without restriction, including without limitation the +// rights to use, copy, modify, merge, publish, distribute, sublicense, and/or +// sell copies of the Software, and to permit persons to whom the Software is +// furnished to do so, subject to the following conditions: +// +// The above copyright notice and this permission notice shall be included in +// all copies or substantial portions of the Software. +// +// THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR +// IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, +// FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE +// AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER +// LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, +// OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN +// THE SOFTWARE. + +import { Component } from "@angular/core"; + +@Component({ + selector: "nui-dashboard-data-source-docs", + templateUrl: "./data-source-setup-docs.component.html", + standalone: false, +}) +export class DataSourceDocsComponent {} +`, + "tutorials/data-source-setup/data-source-setup.component.ts": `// © 2022 SolarWinds Worldwide, LLC. All rights reserved. +// +// Permission is hereby granted, free of charge, to any person obtaining a copy +// of this software and associated documentation files (the "Software"), to +// deal in the Software without restriction, including without limitation the +// rights to use, copy, modify, merge, publish, distribute, sublicense, and/or +// sell copies of the Software, and to permit persons to whom the Software is +// furnished to do so, subject to the following conditions: +// +// The above copyright notice and this permission notice shall be included in +// all copies or substantial portions of the Software. +// +// THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR +// IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, +// FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE +// AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER +// LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, +// OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN +// THE SOFTWARE. + +import { HttpClient, HttpErrorResponse } from "@angular/common/http"; +import { Component, Injectable, OnDestroy, OnInit } from "@angular/core"; +import { GridsterConfig, GridsterItem } from "angular-gridster2"; +import { BehaviorSubject } from "rxjs"; +import { finalize } from "rxjs/operators"; + +import { DataSourceService, IFilteringOutputs } from "@nova-ui/bits"; +import { + DATA_SOURCE, + DEFAULT_PIZZAGNA_ROOT, + IDashboard, + IKpiData, + IProviderConfiguration, + IRefresherProperties, + IWidget, + IWidgets, + KpiComponent, + NOVA_KPI_DATASOURCE_ADAPTER, + PizzagnaLayer, + ProviderRegistryService, + WellKnownProviders, + WidgetTypesService, +} from "@nova-ui/dashboards"; + +/** + * A simple KPI data source to retrieve the average rating of Harry Potter and the Sorcerer's Stone (book) via googleapis + */ +@Injectable() +export class AverageRatingKpiDataSource + extends DataSourceService + implements OnDestroy +{ + // This is the ID we'll use to identify the provider + public static providerId = "AverageRatingKpiDataSource"; + + // Use this subject to communicate the data source's busy state + public busy = new BehaviorSubject(false); + + constructor(private http: HttpClient) { + super(); + } + + // In this example, getFilteredData is invoked every 10 minutes (Take a look at the refresher + // provider definition in the widget configuration below to see how the interval is set) + public async getFilteredData(): Promise { + this.busy.next(true); + return new Promise((resolve) => { + // *** Make a resource request to an external API (if needed) + this.http + .get("https://www.googleapis.com/books/v1/volumes/5MQFrgEACAAJ") + .pipe(finalize(() => this.busy.next(false))) + .subscribe({ + next: (data: any) => { + resolve({ + result: { + value: data.volumeInfo.averageRating, + }, + }); + }, + error: (error: HttpErrorResponse) => { + resolve({ + result: null, + error: { + type: error.status, + }, + }); + }, + }); + }); + } + + public ngOnDestroy(): void { + this.outputsSubject.complete(); + } +} + +/** + * A component that instantiates the dashboard + */ +@Component({ + selector: "data-source-setup", + templateUrl: "./data-source-setup.component.html", + styleUrls: ["./data-source-setup.component.less"], + standalone: false, +}) +export class DataSourceSetupComponent implements OnInit { + // This variable will hold all the data needed to define the layout and behavior of the widgets. + // Pass this to the dashboard component's dashboard input in the template. + public dashboard: IDashboard; + + // Angular gridster requires a configuration object even if it's empty. + // Pass this to the dashboard component's gridsterConfig input in the template. + public gridsterConfig: GridsterConfig = {}; + + constructor( + // WidgetTypesService provides the widget's necessary structure information + private widgetTypesService: WidgetTypesService, + + // In general, the ProviderRegistryService is used for making entities available for injection into dynamically loaded components. + private providerRegistry: ProviderRegistryService + ) {} + + public ngOnInit(): void { + // Registering the data source for injection into the KPI tile. + // Note: Each tile of a KPI widget is assigned its own instance of the data source + this.providerRegistry.setProviders({ + [AverageRatingKpiDataSource.providerId]: { + provide: DATA_SOURCE, + useClass: AverageRatingKpiDataSource, + // Any dependencies that need to be injected into the provider must be listed here + deps: [HttpClient], + }, + }); + // We're using a static configuration object for this example, but this is where + // the widget's configuration could potentially be populated from a database + const kpiWidget = widgetConfig; + const widgetIndex: IWidgets = { + // Complete the KPI widget with information coming from its type definition + [kpiWidget.id]: + this.widgetTypesService.mergeWithWidgetType(kpiWidget), + }; + + // Setting the widget dimensions and position (this is for gridster) + const positions: Record = { + [kpiWidget.id]: { + cols: 4, + rows: 6, + y: 0, + x: 0, + }, + }; + + // Finally, assigning the variables we created above to the dashboard + this.dashboard = { + positions, + widgets: widgetIndex, + }; + } +} + +const widgetConfig: IWidget = { + id: "widget1", + type: "kpi", + pizzagna: { + [PizzagnaLayer.Configuration]: { + [DEFAULT_PIZZAGNA_ROOT]: { + providers: { + [WellKnownProviders.Refresher]: { + properties: { + // Configuring the refresher interval so that our data source is invoked every ten minutes + interval: 60 * 10, + enabled: true, + } as IRefresherProperties, + } as Partial, + }, + }, + header: { + properties: { + title: "Harry Potter and the Sorcerer's Stone", + subtitle: "By J. K. Rowling", + }, + }, + tiles: { + properties: { + nodes: ["kpi1"], + }, + }, + kpi1: { + id: "kpi1", + componentType: KpiComponent.lateLoadKey, + properties: { + widgetData: { + units: "out of 5 Stars", + label: "Average Rating", + }, + }, + providers: { + [WellKnownProviders.DataSource]: { + // Setting the data source providerId for the tile with id "kpi1" + providerId: AverageRatingKpiDataSource.providerId, + } as IProviderConfiguration, + [WellKnownProviders.Adapter]: { + providerId: NOVA_KPI_DATASOURCE_ADAPTER, + properties: { + componentId: "kpi1", + propertyPath: "widgetData", + }, + } as IProviderConfiguration, + }, + }, + }, + }, +}; +`, + "tutorials/data-source-setup/data-source-setup.module.ts": `// © 2022 SolarWinds Worldwide, LLC. All rights reserved. +// +// Permission is hereby granted, free of charge, to any person obtaining a copy +// of this software and associated documentation files (the "Software"), to +// deal in the Software without restriction, including without limitation the +// rights to use, copy, modify, merge, publish, distribute, sublicense, and/or +// sell copies of the Software, and to permit persons to whom the Software is +// furnished to do so, subject to the following conditions: +// +// The above copyright notice and this permission notice shall be included in +// all copies or substantial portions of the Software. +// +// THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR +// IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, +// FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE +// AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER +// LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, +// OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN +// THE SOFTWARE. + +import { CommonModule } from "@angular/common"; +import { HttpClientModule } from "@angular/common/http"; +import { NgModule } from "@angular/core"; +import { RouterModule } from "@angular/router"; + +import { + NuiDocsModule, + NuiMessageModule, + DEMO_PATH_TOKEN, +} from "@nova-ui/bits"; +import { NuiDashboardsModule } from "@nova-ui/dashboards"; + +import { DataSourceDocsComponent } from "./data-source-setup-docs.component"; +import { DataSourceSetupComponent } from "./data-source-setup.component"; +import { getDemoFiles } from "../../../../demo-files-factory"; + +const routes = [ + { + path: "", + component: DataSourceDocsComponent, + data: { + srlc: { + hideIndicator: true, + }, + showThemeSwitcher: true, + }, + }, + { + path: "example", + component: DataSourceSetupComponent, + data: { + srlc: { + hideIndicator: true, + }, + }, + }, +]; + +@NgModule({ + imports: [ + CommonModule, + HttpClientModule, + NuiDashboardsModule, + NuiDocsModule, + NuiMessageModule, + RouterModule.forChild(routes), + ], + declarations: [DataSourceDocsComponent, DataSourceSetupComponent], + providers: [ + { + provide: DEMO_PATH_TOKEN, + useValue: getDemoFiles("data-source-setup"), + }, + ], +}) +export default class DataSourceSetupModule {} +`, + "tutorials/dynamic-header-links/dynamic-header-links-docs.component.ts": `// © 2022 SolarWinds Worldwide, LLC. All rights reserved. +// +// Permission is hereby granted, free of charge, to any person obtaining a copy +// of this software and associated documentation files (the "Software"), to +// deal in the Software without restriction, including without limitation the +// rights to use, copy, modify, merge, publish, distribute, sublicense, and/or +// sell copies of the Software, and to permit persons to whom the Software is +// furnished to do so, subject to the following conditions: +// +// The above copyright notice and this permission notice shall be included in +// all copies or substantial portions of the Software. +// +// THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR +// IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, +// FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE +// AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER +// LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, +// OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN +// THE SOFTWARE. + +import { Component } from "@angular/core"; + +@Component({ + selector: "nui-dynamic-header-links-docs", + templateUrl: "./dynamic-header-links-docs.component.html", + standalone: false, +}) +export class DynamicHeaderLinksDocsComponent {} +`, + "tutorials/dynamic-header-links/dynamic-header-links-docs.module.ts": `// © 2022 SolarWinds Worldwide, LLC. All rights reserved. +// +// Permission is hereby granted, free of charge, to any person obtaining a copy +// of this software and associated documentation files (the "Software"), to +// deal in the Software without restriction, including without limitation the +// rights to use, copy, modify, merge, publish, distribute, sublicense, and/or +// sell copies of the Software, and to permit persons to whom the Software is +// furnished to do so, subject to the following conditions: +// +// The above copyright notice and this permission notice shall be included in +// all copies or substantial portions of the Software. +// +// THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR +// IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, +// FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE +// AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER +// LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, +// OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN +// THE SOFTWARE. + +import { NgModule } from "@angular/core"; +import { RouterModule } from "@angular/router"; + +import { DynamicHeaderLinksDocsComponent } from "./dynamic-header-links-docs.component"; + +const routes = [ + { + path: "", + component: DynamicHeaderLinksDocsComponent, + data: { + srlc: { + hideIndicator: true, + }, + showThemeSwitcher: true, + }, + }, +]; + +@NgModule({ + imports: [RouterModule.forChild(routes)], + declarations: [DynamicHeaderLinksDocsComponent], +}) +export default class DynamicHeaderLinksDocsModule {} +`, + "tutorials/hello-dashboards/hello-dashboards-docs.component.ts": `// © 2022 SolarWinds Worldwide, LLC. All rights reserved. +// +// Permission is hereby granted, free of charge, to any person obtaining a copy +// of this software and associated documentation files (the "Software"), to +// deal in the Software without restriction, including without limitation the +// rights to use, copy, modify, merge, publish, distribute, sublicense, and/or +// sell copies of the Software, and to permit persons to whom the Software is +// furnished to do so, subject to the following conditions: +// +// The above copyright notice and this permission notice shall be included in +// all copies or substantial portions of the Software. +// +// THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR +// IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, +// FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE +// AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER +// LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, +// OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN +// THE SOFTWARE. + +import { Component } from "@angular/core"; + +@Component({ + selector: "nui-dashboard-hello-dashboards-docs", + templateUrl: "./hello-dashboards-docs.component.html", + standalone: false, +}) +export class HelloDashboardsDocsComponent {} +`, + "tutorials/hello-dashboards/hello-dashboards-example/hello-dashboards-example.component.ts": `// © 2022 SolarWinds Worldwide, LLC. All rights reserved. +// +// Permission is hereby granted, free of charge, to any person obtaining a copy +// of this software and associated documentation files (the "Software"), to +// deal in the Software without restriction, including without limitation the +// rights to use, copy, modify, merge, publish, distribute, sublicense, and/or +// sell copies of the Software, and to permit persons to whom the Software is +// furnished to do so, subject to the following conditions: +// +// The above copyright notice and this permission notice shall be included in +// all copies or substantial portions of the Software. +// +// THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR +// IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, +// FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE +// AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER +// LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, +// OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN +// THE SOFTWARE. + +import { Component, OnInit } from "@angular/core"; +import { GridsterConfig, GridsterItem } from "angular-gridster2"; + +import { + IDashboard, + IWidget, + IWidgets, + KpiComponent, + PizzagnaLayer, + WidgetTypesService, +} from "@nova-ui/dashboards"; + +/** + * A component that instantiates the dashboard + */ +@Component({ + selector: "hello-dashboards-example", + templateUrl: "./hello-dashboards-example.component.html", + styleUrls: ["./hello-dashboards-example.component.less"], + standalone: false, +}) +export class HelloDashboardsExampleComponent implements OnInit { + // This variable will have all the data needed to render the widgets widgets. + // Pass this to the dashboard component's dashboard input. + public dashboard: IDashboard; + // Angular gridster requires a configuration object even if its empty. + // Pass this to the dashboard component's gridsterConfig input. + public gridsterConfig: GridsterConfig = {}; + + // WidgetTypesService provides the widget's necessary structure information + constructor(private widgetTypesService: WidgetTypesService) {} + + public ngOnInit(): void { + // Here we are hard-coding the widget config for this example, but this is where you + // could potentially populate the widget's configuration from a database + const kpiWidget = widgetConfig; + const widgetIndex: IWidgets = { + // Complete the KPI widget with information coming from its type definition + [kpiWidget.id]: + this.widgetTypesService.mergeWithWidgetType(kpiWidget), + }; + // Setting widget position and dimensions (this is for gridster) + const positions: Record = { + [kpiWidget.id]: { + cols: 4, + rows: 6, + y: 0, + x: 0, + }, + }; + // Finally, assigning the variables we created above to the dashboard + this.dashboard = { + positions, + widgets: widgetIndex, + }; + } +} + +// In a real-world scenario, this configuration would typically be fetched from a database or at least live in another file +const widgetConfig: IWidget = { + id: "widget1", + type: "kpi", + pizzagna: { + [PizzagnaLayer.Configuration]: { + header: { + properties: { + title: "Hello, KPI Widget!", + subtitle: "A Venue for Meaningful Values", + }, + }, + tiles: { + properties: { + nodes: ["kpi1"], + }, + }, + kpi1: { + id: "kpi1", + componentType: KpiComponent.lateLoadKey, + properties: { + widgetData: { + id: "totalStorage", + value: 1, + label: "Total storage", + units: "TB", + }, + }, + }, + }, + }, +}; +`, + "tutorials/hello-dashboards/hello-dashboards.module.ts": `// © 2022 SolarWinds Worldwide, LLC. All rights reserved. +// +// Permission is hereby granted, free of charge, to any person obtaining a copy +// of this software and associated documentation files (the "Software"), to +// deal in the Software without restriction, including without limitation the +// rights to use, copy, modify, merge, publish, distribute, sublicense, and/or +// sell copies of the Software, and to permit persons to whom the Software is +// furnished to do so, subject to the following conditions: +// +// The above copyright notice and this permission notice shall be included in +// all copies or substantial portions of the Software. +// +// THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR +// IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, +// FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE +// AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER +// LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, +// OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN +// THE SOFTWARE. + +import { CommonModule } from "@angular/common"; +import { NgModule } from "@angular/core"; +import { RouterModule } from "@angular/router"; + +import { + NuiDocsModule, + NuiMessageModule, + DEMO_PATH_TOKEN, +} from "@nova-ui/bits"; +import { NuiDashboardsModule } from "@nova-ui/dashboards"; + +import { HelloDashboardsDocsComponent } from "./hello-dashboards-docs.component"; +import { HelloDashboardsExampleComponent } from "./hello-dashboards-example/hello-dashboards-example.component"; +import { getDemoFiles } from "../../../../demo-files-factory"; + +const routes = [ + { + path: "", + component: HelloDashboardsDocsComponent, + data: { + srlc: { + hideIndicator: true, + }, + showThemeSwitcher: true, + }, + }, + { + path: "example", + component: HelloDashboardsExampleComponent, + data: { + srlc: { + hideIndicator: true, + }, + }, + }, +]; + +@NgModule({ + imports: [ + CommonModule, + NuiDashboardsModule, + NuiDocsModule, + NuiMessageModule, + RouterModule.forChild(routes), + ], + declarations: [ + HelloDashboardsDocsComponent, + HelloDashboardsExampleComponent, + ], + providers: [ + { + provide: DEMO_PATH_TOKEN, + useValue: getDemoFiles("hello-dashboards"), + }, + ], +}) +export default class HelloDashboardsModule {} +`, + "tutorials/persistence-handler-setup/persistence-handler-setup-docs.component.ts": `// © 2022 SolarWinds Worldwide, LLC. All rights reserved. +// +// Permission is hereby granted, free of charge, to any person obtaining a copy +// of this software and associated documentation files (the "Software"), to +// deal in the Software without restriction, including without limitation the +// rights to use, copy, modify, merge, publish, distribute, sublicense, and/or +// sell copies of the Software, and to permit persons to whom the Software is +// furnished to do so, subject to the following conditions: +// +// The above copyright notice and this permission notice shall be included in +// all copies or substantial portions of the Software. +// +// THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR +// IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, +// FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE +// AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER +// LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, +// OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN +// THE SOFTWARE. + +import { Component } from "@angular/core"; + +@Component({ + selector: "nui-dashboard-persistence-handler-setup-docs", + templateUrl: "./persistence-handler-setup-docs.component.html", + standalone: false, +}) +export class PersistenceHandlerSetupDocsComponent {} +`, + "tutorials/persistence-handler-setup/persistence-handler-setup.component.ts": `// © 2022 SolarWinds Worldwide, LLC. All rights reserved. +// +// Permission is hereby granted, free of charge, to any person obtaining a copy +// of this software and associated documentation files (the "Software"), to +// deal in the Software without restriction, including without limitation the +// rights to use, copy, modify, merge, publish, distribute, sublicense, and/or +// sell copies of the Software, and to permit persons to whom the Software is +// furnished to do so, subject to the following conditions: +// +// The above copyright notice and this permission notice shall be included in +// all copies or substantial portions of the Software. +// +// THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR +// IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, +// FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE +// AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER +// LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, +// OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN +// THE SOFTWARE. + +import { HttpClient, HttpErrorResponse } from "@angular/common/http"; +import { + ChangeDetectorRef, + Component, + Injectable, + OnDestroy, + OnInit, +} from "@angular/core"; +import { GridsterConfig, GridsterItem } from "angular-gridster2"; +import { BehaviorSubject, Observable, Subject } from "rxjs"; +import { finalize } from "rxjs/operators"; + +import { + DataSourceService, + IFilteringOutputs, + ToastService, + uuid, +} from "@nova-ui/bits"; +import { + DATA_SOURCE, + DEFAULT_PIZZAGNA_ROOT, + IDashboard, + IDashboardPersistenceHandler, + IKpiData, + IProviderConfiguration, + IRefresherProperties, + IWidget, + IWidgets, + KpiComponent, + NOVA_KPI_DATASOURCE_ADAPTER, + PizzagnaLayer, + ProviderRegistryService, + WellKnownPathKey, + WellKnownProviders, + WidgetTypesService, +} from "@nova-ui/dashboards"; + +/** + * A simple persistence handler that is tied to the widget editor directive + */ +@Injectable() +// The realizer of IDashboardPersistenceHandler may implement a trySubmit and/or a tryRemove method. +export class PersistenceHandler implements IDashboardPersistenceHandler { + // This variable is just to show how to handle error handling. + private persistenceSucceeded: boolean = true; + + // The example uses the toast service to demonstrate the + // invocation of each of the persistence handler callbacks + constructor(private toastService: ToastService) { + // toastService options to let it sit on the page for 2 seconds. + this.toastService.setConfig({ + timeOut: 2000, + }); + } + + // This method will be invoked anytime the widget editor form gets submitted. + public trySubmit = (widget: IWidget): Observable => { + // Since we are working asynchronously, we'll return a subject. So, after the submit attempt + // succeeds or fails, we can let the subscriber know the result. + const subject = new Subject(); + + if (!widget.id) { + // Creates an id if the widget has no id. + // (This step will make more sense in the context of the widget cloning tutorial + // in which we handle the persistence of a newly created widget.) + widget.id = uuid(); + } + + // For this example, we're using a setTimeout to mock an asynchronous persistence request to a backend + setTimeout(() => { + if (this.persistenceSucceeded) { + // Passes along the new widget after one second. + subject.next(widget); + // Toast on the page on success. + this.toastService.success({ + title: $localize\`Submit succeeded.\`, + }); + } else { + const errorText = $localize\`Submit failed.\`; + // Toast on the page on failure. + this.toastService.error({ title: errorText }); + // Makes the subject say there is an error. + subject.error(errorText); + } + // Completes the subject so whoever subscribes to it knows its finished. + subject.complete(); + }, 1000); + + // Returns the subject as an observable. + return subject.asObservable(); + }; + + // This method will be invoked anytime there's a widget removal attempt. + public tryRemove = (widgetId: string): Observable => { + const subject = new Subject(); + + setTimeout(() => { + if (this.persistenceSucceeded) { + // Pass through the id of the widget that was removed. + subject.next(widgetId); + this.toastService.success({ + title: $localize\`Removal succeeded.\`, + }); + } else { + const errorText = $localize\`Removal failed.\`; + this.toastService.error({ title: errorText }); + subject.error(errorText); + } + subject.complete(); + }, 1000); + + return subject.asObservable(); + }; +} + +/** + * A component that instantiates the dashboard + */ +@Component({ + selector: "persistence-handler-setup", + templateUrl: "./persistence-handler-setup.component.html", + styleUrls: ["./persistence-handler-setup.component.less"], + // Here we provide our persistence handler at the component level; this can also be done in the module. + providers: [PersistenceHandler], + standalone: false, +}) +export class PersistenceHandlerSetupComponent implements OnInit { + // This variable will hold all the data needed to define the layout and behavior of the widgets. + // Pass this to the dashboard component's dashboard input in the template. + public dashboard: IDashboard | undefined; + + // Angular gridster requires a configuration object even if it's empty. + // Pass this to the dashboard component's gridsterConfig input in the template. + public gridsterConfig: GridsterConfig = {}; + + // Boolean which dashboard takes in as an input if its true it allows you to move widgets around. + public editMode: boolean = false; + + constructor( + // WidgetTypesService provides the widget's necessary structure information + private widgetTypesService: WidgetTypesService, + + // In general, the ProviderRegistryService is used for making entities available for injection into dynamically loaded components. + private providerRegistry: ProviderRegistryService, + + // We are injecting the PersistenceHandler we created and assigning it to a property we use in the template. + public persistenceHandler: PersistenceHandler, + private changeDetectorRef: ChangeDetectorRef + ) {} + + public ngOnInit(): void { + // Grabbing the widget's default template which will be needed as a parameter for setNode + const widgetTemplate = this.widgetTypesService.getWidgetType("kpi", 1); + // Registering our data sources as dropdown options in the widget editor/configurator + // Note: This could also be done in the parent module's constructor so that + // multiple dashboards could have access to the same widget template modification. + this.widgetTypesService.setNode( + // This is the template we grabbed above with getWidgetType + widgetTemplate, + // We are setting the editor/configurator part of the widget template + "configurator", + // This indicates which node you are changing and we want to change + // the data source providers available for selection in the editor. + WellKnownPathKey.DataSourceProviders, + // We are setting the data sources available for selection in the editor + [ + AverageRatingKpiDataSource.providerId, + RatingsCountKpiDataSource.providerId, + ] + ); + + // Registering the data sources available for injection into the KPI tiles. + // Note: Each tile of a KPI widget is assigned its own instance of a data source + this.providerRegistry.setProviders({ + [AverageRatingKpiDataSource.providerId]: { + provide: DATA_SOURCE, + useClass: AverageRatingKpiDataSource, + // Any dependencies that need to be injected into the provider must be listed here + deps: [HttpClient], + }, + [RatingsCountKpiDataSource.providerId]: { + provide: DATA_SOURCE, + useClass: RatingsCountKpiDataSource, + deps: [HttpClient], + }, + }); + + this.initializeDashboard(); + } + + /** Used for restoring widgets state */ + public reInitializeDashboard(): void { + // destroys the components and their providers so the dashboard can re init data + this.dashboard = undefined; + this.changeDetectorRef.detectChanges(); + + this.initializeDashboard(); + } + + public initializeDashboard(): void { + // We're using a static configuration object for this example (see widgetConfig at the bottom of the file), + // but this is where the widget's configuration could potentially be populated from a database + const kpiWidget = widgetConfig; + const widgetIndex: IWidgets = { + // Complete the KPI widget with information coming from its type definition + [kpiWidget.id]: + this.widgetTypesService.mergeWithWidgetType(kpiWidget), + }; + + // Setting the widget dimensions and position (this is for gridster) + const positions: Record = { + [kpiWidget.id]: { + cols: 4, + rows: 6, + y: 0, + x: 0, + }, + }; + + // Finally, assigning the variables we created above to the dashboard + this.dashboard = { + positions, + widgets: widgetIndex, + }; + } +} + +/** + * A simple KPI data source to retrieve the average rating of Harry Potter and the Sorcerer's Stone (book) via googleapis + */ +@Injectable() +export class AverageRatingKpiDataSource + extends DataSourceService + implements OnDestroy +{ + // This is the ID we'll use to identify the provider + public static providerId = "AverageRatingKpiDataSource"; + + // Use this subject to communicate the data source's busy state + public busy = new BehaviorSubject(false); + + constructor(private http: HttpClient) { + super(); + } + + // In this example, getFilteredData is invoked every 10 minutes (Take a look at the refresher + // provider definition in the widget configuration below to see how the interval is set) + public async getFilteredData(): Promise { + this.busy.next(true); + return new Promise((resolve) => { + // *** Make a resource request to an external API (if needed) + this.http + .get("https://www.googleapis.com/books/v1/volumes/5MQFrgEACAAJ") + .pipe(finalize(() => this.busy.next(false))) + .subscribe({ + next: (data: any) => { + resolve({ + result: { + value: data.volumeInfo.averageRating, + }, + }); + }, + error: (error: HttpErrorResponse) => { + resolve({ + result: null, + error: { + type: error.status, + }, + }); + }, + }); + }); + } + + public ngOnDestroy(): void { + this.outputsSubject.complete(); + } +} + +/** + * A simple KPI data source to retrieve the ratings count of Harry Potter and the Sorcerer's Stone (book) via googleapis + */ +@Injectable() +export class RatingsCountKpiDataSource + extends DataSourceService + implements OnDestroy +{ + public static providerId = "RatingsCountKpiDataSource"; + + // Use this subject to communicate the data source's busy state + public busy = new BehaviorSubject(false); + + constructor(private http: HttpClient) { + super(); + } + + public async getFilteredData(): Promise { + this.busy.next(true); + return new Promise((resolve) => { + this.http + .get("https://www.googleapis.com/books/v1/volumes/5MQFrgEACAAJ") + .pipe(finalize(() => this.busy.next(false))) + .subscribe({ + next: (data: any) => { + resolve({ + result: { + value: data.volumeInfo.ratingsCount, + }, + }); + }, + error: (error: HttpErrorResponse) => { + resolve({ + result: null, + error: { + type: error.status, + }, + }); + }, + }); + }); + } + + public ngOnDestroy(): void { + this.outputsSubject.complete(); + } +} + +const widgetConfig: IWidget = { + id: "widget1", + type: "kpi", + pizzagna: { + [PizzagnaLayer.Configuration]: { + [DEFAULT_PIZZAGNA_ROOT]: { + providers: { + [WellKnownProviders.Refresher]: { + properties: { + // Configuring the refresher interval so that our data source is invoked every ten minutes + interval: 60 * 10, + enabled: true, + } as IRefresherProperties, + } as Partial, + }, + }, + header: { + properties: { + title: "Harry Potter and the Sorcerer's Stone", + subtitle: "By J. K. Rowling", + }, + }, + tiles: { + properties: { + nodes: ["kpi1"], + }, + }, + kpi1: { + id: "kpi1", + componentType: KpiComponent.lateLoadKey, + properties: { + widgetData: { + units: "out of 5 Stars", + label: "Average Rating", + }, + }, + providers: { + [WellKnownProviders.DataSource]: { + // Setting the data source providerId for the tile with id "kpi1" + providerId: AverageRatingKpiDataSource.providerId, + } as IProviderConfiguration, + [WellKnownProviders.Adapter]: { + providerId: NOVA_KPI_DATASOURCE_ADAPTER, + properties: { + componentId: "kpi1", + propertyPath: "widgetData", + }, + } as IProviderConfiguration, + }, + }, + }, + }, +}; +`, + "tutorials/persistence-handler-setup/persistence-handler-setup.module.ts": `// © 2022 SolarWinds Worldwide, LLC. All rights reserved. +// +// Permission is hereby granted, free of charge, to any person obtaining a copy +// of this software and associated documentation files (the "Software"), to +// deal in the Software without restriction, including without limitation the +// rights to use, copy, modify, merge, publish, distribute, sublicense, and/or +// sell copies of the Software, and to permit persons to whom the Software is +// furnished to do so, subject to the following conditions: +// +// The above copyright notice and this permission notice shall be included in +// all copies or substantial portions of the Software. +// +// THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR +// IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, +// FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE +// AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER +// LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, +// OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN +// THE SOFTWARE. + +import { CommonModule } from "@angular/common"; +import { HttpClientModule } from "@angular/common/http"; +import { NgModule } from "@angular/core"; +import { RouterModule } from "@angular/router"; + +import { + NuiButtonModule, + NuiDocsModule, + NuiMessageModule, + NuiSwitchModule, + NuiToastModule, + DEMO_PATH_TOKEN, +} from "@nova-ui/bits"; +import { NuiDashboardsModule } from "@nova-ui/dashboards"; + +import { PersistenceHandlerSetupDocsComponent } from "./persistence-handler-setup-docs.component"; +import { PersistenceHandlerSetupComponent } from "./persistence-handler-setup.component"; +import { getDemoFiles } from "../../../../demo-files-factory"; + +const routes = [ + { + path: "", + component: PersistenceHandlerSetupDocsComponent, + data: { + srlc: { + hideIndicator: true, + }, + showThemeSwitcher: true, + }, + }, + { + path: "example", + component: PersistenceHandlerSetupComponent, + data: { + srlc: { + hideIndicator: true, + }, + }, + }, +]; + +@NgModule({ + imports: [ + CommonModule, + HttpClientModule, + NuiDashboardsModule, + NuiDocsModule, + NuiMessageModule, + NuiSwitchModule, + NuiToastModule, + NuiButtonModule, + RouterModule.forChild(routes), + ], + declarations: [ + PersistenceHandlerSetupDocsComponent, + PersistenceHandlerSetupComponent, + ], + providers: [ + { + provide: DEMO_PATH_TOKEN, + useValue: getDemoFiles("persistence-handler-setup"), + }, + ], +}) +export default class PersistenceHandlerSetupModule {} +`, + "tutorials/tutorials.module.ts": `// © 2022 SolarWinds Worldwide, LLC. All rights reserved. +// +// Permission is hereby granted, free of charge, to any person obtaining a copy +// of this software and associated documentation files (the "Software"), to +// deal in the Software without restriction, including without limitation the +// rights to use, copy, modify, merge, publish, distribute, sublicense, and/or +// sell copies of the Software, and to permit persons to whom the Software is +// furnished to do so, subject to the following conditions: +// +// The above copyright notice and this permission notice shall be included in +// all copies or substantial portions of the Software. +// +// THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR +// IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, +// FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE +// AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER +// LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, +// OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN +// THE SOFTWARE. + +import { NgModule, Type } from "@angular/core"; +import { RouterModule, Routes } from "@angular/router"; + +import { ConfiguratorHeadingService } from "@nova-ui/dashboards"; + +export enum TutorialsModuleRoute { + HelloDashboards = "hello-dashboards", + DataSource = "data-source-setup", + WidgetEditor = "widget-editor-setup", + SubmitHandler = "persistence-handler-setup", + WidgetCreation = "widget-creation", + Customization = "customization", + WidgetErrorHandling = "widget-error-handling", + DynamicHeaderLinks = "dynamic-header-links", +} + +const routes: Routes = [ + { + path: TutorialsModuleRoute.HelloDashboards, + loadChildren: async () => + import( + "./hello-dashboards/hello-dashboards.module" + ) as object as Promise>, + }, + { + path: TutorialsModuleRoute.DataSource, + loadChildren: async () => + import( + "./data-source-setup/data-source-setup.module" + ) as object as Promise>, + }, + { + path: TutorialsModuleRoute.WidgetEditor, + loadChildren: async () => + import( + "./widget-editor-setup/widget-editor-setup.module" + ) as object as Promise>, + }, + { + path: TutorialsModuleRoute.SubmitHandler, + loadChildren: async () => + import( + "./persistence-handler-setup/persistence-handler-setup.module" + ) as object as Promise>, + }, + { + path: TutorialsModuleRoute.WidgetCreation, + loadChildren: async () => + import( + "./widget-creation/widget-creation.module" + ) as object as Promise>, + }, + { + path: TutorialsModuleRoute.Customization, + loadChildren: async () => + import("./customization/customization.module") as object as Promise< + Type + >, + }, + { + path: TutorialsModuleRoute.WidgetErrorHandling, + loadChildren: async () => + import( + "./widget-error-handling/widget-error-handling.module" + ) as object as Promise>, + }, + { + path: TutorialsModuleRoute.DynamicHeaderLinks, + loadChildren: async () => + import( + "./dynamic-header-links/dynamic-header-links-docs.module" + ) as object as Promise>, + }, +]; + +@NgModule({ + imports: [RouterModule.forChild(routes)], + providers: [ConfiguratorHeadingService], +}) +export default class TutorialsModule {} +`, + "tutorials/widget-creation/widget-creation-docs.component.ts": `// © 2022 SolarWinds Worldwide, LLC. All rights reserved. +// +// Permission is hereby granted, free of charge, to any person obtaining a copy +// of this software and associated documentation files (the "Software"), to +// deal in the Software without restriction, including without limitation the +// rights to use, copy, modify, merge, publish, distribute, sublicense, and/or +// sell copies of the Software, and to permit persons to whom the Software is +// furnished to do so, subject to the following conditions: +// +// The above copyright notice and this permission notice shall be included in +// all copies or substantial portions of the Software. +// +// THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR +// IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, +// FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE +// AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER +// LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, +// OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN +// THE SOFTWARE. + +import { Component } from "@angular/core"; + +@Component({ + selector: "nui-dashboard-widget-creation-docs", + templateUrl: "./widget-creation-docs.component.html", + standalone: false, +}) +export class WidgetCreationDocsComponent {} +`, + "tutorials/widget-creation/widget-creation.component.ts": `// © 2022 SolarWinds Worldwide, LLC. All rights reserved. +// +// Permission is hereby granted, free of charge, to any person obtaining a copy +// of this software and associated documentation files (the "Software"), to +// deal in the Software without restriction, including without limitation the +// rights to use, copy, modify, merge, publish, distribute, sublicense, and/or +// sell copies of the Software, and to permit persons to whom the Software is +// furnished to do so, subject to the following conditions: +// +// The above copyright notice and this permission notice shall be included in +// all copies or substantial portions of the Software. +// +// THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR +// IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, +// FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE +// AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER +// LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, +// OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN +// THE SOFTWARE. + +import { HttpClient, HttpErrorResponse } from "@angular/common/http"; +import { + Component, + EventEmitter, + Injectable, + OnDestroy, + OnInit, + Output, + ViewChild, +} from "@angular/core"; +import { GridsterConfig, GridsterItem } from "angular-gridster2"; +import { BehaviorSubject, Observable, Subject } from "rxjs"; +import { finalize, take, takeUntil } from "rxjs/operators"; + +import { + DataSourceService, + IFilteringOutputs, + ToastService, + uuid, +} from "@nova-ui/bits"; +import { + DashboardComponent, + DATA_SOURCE, + DEFAULT_PIZZAGNA_ROOT, + IDashboard, + IDashboardPersistenceHandler, + IDataSourceOutput, + IKpiData, + IProviderConfiguration, + IRefresherProperties, + IWidget, + IWidgets, + IWidgetSelector, + IWidgetTemplateSelector, + KpiComponent, + NOVA_KPI_DATASOURCE_ADAPTER, + PizzagnaLayer, + ProviderRegistryService, + WellKnownPathKey, + WellKnownProviders, + WidgetClonerService, + WidgetTypesService, +} from "@nova-ui/dashboards"; + +// Interface of a widget item +interface IWidgetItem { + name: string; + widget: IWidget; +} + +// This component acts as the first step, or page, in the wizard where the user selects a wizard type to create. +// It's recommended to have this component in a different file. For this tutorial, it's included in the same +// file for simplicity. +@Component({ + selector: "widget-template-selection", + styleUrls: ["./widget-creation.component.less"], + template: \` +
+ + +
+ + +
+
{{ item.name }}
+
+
+ \`, + standalone: false, +}) +export class WidgetTemplateSelectionComponent + implements IWidgetTemplateSelector, OnInit +{ + // This output will notify the wizard that a widget has been selected. + @Output() public widgetSelected = new EventEmitter(); + + public widgetItems: IWidgetItem[] = []; + public widgetSelection: IWidgetItem[]; + + constructor(private widgetTypesService: WidgetTypesService) {} + + public ngOnInit(): void { + // Here we combine the widget structure from the WidgetTypesService with the corresponding widget + // configuration to create an array of widget objects for the itemSource on the repeat component. + this.widgetItems = [ + { + name: "Fully Configured KPI Widget", + widget: this.widgetTypesService.mergeWithWidgetType( + fullKpiWidgetConfig + ), + }, + { + name: "Unconfigured Proportional Widget", + // Note that 'partialPropWidgetConfig' sets 'metadata.needsConfiguration' to true. + // When this widget is selected in the wizard, the 'Create Widget' button will be hidden + // to guide the user to the second step where they can complete the configuration. + widget: this.widgetTypesService.mergeWithWidgetType( + partialPropWidgetConfig + ), + }, + ]; + + // You can optionally auto-select a widget by doing the following + // this.onSelect([this.widgetItems[0]]); + } + + public onSelect(selectedItems: any[]): void { + // We emit the selected widget to communicate the selection to the configurator + this.widgetSelected.emit(selectedItems[0].widget); + this.widgetSelection = selectedItems; + } +} + +/** + * A simple persistence handler that is tied to the widget editor directive + */ +@Injectable() +// The realizer of IDashboardPersistenceHandler may implement a trySubmit and/or a tryRemove method. +export class PersistenceHandler implements IDashboardPersistenceHandler { + // This variable is just to show how to handle error handling. + private persistenceSucceeded: boolean = true; + + // The example uses the toast service to demonstrate the + // invocation of each of the persistence handler callbacks + constructor(private toastService: ToastService) { + // toastService options to let it sit on the page for 2 seconds. + this.toastService.setConfig({ + timeOut: 2000, + }); + } + + // This method will be invoked anytime the widget editor form gets submitted. + public trySubmit = (widget: IWidget): Observable => { + // Since we are working asynchronously, we'll return a subject. So, after the submit attempt + // succeeds or fails, we can let the subscriber know the result. + const subject = new Subject(); + + if (!widget.id) { + // Creates an id if the widget has no id. + // (This step will make more sense in the context of the widget cloning tutorial + // in which we handle the persistence of a newly created widget.) + widget.id = uuid(); + } + + // For this example, we're using a setTimeout to mock an asynchronous persistence request to a backend + setTimeout(() => { + if (this.persistenceSucceeded) { + // Passes along the new widget after one second. + subject.next(widget); + // Toast on the page on success. + this.toastService.success({ + title: $localize\`Submit succeeded.\`, + }); + } else { + const errorText = $localize\`Submit failed.\`; + // Toast on the page on failure. + this.toastService.error({ title: errorText }); + // Makes the subject say there is an error. + subject.error(errorText); + } + // Completes the subject so whoever subscribes to it knows its finished. + subject.complete(); + }, 1000); + + // Returns the subject as an observable. + return subject.asObservable(); + }; + + // This method will be invoked anytime there's a widget removal attempt. + public tryRemove = (widgetId: string): Observable => { + const subject = new Subject(); + + setTimeout(() => { + if (this.persistenceSucceeded) { + // Pass through the id of the widget that was removed. + subject.next(widgetId); + this.toastService.success({ + title: $localize\`Removal success\`, + }); + } else { + const errorText = $localize\`Removal failed.\`; + this.toastService.error({ title: errorText }); + subject.error(errorText); + } + subject.complete(); + }, 1000); + + return subject.asObservable(); + }; +} + +/** + * A component that instantiates the dashboard + */ +@Component({ + selector: "widget-creation", + templateUrl: "./widget-creation.component.html", + styleUrls: ["./widget-creation.component.less"], + // Here we provide our persistence handler at the component level; this can also be done in the module. + providers: [PersistenceHandler], + standalone: false, +}) +export class WidgetCreationComponent implements OnInit { + // The WidgetClonerService will need this for updating the dashboard + @ViewChild(DashboardComponent, { static: true }) + dashboardComponent: DashboardComponent; + // This variable will hold all the data needed to define the layout and behavior of the widgets. + // Pass this to the dashboard component's dashboard input in the template. + public dashboard: IDashboard; + + // Angular gridster requires a configuration object even if it's empty. + // Pass this to the dashboard component's gridsterConfig input in the template. + public gridsterConfig: GridsterConfig = { + // These values will be used to set the initial widget dimensions on creation. + // If not set, they each default to 6. + defaultItemCols: 3, + defaultItemRows: 5, + }; + + // Boolean the dashboard takes in as an input; if it's set to true + // the dashboard allows you to resize widgets and move them around. + public editMode: boolean = false; + + // Subject used for auto-unsubscribing from subscriptions on component destruction + private readonly destroy$ = new Subject(); + + constructor( + // WidgetTypesService provides the widget's necessary structure information + private widgetTypesService: WidgetTypesService, + + // In general, the ProviderRegistryService is used for making entities available for injection into dynamically loaded components. + private providerRegistry: ProviderRegistryService, + + // Injecting the PersistenceHandler we created and assigning it to a property we use in the template. + public persistenceHandler: PersistenceHandler, + + // Injecting the cloner service which is needed for opening up the cloner wizard. + private widgetClonerService: WidgetClonerService + ) {} + + public ngOnInit(): void { + // Grabbing the widget's default template which will be needed as a parameter for setNode + const kpiTemplate = this.widgetTypesService.getWidgetType("kpi", 1); + const proportionalTemplate = this.widgetTypesService.getWidgetType( + "proportional", + 1 + ); + + // Registering our data sources as dropdown options in the widget editor/configurator + // Note: This could also be done in the parent module's constructor so that + // multiple dashboards could have access to the same widget template modification. + this.widgetTypesService.setNode( + // This is the template we grabbed above with getWidgetType + proportionalTemplate, + // Setting the editor/configurator part of the widget template + "configurator", + // This indicates which node you are changing and we want to change + // the data source providers available for selection in the editor. + WellKnownPathKey.DataSourceProviders, + // Setting the data sources available for selection in the editor + [RandomCitiesProportionalDataSource.providerId] + ); + + // Same as above, but for the KPI data sources + this.widgetTypesService.setNode( + kpiTemplate, + "configurator", + WellKnownPathKey.DataSourceProviders, + [ + AverageRatingKpiDataSource.providerId, + RatingsCountKpiDataSource.providerId, + ] + ); + + // Registering the data sources available for injection into the KPI tiles and proportional widget. + // Note: Each tile of a KPI widget is assigned its own instance of a data source. + this.providerRegistry.setProviders({ + [AverageRatingKpiDataSource.providerId]: { + provide: DATA_SOURCE, + useClass: AverageRatingKpiDataSource, + // Any dependencies that need to be injected into the provider must be listed here + deps: [HttpClient], + }, + [RatingsCountKpiDataSource.providerId]: { + provide: DATA_SOURCE, + useClass: RatingsCountKpiDataSource, + deps: [HttpClient], + }, + [RandomCitiesProportionalDataSource.providerId]: { + provide: DATA_SOURCE, + useClass: RandomCitiesProportionalDataSource, + deps: [], + }, + }); + + this.initializeDashboard(); + } + + public onCreateWidget(): void { + const widgetSelector: IWidgetSelector = { + // Template ref of the dashboard component. + dashboardComponent: this.dashboardComponent, + // A trySubmit function; in this case, we use the trySubmit from the PersistenceHandler created in the previous tutorial. + trySubmit: this.persistenceHandler.trySubmit, + // WidgetTemplateSelectionComponent will act as step one of the wizard to allow the user to select which widget will be cloned. + widgetSelectionComponentType: WidgetTemplateSelectionComponent, + }; + this.widgetClonerService + .open(widgetSelector) + .pipe( + // Auto-unsubscribe after one emission or on component destruction + take(1), + takeUntil(this.destroy$) + ) + .subscribe(); + } + + public initializeDashboard(): void { + // We're using a static configuration object for this example (see widgetConfig at the bottom of the file), + // but this is where the widget's configuration could potentially be populated from a database + const kpiWidget = fullKpiWidgetConfig; + const widgetIndex: IWidgets = { + // Complete the KPI widget with information coming from its type definition + [kpiWidget.id]: + this.widgetTypesService.mergeWithWidgetType(kpiWidget), + }; + + // Setting the widget dimensions and position (this is for gridster) + // Note: If no position is given for a widget the 'defaultItemCols' and 'defaultItemRows' properties + // from the gridsterConfig will be used for the dimensions + const positions: Record = { + [kpiWidget.id]: { + cols: 3, + rows: 5, + y: 0, + x: 0, + }, + }; + + // Finally, assigning the variables we created above to the dashboard + this.dashboard = { + positions, + widgets: widgetIndex, + }; + } +} + +// Interface for each data point in a proportional widget. +interface IProportionalWidgetData { + id: string; + name: string; + data: number[]; + icon: string; + link: string; + value: string; +} + +@Injectable() +export class RandomCitiesProportionalDataSource implements OnDestroy { + public static providerId = "RandomCitiesProportionalDataSource"; + + public outputsSubject = new Subject< + IDataSourceOutput + >(); + + // Every time applyFilters gets ran we are changing the data source. + public applyFilters(): void { + setTimeout(() => { + this.outputsSubject.next({ + result: this.getRandomProportionalWidgetData(), + }); + }, 1000); + } + + public ngOnDestroy(): void { + this.outputsSubject.complete(); + } + + private getRandomProportionalWidgetData(): IProportionalWidgetData[] { + return [ + { + id: "Down", + name: "Down", + data: [Math.round(Math.random() * 100)], + icon: "status_down", + link: "https://en.wikipedia.org/wiki/Brno", + value: "Brno", + }, + { + id: "Critical", + name: "Critical", + data: [Math.round(Math.random() * 100)], + icon: "status_critical", + link: "https://en.wikipedia.org/wiki/Kyiv", + value: "Kyiv", + }, + { + id: "Warning", + name: "Warning", + data: [Math.round(Math.random() * 100)], + icon: "status_warning", + link: "https://en.wikipedia.org/wiki/Austin", + value: "Austin", + }, + { + id: "Unknown", + name: "Unknown", + data: [Math.round(Math.random() * 100)], + icon: "status_unknown", + link: "https://en.wikipedia.org/wiki/Lisbon", + value: "Lisbon", + }, + { + id: "Up", + name: "Up", + data: [Math.round(Math.random() * 100)], + icon: "status_up", + link: "https://en.wikipedia.org/wiki/Sydney", + value: "Sydney", + }, + { + id: "Unmanaged", + name: "Unmanaged", + data: [Math.round(Math.random() * 100)], + icon: "status_unmanaged", + link: "https://en.wikipedia.org/wiki/Nur-Sultan", + value: "Nur-Sultan", + }, + ]; + } +} + +/** + * A simple KPI data source to retrieve the average rating of Harry Potter and the Sorcerer's Stone (book) via googleapis + */ +@Injectable() +export class AverageRatingKpiDataSource + extends DataSourceService + implements OnDestroy +{ + // This is the ID we'll use to identify the provider + public static providerId = "AverageRatingKpiDataSource"; + + // Use this subject to communicate the data source's busy state + public busy = new BehaviorSubject(false); + + constructor(private http: HttpClient) { + super(); + } + + // In this example, getFilteredData is invoked every 10 minutes (Take a look at the refresher + // provider definition in the widget configuration below to see how the interval is set) + public async getFilteredData(): Promise { + this.busy.next(true); + return new Promise((resolve) => { + // *** Make a resource request to an external API (if needed) + this.http + .get("https://www.googleapis.com/books/v1/volumes/5MQFrgEACAAJ") + .pipe(finalize(() => this.busy.next(false))) + .subscribe({ + next: (data: any) => { + resolve({ + result: { + value: data.volumeInfo.averageRating, + }, + }); + }, + error: (error: HttpErrorResponse) => { + resolve({ + result: null, + error: { + type: error.status, + }, + }); + }, + }); + }); + } + + public ngOnDestroy(): void { + this.outputsSubject.complete(); + } +} + +/** + * A simple KPI data source to retrieve the ratings count of Harry Potter and the Sorcerer's Stone (book) via googleapis + */ +@Injectable() +export class RatingsCountKpiDataSource + extends DataSourceService + implements OnDestroy +{ + public static providerId = "RatingsCountKpiDataSource"; + + // Use this subject to communicate the data source's busy state + public busy = new BehaviorSubject(false); + + constructor(private http: HttpClient) { + super(); + } + + public async getFilteredData(): Promise { + this.busy.next(true); + return new Promise((resolve) => { + this.http + .get("https://www.googleapis.com/books/v1/volumes/5MQFrgEACAAJ") + .pipe(finalize(() => this.busy.next(false))) + .subscribe({ + next: (data: any) => { + resolve({ + result: { + value: data.volumeInfo.ratingsCount, + }, + }); + }, + error: (error: HttpErrorResponse) => { + resolve({ + result: null, + error: { + type: error.status, + }, + }); + }, + }); + }); + } + + public ngOnDestroy(): void { + this.outputsSubject.complete(); + } +} + +const fullKpiWidgetConfig: IWidget = { + id: "widget1", + type: "kpi", + pizzagna: { + [PizzagnaLayer.Configuration]: { + [DEFAULT_PIZZAGNA_ROOT]: { + providers: { + [WellKnownProviders.Refresher]: { + properties: { + // Configuring the refresher interval so that our data source is invoked every ten minutes + interval: 60 * 10, + enabled: true, + } as IRefresherProperties, + } as Partial, + }, + }, + header: { + properties: { + title: "Harry Potter and the Sorcerer's Stone", + subtitle: "By J. K. Rowling", + }, + }, + tiles: { + properties: { + nodes: ["kpi1"], + }, + }, + kpi1: { + id: "kpi1", + componentType: KpiComponent.lateLoadKey, + properties: { + widgetData: { + units: \`out of 5 Stars\`, + label: \`Average Rating\`, + }, + }, + providers: { + [WellKnownProviders.DataSource]: { + // Setting the data source providerId for the tile with id "kpi1" + providerId: AverageRatingKpiDataSource.providerId, + } as IProviderConfiguration, + [WellKnownProviders.Adapter]: { + providerId: NOVA_KPI_DATASOURCE_ADAPTER, + properties: { + componentId: "kpi1", + propertyPath: "widgetData", + }, + } as IProviderConfiguration, + }, + }, + }, + }, +}; + +const partialPropWidgetConfig: IWidget = { + id: "widget2", + type: "proportional", + metadata: { + // Set 'needsConfiguration' to true if the widget needs further configuration before it can be + // placed on the dashboard. The "Create Widget" button will be hidden in the wizard when this + // widget is selected. + needsConfiguration: true, + }, + pizzagna: { + [PizzagnaLayer.Configuration]: { + header: { + properties: { + title: "*New Proportional Widget*", + }, + }, + }, + }, +}; +`, + "tutorials/widget-creation/widget-creation.module.ts": `// © 2022 SolarWinds Worldwide, LLC. All rights reserved. +// +// Permission is hereby granted, free of charge, to any person obtaining a copy +// of this software and associated documentation files (the "Software"), to +// deal in the Software without restriction, including without limitation the +// rights to use, copy, modify, merge, publish, distribute, sublicense, and/or +// sell copies of the Software, and to permit persons to whom the Software is +// furnished to do so, subject to the following conditions: +// +// The above copyright notice and this permission notice shall be included in +// all copies or substantial portions of the Software. +// +// THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR +// IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, +// FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE +// AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER +// LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, +// OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN +// THE SOFTWARE. + +import { CommonModule } from "@angular/common"; +import { HttpClientModule } from "@angular/common/http"; +import { NgModule } from "@angular/core"; +import { RouterModule } from "@angular/router"; + +import { + NuiButtonModule, + NuiDocsModule, + NuiImageModule, + NuiMessageModule, + NuiRepeatModule, + NuiSwitchModule, + NuiToastModule, + DEMO_PATH_TOKEN, +} from "@nova-ui/bits"; +import { NuiDashboardsModule } from "@nova-ui/dashboards"; + +import { WidgetCreationDocsComponent } from "./widget-creation-docs.component"; +import { + WidgetCreationComponent, + WidgetTemplateSelectionComponent, +} from "./widget-creation.component"; +import { getDemoFiles } from "../../../../demo-files-factory"; + +const routes = [ + { + path: "", + component: WidgetCreationDocsComponent, + data: { + srlc: { + hideIndicator: true, + }, + showThemeSwitcher: true, + }, + }, + { + path: "example", + component: WidgetCreationComponent, + data: { + srlc: { + hideIndicator: true, + }, + }, + }, +]; + +@NgModule({ + imports: [ + CommonModule, + HttpClientModule, + NuiDashboardsModule, + NuiDocsModule, + NuiMessageModule, + NuiSwitchModule, + NuiToastModule, + NuiButtonModule, + NuiRepeatModule, + NuiImageModule, + RouterModule.forChild(routes), + ], + declarations: [ + WidgetCreationDocsComponent, + WidgetCreationComponent, + WidgetTemplateSelectionComponent, + ], + providers: [ + { + provide: DEMO_PATH_TOKEN, + useValue: getDemoFiles("widget-creation"), + }, + ], +}) +export default class WidgetCreationModule {} +`, + "tutorials/widget-editor-setup/widget-editor-setup-docs.component.ts": `// © 2022 SolarWinds Worldwide, LLC. All rights reserved. +// +// Permission is hereby granted, free of charge, to any person obtaining a copy +// of this software and associated documentation files (the "Software"), to +// deal in the Software without restriction, including without limitation the +// rights to use, copy, modify, merge, publish, distribute, sublicense, and/or +// sell copies of the Software, and to permit persons to whom the Software is +// furnished to do so, subject to the following conditions: +// +// The above copyright notice and this permission notice shall be included in +// all copies or substantial portions of the Software. +// +// THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR +// IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, +// FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE +// AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER +// LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, +// OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN +// THE SOFTWARE. + +import { Component } from "@angular/core"; + +@Component({ + selector: "nui-dashboard-widget-editor-docs", + templateUrl: "./widget-editor-setup-docs.component.html", + standalone: false, +}) +export class WidgetEditorDocsComponent {} +`, + "tutorials/widget-editor-setup/widget-editor-setup.component.ts": `// © 2022 SolarWinds Worldwide, LLC. All rights reserved. +// +// Permission is hereby granted, free of charge, to any person obtaining a copy +// of this software and associated documentation files (the "Software"), to +// deal in the Software without restriction, including without limitation the +// rights to use, copy, modify, merge, publish, distribute, sublicense, and/or +// sell copies of the Software, and to permit persons to whom the Software is +// furnished to do so, subject to the following conditions: +// +// The above copyright notice and this permission notice shall be included in +// all copies or substantial portions of the Software. +// +// THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR +// IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, +// FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE +// AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER +// LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, +// OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN +// THE SOFTWARE. + +import { HttpClient, HttpErrorResponse } from "@angular/common/http"; +import { + ChangeDetectorRef, + Component, + Injectable, + OnDestroy, + OnInit, +} from "@angular/core"; +import { GridsterConfig, GridsterItem } from "angular-gridster2"; +import { BehaviorSubject } from "rxjs"; +import { finalize } from "rxjs/operators"; + +import { DataSourceService, IFilteringOutputs } from "@nova-ui/bits"; +import { + DATA_SOURCE, + DEFAULT_PIZZAGNA_ROOT, + IDashboard, + IKpiData, + IProviderConfiguration, + IRefresherProperties, + IWidget, + IWidgets, + KpiComponent, + NOVA_KPI_DATASOURCE_ADAPTER, + PizzagnaLayer, + ProviderRegistryService, + WellKnownPathKey, + WellKnownProviders, + WidgetTypesService, +} from "@nova-ui/dashboards"; + +/** + * A simple KPI data source to retrieve the average rating of Harry Potter and the Sorcerer's Stone (book) via googleapis + */ +@Injectable() +export class AverageRatingKpiDataSource + extends DataSourceService + implements OnDestroy +{ + // This is the ID we'll use to identify the provider + public static providerId = "AverageRatingKpiDataSource"; + + // Use this subject to communicate the data source's busy state + public busy = new BehaviorSubject(false); + + constructor(private http: HttpClient) { + super(); + } + + // In this example, getFilteredData is invoked every 10 minutes (Take a look at the refresher + // provider definition in the widget configuration below to see how the interval is set) + public async getFilteredData(): Promise { + this.busy.next(true); + return new Promise((resolve) => { + // *** Make a resource request to an external API (if needed) + this.http + .get("https://www.googleapis.com/books/v1/volumes/5MQFrgEACAAJ") + .pipe(finalize(() => this.busy.next(false))) + .subscribe({ + next: (data: any) => { + resolve({ + result: { + value: data.volumeInfo.averageRating, + }, + }); + }, + error: (error: HttpErrorResponse) => { + resolve({ + result: null, + error: { + type: error.status, + }, + }); + }, + }); + }); + } + + public ngOnDestroy(): void { + this.outputsSubject.complete(); + } +} + +/** + * A simple KPI data source to retrieve the ratings count of Harry Potter and the Sorcerer's Stone (book) via googleapis + */ +@Injectable() +export class RatingsCountKpiDataSource + extends DataSourceService + implements OnDestroy +{ + public static providerId = "RatingsCountKpiDataSource"; + + // Use this subject to communicate the data source's busy state + public busy = new BehaviorSubject(false); + + constructor(private http: HttpClient) { + super(); + } + + public async getFilteredData(): Promise { + this.busy.next(true); + return new Promise((resolve) => { + this.http + .get("https://www.googleapis.com/books/v1/volumes/5MQFrgEACAAJ") + .pipe(finalize(() => this.busy.next(false))) + .subscribe({ + next: (data: any) => { + resolve({ + result: { + value: data.volumeInfo.ratingsCount, + }, + }); + }, + error: (error: HttpErrorResponse) => { + resolve({ + result: null, + error: { + type: error.status, + }, + }); + }, + }); + }); + } + + public ngOnDestroy(): void { + this.outputsSubject.complete(); + } +} + +/** + * A component that instantiates the dashboard + */ +@Component({ + selector: "widget-editor-setup", + templateUrl: "./widget-editor-setup.component.html", + styleUrls: ["./widget-editor-setup.component.less"], + standalone: false, +}) +export class WidgetEditorSetupComponent implements OnInit { + // This variable will hold all the data needed to define the layout and behavior of the widgets. + // Pass this to the dashboard component's dashboard input in the template. + public dashboard: IDashboard | undefined; + + // Angular gridster requires a configuration object even if it's empty. + // Pass this to the dashboard component's gridsterConfig input in the template. + public gridsterConfig: GridsterConfig = {}; + + // Boolean which dashboard takes in as an input if its true it allows you to move widgets around. + public editMode: boolean = false; + + constructor( + // WidgetTypesService provides the widget's necessary structure information + private widgetTypesService: WidgetTypesService, + + // In general, the ProviderRegistryService is used for making entities available for injection into dynamically loaded components. + private providerRegistry: ProviderRegistryService, + private changeDetectorRef: ChangeDetectorRef + ) {} + + public ngOnInit(): void { + // Grabbing the widget's default template which will be needed as a parameter for setNode + const widgetTemplate = this.widgetTypesService.getWidgetType("kpi", 1); + // Registering our data sources as dropdown options in the widget editor/configurator + // Note: This could also be done in the parent module's constructor so that + // multiple dashboards could have access to the same widget template modification. + this.widgetTypesService.setNode( + // This is the template we grabbed above with getWidgetType + widgetTemplate, + // We are setting the editor/configurator part of the widget template + "configurator", + // This indicates which node you are changing and we want to change + // the data source providers available for selection in the editor. + WellKnownPathKey.DataSourceProviders, + // We are setting the data sources available for selection in the editor + [ + AverageRatingKpiDataSource.providerId, + RatingsCountKpiDataSource.providerId, + ] + ); + + // Registering the data sources available for injection into the KPI tiles. + // Note: Each tile of a KPI widget is assigned its own instance of a data source + this.providerRegistry.setProviders({ + [AverageRatingKpiDataSource.providerId]: { + provide: DATA_SOURCE, + useClass: AverageRatingKpiDataSource, + // Any dependencies that need to be injected into the provider must be listed here + deps: [HttpClient], + }, + [RatingsCountKpiDataSource.providerId]: { + provide: DATA_SOURCE, + useClass: RatingsCountKpiDataSource, + deps: [HttpClient], + }, + }); + + this.initializeDashboard(); + } + + /** Used for restoring widgets state */ + public reInitializeDashboard(): void { + // destroys the components and their providers so the dashboard can re init data + this.dashboard = undefined; + this.changeDetectorRef.detectChanges(); + + this.initializeDashboard(); + } + + public initializeDashboard(): void { + // We're using a static configuration object for this example (see widgetConfig at the bottom of the file), + // but this is where the widget's configuration could potentially be populated from a database + const kpiWidget = widgetConfig; + const widgetIndex: IWidgets = { + // Complete the KPI widget with information coming from its type definition + [kpiWidget.id]: + this.widgetTypesService.mergeWithWidgetType(kpiWidget), + }; + + // Setting the widget dimensions and position (this is for gridster) + const positions: Record = { + [kpiWidget.id]: { + cols: 4, + rows: 6, + y: 0, + x: 0, + }, + }; + + // Finally, assigning the variables we created above to the dashboard + this.dashboard = { + positions, + widgets: widgetIndex, + }; + } +} + +const widgetConfig: IWidget = { + id: "widget1", + type: "kpi", + pizzagna: { + [PizzagnaLayer.Configuration]: { + [DEFAULT_PIZZAGNA_ROOT]: { + providers: { + [WellKnownProviders.Refresher]: { + properties: { + // Configuring the refresher interval so that our data source is invoked every ten minutes + interval: 60 * 10, + enabled: true, + } as IRefresherProperties, + } as Partial, + }, + }, + header: { + properties: { + title: "Harry Potter and the Sorcerer's Stone", + subtitle: "By J. K. Rowling", + }, + }, + tiles: { + properties: { + nodes: ["kpi1"], + }, + }, + kpi1: { + id: "kpi1", + componentType: KpiComponent.lateLoadKey, + properties: { + widgetData: { + units: "out of 5 Stars", + label: "Average Rating", + }, + }, + providers: { + [WellKnownProviders.DataSource]: { + // Setting the data source providerId for the tile with id "kpi1" + providerId: AverageRatingKpiDataSource.providerId, + } as IProviderConfiguration, + [WellKnownProviders.Adapter]: { + providerId: NOVA_KPI_DATASOURCE_ADAPTER, + properties: { + componentId: "kpi1", + propertyPath: "widgetData", + }, + } as IProviderConfiguration, + }, + }, + }, + }, +}; +`, + "tutorials/widget-editor-setup/widget-editor-setup.module.ts": `// © 2022 SolarWinds Worldwide, LLC. All rights reserved. +// +// Permission is hereby granted, free of charge, to any person obtaining a copy +// of this software and associated documentation files (the "Software"), to +// deal in the Software without restriction, including without limitation the +// rights to use, copy, modify, merge, publish, distribute, sublicense, and/or +// sell copies of the Software, and to permit persons to whom the Software is +// furnished to do so, subject to the following conditions: +// +// The above copyright notice and this permission notice shall be included in +// all copies or substantial portions of the Software. +// +// THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR +// IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, +// FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE +// AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER +// LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, +// OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN +// THE SOFTWARE. + +import { CommonModule } from "@angular/common"; +import { HttpClientModule } from "@angular/common/http"; +import { NgModule } from "@angular/core"; +import { RouterModule } from "@angular/router"; + +import { + NuiButtonModule, + NuiDocsModule, + NuiMessageModule, + NuiSwitchModule, + DEMO_PATH_TOKEN, +} from "@nova-ui/bits"; +import { NuiDashboardsModule } from "@nova-ui/dashboards"; + +import { WidgetEditorDocsComponent } from "./widget-editor-setup-docs.component"; +import { WidgetEditorSetupComponent } from "./widget-editor-setup.component"; +import { getDemoFiles } from "../../../../demo-files-factory"; + +const routes = [ + { + path: "", + component: WidgetEditorDocsComponent, + data: { + srlc: { + hideIndicator: true, + }, + showThemeSwitcher: true, + }, + }, + { + path: "example", + component: WidgetEditorSetupComponent, + data: { + srlc: { + hideIndicator: true, + }, + }, + }, +]; + +@NgModule({ + imports: [ + CommonModule, + HttpClientModule, + NuiDashboardsModule, + NuiDocsModule, + NuiMessageModule, + NuiSwitchModule, + NuiButtonModule, + RouterModule.forChild(routes), + ], + declarations: [WidgetEditorDocsComponent, WidgetEditorSetupComponent], + providers: [ + { + provide: DEMO_PATH_TOKEN, + useValue: getDemoFiles("widget-editor-setup"), + }, + ], +}) +export default class WidgetEditorSetupModule {} +`, + "tutorials/widget-error-handling/widget-error-handling-docs.component.ts": `// © 2022 SolarWinds Worldwide, LLC. All rights reserved. +// +// Permission is hereby granted, free of charge, to any person obtaining a copy +// of this software and associated documentation files (the "Software"), to +// deal in the Software without restriction, including without limitation the +// rights to use, copy, modify, merge, publish, distribute, sublicense, and/or +// sell copies of the Software, and to permit persons to whom the Software is +// furnished to do so, subject to the following conditions: +// +// The above copyright notice and this permission notice shall be included in +// all copies or substantial portions of the Software. +// +// THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR +// IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, +// FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE +// AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER +// LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, +// OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN +// THE SOFTWARE. + +import { Component } from "@angular/core"; + +@Component({ + selector: "nui-widget-error-handling-docs", + templateUrl: "./widget-error-handling-docs.component.html", + standalone: false, +}) +export class WidgetErrorHandlingDocsComponent { + public fallbackAdapter = \` +@Injectable() +export class StatusContentFallbackAdapter implements OnDestroy, IHasComponent { + + protected readonly destroy$ = new Subject(); + protected componentId: string; + + constructor(@Inject(PIZZAGNA_EVENT_BUS) protected eventBus: EventBus, + protected pizzagnaService: PizzagnaService) { + this.eventBus.getStream(DATA_SOURCE_OUTPUT) + .pipe(takeUntil(this.destroy$)).subscribe((event: IEvent>) => { + this.handleDataSourceOutput(event); + }); + } + + public ngOnDestroy(): void { + this.destroy$.next(); + this.destroy$.complete(); + } + + public setComponent(component: any, componentId: string) { + this.componentId = componentId; + } + + protected handleDataSourceOutput(event: IEvent>) { + this.pizzagnaService.setProperty({ + componentId: this.componentId, + propertyPath: ["fallbackKey"], + pizzagnaKey: PizzagnaLayer.Data, + }, typeof event.payload?.error?.type !== "undefined" ? event.payload?.error?.type.toString() : undefined); + } +}\`; + public errorsMap = \` +export const ERROR_FALLBACK_MAP: Record = { + [HttpStatusCode.Unknown]: ErrorNodeKey.ErrorUnknown, + [HttpStatusCode.Forbidden]: ErrorNodeKey.ErrorForbidden, + [HttpStatusCode.NotFound]: ErrorNodeKey.ErrorNotFound, +}; +\`; + public errorNodes = \` +export const ERROR_NODES: Record = { + [ErrorNodeKey.ErrorUnknown]: { + id: ErrorNodeKey.ErrorUnknown, + componentType: WidgetErrorComponent.lateLoadKey, + properties: { + image: "no-data-to-show", + title: $localize\\\`Whoops, something went wrong\\\`, + description: $localize\\\`There was an unexpected error.\\\`, + } as IWidgetErrorDisplayProperties, + }, + [ErrorNodeKey.ErrorForbidden]: { + id: ErrorNodeKey.ErrorForbidden, + componentType: WidgetErrorComponent.lateLoadKey, + properties: { + image: "no-data-to-show", + title: $localize\\\`403 - Forbidden\\\`, + description: $localize\\\`The requested action was forbidden.\\\`, + } as IWidgetErrorDisplayProperties, + }, + [ErrorNodeKey.ErrorNotFound]: { + id: ErrorNodeKey.ErrorNotFound, + componentType: WidgetErrorComponent.lateLoadKey, + properties: { + image: "no-data-to-show", + title: $localize\\\`404 - Not Found\\\`, + description: $localize\\\`The requested resource could not be found.\\\`, + } as IWidgetErrorDisplayProperties, + }, +};\`; + public widgetBodyContentNodesSignature = \` +/** + * Retrieves an index of the basic widget body content nodes including fallback nodes + * + * @param mainContentNodeKey The key corresponding to the main body content node + * @param fallbackAdapterId The id for the adapter responsible for activating fallback content in case of an error + * @param fallbackMap A map of node keys to fallback content definitions + * @param fallbackNodes An index of fallback content definitions + * + * @returns An index of component configurations + */ +export function widgetBodyContentNodes( + mainContentNodeKey: string, + fallbackAdapterId = NOVA_STATUS_CONTENT_FALLBACK_ADAPTER, + fallbackMap: Record = ERROR_FALLBACK_MAP, + fallbackNodes: Record = ERROR_NODES +): Record { ... } +\`; +} +`, + "tutorials/widget-error-handling/widget-error-handling.component.ts": `// © 2022 SolarWinds Worldwide, LLC. All rights reserved. +// +// Permission is hereby granted, free of charge, to any person obtaining a copy +// of this software and associated documentation files (the "Software"), to +// deal in the Software without restriction, including without limitation the +// rights to use, copy, modify, merge, publish, distribute, sublicense, and/or +// sell copies of the Software, and to permit persons to whom the Software is +// furnished to do so, subject to the following conditions: +// +// The above copyright notice and this permission notice shall be included in +// all copies or substantial portions of the Software. +// +// THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR +// IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, +// FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE +// AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER +// LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, +// OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN +// THE SOFTWARE. + +import { HttpClient, HttpErrorResponse } from "@angular/common/http"; +import { + ChangeDetectorRef, + Component, + Injectable, + OnDestroy, + OnInit, +} from "@angular/core"; +import { GridsterConfig, GridsterItem } from "angular-gridster2"; +import { BehaviorSubject } from "rxjs"; +import { finalize } from "rxjs/operators"; + +import { DataSourceService, IFilteringOutputs } from "@nova-ui/bits"; +import { + DATA_SOURCE, + DEFAULT_PIZZAGNA_ROOT, + HttpStatusCode, + IDashboard, + IKpiData, + IProviderConfiguration, + IRefresherProperties, + IWidget, + IWidgets, + KpiComponent, + NOVA_KPI_DATASOURCE_ADAPTER, + PizzagnaLayer, + ProviderRegistryService, + WellKnownPathKey, + WellKnownProviders, + WidgetTypesService, +} from "@nova-ui/dashboards"; + +/** + * A simple KPI data source to retrieve the average rating of Harry Potter and the Sorcerer's Stone (book) via googleapis + */ +@Injectable() +export class AverageRatingKpiDataSource + extends DataSourceService + implements OnDestroy +{ + // This is the ID we'll use to identify the provider + public static providerId = "AverageRatingKpiDataSource"; + + // Use this subject to communicate the data source's busy state + public busy = new BehaviorSubject(false); + + constructor(private http: HttpClient) { + super(); + } + + // In this example, getFilteredData is invoked every 10 minutes (Take a look at the refresher + // provider definition in the widget configuration below to see how the interval is set) + public async getFilteredData(): Promise { + this.busy.next(true); + return new Promise((resolve) => { + // *** Make a resource request to an external API (if needed) + this.http + .get("https://www.googleapis.com/books/v1/volumes/5MQFrgEACAAJ") + .pipe(finalize(() => this.busy.next(false))) + .subscribe({ + next: (data: any) => { + resolve({ + result: { + value: data.volumeInfo.averageRating, + }, + }); + }, + error: (error: HttpErrorResponse) => { + resolve({ + result: null, + error: { + type: error.status, + }, + }); + }, + }); + }); + } + + public ngOnDestroy(): void { + this.outputsSubject.complete(); + } +} + +/** + * A simple KPI data source to retrieve the average rating of Harry Potter and the Sorcerer's Stone (book) via googleapis + */ +@Injectable() +export class ErrorUnknownDataSource + extends DataSourceService + implements OnDestroy +{ + // This is the ID we'll use to identify the provider + public static providerId = "ErrorUnknownDataSource"; + + // Use this subject to communicate the data source's busy state + public busy = new BehaviorSubject(false); + + // In this example, getFilteredData is invoked every 10 minutes (Take a look at the refresher + // provider definition in the widget configuration below to see how the interval is set) + public async getFilteredData(): Promise { + this.busy.next(true); + const mockError = { + result: null, + error: { type: HttpStatusCode.Unknown }, + }; + this.busy.next(false); + return mockError; + } + + public ngOnDestroy(): void { + this.outputsSubject.complete(); + } +} + +/** + * A simple KPI data source to retrieve the ratings count of Harry Potter and the Sorcerer's Stone (book) via googleapis + */ +@Injectable() +export class ErrorForbiddenDataSource + extends DataSourceService + implements OnDestroy +{ + public static providerId = "ErrorForbiddenDataSource"; + + // Use this subject to communicate the data source's busy state + public busy = new BehaviorSubject(false); + + constructor(private http: HttpClient) { + super(); + } + + public async getFilteredData(): Promise { + this.busy.next(true); + // generate a 403 + return new Promise((resolve) => { + this.http + .get( + "http://www.mocky.io/v2/5ecc724a3200000f0023614a?mocky-delay=4000ms" + ) + .pipe(finalize(() => this.busy.next(false))) + .subscribe({ + error: (error: HttpErrorResponse) => { + resolve({ + result: null, + error: { + type: error.status, + }, + }); + }, + }); + }); + } + + public ngOnDestroy(): void { + this.outputsSubject.complete(); + } +} + +/** + * A simple KPI data source to retrieve the ratings count of Harry Potter and the Sorcerer's Stone (book) via googleapis + */ +@Injectable() +export class ErrorNotFoundDataSource + extends DataSourceService + implements OnDestroy +{ + public static providerId = "ErrorNotFoundDataSource"; + + // Use this subject to communicate the data source's busy state + public busy = new BehaviorSubject(false); + + constructor(private http: HttpClient) { + super(); + } + + public async getFilteredData(): Promise { + this.busy.next(true); + // generate a 404 + return new Promise((resolve) => { + this.http + .get( + "http://www.mocky.io/v2/5ec6bfd93200007800d75100?mocky-delay=1000ms" + ) + .pipe(finalize(() => this.busy.next(false))) + .subscribe({ + error: (error: HttpErrorResponse) => { + resolve({ + result: null, + error: { + type: error.status, + }, + }); + }, + }); + }); + } + + public ngOnDestroy(): void { + this.outputsSubject.complete(); + } +} + +/** + * A component that instantiates the dashboard + */ +@Component({ + selector: "widget-error-handling", + templateUrl: "./widget-error-handling.component.html", + styleUrls: ["./widget-error-handling.component.less"], + standalone: false, +}) +export class WidgetErrorHandlingComponent implements OnInit { + // This variable will hold all the data needed to define the layout and behavior of the widgets. + // Pass this to the dashboard component's dashboard input in the template. + public dashboard: IDashboard | undefined; + + // Angular gridster requires a configuration object even if it's empty. + // Pass this to the dashboard component's gridsterConfig input in the template. + public gridsterConfig: GridsterConfig = {}; + + // Boolean which dashboard takes in as an input if its true it allows you to move widgets around. + public editMode: boolean = false; + + constructor( + // WidgetTypesService provides the widget's necessary structure information + private widgetTypesService: WidgetTypesService, + + // In general, the ProviderRegistryService is used for making entities available for injection into dynamically loaded components. + private providerRegistry: ProviderRegistryService, + private changeDetectorRef: ChangeDetectorRef + ) {} + + public ngOnInit(): void { + // Grab the widget's default template which will be needed as a parameter for setNode. + const widgetTemplate = this.widgetTypesService.getWidgetType("kpi", 1); + // Register our data sources as dropdown options in the widget editor/configurator + // Note: This could also be done in the parent module's constructor so that + // multiple dashboards could have access to the same widget template modification. + this.widgetTypesService.setNode( + // This is the template we grabbed above with getWidgetType + widgetTemplate, + // We are setting the editor/configurator part of the widget template + "configurator", + // This indicates which node you are changing and we want to change + // the data source providers available for selection in the editor. + WellKnownPathKey.DataSourceProviders, + // We are setting the data sources available for selection in the editor + [ + ErrorUnknownDataSource.providerId, + ErrorForbiddenDataSource.providerId, + ErrorNotFoundDataSource.providerId, + AverageRatingKpiDataSource.providerId, + ] + ); + + // Register the data sources available for injection into the KPI tiles. + // Note: Each tile of a KPI widget is assigned its own instance of a data source + this.providerRegistry.setProviders({ + [ErrorUnknownDataSource.providerId]: { + provide: DATA_SOURCE, + useClass: ErrorUnknownDataSource, + // Any dependencies that need to be injected into the provider must be listed here + deps: [HttpClient], + }, + [ErrorForbiddenDataSource.providerId]: { + provide: DATA_SOURCE, + useClass: ErrorForbiddenDataSource, + deps: [HttpClient], + }, + [ErrorNotFoundDataSource.providerId]: { + provide: DATA_SOURCE, + useClass: ErrorNotFoundDataSource, + deps: [HttpClient], + }, + [AverageRatingKpiDataSource.providerId]: { + provide: DATA_SOURCE, + useClass: AverageRatingKpiDataSource, + // Any dependencies that need to be injected into the provider must be listed here + deps: [HttpClient], + }, + }); + + this.initializeDashboard(); + } + + /** Used for restoring widgets state */ + public reInitializeDashboard(): void { + // destroys the components and their providers so the dashboard can re init data + this.dashboard = undefined; + this.changeDetectorRef.detectChanges(); + + this.initializeDashboard(); + } + + public initializeDashboard(): void { + // We're using a static configuration object for this example (see widgetConfig at the bottom of the file), + // but this is where the widget's configuration could potentially be populated from a database + const kpiWidget = widgetConfig; + const widgetIndex: IWidgets = { + // Complete the KPI widget with information coming from its type definition + [kpiWidget.id]: + this.widgetTypesService.mergeWithWidgetType(kpiWidget), + }; + + // Setting the widget dimensions and position (this is for gridster) + const positions: Record = { + [kpiWidget.id]: { + cols: 4, + rows: 6, + y: 0, + x: 0, + }, + }; + + // Finally, assigning the variables we created above to the dashboard + this.dashboard = { + positions, + widgets: widgetIndex, + }; + } +} + +const widgetConfig: IWidget = { + id: "widget1", + type: "kpi", + pizzagna: { + [PizzagnaLayer.Configuration]: { + [DEFAULT_PIZZAGNA_ROOT]: { + providers: { + [WellKnownProviders.Refresher]: { + properties: { + // Configuring the refresher interval so that our data source is invoked every ten minutes + interval: 60 * 10, + enabled: true, + } as IRefresherProperties, + } as Partial, + }, + }, + header: { + properties: { + title: "Harry Potter and the Sorcerer's Stone", + subtitle: "By J. K. Rowling", + }, + }, + tiles: { + properties: { + nodes: ["kpi1"], + }, + }, + kpi1: { + id: "kpi1", + componentType: KpiComponent.lateLoadKey, + properties: { + widgetData: { + units: "out of 5 Stars", + label: "Average Rating", + }, + }, + providers: { + [WellKnownProviders.DataSource]: { + // Setting the data source providerId for the tile with id "kpi1" + providerId: ErrorUnknownDataSource.providerId, + } as IProviderConfiguration, + [WellKnownProviders.Adapter]: { + providerId: NOVA_KPI_DATASOURCE_ADAPTER, + properties: { + componentId: "kpi1", + propertyPath: "widgetData", + }, + } as IProviderConfiguration, + }, + }, + }, + }, +}; +`, + "tutorials/widget-error-handling/widget-error-handling.module.ts": `// © 2022 SolarWinds Worldwide, LLC. All rights reserved. +// +// Permission is hereby granted, free of charge, to any person obtaining a copy +// of this software and associated documentation files (the "Software"), to +// deal in the Software without restriction, including without limitation the +// rights to use, copy, modify, merge, publish, distribute, sublicense, and/or +// sell copies of the Software, and to permit persons to whom the Software is +// furnished to do so, subject to the following conditions: +// +// The above copyright notice and this permission notice shall be included in +// all copies or substantial portions of the Software. +// +// THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR +// IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, +// FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE +// AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER +// LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, +// OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN +// THE SOFTWARE. + +import { CommonModule } from "@angular/common"; +import { HttpClientModule } from "@angular/common/http"; +import { NgModule } from "@angular/core"; +import { ReactiveFormsModule } from "@angular/forms"; +import { RouterModule } from "@angular/router"; + +import { + NuiButtonModule, + NuiDocsModule, + NuiFormFieldModule, + NuiIconModule, + NuiMessageModule, + NuiSwitchModule, + NuiTextboxModule, + DEMO_PATH_TOKEN, +} from "@nova-ui/bits"; +import { + NuiDashboardConfiguratorModule, + NuiDashboardsModule, +} from "@nova-ui/dashboards"; + +import { WidgetErrorHandlingDocsComponent } from "./widget-error-handling-docs.component"; +import { WidgetErrorHandlingComponent } from "./widget-error-handling.component"; +import { getDemoFiles } from "../../../../demo-files-factory"; + +const routes = [ + { + path: "", + component: WidgetErrorHandlingDocsComponent, + data: { + srlc: { + hideIndicator: true, + }, + showThemeSwitcher: true, + }, + }, + { + path: "example", + component: WidgetErrorHandlingComponent, + data: { + srlc: { + hideIndicator: true, + }, + }, + }, +]; + +@NgModule({ + imports: [ + CommonModule, + ReactiveFormsModule, + HttpClientModule, + NuiButtonModule, + NuiDashboardsModule, + NuiDashboardConfiguratorModule, + NuiDocsModule, + NuiFormFieldModule, + NuiIconModule, + NuiMessageModule, + NuiIconModule, + NuiTextboxModule, + NuiIconModule, + NuiSwitchModule, + RouterModule.forChild(routes), + ], + declarations: [ + WidgetErrorHandlingDocsComponent, + WidgetErrorHandlingComponent, + ], + providers: [ + { + provide: DEMO_PATH_TOKEN, + useValue: getDemoFiles("widget-error-handling"), + }, + ], +}) +export default class WidgetErrorHandlingModule {} +`, + "types.ts": `// © 2022 SolarWinds Worldwide, LLC. All rights reserved. +// +// Permission is hereby granted, free of charge, to any person obtaining a copy +// of this software and associated documentation files (the "Software"), to +// deal in the Software without restriction, including without limitation the +// rights to use, copy, modify, merge, publish, distribute, sublicense, and/or +// sell copies of the Software, and to permit persons to whom the Software is +// furnished to do so, subject to the following conditions: +// +// The above copyright notice and this permission notice shall be included in +// all copies or substantial portions of the Software. +// +// THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR +// IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, +// FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE +// AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER +// LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, +// OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN +// THE SOFTWARE. + +export enum APOLLO_API_NAMESPACE { + COUNTRIES = "countries", +} +`, + "widget-types/drilldown/drilldown-multi-request-widget/drilldown-multi-request-widget-example.component.ts": `// © 2022 SolarWinds Worldwide, LLC. All rights reserved. +// +// Permission is hereby granted, free of charge, to any person obtaining a copy +// of this software and associated documentation files (the "Software"), to +// deal in the Software without restriction, including without limitation the +// rights to use, copy, modify, merge, publish, distribute, sublicense, and/or +// sell copies of the Software, and to permit persons to whom the Software is +// furnished to do so, subject to the following conditions: +// +// The above copyright notice and this permission notice shall be included in +// all copies or substantial portions of the Software. +// +// THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR +// IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, +// FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE +// AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER +// LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, +// OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN +// THE SOFTWARE. + +import { HttpClient } from "@angular/common/http"; +import { + ChangeDetectorRef, + Component, + Injectable, + OnDestroy, + OnInit, +} from "@angular/core"; +import { GridsterConfig, GridsterItem } from "angular-gridster2"; +import { Apollo, gql } from "apollo-angular"; +import { BehaviorSubject, Observable, of, Subject } from "rxjs"; +// eslint-disable-next-line import/no-deprecated +import { finalize, map, switchMap, tap } from "rxjs/operators"; + +import { + DataSourceService, + IconStatus, + IDataField, + IFilters, + INovaFilters, +} from "@nova-ui/bits"; +import { + DATA_SOURCE, + DEFAULT_PIZZAGNA_ROOT, + IDashboard, + IDrilldownComponentsConfiguration, + IListWidgetConfiguration, + IProviderConfiguration, + IWidget, + IWidgets, + ListGroupItemComponent, + ListLeafItemComponent, + NOVA_DRILLDOWN_DATASOURCE_ADAPTER, + PizzagnaLayer, + ProviderRegistryService, + WellKnownPathKey, + WellKnownProviders, + WidgetTypesService, +} from "@nova-ui/dashboards"; + +import { APOLLO_API_NAMESPACE } from "../../../types"; + +/** + * A simple KPI data source to retrieve the average rating of Harry Potter and the Sorcerer's Stone (book) via googleapis + */ +@Injectable() +export class DrilldownDataSource + extends DataSourceService + implements OnDestroy +{ + // This is the ID we'll use to identify the provider + public static providerId = "DrilldownDataSource"; + + // Use this subject to communicate the data source's busy state + public busy = new BehaviorSubject(false); + + public dataFields: Partial[] = [ + { id: "Region", label: "Region name" }, + { id: "Subregion", label: "Subregion name" }, + ]; + + private drillState: string[] = []; + private groupBy: string[]; + private cache: any; + private lastDrillState: string[] = []; + private leafGroup: string = "country"; + private applyFilters$ = new Subject(); + + constructor(private http: HttpClient, private apollo: Apollo) { + super(); + + // TODO: remove Partial in vNext after marking dataType field as optional - NUI-5838 + ( + this.dataFieldsConfig.dataFields$ as BehaviorSubject< + Partial[] + > + ).next(this.dataFields); + + this.applyFilters$ + // eslint-disable-next-line import/no-deprecated + .pipe(switchMap((filters) => this.getData(filters))) + .subscribe(async (res) => { + this.outputsSubject.next(await this.getFilteredData(res)); + }); + } + + private groupedDataHistory: any[] = []; + + // In this example, getFilteredData is invoked every 10 minutes (Take a look at the refresher + // provider definition in the widget configuration below to see how the interval is set) + public async getFilteredData(data: any): Promise { + return of(data) + .pipe( + map((entries) => { + if (this.isDrillDown()) { + const activeDrillLvl = this.drillState.length; + const group = this.groupBy[activeDrillLvl]; + const lastGroupedValue = + this.getTransformedDataForGroup( + entries, + group, + getLast(this.drillState) + ); + + this.groupedDataHistory.push(lastGroupedValue); + + return lastGroupedValue; + } + + const mapIconsToEntries = entries.map((item: any) => ({ + ...item, + icon: "virtual-host", + icon_status: IconStatus.Up, + })); + this.groupedDataHistory.push(mapIconsToEntries); + const widgetInput = this.getOutput(entries); + + return widgetInput; + }) + ) + .toPromise(); + } + + public ngOnDestroy(): void { + this.outputsSubject.complete(); + } + + // redefine parent method + public async applyFilters(): Promise { + this.applyFilters$.next(this.getFilters()); + } + + private getQuery(key: string, value: string) { + const groupToRequestMap: Record = { + Region: \`{ Region { name } }\`, + Subregion: \`{ Subregion(filter: { region: { name: "\${value}" } } ) { name } }\`, + Country: \`{ Country(filter: { subregion: { name: "\${value}" } } ) { name capital } }\`, + }; + + return gql\` + \${groupToRequestMap[key]} + \`; + } + + private getData(filters: INovaFilters): Observable { + this.drillState = filters.drillstate?.value; + this.groupBy = filters.group?.value; + const group = this.groupBy[this.drillState.length]; + const isDrillUp = this.drillState.length < this.lastDrillState.length; + + this.lastDrillState = [...this.drillState]; + + if (!this.drillState.length) { + this.groupedDataHistory.length = 0; + } + + this.busy.next(true); + + if (this.cache && (isDrillUp || this.isHome())) { + return of(this.cache).pipe( + map((data) => data.data[group]), + finalize(() => this.busy.next(false)) + ); + } else { + return this.apollo + .use(APOLLO_API_NAMESPACE.COUNTRIES) + .query({ + query: this.getQuery( + group || this.leafGroup, + getLast(this.drillState) + ), + }) + .pipe( + tap( + (data) => + (this.cache = { + data: { ...this.cache?.data, ...data?.data }, + }) + ), + map((data) => data.data[group || this.leafGroup]), + finalize(() => this.busy.next(false)) + ); + } + } + + private getTransformedDataForGroup( + data: any, + group: string, + drillStateValue: string + ) { + const fallback: string = \`No \${group} for \${drillStateValue}\`; + const dataArr = Object.values(data).map((val: any) => ({ + id: val.name || fallback, + label: val.name || fallback, + statuses: [ + { key: "state_ok", value: val.name?.length }, + { + key: "status_unreachable", + value: generateNumberUpTo(100000), + }, + { key: "status_warning", value: generateNumberUpTo(10000) }, + { key: "status_unknown", value: generateNumberUpTo(1000) }, + ], + })); + + return dataArr; + } + + private isHome(): boolean { + return this.drillState.length === 0; + } + + private isDrillDown(): boolean { + return this.drillState.length !== this.groupBy.length; + } + + private getOutput(data: any) { + if (this.isHome()) { + this.groupedDataHistory.length = 0; + } + + const lastHistoryValue = getLast(this.groupedDataHistory); + + if (!lastHistoryValue) { + return data; + } + + return lastHistoryValue[getLast(this.drillState)] || lastHistoryValue; + } +} + +@Component({ + selector: "drilldown-multi-request-widget-example", + templateUrl: "./drilldown-multi-request-widget-example.component.html", + styleUrls: ["./drilldown-multi-request-widget-example.component.less"], + standalone: false, +}) +export class DrilldownMultiRequestWidgetExampleComponent implements OnInit { + // This variable will hold all the data needed to define the layout and behavior of the widgets. + // Pass this to the dashboard component's dashboard input in the template. + public dashboard: IDashboard | undefined; + + // Angular gridster requires a configuration object even if it's empty. + // Pass this to the dashboard component's gridsterConfig input in the template. + public gridsterConfig: GridsterConfig = {}; + + // Boolean passed as an input to the dashboard. When true, widgets can be moved, resized, removed, or edited + public editMode: boolean = false; + + constructor( + // WidgetTypesService provides the widget's necessary structure information + private widgetTypesService: WidgetTypesService, + private providerRegistry: ProviderRegistryService, + private changeDetectorRef: ChangeDetectorRef + ) {} + + public ngOnInit(): void { + // this.prepareNovaDashboards(); + this.initializeDashboard(); + const widgetTemplate = this.widgetTypesService.getWidgetType( + "drilldown", + 1 + ); + this.widgetTypesService.setNode( + widgetTemplate, + "configurator", + WellKnownPathKey.DataSourceProviders, + [DrilldownDataSource.providerId] + ); + + // Registering the data source for injection into the KPI tile. + // Note: Each tile of a KPI widget is assigned its own instance of the data source + this.providerRegistry.setProviders({ + [DrilldownDataSource.providerId]: { + provide: DATA_SOURCE, + useClass: DrilldownDataSource, + // Any dependencies that need to be injected into the provider must be listed here + deps: [HttpClient, Apollo], + }, + }); + } + + /** Used for restoring widgets state */ + public reInitializeDashboard(): void { + // destroys the components and their providers so the dashboard can re init data + this.dashboard = undefined; + this.changeDetectorRef.detectChanges(); + + this.initializeDashboard(); + } + + public initializeDashboard(): void { + // We're using a static configuration object for this example, but this is where + // the widget's configuration could potentially be populated from a database + const drilldownWidget = widgetConfig; + const widgets: IWidgets = { + // Complete the widget with information coming from its type definition + [drilldownWidget.id]: + this.widgetTypesService.mergeWithWidgetType(drilldownWidget), + }; + + // Setting the widget dimensions and position (this is for gridster) + const positions: Record = { + [drilldownWidget.id]: { + cols: 10, + rows: 10, + y: 0, + x: 0, + }, + }; + + // Finally, assigning the variables we created above to the dashboard + this.dashboard = { positions, widgets }; + } +} + +const widgetConfig: IWidget = { + id: "drilldown", + type: "drilldown", + pizzagna: { + [PizzagnaLayer.Configuration]: { + header: { + properties: { + title: "Drilldown Widget", + subtitle: "Countries BY continent THEN currency", + }, + }, + [DEFAULT_PIZZAGNA_ROOT]: { + providers: { + [WellKnownProviders.DataSource]: { + // Setting the data source providerId for the tile with id "kpi1" + providerId: DrilldownDataSource.providerId, + properties: {}, + } as IProviderConfiguration, + }, + }, + listWidget: { + providers: { + [WellKnownProviders.Adapter]: { + providerId: NOVA_DRILLDOWN_DATASOURCE_ADAPTER, + properties: { + // widget + navigationBarId: "navigationBar", + componentId: "listWidget", + dataPath: "data", + + // adapter props + drillstate: [], + groups: ["Region", "Subregion"], + groupBy: ["Region", "Subregion"], + + // components + componentsConfig: { + group: { + componentType: + ListGroupItemComponent.lateLoadKey, + properties: { + dataFieldIds: { + id: "id", + label: "label", + statuses: "statuses", + }, + }, + itemProperties: { + canNavigate: true, + }, + }, + leaf: { + componentType: + ListLeafItemComponent.lateLoadKey, + properties: { + dataFieldIds: { + icon: "icon", + status: "icon_status", + detailedUrl: "capital", + label: "name", + }, + }, + itemProperties: { + canNavigate: false, + }, + }, + } as IDrilldownComponentsConfiguration, + }, + }, + }, + properties: { + configuration: { + // FORMAT: + // componentType: ListLeafItemComponent.lateLoadKey, + // properties: { + // dataFieldIds: { + // icon: "", + // status: "code", + // detailedUrl: "capital", + // label: "name", + // }, + // }, + // + } as IListWidgetConfiguration, + }, + }, + }, + }, +}; + +const getLast = (arr: any[]) => arr[arr.length - 1]; + +const generateNumberUpTo = (upperLimit: number): number => + Math.floor(Math.random() * upperLimit + 1); +`, + "widget-types/drilldown/drilldown-widget/data-mock.ts": `// © 2022 SolarWinds Worldwide, LLC. All rights reserved. +// +// Permission is hereby granted, free of charge, to any person obtaining a copy +// of this software and associated documentation files (the "Software"), to +// deal in the Software without restriction, including without limitation the +// rights to use, copy, modify, merge, publish, distribute, sublicense, and/or +// sell copies of the Software, and to permit persons to whom the Software is +// furnished to do so, subject to the following conditions: +// +// The above copyright notice and this permission notice shall be included in +// all copies or substantial portions of the Software. +// +// THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR +// IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, +// FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE +// AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER +// LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, +// OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN +// THE SOFTWARE. + +import { IconStatus } from "@nova-ui/bits"; + +export const GRAPH_DATA_MOCK = { + data: { + countries: [ + { + name: "Andorra", + code: "AD", + icon: "virtual-host", + icon_status: IconStatus.Up, + capital: "Andorra la Vella", + continent: { + name: "Europe", + }, + currency: "EUR", + languages: [ + { + name: "Catalan", + }, + ], + url: "https://en.wikipedia.org/wiki/Andorra", + }, + { + name: "United Arab Emirates", + code: "AE", + icon: "virtual-host", + icon_status: IconStatus.Up, + capital: "Abu Dhabi", + continent: { + name: "Asia", + }, + currency: "AED", + languages: [ + { + name: "Arabic", + }, + ], + url: "https://en.wikipedia.org/wiki/United_Arab_Emirates", + }, + { + name: "Afghanistan", + code: "AF", + icon: "virtual-host", + icon_status: IconStatus.Up, + capital: "Kabul", + continent: { + name: "Asia", + }, + currency: "AFN", + languages: [ + { + name: "Pashto", + }, + { + name: "Uzbek", + }, + { + name: "Turkmen", + }, + ], + url: "https://en.wikipedia.org/wiki/Afghanistan", + }, + { + name: "Antigua and Barbuda", + code: "AG", + icon: "virtual-host", + icon_status: IconStatus.Up, + capital: "Saint John's", + continent: { + name: "North America", + }, + currency: "XCD", + languages: [ + { + name: "English", + }, + ], + url: "https://en.wikipedia.org/wiki/Antigua_and_Barbuda", + }, + { + name: "Anguilla", + code: "AI", + icon: "virtual-host", + icon_status: IconStatus.Up, + capital: "The Valley", + continent: { + name: "North America", + }, + currency: "XCD", + languages: [ + { + name: "English", + }, + ], + url: "https://en.wikipedia.org/wiki/Anguilla", + }, + { + name: "Albania", + code: "AL", + icon: "virtual-host", + icon_status: IconStatus.Up, + capital: "Tirana", + continent: { + name: "Europe", + }, + currency: "ALL", + languages: [ + { + name: "Albanian", + }, + ], + url: "https://en.wikipedia.org/wiki/Albania", + }, + { + name: "Armenia", + code: "AM", + icon: "virtual-host", + icon_status: IconStatus.Up, + capital: "Yerevan", + continent: { + name: "Asia", + }, + currency: "AMD", + languages: [ + { + name: "Armenian", + }, + { + name: "Russian", + }, + ], + url: "https://en.wikipedia.org/wiki/Armenia", + }, + { + name: "Angola", + code: "AO", + icon: "virtual-host", + icon_status: IconStatus.Up, + capital: "Luanda", + continent: { + name: "Africa", + }, + currency: "AOA", + languages: [ + { + name: "Portuguese", + }, + ], + }, + { + name: "Antarctica", + code: "AQ", + icon: "virtual-host", + icon_status: IconStatus.Up, + capital: null, + continent: { + name: "Antarctica", + }, + currency: null, + languages: [], + }, + { + name: "Argentina", + code: "AR", + icon: "virtual-host", + icon_status: IconStatus.Up, + capital: "Buenos Aires", + continent: { + name: "South America", + }, + currency: "ARS", + languages: [ + { + name: "Spanish", + }, + { + name: "Guarani", + }, + ], + }, + { + name: "American Samoa", + code: "AS", + icon: "virtual-host", + icon_status: IconStatus.Up, + capital: "Pago Pago", + continent: { + name: "Oceania", + }, + currency: "USD", + languages: [ + { + name: "English", + }, + { + name: "Samoan", + }, + ], + }, + { + name: "Austria", + code: "AT", + icon: "virtual-host", + icon_status: IconStatus.Up, + capital: "Vienna", + continent: { + name: "Europe", + }, + currency: "EUR", + languages: [ + { + name: "German", + }, + ], + }, + { + name: "Australia", + code: "AU", + icon: "virtual-host", + icon_status: IconStatus.Up, + capital: "Canberra", + continent: { + name: "Oceania", + }, + currency: "AUD", + languages: [ + { + name: "English", + }, + ], + }, + { + name: "Aruba", + code: "AW", + icon: "virtual-host", + icon_status: IconStatus.Up, + capital: "Oranjestad", + continent: { + name: "North America", + }, + currency: "AWG", + languages: [ + { + name: "Dutch", + }, + { + name: "Panjabi / Punjabi", + }, + ], + }, + { + name: "Åland", + code: "AX", + icon: "virtual-host", + icon_status: IconStatus.Up, + capital: "Mariehamn", + continent: { + name: "Europe", + }, + currency: "EUR", + languages: [ + { + name: "Swedish", + }, + ], + }, + { + name: "Azerbaijan", + code: "AZ", + icon: "virtual-host", + icon_status: IconStatus.Up, + capital: "Baku", + continent: { + name: "Asia", + }, + currency: "AZN", + languages: [ + { + name: "Azerbaijani", + }, + ], + }, + { + name: "Bosnia and Herzegovina", + code: "BA", + icon: "virtual-host", + icon_status: IconStatus.Up, + capital: "Sarajevo", + continent: { + name: "Europe", + }, + currency: "BAM", + languages: [ + { + name: "Bosnian", + }, + { + name: "Croatian", + }, + { + name: "Serbian", + }, + ], + }, + { + name: "Barbados", + code: "BB", + icon: "virtual-host", + icon_status: IconStatus.Up, + capital: "Bridgetown", + continent: { + name: "North America", + }, + currency: "BBD", + languages: [ + { + name: "English", + }, + ], + }, + { + name: "Bangladesh", + code: "BD", + icon: "virtual-host", + icon_status: IconStatus.Up, + capital: "Dhaka", + continent: { + name: "Asia", + }, + currency: "BDT", + languages: [ + { + name: "Bengali", + }, + ], + }, + { + name: "Belgium", + code: "BE", + icon: "virtual-host", + icon_status: IconStatus.Up, + capital: "Brussels", + continent: { + name: "Europe", + }, + currency: "EUR", + languages: [ + { + name: "Dutch", + }, + { + name: "French", + }, + { + name: "German", + }, + ], + }, + { + name: "Burkina Faso", + code: "BF", + icon: "virtual-host", + icon_status: IconStatus.Up, + capital: "Ouagadougou", + continent: { + name: "Africa", + }, + currency: "XOF", + languages: [ + { + name: "French", + }, + { + name: "Peul", + }, + ], + }, + { + name: "Bulgaria", + code: "BG", + icon: "virtual-host", + icon_status: IconStatus.Up, + capital: "Sofia", + continent: { + name: "Europe", + }, + currency: "BGN", + languages: [ + { + name: "Bulgarian", + }, + ], + }, + { + name: "Bahrain", + code: "BH", + icon: "virtual-host", + icon_status: IconStatus.Up, + capital: "Manama", + continent: { + name: "Asia", + }, + currency: "BHD", + languages: [ + { + name: "Arabic", + }, + ], + }, + { + name: "Burundi", + code: "BI", + icon: "virtual-host", + icon_status: IconStatus.Up, + capital: "Bujumbura", + continent: { + name: "Africa", + }, + currency: "BIF", + languages: [ + { + name: "French", + }, + { + name: "Kirundi", + }, + ], + }, + { + name: "Benin", + code: "BJ", + icon: "virtual-host", + icon_status: IconStatus.Up, + capital: "Porto-Novo", + continent: { + name: "Africa", + }, + currency: "XOF", + languages: [ + { + name: "French", + }, + ], + }, + { + name: "Saint Barthélemy", + code: "BL", + icon: "virtual-host", + icon_status: IconStatus.Up, + capital: "Gustavia", + continent: { + name: "North America", + }, + currency: "EUR", + languages: [ + { + name: "French", + }, + ], + }, + { + name: "Bermuda", + code: "BM", + icon: "virtual-host", + icon_status: IconStatus.Up, + capital: "Hamilton", + continent: { + name: "North America", + }, + currency: "BMD", + languages: [ + { + name: "English", + }, + ], + }, + { + name: "Brunei", + code: "BN", + icon: "virtual-host", + icon_status: IconStatus.Up, + capital: "Bandar Seri Begawan", + continent: { + name: "Asia", + }, + currency: "BND", + languages: [ + { + name: "Malay", + }, + ], + }, + { + name: "Bolivia", + code: "BO", + icon: "virtual-host", + icon_status: IconStatus.Up, + capital: "Sucre", + continent: { + name: "South America", + }, + currency: "BOB,BOV", + languages: [ + { + name: "Spanish", + }, + { + name: "Aymara", + }, + { + name: "Quechua", + }, + ], + }, + { + name: "Bonaire", + code: "BQ", + icon: "virtual-host", + icon_status: IconStatus.Up, + capital: "Kralendijk", + continent: { + name: "North America", + }, + currency: "USD", + languages: [ + { + name: "Dutch", + }, + ], + }, + { + name: "Brazil", + code: "BR", + icon: "virtual-host", + icon_status: IconStatus.Up, + capital: "Brasília", + continent: { + name: "South America", + }, + currency: "BRL", + languages: [ + { + name: "Portuguese", + }, + ], + }, + { + name: "Bahamas", + code: "BS", + icon: "virtual-host", + icon_status: IconStatus.Up, + capital: "Nassau", + continent: { + name: "North America", + }, + currency: "BSD", + languages: [ + { + name: "English", + }, + ], + }, + { + name: "Bhutan", + code: "BT", + icon: "virtual-host", + icon_status: IconStatus.Up, + capital: "Thimphu", + continent: { + name: "Asia", + }, + currency: "BTN,INR", + languages: [ + { + name: "Dzongkha", + }, + ], + }, + { + name: "Bouvet Island", + code: "BV", + icon: "virtual-host", + icon_status: IconStatus.Up, + capital: null, + continent: { + name: "Antarctica", + }, + currency: "NOK", + languages: [ + { + name: "Norwegian", + }, + { + name: "Norwegian Bokmål", + }, + { + name: "Norwegian Nynorsk", + }, + ], + }, + { + name: "Botswana", + code: "BW", + icon: "virtual-host", + icon_status: IconStatus.Up, + capital: "Gaborone", + continent: { + name: "Africa", + }, + currency: "BWP", + languages: [ + { + name: "English", + }, + { + name: "Tswana", + }, + ], + }, + { + name: "Belarus", + code: "BY", + icon: "virtual-host", + icon_status: IconStatus.Up, + capital: "Minsk", + continent: { + name: "Europe", + }, + currency: "BYN", + languages: [ + { + name: "Belarusian", + }, + { + name: "Russian", + }, + ], + }, + { + name: "Belize", + code: "BZ", + icon: "virtual-host", + icon_status: IconStatus.Up, + capital: "Belmopan", + continent: { + name: "North America", + }, + currency: "BZD", + languages: [ + { + name: "English", + }, + { + name: "Spanish", + }, + ], + }, + { + name: "Canada", + code: "CA", + icon: "virtual-host", + icon_status: IconStatus.Up, + capital: "Ottawa", + continent: { + name: "North America", + }, + currency: "CAD", + languages: [ + { + name: "English", + }, + { + name: "French", + }, + ], + }, + { + name: "Cocos [Keeling] Islands", + code: "CC", + icon: "virtual-host", + icon_status: IconStatus.Up, + capital: "West Island", + continent: { + name: "Asia", + }, + currency: "AUD", + languages: [ + { + name: "English", + }, + ], + }, + { + name: "Democratic Republic of the Congo", + code: "CD", + icon: "virtual-host", + icon_status: IconStatus.Up, + capital: "Kinshasa", + continent: { + name: "Africa", + }, + currency: "CDF", + languages: [ + { + name: "French", + }, + { + name: "Lingala", + }, + { + name: "Kongo", + }, + { + name: "Swahili", + }, + { + name: "Luba-Katanga", + }, + ], + }, + { + name: "Central African Republic", + code: "CF", + icon: "virtual-host", + icon_status: IconStatus.Up, + capital: "Bangui", + continent: { + name: "Africa", + }, + currency: "XAF", + languages: [ + { + name: "French", + }, + { + name: "Sango", + }, + ], + }, + { + name: "Republic of the Congo", + code: "CG", + icon: "virtual-host", + icon_status: IconStatus.Up, + capital: "Brazzaville", + continent: { + name: "Africa", + }, + currency: "XAF", + languages: [ + { + name: "French", + }, + { + name: "Lingala", + }, + ], + }, + { + name: "Switzerland", + code: "CH", + icon: "virtual-host", + icon_status: IconStatus.Up, + capital: "Bern", + continent: { + name: "Europe", + }, + currency: "CHE,CHF,CHW", + languages: [ + { + name: "German", + }, + { + name: "French", + }, + { + name: "Italian", + }, + ], + }, + { + name: "Ivory Coast", + code: "CI", + icon: "virtual-host", + icon_status: IconStatus.Up, + capital: "Yamoussoukro", + continent: { + name: "Africa", + }, + currency: "XOF", + languages: [ + { + name: "French", + }, + ], + }, + { + name: "Cook Islands", + code: "CK", + icon: "virtual-host", + icon_status: IconStatus.Up, + capital: "Avarua", + continent: { + name: "Oceania", + }, + currency: "NZD", + languages: [ + { + name: "English", + }, + ], + }, + { + name: "Chile", + code: "CL", + icon: "virtual-host", + icon_status: IconStatus.Up, + capital: "Santiago", + continent: { + name: "South America", + }, + currency: "CLF,CLP", + languages: [ + { + name: "Spanish", + }, + ], + }, + { + name: "Cameroon", + code: "CM", + icon: "virtual-host", + icon_status: IconStatus.Up, + capital: "Yaoundé", + continent: { + name: "Africa", + }, + currency: "XAF", + languages: [ + { + name: "English", + }, + { + name: "French", + }, + ], + }, + { + name: "China", + code: "CN", + icon: "virtual-host", + icon_status: IconStatus.Up, + capital: "Beijing", + continent: { + name: "Asia", + }, + currency: "CNY", + languages: [ + { + name: "Chinese", + }, + ], + }, + { + name: "Colombia", + code: "CO", + icon: "virtual-host", + icon_status: IconStatus.Up, + capital: "Bogotá", + continent: { + name: "South America", + }, + currency: "COP", + languages: [ + { + name: "Spanish", + }, + ], + }, + { + name: "Costa Rica", + code: "CR", + icon: "virtual-host", + icon_status: IconStatus.Up, + capital: "San José", + continent: { + name: "North America", + }, + currency: "CRC", + languages: [ + { + name: "Spanish", + }, + ], + }, + { + name: "Cuba", + code: "CU", + icon: "virtual-host", + icon_status: IconStatus.Up, + capital: "Havana", + continent: { + name: "North America", + }, + currency: "CUC,CUP", + languages: [ + { + name: "Spanish", + }, + ], + }, + { + name: "Cape Verde", + code: "CV", + icon: "virtual-host", + icon_status: IconStatus.Up, + capital: "Praia", + continent: { + name: "Africa", + }, + currency: "CVE", + languages: [ + { + name: "Portuguese", + }, + ], + }, + { + name: "Curacao", + code: "CW", + icon: "virtual-host", + icon_status: IconStatus.Up, + capital: "Willemstad", + continent: { + name: "North America", + }, + currency: "ANG", + languages: [ + { + name: "Dutch", + }, + { + name: "Panjabi / Punjabi", + }, + { + name: "English", + }, + ], + }, + { + name: "Christmas Island", + code: "CX", + icon: "virtual-host", + icon_status: IconStatus.Up, + capital: "Flying Fish Cove", + continent: { + name: "Asia", + }, + currency: "AUD", + languages: [ + { + name: "English", + }, + ], + }, + { + name: "Cyprus", + code: "CY", + icon: "virtual-host", + icon_status: IconStatus.Up, + capital: "Nicosia", + continent: { + name: "Europe", + }, + currency: "EUR", + languages: [ + { + name: "Greek", + }, + { + name: "Turkish", + }, + { + name: "Armenian", + }, + ], + }, + { + name: "Czech Republic", + code: "CZ", + icon: "virtual-host", + icon_status: IconStatus.Up, + capital: "Prague", + continent: { + name: "Europe", + }, + currency: "CZK", + languages: [ + { + name: "Czech", + }, + { + name: "Slovak", + }, + ], + }, + { + name: "Germany", + code: "DE", + icon: "virtual-host", + icon_status: IconStatus.Up, + capital: "Berlin", + continent: { + name: "Europe", + }, + currency: "EUR", + languages: [ + { + name: "German", + }, + ], + }, + { + name: "Djibouti", + code: "DJ", + icon: "virtual-host", + icon_status: IconStatus.Up, + capital: "Djibouti", + continent: { + name: "Africa", + }, + currency: "DJF", + languages: [ + { + name: "French", + }, + { + name: "Arabic", + }, + ], + }, + { + name: "Denmark", + code: "DK", + icon: "virtual-host", + icon_status: IconStatus.Up, + capital: "Copenhagen", + continent: { + name: "Europe", + }, + currency: "DKK", + languages: [ + { + name: "Danish", + }, + ], + }, + { + name: "Dominica", + code: "DM", + icon: "virtual-host", + icon_status: IconStatus.Up, + capital: "Roseau", + continent: { + name: "North America", + }, + currency: "XCD", + languages: [ + { + name: "English", + }, + ], + }, + { + name: "Dominican Republic", + code: "DO", + icon: "virtual-host", + icon_status: IconStatus.Up, + capital: "Santo Domingo", + continent: { + name: "North America", + }, + currency: "DOP", + languages: [ + { + name: "Spanish", + }, + ], + }, + { + name: "Algeria", + code: "DZ", + icon: "virtual-host", + icon_status: IconStatus.Up, + capital: "Algiers", + continent: { + name: "Africa", + }, + currency: "DZD", + languages: [ + { + name: "Arabic", + }, + ], + }, + { + name: "Ecuador", + code: "EC", + icon: "virtual-host", + icon_status: IconStatus.Up, + capital: "Quito", + continent: { + name: "South America", + }, + currency: "USD", + languages: [ + { + name: "Spanish", + }, + ], + }, + { + name: "Estonia", + code: "EE", + icon: "virtual-host", + icon_status: IconStatus.Up, + capital: "Tallinn", + continent: { + name: "Europe", + }, + currency: "EUR", + languages: [ + { + name: "Estonian", + }, + ], + }, + { + name: "Egypt", + code: "EG", + icon: "virtual-host", + icon_status: IconStatus.Up, + capital: "Cairo", + continent: { + name: "Africa", + }, + currency: "EGP", + languages: [ + { + name: "Arabic", + }, + ], + }, + { + name: "Western Sahara", + code: "EH", + icon: "virtual-host", + icon_status: IconStatus.Up, + capital: "El Aaiún", + continent: { + name: "Africa", + }, + currency: "MAD,DZD,MRU", + languages: [ + { + name: "Spanish", + }, + ], + }, + { + name: "Eritrea", + code: "ER", + icon: "virtual-host", + icon_status: IconStatus.Up, + capital: "Asmara", + continent: { + name: "Africa", + }, + currency: "ERN", + languages: [ + { + name: "Tigrinya", + }, + { + name: "Arabic", + }, + { + name: "English", + }, + ], + }, + { + name: "Spain", + code: "ES", + icon: "virtual-host", + icon_status: IconStatus.Up, + capital: "Madrid", + continent: { + name: "Europe", + }, + currency: "EUR", + languages: [ + { + name: "Spanish", + }, + { + name: "Basque", + }, + { + name: "Catalan", + }, + { + name: "Galician", + }, + { + name: "Occitan", + }, + ], + }, + { + name: "Ethiopia", + code: "ET", + icon: "virtual-host", + icon_status: IconStatus.Up, + capital: "Addis Ababa", + continent: { + name: "Africa", + }, + currency: "ETB", + languages: [ + { + name: "Amharic", + }, + ], + }, + { + name: "Finland", + code: "FI", + icon: "virtual-host", + icon_status: IconStatus.Up, + capital: "Helsinki", + continent: { + name: "Europe", + }, + currency: "EUR", + languages: [ + { + name: "Finnish", + }, + { + name: "Swedish", + }, + ], + }, + { + name: "Fiji", + code: "FJ", + icon: "virtual-host", + icon_status: IconStatus.Up, + capital: "Suva", + continent: { + name: "Oceania", + }, + currency: "FJD", + languages: [ + { + name: "English", + }, + { + name: "Fijian", + }, + { + name: "Hindi", + }, + { + name: "Urdu", + }, + ], + }, + { + name: "Falkland Islands", + code: "FK", + icon: "virtual-host", + icon_status: IconStatus.Up, + capital: "Stanley", + continent: { + name: "South America", + }, + currency: "FKP", + languages: [ + { + name: "English", + }, + ], + }, + { + name: "Micronesia", + code: "FM", + icon: "virtual-host", + icon_status: IconStatus.Up, + capital: "Palikir", + continent: { + name: "Oceania", + }, + currency: "USD", + languages: [ + { + name: "English", + }, + ], + }, + { + name: "Faroe Islands", + code: "FO", + icon: "virtual-host", + icon_status: IconStatus.Up, + capital: "Tórshavn", + continent: { + name: "Europe", + }, + currency: "DKK", + languages: [ + { + name: "Faroese", + }, + ], + }, + { + name: "France", + code: "FR", + icon: "virtual-host", + icon_status: IconStatus.Up, + capital: "Paris", + continent: { + name: "Europe", + }, + currency: "EUR", + languages: [ + { + name: "French", + }, + ], + }, + { + name: "Gabon", + code: "GA", + icon: "virtual-host", + icon_status: IconStatus.Up, + capital: "Libreville", + continent: { + name: "Africa", + }, + currency: "XAF", + languages: [ + { + name: "French", + }, + ], + }, + { + name: "United Kingdom", + code: "GB", + icon: "virtual-host", + icon_status: IconStatus.Up, + capital: "London", + continent: { + name: "Europe", + }, + currency: "GBP", + languages: [ + { + name: "English", + }, + ], + }, + { + name: "Grenada", + code: "GD", + icon: "virtual-host", + icon_status: IconStatus.Up, + capital: "St. George's", + continent: { + name: "North America", + }, + currency: "XCD", + languages: [ + { + name: "English", + }, + ], + }, + { + name: "Georgia", + code: "GE", + icon: "virtual-host", + icon_status: IconStatus.Up, + capital: "Tbilisi", + continent: { + name: "Asia", + }, + currency: "GEL", + languages: [ + { + name: "Georgian", + }, + ], + }, + { + name: "French Guiana", + code: "GF", + icon: "virtual-host", + icon_status: IconStatus.Up, + capital: "Cayenne", + continent: { + name: "South America", + }, + currency: "EUR", + languages: [ + { + name: "French", + }, + ], + }, + { + name: "Guernsey", + code: "GG", + icon: "virtual-host", + icon_status: IconStatus.Up, + capital: "St. Peter Port", + continent: { + name: "Europe", + }, + currency: "GBP", + languages: [ + { + name: "English", + }, + { + name: "French", + }, + ], + }, + { + name: "Ghana", + code: "GH", + icon: "virtual-host", + icon_status: IconStatus.Up, + capital: "Accra", + continent: { + name: "Africa", + }, + currency: "GHS", + languages: [ + { + name: "English", + }, + ], + }, + { + name: "Gibraltar", + code: "GI", + icon: "virtual-host", + icon_status: IconStatus.Up, + capital: "Gibraltar", + continent: { + name: "Europe", + }, + currency: "GIP", + languages: [ + { + name: "English", + }, + ], + }, + { + name: "Greenland", + code: "GL", + icon: "virtual-host", + icon_status: IconStatus.Up, + capital: "Nuuk", + continent: { + name: "North America", + }, + currency: "DKK", + languages: [ + { + name: "Greenlandic", + }, + ], + }, + { + name: "Gambia", + code: "GM", + icon: "virtual-host", + icon_status: IconStatus.Up, + capital: "Banjul", + continent: { + name: "Africa", + }, + currency: "GMD", + languages: [ + { + name: "English", + }, + ], + }, + { + name: "Guinea", + code: "GN", + icon: "virtual-host", + icon_status: IconStatus.Up, + capital: "Conakry", + continent: { + name: "Africa", + }, + currency: "GNF", + languages: [ + { + name: "French", + }, + { + name: "Peul", + }, + ], + }, + { + name: "Guadeloupe", + code: "GP", + icon: "virtual-host", + icon_status: IconStatus.Up, + capital: "Basse-Terre", + continent: { + name: "North America", + }, + currency: "EUR", + languages: [ + { + name: "French", + }, + ], + }, + { + name: "Equatorial Guinea", + code: "GQ", + icon: "virtual-host", + icon_status: IconStatus.Up, + capital: "Malabo", + continent: { + name: "Africa", + }, + currency: "XAF", + languages: [ + { + name: "Spanish", + }, + { + name: "French", + }, + ], + }, + { + name: "Greece", + code: "GR", + icon: "virtual-host", + icon_status: IconStatus.Up, + capital: "Athens", + continent: { + name: "Europe", + }, + currency: "EUR", + languages: [ + { + name: "Greek", + }, + ], + }, + { + name: "South Georgia and the South Sandwich Islands", + code: "GS", + icon: "virtual-host", + icon_status: IconStatus.Up, + capital: "King Edward Point", + continent: { + name: "Antarctica", + }, + currency: "GBP", + languages: [ + { + name: "English", + }, + ], + }, + { + name: "Guatemala", + code: "GT", + icon: "virtual-host", + icon_status: IconStatus.Up, + capital: "Guatemala City", + continent: { + name: "North America", + }, + currency: "GTQ", + languages: [ + { + name: "Spanish", + }, + ], + }, + { + name: "Guam", + code: "GU", + icon: "virtual-host", + icon_status: IconStatus.Up, + capital: "Hagåtña", + continent: { + name: "Oceania", + }, + currency: "USD", + languages: [ + { + name: "English", + }, + { + name: "Chamorro", + }, + { + name: "Spanish", + }, + ], + }, + { + name: "Guinea-Bissau", + code: "GW", + icon: "virtual-host", + icon_status: IconStatus.Up, + capital: "Bissau", + continent: { + name: "Africa", + }, + currency: "XOF", + languages: [ + { + name: "Portuguese", + }, + ], + }, + { + name: "Guyana", + code: "GY", + icon: "virtual-host", + icon_status: IconStatus.Up, + capital: "Georgetown", + continent: { + name: "South America", + }, + currency: "GYD", + languages: [ + { + name: "English", + }, + ], + }, + { + name: "Hong Kong", + code: "HK", + icon: "virtual-host", + icon_status: IconStatus.Up, + capital: "City of Victoria", + continent: { + name: "Asia", + }, + currency: "HKD", + languages: [ + { + name: "Chinese", + }, + { + name: "English", + }, + ], + }, + { + name: "Heard Island and McDonald Islands", + code: "HM", + icon: "virtual-host", + icon_status: IconStatus.Up, + capital: null, + continent: { + name: "Antarctica", + }, + currency: "AUD", + languages: [ + { + name: "English", + }, + ], + }, + { + name: "Honduras", + code: "HN", + icon: "virtual-host", + icon_status: IconStatus.Up, + capital: "Tegucigalpa", + continent: { + name: "North America", + }, + currency: "HNL", + languages: [ + { + name: "Spanish", + }, + ], + }, + { + name: "Croatia", + code: "HR", + icon: "virtual-host", + icon_status: IconStatus.Up, + capital: "Zagreb", + continent: { + name: "Europe", + }, + currency: "HRK", + languages: [ + { + name: "Croatian", + }, + ], + }, + { + name: "Haiti", + code: "HT", + icon: "virtual-host", + icon_status: IconStatus.Up, + capital: "Port-au-Prince", + continent: { + name: "North America", + }, + currency: "HTG,USD", + languages: [ + { + name: "French", + }, + { + name: "Haitian", + }, + ], + }, + { + name: "Hungary", + code: "HU", + icon: "virtual-host", + icon_status: IconStatus.Up, + capital: "Budapest", + continent: { + name: "Europe", + }, + currency: "HUF", + languages: [ + { + name: "Hungarian", + }, + ], + }, + { + name: "Indonesia", + code: "ID", + icon: "virtual-host", + icon_status: IconStatus.Up, + capital: "Jakarta", + continent: { + name: "Asia", + }, + currency: "IDR", + languages: [ + { + name: "Indonesian", + }, + ], + }, + { + name: "Ireland", + code: "IE", + icon: "virtual-host", + icon_status: IconStatus.Up, + capital: "Dublin", + continent: { + name: "Europe", + }, + currency: "EUR", + languages: [ + { + name: "Irish", + }, + { + name: "English", + }, + ], + }, + { + name: "Israel", + code: "IL", + icon: "virtual-host", + icon_status: IconStatus.Up, + capital: "Jerusalem", + continent: { + name: "Asia", + }, + currency: "ILS", + languages: [ + { + name: "Hebrew", + }, + { + name: "Arabic", + }, + ], + }, + { + name: "Isle of Man", + code: "IM", + icon: "virtual-host", + icon_status: IconStatus.Up, + capital: "Douglas", + continent: { + name: "Europe", + }, + currency: "GBP", + languages: [ + { + name: "English", + }, + { + name: "Manx", + }, + ], + }, + { + name: "India", + code: "IN", + icon: "virtual-host", + icon_status: IconStatus.Up, + capital: "New Delhi", + continent: { + name: "Asia", + }, + currency: "INR", + languages: [ + { + name: "Hindi", + }, + { + name: "English", + }, + ], + }, + { + name: "British Indian Ocean Territory", + code: "IO", + icon: "virtual-host", + icon_status: IconStatus.Up, + capital: "Diego Garcia", + continent: { + name: "Asia", + }, + currency: "USD", + languages: [ + { + name: "English", + }, + ], + }, + { + name: "Iraq", + code: "IQ", + icon: "virtual-host", + icon_status: IconStatus.Up, + capital: "Baghdad", + continent: { + name: "Asia", + }, + currency: "IQD", + languages: [ + { + name: "Arabic", + }, + { + name: "Kurdish", + }, + ], + }, + { + name: "Iran", + code: "IR", + icon: "virtual-host", + icon_status: IconStatus.Up, + capital: "Tehran", + continent: { + name: "Asia", + }, + currency: "IRR", + languages: [ + { + name: "Persian", + }, + ], + }, + { + name: "Iceland", + code: "IS", + icon: "virtual-host", + icon_status: IconStatus.Up, + capital: "Reykjavik", + continent: { + name: "Europe", + }, + currency: "ISK", + languages: [ + { + name: "Icelandic", + }, + ], + }, + { + name: "Italy", + code: "IT", + icon: "virtual-host", + icon_status: IconStatus.Up, + capital: "Rome", + continent: { + name: "Europe", + }, + currency: "EUR", + languages: [ + { + name: "Italian", + }, + ], + }, + { + name: "Jersey", + code: "JE", + icon: "virtual-host", + icon_status: IconStatus.Up, + capital: "Saint Helier", + continent: { + name: "Europe", + }, + currency: "GBP", + languages: [ + { + name: "English", + }, + { + name: "French", + }, + ], + }, + { + name: "Jamaica", + code: "JM", + icon: "virtual-host", + icon_status: IconStatus.Up, + capital: "Kingston", + continent: { + name: "North America", + }, + currency: "JMD", + languages: [ + { + name: "English", + }, + ], + }, + { + name: "Jordan", + code: "JO", + icon: "virtual-host", + icon_status: IconStatus.Up, + capital: "Amman", + continent: { + name: "Asia", + }, + currency: "JOD", + languages: [ + { + name: "Arabic", + }, + ], + }, + { + name: "Japan", + code: "JP", + icon: "virtual-host", + icon_status: IconStatus.Up, + capital: "Tokyo", + continent: { + name: "Asia", + }, + currency: "JPY", + languages: [ + { + name: "Japanese", + }, + ], + }, + { + name: "Kenya", + code: "KE", + icon: "virtual-host", + icon_status: IconStatus.Up, + capital: "Nairobi", + continent: { + name: "Africa", + }, + currency: "KES", + languages: [ + { + name: "English", + }, + { + name: "Swahili", + }, + ], + }, + { + name: "Kyrgyzstan", + code: "KG", + icon: "virtual-host", + icon_status: IconStatus.Up, + capital: "Bishkek", + continent: { + name: "Asia", + }, + currency: "KGS", + languages: [ + { + name: "Kirghiz", + }, + { + name: "Russian", + }, + ], + }, + { + name: "Cambodia", + code: "KH", + icon: "virtual-host", + icon_status: IconStatus.Up, + capital: "Phnom Penh", + continent: { + name: "Asia", + }, + currency: "KHR", + languages: [ + { + name: "Cambodian", + }, + ], + }, + { + name: "Kiribati", + code: "KI", + icon: "virtual-host", + icon_status: IconStatus.Up, + capital: "South Tarawa", + continent: { + name: "Oceania", + }, + currency: "AUD", + languages: [ + { + name: "English", + }, + ], + }, + { + name: "Comoros", + code: "KM", + icon: "virtual-host", + icon_status: IconStatus.Up, + capital: "Moroni", + continent: { + name: "Africa", + }, + currency: "KMF", + languages: [ + { + name: "Arabic", + }, + { + name: "French", + }, + ], + }, + { + name: "Saint Kitts and Nevis", + code: "KN", + icon: "virtual-host", + icon_status: IconStatus.Up, + capital: "Basseterre", + continent: { + name: "North America", + }, + currency: "XCD", + languages: [ + { + name: "English", + }, + ], + }, + { + name: "North Korea", + code: "KP", + icon: "virtual-host", + icon_status: IconStatus.Up, + capital: "Pyongyang", + continent: { + name: "Asia", + }, + currency: "KPW", + languages: [ + { + name: "Korean", + }, + ], + }, + { + name: "South Korea", + code: "KR", + icon: "virtual-host", + icon_status: IconStatus.Up, + capital: "Seoul", + continent: { + name: "Asia", + }, + currency: "KRW", + languages: [ + { + name: "Korean", + }, + ], + }, + { + name: "Kuwait", + code: "KW", + icon: "virtual-host", + icon_status: IconStatus.Up, + capital: "Kuwait City", + continent: { + name: "Asia", + }, + currency: "KWD", + languages: [ + { + name: "Arabic", + }, + ], + }, + { + name: "Cayman Islands", + code: "KY", + icon: "virtual-host", + icon_status: IconStatus.Up, + capital: "George Town", + continent: { + name: "North America", + }, + currency: "KYD", + languages: [ + { + name: "English", + }, + ], + }, + { + name: "Kazakhstan", + code: "KZ", + icon: "virtual-host", + icon_status: IconStatus.Up, + capital: "Astana", + continent: { + name: "Asia", + }, + currency: "KZT", + languages: [ + { + name: "Kazakh", + }, + { + name: "Russian", + }, + ], + }, + { + name: "Laos", + code: "LA", + icon: "virtual-host", + icon_status: IconStatus.Up, + capital: "Vientiane", + continent: { + name: "Asia", + }, + currency: "LAK", + languages: [ + { + name: "Laotian", + }, + ], + }, + { + name: "Lebanon", + code: "LB", + icon: "virtual-host", + icon_status: IconStatus.Up, + capital: "Beirut", + continent: { + name: "Asia", + }, + currency: "LBP", + languages: [ + { + name: "Arabic", + }, + { + name: "French", + }, + ], + }, + { + name: "Saint Lucia", + code: "LC", + icon: "virtual-host", + icon_status: IconStatus.Up, + capital: "Castries", + continent: { + name: "North America", + }, + currency: "XCD", + languages: [ + { + name: "English", + }, + ], + }, + { + name: "Liechtenstein", + code: "LI", + icon: "virtual-host", + icon_status: IconStatus.Up, + capital: "Vaduz", + continent: { + name: "Europe", + }, + currency: "CHF", + languages: [ + { + name: "German", + }, + ], + }, + { + name: "Sri Lanka", + code: "LK", + icon: "virtual-host", + icon_status: IconStatus.Up, + capital: "Colombo", + continent: { + name: "Asia", + }, + currency: "LKR", + languages: [ + { + name: "Sinhalese", + }, + { + name: "Tamil", + }, + ], + }, + { + name: "Liberia", + code: "LR", + icon: "virtual-host", + icon_status: IconStatus.Up, + capital: "Monrovia", + continent: { + name: "Africa", + }, + currency: "LRD", + languages: [ + { + name: "English", + }, + ], + }, + { + name: "Lesotho", + code: "LS", + icon: "virtual-host", + icon_status: IconStatus.Up, + capital: "Maseru", + continent: { + name: "Africa", + }, + currency: "LSL,ZAR", + languages: [ + { + name: "English", + }, + { + name: "Southern Sotho", + }, + ], + }, + { + name: "Lithuania", + code: "LT", + icon: "virtual-host", + icon_status: IconStatus.Up, + capital: "Vilnius", + continent: { + name: "Europe", + }, + currency: "EUR", + languages: [ + { + name: "Lithuanian", + }, + ], + }, + { + name: "Luxembourg", + code: "LU", + icon: "virtual-host", + icon_status: IconStatus.Up, + capital: "Luxembourg", + continent: { + name: "Europe", + }, + currency: "EUR", + languages: [ + { + name: "French", + }, + { + name: "German", + }, + { + name: "Luxembourgish", + }, + ], + }, + { + name: "Latvia", + code: "LV", + icon: "virtual-host", + icon_status: IconStatus.Up, + capital: "Riga", + continent: { + name: "Europe", + }, + currency: "EUR", + languages: [ + { + name: "Latvian", + }, + ], + }, + { + name: "Libya", + code: "LY", + icon: "virtual-host", + icon_status: IconStatus.Up, + capital: "Tripoli", + continent: { + name: "Africa", + }, + currency: "LYD", + languages: [ + { + name: "Arabic", + }, + ], + }, + { + name: "Morocco", + code: "MA", + icon: "virtual-host", + icon_status: IconStatus.Up, + capital: "Rabat", + continent: { + name: "Africa", + }, + currency: "MAD", + languages: [ + { + name: "Arabic", + }, + ], + }, + { + name: "Monaco", + code: "MC", + icon: "virtual-host", + icon_status: IconStatus.Up, + capital: "Monaco", + continent: { + name: "Europe", + }, + currency: "EUR", + languages: [ + { + name: "French", + }, + ], + }, + { + name: "Moldova", + code: "MD", + icon: "virtual-host", + icon_status: IconStatus.Up, + capital: "Chișinău", + continent: { + name: "Europe", + }, + currency: "MDL", + languages: [ + { + name: "Romanian", + }, + ], + }, + { + name: "Montenegro", + code: "ME", + icon: "virtual-host", + icon_status: IconStatus.Up, + capital: "Podgorica", + continent: { + name: "Europe", + }, + currency: "EUR", + languages: [ + { + name: "Serbian", + }, + { + name: "Bosnian", + }, + { + name: "Albanian", + }, + { + name: "Croatian", + }, + ], + }, + { + name: "Saint Martin", + code: "MF", + icon: "virtual-host", + icon_status: IconStatus.Up, + capital: "Marigot", + continent: { + name: "North America", + }, + currency: "EUR", + languages: [ + { + name: "English", + }, + { + name: "French", + }, + { + name: "Dutch", + }, + ], + }, + { + name: "Madagascar", + code: "MG", + icon: "virtual-host", + icon_status: IconStatus.Up, + capital: "Antananarivo", + continent: { + name: "Africa", + }, + currency: "MGA", + languages: [ + { + name: "French", + }, + { + name: "Malagasy", + }, + ], + }, + { + name: "Marshall Islands", + code: "MH", + icon: "virtual-host", + icon_status: IconStatus.Up, + capital: "Majuro", + continent: { + name: "Oceania", + }, + currency: "USD", + languages: [ + { + name: "English", + }, + { + name: "Marshallese", + }, + ], + }, + { + name: "North Macedonia", + code: "MK", + icon: "virtual-host", + icon_status: IconStatus.Up, + capital: "Skopje", + continent: { + name: "Europe", + }, + currency: "MKD", + languages: [ + { + name: "Macedonian", + }, + ], + }, + { + name: "Mali", + code: "ML", + icon: "virtual-host", + icon_status: IconStatus.Up, + capital: "Bamako", + continent: { + name: "Africa", + }, + currency: "XOF", + languages: [ + { + name: "French", + }, + ], + }, + { + name: "Myanmar [Burma]", + code: "MM", + icon: "virtual-host", + icon_status: IconStatus.Up, + capital: "Naypyidaw", + continent: { + name: "Asia", + }, + currency: "MMK", + languages: [ + { + name: "Burmese", + }, + ], + }, + { + name: "Mongolia", + code: "MN", + icon: "virtual-host", + icon_status: IconStatus.Up, + capital: "Ulan Bator", + continent: { + name: "Asia", + }, + currency: "MNT", + languages: [ + { + name: "Mongolian", + }, + ], + }, + { + name: "Macao", + code: "MO", + icon: "virtual-host", + icon_status: IconStatus.Up, + capital: null, + continent: { + name: "Asia", + }, + currency: "MOP", + languages: [ + { + name: "Chinese", + }, + { + name: "Portuguese", + }, + ], + }, + { + name: "Northern Mariana Islands", + code: "MP", + icon: "virtual-host", + icon_status: IconStatus.Up, + capital: "Saipan", + continent: { + name: "Oceania", + }, + currency: "USD", + languages: [ + { + name: "English", + }, + { + name: "Chamorro", + }, + ], + }, + { + name: "Martinique", + code: "MQ", + icon: "virtual-host", + icon_status: IconStatus.Up, + capital: "Fort-de-France", + continent: { + name: "North America", + }, + currency: "EUR", + languages: [ + { + name: "French", + }, + ], + }, + { + name: "Mauritania", + code: "MR", + icon: "virtual-host", + icon_status: IconStatus.Up, + capital: "Nouakchott", + continent: { + name: "Africa", + }, + currency: "MRU", + languages: [ + { + name: "Arabic", + }, + ], + }, + { + name: "Montserrat", + code: "MS", + icon: "virtual-host", + icon_status: IconStatus.Up, + capital: "Plymouth", + continent: { + name: "North America", + }, + currency: "XCD", + languages: [ + { + name: "English", + }, + ], + }, + { + name: "Malta", + code: "MT", + icon: "virtual-host", + icon_status: IconStatus.Up, + capital: "Valletta", + continent: { + name: "Europe", + }, + currency: "EUR", + languages: [ + { + name: "Maltese", + }, + { + name: "English", + }, + ], + }, + { + name: "Mauritius", + code: "MU", + icon: "virtual-host", + icon_status: IconStatus.Up, + capital: "Port Louis", + continent: { + name: "Africa", + }, + currency: "MUR", + languages: [ + { + name: "English", + }, + ], + }, + { + name: "Maldives", + code: "MV", + icon: "virtual-host", + icon_status: IconStatus.Up, + capital: "Malé", + continent: { + name: "Asia", + }, + currency: "MVR", + languages: [ + { + name: "Divehi", + }, + ], + }, + { + name: "Malawi", + code: "MW", + icon: "virtual-host", + icon_status: IconStatus.Up, + capital: "Lilongwe", + continent: { + name: "Africa", + }, + currency: "MWK", + languages: [ + { + name: "English", + }, + { + name: "Chichewa", + }, + ], + }, + { + name: "Mexico", + code: "MX", + icon: "virtual-host", + icon_status: IconStatus.Up, + capital: "Mexico City", + continent: { + name: "North America", + }, + currency: "MXN", + languages: [ + { + name: "Spanish", + }, + ], + }, + { + name: "Malaysia", + code: "MY", + icon: "virtual-host", + icon_status: IconStatus.Up, + capital: "Kuala Lumpur", + continent: { + name: "Asia", + }, + currency: "MYR", + languages: [ + { + name: "Malay", + }, + ], + }, + { + name: "Mozambique", + code: "MZ", + icon: "virtual-host", + icon_status: IconStatus.Up, + capital: "Maputo", + continent: { + name: "Africa", + }, + currency: "MZN", + languages: [ + { + name: "Portuguese", + }, + ], + }, + { + name: "Namibia", + code: "NA", + icon: "virtual-host", + icon_status: IconStatus.Up, + capital: "Windhoek", + continent: { + name: "Africa", + }, + currency: "NAD,ZAR", + languages: [ + { + name: "English", + }, + { + name: "Afrikaans", + }, + ], + }, + { + name: "New Caledonia", + code: "NC", + icon: "virtual-host", + icon_status: IconStatus.Up, + capital: "Nouméa", + continent: { + name: "Oceania", + }, + currency: "XPF", + languages: [ + { + name: "French", + }, + ], + }, + { + name: "Niger", + code: "NE", + icon: "virtual-host", + icon_status: IconStatus.Up, + capital: "Niamey", + continent: { + name: "Africa", + }, + currency: "XOF", + languages: [ + { + name: "French", + }, + ], + }, + { + name: "Norfolk Island", + code: "NF", + icon: "virtual-host", + icon_status: IconStatus.Up, + capital: "Kingston", + continent: { + name: "Oceania", + }, + currency: "AUD", + languages: [ + { + name: "English", + }, + ], + }, + { + name: "Nigeria", + code: "NG", + icon: "virtual-host", + icon_status: IconStatus.Up, + capital: "Abuja", + continent: { + name: "Africa", + }, + currency: "NGN", + languages: [ + { + name: "English", + }, + ], + }, + { + name: "Nicaragua", + code: "NI", + icon: "virtual-host", + icon_status: IconStatus.Up, + capital: "Managua", + continent: { + name: "North America", + }, + currency: "NIO", + languages: [ + { + name: "Spanish", + }, + ], + }, + { + name: "Netherlands", + code: "NL", + icon: "virtual-host", + icon_status: IconStatus.Up, + capital: "Amsterdam", + continent: { + name: "Europe", + }, + currency: "EUR", + languages: [ + { + name: "Dutch", + }, + ], + }, + { + name: "Norway", + code: "NO", + icon: "virtual-host", + icon_status: IconStatus.Up, + capital: "Oslo", + continent: { + name: "Europe", + }, + currency: "NOK", + languages: [ + { + name: "Norwegian", + }, + { + name: "Norwegian Bokmål", + }, + { + name: "Norwegian Nynorsk", + }, + ], + }, + { + name: "Nepal", + code: "NP", + icon: "virtual-host", + icon_status: IconStatus.Up, + capital: "Kathmandu", + continent: { + name: "Asia", + }, + currency: "NPR", + languages: [ + { + name: "Nepali", + }, + ], + }, + { + name: "Nauru", + code: "NR", + icon: "virtual-host", + icon_status: IconStatus.Up, + capital: "Yaren", + continent: { + name: "Oceania", + }, + currency: "AUD", + languages: [ + { + name: "English", + }, + { + name: "Nauruan", + }, + ], + }, + { + name: "Niue", + code: "NU", + icon: "virtual-host", + icon_status: IconStatus.Up, + capital: "Alofi", + continent: { + name: "Oceania", + }, + currency: "NZD", + languages: [ + { + name: "English", + }, + ], + }, + { + name: "New Zealand", + code: "NZ", + icon: "virtual-host", + icon_status: IconStatus.Up, + capital: "Wellington", + continent: { + name: "Oceania", + }, + currency: "NZD", + languages: [ + { + name: "English", + }, + { + name: "Maori", + }, + ], + }, + { + name: "Oman", + code: "OM", + icon: "virtual-host", + icon_status: IconStatus.Up, + capital: "Muscat", + continent: { + name: "Asia", + }, + currency: "OMR", + languages: [ + { + name: "Arabic", + }, + ], + }, + { + name: "Panama", + code: "PA", + icon: "virtual-host", + icon_status: IconStatus.Up, + capital: "Panama City", + continent: { + name: "North America", + }, + currency: "PAB,USD", + languages: [ + { + name: "Spanish", + }, + ], + }, + { + name: "Peru", + code: "PE", + icon: "virtual-host", + icon_status: IconStatus.Up, + capital: "Lima", + continent: { + name: "South America", + }, + currency: "PEN", + languages: [ + { + name: "Spanish", + }, + ], + }, + { + name: "French Polynesia", + code: "PF", + icon: "virtual-host", + icon_status: IconStatus.Up, + capital: "Papeetē", + continent: { + name: "Oceania", + }, + currency: "XPF", + languages: [ + { + name: "French", + }, + ], + }, + { + name: "Papua New Guinea", + code: "PG", + icon: "virtual-host", + icon_status: IconStatus.Up, + capital: "Port Moresby", + continent: { + name: "Oceania", + }, + currency: "PGK", + languages: [ + { + name: "English", + }, + ], + }, + { + name: "Philippines", + code: "PH", + icon: "virtual-host", + icon_status: IconStatus.Up, + capital: "Manila", + continent: { + name: "Asia", + }, + currency: "PHP", + languages: [ + { + name: "English", + }, + ], + }, + { + name: "Pakistan", + code: "PK", + icon: "virtual-host", + icon_status: IconStatus.Up, + capital: "Islamabad", + continent: { + name: "Asia", + }, + currency: "PKR", + languages: [ + { + name: "English", + }, + { + name: "Urdu", + }, + ], + }, + { + name: "Poland", + code: "PL", + icon: "virtual-host", + icon_status: IconStatus.Up, + capital: "Warsaw", + continent: { + name: "Europe", + }, + currency: "PLN", + languages: [ + { + name: "Polish", + }, + ], + }, + { + name: "Saint Pierre and Miquelon", + code: "PM", + icon: "virtual-host", + icon_status: IconStatus.Up, + capital: "Saint-Pierre", + continent: { + name: "North America", + }, + currency: "EUR", + languages: [ + { + name: "French", + }, + ], + }, + { + name: "Pitcairn Islands", + code: "PN", + icon: "virtual-host", + icon_status: IconStatus.Up, + capital: "Adamstown", + continent: { + name: "Oceania", + }, + currency: "NZD", + languages: [ + { + name: "English", + }, + ], + }, + { + name: "Puerto Rico", + code: "PR", + icon: "virtual-host", + icon_status: IconStatus.Up, + capital: "San Juan", + continent: { + name: "North America", + }, + currency: "USD", + languages: [ + { + name: "Spanish", + }, + { + name: "English", + }, + ], + }, + { + name: "Palestine", + code: "PS", + icon: "virtual-host", + icon_status: IconStatus.Up, + capital: "Ramallah", + continent: { + name: "Asia", + }, + currency: "ILS", + languages: [ + { + name: "Arabic", + }, + ], + }, + { + name: "Portugal", + code: "PT", + icon: "virtual-host", + icon_status: IconStatus.Up, + capital: "Lisbon", + continent: { + name: "Europe", + }, + currency: "EUR", + languages: [ + { + name: "Portuguese", + }, + ], + }, + { + name: "Palau", + code: "PW", + icon: "virtual-host", + icon_status: IconStatus.Up, + capital: "Ngerulmud", + continent: { + name: "Oceania", + }, + currency: "USD", + languages: [ + { + name: "English", + }, + ], + }, + { + name: "Paraguay", + code: "PY", + icon: "virtual-host", + icon_status: IconStatus.Up, + capital: "Asunción", + continent: { + name: "South America", + }, + currency: "PYG", + languages: [ + { + name: "Spanish", + }, + { + name: "Guarani", + }, + ], + }, + { + name: "Qatar", + code: "QA", + icon: "virtual-host", + icon_status: IconStatus.Up, + capital: "Doha", + continent: { + name: "Asia", + }, + currency: "QAR", + languages: [ + { + name: "Arabic", + }, + ], + }, + { + name: "Réunion", + code: "RE", + icon: "virtual-host", + icon_status: IconStatus.Up, + capital: "Saint-Denis", + continent: { + name: "Africa", + }, + currency: "EUR", + languages: [ + { + name: "French", + }, + ], + }, + { + name: "Romania", + code: "RO", + icon: "virtual-host", + icon_status: IconStatus.Up, + capital: "Bucharest", + continent: { + name: "Europe", + }, + currency: "RON", + languages: [ + { + name: "Romanian", + }, + ], + }, + { + name: "Serbia", + code: "RS", + icon: "virtual-host", + icon_status: IconStatus.Up, + capital: "Belgrade", + continent: { + name: "Europe", + }, + currency: "RSD", + languages: [ + { + name: "Serbian", + }, + ], + }, + { + name: "Russia", + code: "RU", + icon: "virtual-host", + icon_status: IconStatus.Up, + capital: "Moscow", + continent: { + name: "Europe", + }, + currency: "RUB", + languages: [ + { + name: "Russian", + }, + ], + }, + { + name: "Rwanda", + code: "RW", + icon: "virtual-host", + icon_status: IconStatus.Up, + capital: "Kigali", + continent: { + name: "Africa", + }, + currency: "RWF", + languages: [ + { + name: "Rwandi", + }, + { + name: "English", + }, + { + name: "French", + }, + ], + }, + { + name: "Saudi Arabia", + code: "SA", + icon: "virtual-host", + icon_status: IconStatus.Up, + capital: "Riyadh", + continent: { + name: "Asia", + }, + currency: "SAR", + languages: [ + { + name: "Arabic", + }, + ], + }, + { + name: "Solomon Islands", + code: "SB", + icon: "virtual-host", + icon_status: IconStatus.Up, + capital: "Honiara", + continent: { + name: "Oceania", + }, + currency: "SBD", + languages: [ + { + name: "English", + }, + ], + }, + { + name: "Seychelles", + code: "SC", + icon: "virtual-host", + icon_status: IconStatus.Up, + capital: "Victoria", + continent: { + name: "Africa", + }, + currency: "SCR", + languages: [ + { + name: "French", + }, + { + name: "English", + }, + ], + }, + { + name: "Sudan", + code: "SD", + icon: "virtual-host", + icon_status: IconStatus.Up, + capital: "Khartoum", + continent: { + name: "Africa", + }, + currency: "SDG", + languages: [ + { + name: "Arabic", + }, + { + name: "English", + }, + ], + }, + { + name: "Sweden", + code: "SE", + icon: "virtual-host", + icon_status: IconStatus.Up, + capital: "Stockholm", + continent: { + name: "Europe", + }, + currency: "SEK", + languages: [ + { + name: "Swedish", + }, + ], + }, + { + name: "Singapore", + code: "SG", + icon: "virtual-host", + icon_status: IconStatus.Up, + capital: "Singapore", + continent: { + name: "Asia", + }, + currency: "SGD", + languages: [ + { + name: "English", + }, + { + name: "Malay", + }, + { + name: "Tamil", + }, + { + name: "Chinese", + }, + ], + }, + { + name: "Saint Helena", + code: "SH", + icon: "virtual-host", + icon_status: IconStatus.Up, + capital: "Jamestown", + continent: { + name: "Africa", + }, + currency: "SHP", + languages: [ + { + name: "English", + }, + ], + }, + { + name: "Slovenia", + code: "SI", + icon: "virtual-host", + icon_status: IconStatus.Up, + capital: "Ljubljana", + continent: { + name: "Europe", + }, + currency: "EUR", + languages: [ + { + name: "Slovenian", + }, + ], + }, + { + name: "Svalbard and Jan Mayen", + code: "SJ", + icon: "virtual-host", + icon_status: IconStatus.Up, + capital: "Longyearbyen", + continent: { + name: "Europe", + }, + currency: "NOK", + languages: [ + { + name: "Norwegian", + }, + ], + }, + { + name: "Slovakia", + code: "SK", + icon: "virtual-host", + icon_status: IconStatus.Up, + capital: "Bratislava", + continent: { + name: "Europe", + }, + currency: "EUR", + languages: [ + { + name: "Slovak", + }, + ], + }, + { + name: "Sierra Leone", + code: "SL", + icon: "virtual-host", + icon_status: IconStatus.Up, + capital: "Freetown", + continent: { + name: "Africa", + }, + currency: "SLL", + languages: [ + { + name: "English", + }, + ], + }, + { + name: "San Marino", + code: "SM", + icon: "virtual-host", + icon_status: IconStatus.Up, + capital: "City of San Marino", + continent: { + name: "Europe", + }, + currency: "EUR", + languages: [ + { + name: "Italian", + }, + ], + }, + { + name: "Senegal", + code: "SN", + icon: "virtual-host", + icon_status: IconStatus.Up, + capital: "Dakar", + continent: { + name: "Africa", + }, + currency: "XOF", + languages: [ + { + name: "French", + }, + ], + }, + { + name: "Somalia", + code: "SO", + icon: "virtual-host", + icon_status: IconStatus.Up, + capital: "Mogadishu", + continent: { + name: "Africa", + }, + currency: "SOS", + languages: [ + { + name: "Somalia", + }, + { + name: "Arabic", + }, + ], + }, + { + name: "Suriname", + code: "SR", + icon: "virtual-host", + icon_status: IconStatus.Up, + capital: "Paramaribo", + continent: { + name: "South America", + }, + currency: "SRD", + languages: [ + { + name: "Dutch", + }, + ], + }, + { + name: "South Sudan", + code: "SS", + icon: "virtual-host", + icon_status: IconStatus.Up, + capital: "Juba", + continent: { + name: "Africa", + }, + currency: "SSP", + languages: [ + { + name: "English", + }, + ], + }, + { + name: "São Tomé and Príncipe", + code: "ST", + icon: "virtual-host", + icon_status: IconStatus.Up, + capital: "São Tomé", + continent: { + name: "Africa", + }, + currency: "STN", + languages: [ + { + name: "Portuguese", + }, + ], + }, + { + name: "El Salvador", + code: "SV", + icon: "virtual-host", + icon_status: IconStatus.Up, + capital: "San Salvador", + continent: { + name: "North America", + }, + currency: "SVC,USD", + languages: [ + { + name: "Spanish", + }, + ], + }, + { + name: "Sint Maarten", + code: "SX", + icon: "virtual-host", + icon_status: IconStatus.Up, + capital: "Philipsburg", + continent: { + name: "North America", + }, + currency: "ANG", + languages: [ + { + name: "Dutch", + }, + { + name: "English", + }, + ], + }, + { + name: "Syria", + code: "SY", + icon: "virtual-host", + icon_status: IconStatus.Up, + capital: "Damascus", + continent: { + name: "Asia", + }, + currency: "SYP", + languages: [ + { + name: "Arabic", + }, + ], + }, + { + name: "Swaziland", + code: "SZ", + icon: "virtual-host", + icon_status: IconStatus.Up, + capital: "Lobamba", + continent: { + name: "Africa", + }, + currency: "SZL", + languages: [ + { + name: "English", + }, + { + name: "Swati", + }, + ], + }, + { + name: "Turks and Caicos Islands", + code: "TC", + icon: "virtual-host", + icon_status: IconStatus.Up, + capital: "Cockburn Town", + continent: { + name: "North America", + }, + currency: "USD", + languages: [ + { + name: "English", + }, + ], + }, + { + name: "Chad", + code: "TD", + icon: "virtual-host", + icon_status: IconStatus.Up, + capital: "N'Djamena", + continent: { + name: "Africa", + }, + currency: "XAF", + languages: [ + { + name: "French", + }, + { + name: "Arabic", + }, + ], + }, + { + name: "French Southern Territories", + code: "TF", + icon: "virtual-host", + icon_status: IconStatus.Up, + capital: "Port-aux-Français", + continent: { + name: "Antarctica", + }, + currency: "EUR", + languages: [ + { + name: "French", + }, + ], + }, + { + name: "Togo", + code: "TG", + icon: "virtual-host", + icon_status: IconStatus.Up, + capital: "Lomé", + continent: { + name: "Africa", + }, + currency: "XOF", + languages: [ + { + name: "French", + }, + ], + }, + { + name: "Thailand", + code: "TH", + icon: "virtual-host", + icon_status: IconStatus.Up, + capital: "Bangkok", + continent: { + name: "Asia", + }, + currency: "THB", + languages: [ + { + name: "Thai", + }, + ], + }, + { + name: "Tajikistan", + code: "TJ", + icon: "virtual-host", + icon_status: IconStatus.Up, + capital: "Dushanbe", + continent: { + name: "Asia", + }, + currency: "TJS", + languages: [ + { + name: "Tajik", + }, + { + name: "Russian", + }, + ], + }, + { + name: "Tokelau", + code: "TK", + icon: "virtual-host", + icon_status: IconStatus.Up, + capital: "Fakaofo", + continent: { + name: "Oceania", + }, + currency: "NZD", + languages: [ + { + name: "English", + }, + ], + }, + { + name: "East Timor", + code: "TL", + icon: "virtual-host", + icon_status: IconStatus.Up, + capital: "Dili", + continent: { + name: "Oceania", + }, + currency: "USD", + languages: [ + { + name: "Portuguese", + }, + ], + }, + { + name: "Turkmenistan", + code: "TM", + icon: "virtual-host", + icon_status: IconStatus.Up, + capital: "Ashgabat", + continent: { + name: "Asia", + }, + currency: "TMT", + languages: [ + { + name: "Turkmen", + }, + { + name: "Russian", + }, + ], + }, + { + name: "Tunisia", + code: "TN", + icon: "virtual-host", + icon_status: IconStatus.Up, + capital: "Tunis", + continent: { + name: "Africa", + }, + currency: "TND", + languages: [ + { + name: "Arabic", + }, + ], + }, + { + name: "Tonga", + code: "TO", + icon: "virtual-host", + icon_status: IconStatus.Up, + capital: "Nuku'alofa", + continent: { + name: "Oceania", + }, + currency: "TOP", + languages: [ + { + name: "English", + }, + { + name: "Tonga", + }, + ], + }, + { + name: "Turkey", + code: "TR", + icon: "virtual-host", + icon_status: IconStatus.Up, + capital: "Ankara", + continent: { + name: "Asia", + }, + currency: "TRY", + languages: [ + { + name: "Turkish", + }, + ], + }, + { + name: "Trinidad and Tobago", + code: "TT", + icon: "virtual-host", + icon_status: IconStatus.Up, + capital: "Port of Spain", + continent: { + name: "North America", + }, + currency: "TTD", + languages: [ + { + name: "English", + }, + ], + }, + { + name: "Tuvalu", + code: "TV", + icon: "virtual-host", + icon_status: IconStatus.Up, + capital: "Funafuti", + continent: { + name: "Oceania", + }, + currency: "AUD", + languages: [ + { + name: "English", + }, + ], + }, + { + name: "Taiwan", + code: "TW", + icon: "virtual-host", + icon_status: IconStatus.Up, + capital: "Taipei", + continent: { + name: "Asia", + }, + currency: "TWD", + languages: [ + { + name: "Chinese", + }, + ], + }, + { + name: "Tanzania", + code: "TZ", + icon: "virtual-host", + icon_status: IconStatus.Up, + capital: "Dodoma", + continent: { + name: "Africa", + }, + currency: "TZS", + languages: [ + { + name: "Swahili", + }, + { + name: "English", + }, + ], + }, + { + name: "Ukraine", + code: "UA", + icon: "virtual-host", + icon_status: IconStatus.Up, + capital: "Kyiv", + continent: { + name: "Europe", + }, + currency: "UAH", + languages: [ + { + name: "Ukrainian", + }, + ], + }, + { + name: "Uganda", + code: "UG", + icon: "virtual-host", + icon_status: IconStatus.Up, + capital: "Kampala", + continent: { + name: "Africa", + }, + currency: "UGX", + languages: [ + { + name: "English", + }, + { + name: "Swahili", + }, + ], + }, + { + name: "U.S. Minor Outlying Islands", + code: "UM", + icon: "virtual-host", + icon_status: IconStatus.Up, + capital: null, + continent: { + name: "Oceania", + }, + currency: "USD", + languages: [ + { + name: "English", + }, + ], + }, + { + name: "United States", + code: "US", + icon: "virtual-host", + icon_status: IconStatus.Up, + capital: "Washington D.C.", + continent: { + name: "North America", + }, + currency: "USD,USN,USS", + languages: [ + { + name: "English", + }, + ], + }, + { + name: "Uruguay", + code: "UY", + icon: "virtual-host", + icon_status: IconStatus.Up, + capital: "Montevideo", + continent: { + name: "South America", + }, + currency: "UYI,UYU", + languages: [ + { + name: "Spanish", + }, + ], + }, + { + name: "Uzbekistan", + code: "UZ", + icon: "virtual-host", + icon_status: IconStatus.Up, + capital: "Tashkent", + continent: { + name: "Asia", + }, + currency: "UZS", + languages: [ + { + name: "Uzbek", + }, + { + name: "Russian", + }, + ], + }, + { + name: "Vatican City", + code: "VA", + icon: "virtual-host", + icon_status: IconStatus.Up, + capital: "Vatican City", + continent: { + name: "Europe", + }, + currency: "EUR", + languages: [ + { + name: "Italian", + }, + { + name: "Latin", + }, + ], + }, + { + name: "Saint Vincent and the Grenadines", + code: "VC", + icon: "virtual-host", + icon_status: IconStatus.Up, + capital: "Kingstown", + continent: { + name: "North America", + }, + currency: "XCD", + languages: [ + { + name: "English", + }, + ], + }, + { + name: "Venezuela", + code: "VE", + icon: "virtual-host", + icon_status: IconStatus.Up, + capital: "Caracas", + continent: { + name: "South America", + }, + currency: "VES", + languages: [ + { + name: "Spanish", + }, + ], + }, + { + name: "British Virgin Islands", + code: "VG", + icon: "virtual-host", + icon_status: IconStatus.Up, + capital: "Road Town", + continent: { + name: "North America", + }, + currency: "USD", + languages: [ + { + name: "English", + }, + ], + }, + { + name: "U.S. Virgin Islands", + code: "VI", + icon: "virtual-host", + icon_status: IconStatus.Up, + capital: "Charlotte Amalie", + continent: { + name: "North America", + }, + currency: "USD", + languages: [ + { + name: "English", + }, + ], + }, + { + name: "Vietnam", + code: "VN", + icon: "virtual-host", + icon_status: IconStatus.Up, + capital: "Hanoi", + continent: { + name: "Asia", + }, + currency: "VND", + languages: [ + { + name: "Vietnamese", + }, + ], + }, + { + name: "Vanuatu", + code: "VU", + icon: "virtual-host", + icon_status: IconStatus.Up, + capital: "Port Vila", + continent: { + name: "Oceania", + }, + currency: "VUV", + languages: [ + { + name: "Bislama", + }, + { + name: "English", + }, + { + name: "French", + }, + ], + }, + { + name: "Wallis and Futuna", + code: "WF", + icon: "virtual-host", + icon_status: IconStatus.Up, + capital: "Mata-Utu", + continent: { + name: "Oceania", + }, + currency: "XPF", + languages: [ + { + name: "French", + }, + ], + }, + { + name: "Samoa", + code: "WS", + icon: "virtual-host", + icon_status: IconStatus.Up, + capital: "Apia", + continent: { + name: "Oceania", + }, + currency: "WST", + languages: [ + { + name: "Samoan", + }, + { + name: "English", + }, + ], + }, + { + name: "Kosovo", + code: "XK", + icon: "virtual-host", + icon_status: IconStatus.Up, + capital: "Pristina", + continent: { + name: "Europe", + }, + currency: "EUR", + languages: [ + { + name: "Albanian", + }, + { + name: "Serbian", + }, + ], + }, + { + name: "Yemen", + code: "YE", + icon: "virtual-host", + icon_status: IconStatus.Up, + capital: "Sana'a", + continent: { + name: "Asia", + }, + currency: "YER", + languages: [ + { + name: "Arabic", + }, + ], + }, + { + name: "Mayotte", + code: "YT", + icon: "virtual-host", + icon_status: IconStatus.Up, + capital: "Mamoudzou", + continent: { + name: "Africa", + }, + currency: "EUR", + languages: [ + { + name: "French", + }, + ], + }, + { + name: "South Africa", + code: "ZA", + icon: "virtual-host", + icon_status: IconStatus.Up, + capital: "Pretoria", + continent: { + name: "Africa", + }, + currency: "ZAR", + languages: [ + { + name: "Afrikaans", + }, + { + name: "English", + }, + { + name: "South Ndebele", + }, + { + name: "Southern Sotho", + }, + { + name: "Swati", + }, + { + name: "Tswana", + }, + { + name: "Tsonga", + }, + { + name: "Venda", + }, + { + name: "Xhosa", + }, + { + name: "Zulu", + }, + ], + }, + { + name: "Zambia", + code: "ZM", + icon: "virtual-host", + icon_status: IconStatus.Up, + capital: "Lusaka", + continent: { + name: "Africa", + }, + currency: "ZMW", + languages: [ + { + name: "English", + }, + ], + }, + { + name: "Zimbabwe", + code: "ZW", + icon: "virtual-host", + icon_status: IconStatus.Up, + capital: "Harare", + continent: { + name: "Africa", + }, + currency: "USD,ZAR,BWP,GBP,AUD,CNY,INR,JPY", + languages: [ + { + name: "English", + }, + { + name: "Shona", + }, + { + name: "North Ndebele", + }, + ], + }, + ], + }, +}; +`, + "widget-types/drilldown/drilldown-widget/drilldown-widget-example.component.ts": `// © 2022 SolarWinds Worldwide, LLC. All rights reserved. +// +// Permission is hereby granted, free of charge, to any person obtaining a copy +// of this software and associated documentation files (the "Software"), to +// deal in the Software without restriction, including without limitation the +// rights to use, copy, modify, merge, publish, distribute, sublicense, and/or +// sell copies of the Software, and to permit persons to whom the Software is +// furnished to do so, subject to the following conditions: +// +// The above copyright notice and this permission notice shall be included in +// all copies or substantial portions of the Software. +// +// THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR +// IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, +// FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE +// AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER +// LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, +// OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN +// THE SOFTWARE. + +import { HttpClient } from "@angular/common/http"; +import { + ChangeDetectorRef, + Component, + Injectable, + OnDestroy, + OnInit, +} from "@angular/core"; +import { GridsterConfig, GridsterItem } from "angular-gridster2"; +import { Apollo, gql } from "apollo-angular"; +import groupBy from "lodash/groupBy"; +import { BehaviorSubject, Observable, of } from "rxjs"; +import { catchError, delay, filter, map } from "rxjs/operators"; + +import { + DataSourceFeatures, + IconStatus, + IDataField, + IDataSource, + IDataSourceFeatures, + IDataSourceFeaturesConfiguration, + INovaFilters, + LoggerService, + ServerSideDataSource, + IFilters, +} from "@nova-ui/bits"; +import { + DATA_SOURCE, + DEFAULT_PIZZAGNA_ROOT, + IDashboard, + IDrilldownComponentsConfiguration, + IListWidgetConfiguration, + IProviderConfiguration, + IWidget, + IWidgets, + ListGroupItemComponent, + ListLeafItemComponent, + NOVA_DRILLDOWN_DATASOURCE_ADAPTER, + PizzagnaLayer, + ProviderRegistryService, + WellKnownPathKey, + WellKnownProviders, + WidgetTypesService, +} from "@nova-ui/dashboards"; + +import { DrilldownDataSource } from "./mock-data-source"; + +/** + * A simple KPI data source to retrieve the average rating of Harry Potter and the Sorcerer's Stone (book) via googleapis + */ +@Injectable() +export class DrilldownDataSourceRealApi + extends ServerSideDataSource + implements OnDestroy, IDataSource +{ + // This is the ID we'll use to identify the provider + public static providerId = "DrilldownDataSourceRealApi"; + + // Use this subject to communicate the data source's busy state + public busy = new BehaviorSubject(false); + public dataFields: Partial[] = [ + { id: "regionName", label: "Region name" }, + { id: "subregionName", label: "Subregion name" }, + ]; + + public features: IDataSourceFeaturesConfiguration; + private supportedFeatures: IDataSourceFeatures = { + search: { enabled: true }, + }; + + private drillState: string[] = []; + private groupBy: string[]; + + constructor( + private logger: LoggerService, + private http: HttpClient, + private apollo: Apollo + ) { + super(); + this.features = new DataSourceFeatures(this.supportedFeatures); + // TODO: remove Partial in vNext after marking dataType field as optional - NUI-5838 + ( + this.dataFieldsConfig.dataFields$ as BehaviorSubject< + Partial[] + > + ).next(this.dataFields); + } + + private groupedDataHistory: Array> = []; + + // In this example, getFilteredData is invoked every 10 minutes (Take a look at the refresher + // provider definition in the widget configuration below to see how the interval is set) + public async getFilteredData(data: IFilters): Promise { + return of(data) + .pipe( + filter(() => !!this.drillState), + map((countries) => { + const lastHistory = () => getLast(this.groupedDataHistory); + + if (!this.drillState.length && !this.groupBy.length) { + return countries; + } + + // adding "ROOT" as a root level for drilling + const fullDrillState = ["ROOT", ...this.drillState]; + const activeDrillLvl = fullDrillState.length; + const historyLvl = this.groupedDataHistory.length; + + // checking how many lvls we have to group for drilling, in case some are missed + const drillLvlDiff = activeDrillLvl - historyLvl; + + if (!drillLvlDiff) { + return lastHistory() || countries; + } + + const drillToGroup = fullDrillState.slice( + fullDrillState.length - drillLvlDiff + ); + + for (const drill of drillToGroup) { + const drillIdx = fullDrillState.findIndex( + (v) => v === drill + ); + const group = this.groupBy[drillIdx]; + + if (group) { + const dataToGroup = lastHistory() + ? lastHistory()[drill] + : countries; + const lastGroupedValue = groupBy( + dataToGroup, + group + ); + + this.groupedDataHistory.push(lastGroupedValue); + } + } + + // take last if we have all data grouped + if (this.groupBy.length === this.drillState.length) { + return lastHistory()[getLast(this.drillState)]; + } + + // get groping and transform to raw data format + return this.getGroupsWidgetData(lastHistory()); + }) + ) + .toPromise(); + } + + public ngOnDestroy(): void { + this.outputsSubject.complete(); + } + + // This method is expected to return all data needed for repeat/paginator/filterGroups in order to work. + // In case of custom filtering participants feel free to extend INovaFilteringOutputs. + protected getBackendData(filters: INovaFilters): Observable { + const mainRequest = this.apollo.watchQuery<{ countries: any }>({ + query: this.generateQuery(filters), + }); + + return mainRequest.valueChanges.pipe( + // mock delay + delay(300), + // data mapping, !DS specific! + map((res) => res.data.countries), + // adds mock icons to be displayed on leaf nodes !DS specific! + map((res: any[]) => + res.map((v) => ({ + ...v, + icon: "virtual-host", + icon_status: IconStatus.Up, + subregionName: + v.subregion?.name || "No Subregion Specified", + regionName: + v.subregion?.region?.name || "No Region Specified", + })) + ), + catchError((e) => { + this.logger.error(e); + return of({} as any); + }) + ); + } + + private generateQuery(filters: INovaFilters) { + const { search } = filters; + const searchValue = search?.value ? \`^[\${search.value}]*\` : ""; + + const queryString = \` + query { + countries(filter: {name: {regex: "\${searchValue}"} }) { + name + native + capital + languages { + name + } + currencies + subdivisions { + name + } + } + } + \`; + + return gql\` + \${queryString} + \`; + } + + // Overrides default ServerSideDataSource.beforeApplyFilters implementation + // to save some filters that are used internally + // -- !DS specific + protected beforeApplyFilters(filters: INovaFilters): void { + this.busy.next(true); + + this.drillState = filters.drillstate?.value; + this.groupBy = filters.group?.value; + + if (this.isHome()) { + this.groupedDataHistory.length = 0; + } + + if (this.isBack()) { + this.groupedDataHistory.length = this.groupedDataHistory.length - 1; + } + + if (this.getFilters()["search"] && this.filterChanged("search")) { + this.groupedDataHistory.length = 0; + } + } + + private getGroupsWidgetData(groupByObj: Record) { + return Object.keys(groupByObj).map((property) => ({ + id: property, + label: property, + // statuses that will be displayed on group item + statuses: [ + { key: "virtual-host", value: groupByObj[property].length }, + { + key: "acknowledge", + value: this.getPopulation(groupByObj[property]), + }, + ], + })); + } + + private isHome(): boolean { + return this.drillState?.length === 0; + } + + private isBack(): boolean { + return ( + this.groupedDataHistory?.length > this.drillState?.length && + !this.isHome() + ); + } + + /** + * Gets population for the country(ies) + */ + private getPopulation(countries: any[]) { + const totalPopulation = countries.reduce( + (acc, next) => (acc += next.population), + 0 + ); + return \`\${totalPopulation * Math.pow(10, -3)} k\`; + } +} + +@Component({ + selector: "drilldown-widget-example", + templateUrl: "./drilldown-widget-example.component.html", + styleUrls: ["./drilldown-widget-example.component.less"], + standalone: false, +}) +export class DrilldownWidgetExampleComponent implements OnInit { + // This variable will hold all the data needed to define the layout and behavior of the widgets. + // Pass this to the dashboard component's dashboard input in the template. + public dashboard: IDashboard | undefined; + + // Angular gridster requires a configuration object even if it's empty. + // Pass this to the dashboard component's gridsterConfig input in the template. + public gridsterConfig: GridsterConfig = {}; + + // Boolean passed as an input to the dashboard. When true, widgets can be moved, resized, removed, or edited + public editMode: boolean = false; + + constructor( + // WidgetTypesService provides the widget's necessary structure information + private widgetTypesService: WidgetTypesService, + private providerRegistry: ProviderRegistryService, + private changeDetectorRef: ChangeDetectorRef + ) {} + + public ngOnInit(): void { + // Registering the data source for injection into the KPI tile. + // Note: Each tile of a KPI widget is assigned its own instance of the data source + this.providerRegistry.setProviders({ + [DrilldownDataSource.providerId]: { + provide: DATA_SOURCE, + useClass: DrilldownDataSource, + // Any dependencies that need to be injected into the provider must be listed here + deps: [HttpClient], + }, + [DrilldownDataSourceRealApi.providerId]: { + provide: DATA_SOURCE, + useClass: DrilldownDataSourceRealApi, + // Any dependencies that need to be injected into the provider must be listed here + deps: [LoggerService, HttpClient, Apollo], + }, + }); + + this.initializeDashboard(); + const widgetTemplate = this.widgetTypesService.getWidgetType( + "drilldown", + 1 + ); + this.widgetTypesService.setNode( + widgetTemplate, + "configurator", + WellKnownPathKey.DataSourceProviders, + [ + DrilldownDataSourceRealApi.providerId, + DrilldownDataSource.providerId, + ] + ); + } + + public initializeDashboard(): void { + // We're using a static configuration object for this example, but this is where + // the widget's configuration could potentially be populated from a database + const drilldownWidget = widgetConfig; + const widgets: IWidgets = { + // Complete the widget with information coming from its type definition + [drilldownWidget.id]: + this.widgetTypesService.mergeWithWidgetType(drilldownWidget), + }; + + // Setting the widget dimensions and position (this is for gridster) + const positions: Record = { + [drilldownWidget.id]: { + cols: 10, + rows: 10, + y: 0, + x: 0, + }, + }; + + // Finally, assigning the variables we created above to the dashboard + this.dashboard = { positions, widgets }; + } + + public reInitializeDashboard(): void { + // destroys the components and their providers so the dashboard can re init data + this.dashboard = undefined; + this.changeDetectorRef.detectChanges(); + + const adapterProperties = + widgetConfig.pizzagna[PizzagnaLayer.Configuration].listWidget + .providers?.adapter?.properties; + + if (adapterProperties) { + adapterProperties.drillstate = []; + } + + this.initializeDashboard(); + } +} + +const widgetConfig: IWidget = { + id: "drilldown", + type: "drilldown", + pizzagna: { + [PizzagnaLayer.Configuration]: { + [DEFAULT_PIZZAGNA_ROOT]: { + providers: { + [WellKnownProviders.DataSource]: { + // Setting the data source providerId for the tile with id "kpi1" + providerId: DrilldownDataSourceRealApi.providerId, + properties: {}, + } as IProviderConfiguration, + }, + }, + header: { + properties: { + title: "Drilldown Widget", + subtitle: "Search is case sensitive!", + }, + }, + listWidget: { + providers: { + [WellKnownProviders.Adapter]: { + providerId: NOVA_DRILLDOWN_DATASOURCE_ADAPTER, + properties: { + // widget + navigationBarId: "navigationBar", + componentId: "listWidget", + dataPath: "data", + + // adapter props + drillstate: [], + groupBy: ["regionName", "subregionName"], + groups: ["regionName", "subregionName"], + + // components + componentsConfig: { + group: { + componentType: + ListGroupItemComponent.lateLoadKey, + properties: { + dataFieldIds: { + id: "id", + label: "label", + statuses: "statuses", + }, + }, + itemProperties: { + canNavigate: true, + }, + }, + leaf: { + componentType: + ListLeafItemComponent.lateLoadKey, + properties: { + dataFieldIds: { + icon: "icon", + status: "icon_status", + detailedUrl: "capital", + label: "name", + }, + }, + itemProperties: { + canNavigate: false, + }, + }, + } as IDrilldownComponentsConfiguration, + }, + }, + }, + properties: { + configuration: { + // FORMAT: + // componentType: ListLeafItemComponent.lateLoadKey, + // properties: { + // dataFieldIds: { + // icon: "", + // status: "code", + // detailedUrl: "capital", + // label: "name", + // }, + // }, + // + } as IListWidgetConfiguration, + }, + }, + }, + }, +}; + +const getLast = (arr: any[]) => arr[arr.length - 1]; +`, + "widget-types/drilldown/drilldown-widget/mock-data-source.ts": `// © 2022 SolarWinds Worldwide, LLC. All rights reserved. +// +// Permission is hereby granted, free of charge, to any person obtaining a copy +// of this software and associated documentation files (the "Software"), to +// deal in the Software without restriction, including without limitation the +// rights to use, copy, modify, merge, publish, distribute, sublicense, and/or +// sell copies of the Software, and to permit persons to whom the Software is +// furnished to do so, subject to the following conditions: +// +// The above copyright notice and this permission notice shall be included in +// all copies or substantial portions of the Software. +// +// THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR +// IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, +// FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE +// AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER +// LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, +// OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN +// THE SOFTWARE. + +import { Injectable, OnDestroy } from "@angular/core"; +import groupBy from "lodash/groupBy"; +import { BehaviorSubject, Observable, of, Subject } from "rxjs"; +import { + catchError, + delay, + finalize, + map, + // eslint-disable-next-line import/no-deprecated + switchMap, + tap, +} from "rxjs/operators"; + +import { + DataSourceService, + IDataField, + IDataSource, + IFilters, + INovaFilters, +} from "@nova-ui/bits"; + +import { GRAPH_DATA_MOCK } from "./data-mock"; + +/** + * A simple KPI data source to retrieve the average rating of Harry Potter and the Sorcerer's Stone (book) via googleapis + */ +@Injectable() +export class DrilldownDataSource + extends DataSourceService + implements IDataSource, OnDestroy +{ + // This is the ID we'll use to identify the provider + public static providerId = "DrilldownDataSource"; + + // Use this subject to communicate the data source's busy state + public busy = new BehaviorSubject(false); + public dataFields: Partial[] = [ + { id: "continent.name", label: "Continent name" }, + { id: "currency", label: "Currency" }, + ]; + + private drillState: string[] = []; + private groupBy: string[]; + private cache: any; + private applyFilters$ = new Subject(); + + constructor() { + super(); + + // TODO: remove Partial in vNext after marking dataType field as optional - NUI-5838 + ( + this.dataFieldsConfig.dataFields$ as BehaviorSubject< + Partial[] + > + ).next(this.dataFields); + + this.applyFilters$ + // eslint-disable-next-line import/no-deprecated + .pipe(switchMap((filters) => this.getData(filters))) + .subscribe(async (res) => { + this.outputsSubject.next(await this.getFilteredData(res)); + }); + } + + private groupedDataHistory: any[] = []; + + // In this example, getFilteredData is invoked every 10 minutes (Take a look at the refresher + // provider definition in the widget configuration below to see how the interval is set) + public async getFilteredData(data: any): Promise { + return of(data) + .pipe( + map((countries) => { + const widgetInput = this.getOutput(countries); + + if (this.isDrillDown()) { + const activeDrillLvl = this.drillState.length; + const group = this.groupBy[activeDrillLvl]; + const [lastGroupedValue, groupedData] = + this.getTransformedDataForGroup(widgetInput, group); + + this.groupedDataHistory.push(lastGroupedValue); + + return groupedData; + } + + return widgetInput; + }) + ) + .toPromise(); + } + + public ngOnDestroy(): void { + this.outputsSubject.complete(); + } + + // redefine parent method + public async applyFilters(): Promise { + this.applyFilters$.next(this.getFilters()); + } + + private getData(filters: INovaFilters): Observable { + this.drillState = filters.drillstate?.value; + this.groupBy = filters.group?.value; + + this.busy.next(true); + + return of(this.cache || GRAPH_DATA_MOCK).pipe( + delay(1000), + tap((data) => (this.cache = data)), + map((data) => data.data.countries), + catchError((e) => of([])), + finalize(() => this.busy.next(false)) + ); + } + + private getTransformedDataForGroup(data: any, groupName: string) { + const groupedDict = groupBy(data, groupName); + const dataArr = Object.keys(groupedDict).map((property) => ({ + id: property, + label: property, + // TODO: apply groups mapping here + statuses: [ + { key: "state_ok", value: groupedDict[property].length }, + { + key: "status_unreachable", + value: generateNumberUpTo(100000), + }, + { key: "status_warning", value: generateNumberUpTo(10000) }, + { key: "status_unknown", value: generateNumberUpTo(1000) }, + ], + })); + + return [groupedDict, dataArr]; + } + + private isHome(): boolean { + return !this.drillState || this.drillState.length === 0; + } + + private isBack(): boolean { + return ( + this.groupedDataHistory.length > this.drillState?.length && + !this.isHome() + ); + } + + private isDrillDown(): boolean { + return this.drillState?.length !== this.groupBy?.length; + } + + private getOutput(data: any) { + if (this.isHome()) { + this.groupedDataHistory.length = 0; + } + + if (this.isBack()) { + this.groupedDataHistory.length = this.groupedDataHistory.length - 1; + } + + const lastHistoryValue = getLast(this.groupedDataHistory); + + if (!lastHistoryValue) { + return data; + } + + return lastHistoryValue[getLast(this.drillState)] || lastHistoryValue; + } +} + +const getLast = (arr: any[]) => arr[arr.length - 1]; +const generateNumberUpTo = (upperLimit: number): number => + Math.floor(Math.random() * upperLimit + 1); +`, + "widget-types/drilldown/drilldown-widget-docs.component.ts": `// © 2022 SolarWinds Worldwide, LLC. All rights reserved. +// +// Permission is hereby granted, free of charge, to any person obtaining a copy +// of this software and associated documentation files (the "Software"), to +// deal in the Software without restriction, including without limitation the +// rights to use, copy, modify, merge, publish, distribute, sublicense, and/or +// sell copies of the Software, and to permit persons to whom the Software is +// furnished to do so, subject to the following conditions: +// +// The above copyright notice and this permission notice shall be included in +// all copies or substantial portions of the Software. +// +// THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR +// IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, +// FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE +// AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER +// LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, +// OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN +// THE SOFTWARE. + +import { Component, OnInit } from "@angular/core"; + +import { mapContentFile } from "../../../../demo-files-factory"; + +@Component({ + selector: "nui-drilldown-docs", + templateUrl: "./drilldown-widget-docs.component.html", + standalone: false, +}) +export class DrilldownDocsComponent implements OnInit { + public widgetFileText = ""; + public configuratorFileText = ""; + + public predefinedGroping = \` +listWidget: { + providers: { + [WellKnownProviders.Adapter]: { + providerId: NOVA_DRILLDOWN_DATASOURCE_ADAPTER, + properties: { + ... + // adapter props + drillstate: [], + groupBy: ["regionName", "subregionName"], + groups: ["regionName", "subregionName"], + ... + }, + }, + }, +}, +\`; + public featuredDeclaredText = \` + private supportedFeatures: IDataSourceFeatures = { + search: { enabled: true }, + };\`; + public featuresUsedText = \` + this.features = new DataSourceFeatures(this.supportedFeatures); + \`; + + public async ngOnInit(): Promise { + this.widgetFileText = await import( + "./../../../../../../src/lib/widget-types/drilldown/drilldown-widget" + ).then(mapContentFile); + this.configuratorFileText = await import( + "./../../../../../../src/lib/widget-types/drilldown/drilldown-configurator" + ).then(mapContentFile); + } +} +`, + "widget-types/drilldown/drilldown-widget-docs.module.ts": `// © 2022 SolarWinds Worldwide, LLC. All rights reserved. +// +// Permission is hereby granted, free of charge, to any person obtaining a copy +// of this software and associated documentation files (the "Software"), to +// deal in the Software without restriction, including without limitation the +// rights to use, copy, modify, merge, publish, distribute, sublicense, and/or +// sell copies of the Software, and to permit persons to whom the Software is +// furnished to do so, subject to the following conditions: +// +// The above copyright notice and this permission notice shall be included in +// all copies or substantial portions of the Software. +// +// THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR +// IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, +// FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE +// AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER +// LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, +// OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN +// THE SOFTWARE. + +import { NgModule } from "@angular/core"; +import { RouterModule, Routes } from "@angular/router"; + +// eslint-disable-next-line max-len +import { + NuiButtonModule, + NuiDocsModule, + NuiMessageModule, + NuiSwitchModule, + DEMO_PATH_TOKEN, +} from "@nova-ui/bits"; +import { NuiDashboardsModule } from "@nova-ui/dashboards"; + +import { DrilldownMultiRequestWidgetExampleComponent } from "./drilldown-multi-request-widget/drilldown-multi-request-widget-example.component"; +import { DrilldownWidgetExampleComponent } from "./drilldown-widget/drilldown-widget-example.component"; +import { DrilldownDocsComponent } from "./drilldown-widget-docs.component"; +import { getDemoFiles } from "../../../../demo-files-factory"; + +const routes: Routes = [ + { + path: "", + component: DrilldownDocsComponent, + data: { + srlc: { + hideIndicator: true, + }, + }, + }, + { + path: "example", + component: DrilldownWidgetExampleComponent, + data: { + srlc: { + hideIndicator: true, + }, + }, + }, + { + path: "multiple-requests", + component: DrilldownMultiRequestWidgetExampleComponent, + data: { + srlc: { + hideIndicator: true, + }, + }, + }, +]; + +@NgModule({ + imports: [ + RouterModule.forChild(routes), + NuiButtonModule, + NuiDocsModule, + NuiMessageModule, + NuiDashboardsModule, + NuiSwitchModule, + ], + declarations: [ + DrilldownDocsComponent, + DrilldownWidgetExampleComponent, + DrilldownMultiRequestWidgetExampleComponent, + ], + providers: [ + { + provide: DEMO_PATH_TOKEN, + useValue: getDemoFiles("drilldown"), + }, + ], +}) +export default class DrilldownDocsModule {} +`, + "widget-types/embedded-content/embedded-content-docs.component.ts": `// © 2022 SolarWinds Worldwide, LLC. All rights reserved. +// +// Permission is hereby granted, free of charge, to any person obtaining a copy +// of this software and associated documentation files (the "Software"), to +// deal in the Software without restriction, including without limitation the +// rights to use, copy, modify, merge, publish, distribute, sublicense, and/or +// sell copies of the Software, and to permit persons to whom the Software is +// furnished to do so, subject to the following conditions: +// +// The above copyright notice and this permission notice shall be included in +// all copies or substantial portions of the Software. +// +// THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR +// IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, +// FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE +// AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER +// LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, +// OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN +// THE SOFTWARE. + +import { Component, OnInit } from "@angular/core"; + +import { mapContentFile } from "../../../../demo-files-factory"; + +@Component({ + selector: "nui-embedded-content-docs", + templateUrl: "./embedded-content-docs.component.html", + standalone: false, +}) +export class EmbeddedContentDocsComponent implements OnInit { + public embeddedContentWidgetFileText = ""; + public embeddedContentConfiguratorFileText = ""; + + public async ngOnInit(): Promise { + this.embeddedContentWidgetFileText = await import( + "./../../../../../../src/lib/widget-types/embedded-content/embedded-content-widget" + ).then(mapContentFile); + this.embeddedContentWidgetFileText = await import( + "./../../../../../../src/lib/widget-types/embedded-content/embedded-content-configurator" + ).then(mapContentFile); + } +} +`, + "widget-types/embedded-content/embedded-content-docs.module.ts": `// © 2022 SolarWinds Worldwide, LLC. All rights reserved. +// +// Permission is hereby granted, free of charge, to any person obtaining a copy +// of this software and associated documentation files (the "Software"), to +// deal in the Software without restriction, including without limitation the +// rights to use, copy, modify, merge, publish, distribute, sublicense, and/or +// sell copies of the Software, and to permit persons to whom the Software is +// furnished to do so, subject to the following conditions: +// +// The above copyright notice and this permission notice shall be included in +// all copies or substantial portions of the Software. +// +// THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR +// IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, +// FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE +// AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER +// LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, +// OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN +// THE SOFTWARE. + +import { NgModule } from "@angular/core"; +import { RouterModule, Routes } from "@angular/router"; + +// eslint-disable-next-line max-len +import { + NuiButtonModule, + NuiDocsModule, + NuiMessageModule, + NuiSwitchModule, + DEMO_PATH_TOKEN, +} from "@nova-ui/bits"; +import { NuiDashboardsModule } from "@nova-ui/dashboards"; + +import { EmbeddedContentDocsComponent } from "./embedded-content-docs.component"; +import { EmbeddedContentWidgetExampleComponent } from "./embedded-content-widget-example/embedded-content-widget-example.component"; +import { getDemoFiles } from "../../../../demo-files-factory"; + +const routes: Routes = [ + { + path: "", + component: EmbeddedContentDocsComponent, + data: { + srlc: { + hideIndicator: true, + }, + }, + }, + { + path: "example", + component: EmbeddedContentWidgetExampleComponent, + data: { + srlc: { + hideIndicator: true, + }, + }, + }, +]; + +@NgModule({ + imports: [ + RouterModule.forChild(routes), + NuiButtonModule, + NuiDocsModule, + NuiMessageModule, + NuiDashboardsModule, + NuiSwitchModule, + ], + declarations: [ + EmbeddedContentDocsComponent, + EmbeddedContentWidgetExampleComponent, + ], + providers: [ + { + provide: DEMO_PATH_TOKEN, + useValue: getDemoFiles("embedded-content"), + }, + ], +}) +export default class EmbeddedContentDocsModule {} +`, + "widget-types/embedded-content/embedded-content-widget-example/embedded-content-widget-example.component.ts": `// © 2022 SolarWinds Worldwide, LLC. All rights reserved. +// +// Permission is hereby granted, free of charge, to any person obtaining a copy +// of this software and associated documentation files (the "Software"), to +// deal in the Software without restriction, including without limitation the +// rights to use, copy, modify, merge, publish, distribute, sublicense, and/or +// sell copies of the Software, and to permit persons to whom the Software is +// furnished to do so, subject to the following conditions: +// +// The above copyright notice and this permission notice shall be included in +// all copies or substantial portions of the Software. +// +// THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR +// IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, +// FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE +// AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER +// LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, +// OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN +// THE SOFTWARE. + +import { ChangeDetectorRef, Component, OnInit } from "@angular/core"; +import { GridsterConfig, GridsterItem } from "angular-gridster2"; + +import { + ComponentRegistryService, + EmbeddedContentComponent, + EmbeddedContentConfigurationComponent, + EmbeddedContentMode, + IDashboard, + IWidget, + IWidgets, + PizzagnaLayer, + WidgetTypesService, +} from "@nova-ui/dashboards"; + +@Component({ + selector: "embedded-content-widget-example", + templateUrl: "./embedded-content-widget-example.component.html", + styleUrls: ["./embedded-content-widget-example.component.less"], + standalone: false, +}) +export class EmbeddedContentWidgetExampleComponent implements OnInit { + // This variable will hold all the data needed to define the layout and behavior of the widgets. + // Pass this to the dashboard component's dashboard input in the template. + public dashboard: IDashboard | undefined; + + // Angular gridster requires a configuration object even if it's empty. + // Pass this to the dashboard component's gridsterConfig input in the template. + public gridsterConfig: GridsterConfig = {}; + + // Boolean passed as an input to the dashboard. When true, widgets can be moved, resized, removed, or edited + public editMode: boolean = false; + + constructor( + // WidgetTypesService provides the widget's necessary structure information + private widgetTypesService: WidgetTypesService, + + private componentRegistry: ComponentRegistryService, + private changeDetectorRef: ChangeDetectorRef + ) {} + + public ngOnInit(): void { + this.prepareNovaDashboards(); + this.initializeDashboard(); + } + + /** Used for restoring widgets state */ + public reInitializeDashboard(): void { + // destroys the components and their providers so the dashboard can re init data + this.dashboard = undefined; + this.changeDetectorRef.detectChanges(); + + this.initializeDashboard(); + } + + public initializeDashboard(): void { + // We're using a static configuration object for this example, but this is where + // the widget's configuration could potentially be populated from a database + const embeddedContentWidget = widgetConfig; + const widgets: IWidgets = { + // Complete the widget with information coming from its type definition + [embeddedContentWidget.id]: + this.widgetTypesService.mergeWithWidgetType( + embeddedContentWidget + ), + }; + + // Setting the widget dimensions and position (this is for gridster) + const positions: Record = { + [embeddedContentWidget.id]: { + cols: 10, + rows: 10, + y: 0, + x: 0, + }, + }; + + // Finally, assigning the variables we created above to the dashboard + this.dashboard = { positions, widgets }; + } + + private prepareNovaDashboards() { + this.componentRegistry.registerByLateLoadKey(EmbeddedContentComponent); + this.componentRegistry.registerByLateLoadKey( + EmbeddedContentConfigurationComponent + ); + } +} + +const widgetConfig: IWidget = { + id: "embeddedContentWidgetId", + type: "embedded-content", + pizzagna: { + [PizzagnaLayer.Configuration]: { + header: { + properties: { + title: "Embedded Content Widget", + subtitle: "", + }, + }, + mainContent: { + properties: { + sanitized: true, + mode: EmbeddedContentMode.URL, + customEmbeddedContent: "https://www.ventusky.com/", + }, + }, + }, + }, +}; +`, + "widget-types/kpi/kpi-docs.component.ts": `// © 2022 SolarWinds Worldwide, LLC. All rights reserved. +// +// Permission is hereby granted, free of charge, to any person obtaining a copy +// of this software and associated documentation files (the "Software"), to +// deal in the Software without restriction, including without limitation the +// rights to use, copy, modify, merge, publish, distribute, sublicense, and/or +// sell copies of the Software, and to permit persons to whom the Software is +// furnished to do so, subject to the following conditions: +// +// The above copyright notice and this permission notice shall be included in +// all copies or substantial portions of the Software. +// +// THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR +// IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, +// FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE +// AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER +// LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, +// OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN +// THE SOFTWARE. + +import { Component, OnInit } from "@angular/core"; + +import { mapContentFile } from "../../../../demo-files-factory"; + +@Component({ + selector: "nui-kpi-docs", + templateUrl: "./kpi-docs.component.html", + standalone: false, +}) +export class KpiDocsComponent implements OnInit { + public kpiWidgetFileText = ""; + public kpiConfiguratorFileText = ""; + + public async ngOnInit(): Promise { + this.kpiWidgetFileText = await import( + "./../../../../../../src/lib/widget-types/kpi/kpi-widget" + ).then(mapContentFile); + this.kpiConfiguratorFileText = await import( + "./../../../../../../src/lib/widget-types/kpi/kpi-configurator" + ).then(mapContentFile); + } +} +`, + "widget-types/kpi/kpi-docs.module.ts": `// © 2022 SolarWinds Worldwide, LLC. All rights reserved. +// +// Permission is hereby granted, free of charge, to any person obtaining a copy +// of this software and associated documentation files (the "Software"), to +// deal in the Software without restriction, including without limitation the +// rights to use, copy, modify, merge, publish, distribute, sublicense, and/or +// sell copies of the Software, and to permit persons to whom the Software is +// furnished to do so, subject to the following conditions: +// +// The above copyright notice and this permission notice shall be included in +// all copies or substantial portions of the Software. +// +// THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR +// IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, +// FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE +// AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER +// LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, +// OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN +// THE SOFTWARE. + +import { NgModule } from "@angular/core"; +import { RouterModule, Routes } from "@angular/router"; + +import { + NuiButtonModule, + NuiDocsModule, + NuiMessageModule, + NuiSwitchModule, + DEMO_PATH_TOKEN, +} from "@nova-ui/bits"; +import { + KpiColorComparatorsRegistryService, + NuiDashboardsModule, +} from "@nova-ui/dashboards"; + +import { KpiDocsComponent } from "./kpi-docs.component"; +import { KpiSyncBrokerExampleComponent } from "./kpi-sync-broker/kpi-sync-broker-example.component"; +import { KpiSyncBrokerDocsComponent } from "./kpi-sync-broker-docs.component"; +import { KpiSyncBrokerForAllTilesExampleComponent } from "./kpi-sync-broker-for-all-tiles/kpi-sync-broker-for-all-tiles-example.component"; +import { KpiWidgetExampleComponent } from "./kpi-widget/kpi-widget-example.component"; +import { KpiWidgetBackgroundColorExampleComponent } from "./kpi-widget-background-color/kpi-widget-background-color-example.component"; +import { KpiWidgetBackgroundColorDocsComponent } from "./kpi-widget-background-color-docs.component"; +import { KpiWidgetInteractiveExampleComponent } from "./kpi-widget-interactive/kpi-widget-interactive-example.component"; +import { getDemoFiles } from "../../../../demo-files-factory"; + +const routes: Routes = [ + { + path: "", + component: KpiDocsComponent, + data: { + srlc: { + hideIndicator: true, + }, + showThemeSwitcher: true, + }, + }, + { + path: "example", + component: KpiWidgetExampleComponent, + data: { + srlc: { + hideIndicator: true, + }, + }, + }, + { + path: "background-color", + component: KpiWidgetBackgroundColorDocsComponent, + data: { + srlc: { + hideIndicator: true, + }, + }, + }, + { + path: "sync-broker", + component: KpiSyncBrokerDocsComponent, + data: { + srlc: { + hideIndicator: true, + }, + }, + }, +]; + +@NgModule({ + imports: [ + RouterModule.forChild(routes), + NuiButtonModule, + NuiDocsModule, + NuiMessageModule, + NuiDashboardsModule, + NuiSwitchModule, + ], + declarations: [ + KpiDocsComponent, + KpiWidgetExampleComponent, + KpiWidgetInteractiveExampleComponent, + KpiWidgetBackgroundColorDocsComponent, + KpiWidgetBackgroundColorExampleComponent, + KpiSyncBrokerDocsComponent, + KpiSyncBrokerExampleComponent, + KpiSyncBrokerForAllTilesExampleComponent, + ], + providers: [ + KpiColorComparatorsRegistryService, + { + provide: DEMO_PATH_TOKEN, + useValue: getDemoFiles("kpi"), + }, + ], +}) +export default class KpiDocsModule { + constructor( + private comparatorsRegistry: KpiColorComparatorsRegistryService + ) { + this.backgroundColorDocsSetup(); + } + + private backgroundColorDocsSetup() { + this.comparatorsRegistry.registerComparators({ + "!=": { + comparatorFn: (actual: any, reference: any) => + // eslint-disable-next-line eqeqeq + actual != reference, + label: "Not equal", + }, + }); + } +} +`, + "widget-types/kpi/kpi-sync-broker/kpi-sync-broker-example.component.ts": `// © 2022 SolarWinds Worldwide, LLC. All rights reserved. +// +// Permission is hereby granted, free of charge, to any person obtaining a copy +// of this software and associated documentation files (the "Software"), to +// deal in the Software without restriction, including without limitation the +// rights to use, copy, modify, merge, publish, distribute, sublicense, and/or +// sell copies of the Software, and to permit persons to whom the Software is +// furnished to do so, subject to the following conditions: +// +// The above copyright notice and this permission notice shall be included in +// all copies or substantial portions of the Software. +// +// THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR +// IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, +// FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE +// AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER +// LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, +// OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN +// THE SOFTWARE. + +import { HttpClient, HttpErrorResponse } from "@angular/common/http"; +import { + ChangeDetectorRef, + Component, + Injectable, + OnDestroy, + OnInit, +} from "@angular/core"; +import { GridsterConfig, GridsterItem } from "angular-gridster2"; +import keyBy from "lodash/keyBy"; +import { BehaviorSubject, of } from "rxjs"; +import { delay, finalize, take } from "rxjs/operators"; + +import { DataSourceService, IFilteringOutputs } from "@nova-ui/bits"; +import { + DATA_SOURCE, + IDashboard, + IKpiData, + IProviderConfiguration, + IWidget, + KpiComponent, + NOVA_KPI_DATASOURCE_ADAPTER, + NOVA_KPI_SCALE_SYNC_BROKER, + PizzagnaLayer, + ProviderRegistryService, + WellKnownPathKey, + WellKnownProviders, + WidgetTypesService, +} from "@nova-ui/dashboards"; + +/** + * A simple KPI data source to retrieve the average rating of Harry Potter and the Sorcerer's Stone (book) via googleapis + */ +@Injectable() +export class AverageRatingKpiDataSource + extends DataSourceService + implements OnDestroy +{ + public static providerId = "AverageRatingKpiDataSource"; + public busy = new BehaviorSubject(false); + + constructor(private http: HttpClient) { + super(); + } + + public async getFilteredData(): Promise { + this.busy.next(true); + return new Promise((resolve) => { + // *** Make a resource request to an external API (if needed) + this.http + .get("https://www.googleapis.com/books/v1/volumes/5MQFrgEACAAJ") + .pipe(finalize(() => this.busy.next(false))) + .subscribe({ + next: (data: any) => { + resolve({ + result: { + value: data.volumeInfo.averageRating, + }, + }); + }, + error: (error: HttpErrorResponse) => { + resolve({ + result: null, + error: { + type: error.status, + }, + }); + }, + }); + }); + } + + public ngOnDestroy(): void { + this.outputsSubject.complete(); + } +} + +/** + * A simple KPI data source to retrieve the ratings count of Harry Potter and the Sorcerer's Stone (book) via googleapis + */ +@Injectable() +export class RatingsCountKpiDataSource + extends DataSourceService + implements OnDestroy +{ + public static providerId = "RatingsCountKpiDataSource"; + + // Use this subject to communicate the data source's busy state + public busy = new BehaviorSubject(false); + + constructor(private http: HttpClient) { + super(); + } + + public async getFilteredData(): Promise { + this.busy.next(true); + return new Promise((resolve) => { + this.http + .get("https://www.googleapis.com/books/v1/volumes/5MQFrgEACAAJ") + .pipe( + delay(2000), + finalize(() => this.busy.next(false)) + ) + .subscribe({ + next: (data: any) => { + resolve({ + result: { + value: data.volumeInfo.ratingsCount, + }, + }); + }, + error: (error: HttpErrorResponse) => { + resolve({ + result: null, + error: { + type: error.status, + }, + }); + }, + }); + }); + } + + public ngOnDestroy(): void { + this.outputsSubject.complete(); + } +} +/** + * A simple KPI data source to retrieve the ratings count of Harry Potter and the Sorcerer's Stone (book) via googleapis + */ +@Injectable() +export class MockKpiDataSource + extends DataSourceService + implements OnDestroy +{ + public static providerId = "MockKpiDataSource"; + + // Use this subject to communicate the data source's busy state + public busy = new BehaviorSubject(false); + + constructor() { + super(); + } + + public async getFilteredData(): Promise { + this.busy.next(true); + return new Promise((resolve) => { + of(3381342) + .pipe( + delay(5000), + take(1), + finalize(() => this.busy.next(false)) + ) + .subscribe({ + next: (data: any) => { + resolve({ + result: { + value: data, + }, + }); + }, + }); + }); + } + + public ngOnDestroy(): void { + this.outputsSubject.complete(); + } +} + +/** + * A component that instantiates the dashboard + */ +@Component({ + selector: "kpi-sync-broker-example", + templateUrl: "./kpi-sync-broker-example.component.html", + styleUrls: ["./kpi-sync-broker-example.component.less"], + standalone: false, +}) +export class KpiSyncBrokerExampleComponent implements OnInit { + public dashboard: IDashboard | undefined; + public gridsterConfig: GridsterConfig = {}; + public editMode: boolean = false; + + constructor( + private widgetTypesService: WidgetTypesService, + private providerRegistry: ProviderRegistryService, + private changeDetectorRef: ChangeDetectorRef + ) {} + + public ngOnInit(): void { + this.setupDashboard(); + + this.initializeDashboard(); + } + + private setupDashboard() { + const widgetTemplate = this.widgetTypesService.getWidgetType("kpi", 1); + + this.widgetTypesService.setNode( + widgetTemplate, + "configurator", + WellKnownPathKey.DataSourceProviders, + [ + AverageRatingKpiDataSource.providerId, + RatingsCountKpiDataSource.providerId, + MockKpiDataSource.providerId, + ] + ); + + this.providerRegistry.setProviders({ + [AverageRatingKpiDataSource.providerId]: { + provide: DATA_SOURCE, + useClass: AverageRatingKpiDataSource, + deps: [HttpClient], + }, + [RatingsCountKpiDataSource.providerId]: { + provide: DATA_SOURCE, + useClass: RatingsCountKpiDataSource, + deps: [HttpClient], + }, + [MockKpiDataSource.providerId]: { + provide: DATA_SOURCE, + useClass: MockKpiDataSource, + deps: [], + }, + }); + } + + /** Used for restoring widgets state */ + public reInitializeDashboard(): void { + // destroys the components and their providers so the dashboard can re init data + this.dashboard = undefined; + this.changeDetectorRef.detectChanges(); + + this.initializeDashboard(); + } + + private initializeDashboard(): void { + const widgetsWithStructure = widgetsConfig.map((w) => + this.widgetTypesService.mergeWithWidgetType(w) + ); + const widgetsIndex = keyBy(widgetsWithStructure, (w: IWidget) => w.id); + + const positions: Record = { + kpiWidgetId: { + cols: 3, + rows: 6, + y: 0, + x: 0, + }, + kpiWidgetId2: { + cols: 3, + rows: 6, + y: 0, + x: 0, + }, + }; + + this.dashboard = { + positions, + widgets: widgetsIndex, + }; + } +} + +const widgetsConfig: IWidget[] = [ + { + id: "kpiWidgetId", + type: "kpi", + pizzagna: { + [PizzagnaLayer.Configuration]: { + header: { + properties: { + title: "NO Sync Broker", + subtitle: "Values sizes are being not synced", + }, + }, + tiles: { + properties: { + nodes: ["kpi1", "kpi2", "kpi3"], + }, + }, + kpi1: { + id: "kpi1", + componentType: KpiComponent.lateLoadKey, + properties: { + widgetData: { + units: \`out of 5 Stars\`, + label: \`Average Rating\`, + backgroundColor: "lightpink", + }, + }, + providers: { + [WellKnownProviders.DataSource]: { + providerId: AverageRatingKpiDataSource.providerId, + } as IProviderConfiguration, + [WellKnownProviders.Adapter]: { + providerId: NOVA_KPI_DATASOURCE_ADAPTER, + properties: { + componentId: "kpi1", + propertyPath: "widgetData", + }, + } as IProviderConfiguration, + }, + }, + kpi2: { + id: "kpi2", + componentType: KpiComponent.lateLoadKey, + properties: { + widgetData: { + label: \`Another label which might be a pretty long one\`, + units: \`Which comes from somewhere\`, + backgroundColor: "skyblue", + }, + }, + providers: { + [WellKnownProviders.DataSource]: { + providerId: RatingsCountKpiDataSource.providerId, + } as IProviderConfiguration, + [WellKnownProviders.Adapter]: { + providerId: NOVA_KPI_DATASOURCE_ADAPTER, + properties: { + componentId: "kpi2", + propertyPath: "widgetData", + }, + } as IProviderConfiguration, + }, + }, + kpi3: { + id: "kpi3", + componentType: KpiComponent.lateLoadKey, + properties: { + widgetData: { + label: \`Random\`, + units: \`Data\`, + }, + }, + providers: { + [WellKnownProviders.DataSource]: { + providerId: MockKpiDataSource.providerId, + } as IProviderConfiguration, + [WellKnownProviders.Adapter]: { + providerId: NOVA_KPI_DATASOURCE_ADAPTER, + properties: { + componentId: "kpi3", + propertyPath: "widgetData", + }, + } as IProviderConfiguration, + }, + }, + }, + }, + }, + { + id: "kpiWidgetId2", + type: "kpi", + pizzagna: { + [PizzagnaLayer.Configuration]: { + header: { + properties: { + title: "WITH Sync Broker", + subtitle: + "Now the values of label, units, and value are being synced", + }, + }, + tiles: { + properties: { + nodes: ["kpi4", "kpi5", "kpi6"], + }, + providers: { + // This is where and how you set the sync broker provider + kpiScaleSyncBroker: { + providerId: NOVA_KPI_SCALE_SYNC_BROKER, + properties: { + scaleSyncConfig: [ + // You can decide which values to keep in sync. For instance, you can leave only 'label' id in the array below + { id: "value" }, + { id: "label" }, + { id: "units" }, + ], + }, + }, + }, + }, + kpi4: { + id: "kpi4", + componentType: KpiComponent.lateLoadKey, + properties: { + widgetData: { + units: \`out of 5 Stars\`, + label: \`Average Rating\`, + backgroundColor: "lightpink", + }, + }, + providers: { + [WellKnownProviders.DataSource]: { + providerId: AverageRatingKpiDataSource.providerId, + } as IProviderConfiguration, + [WellKnownProviders.Adapter]: { + providerId: NOVA_KPI_DATASOURCE_ADAPTER, + properties: { + componentId: "kpi4", + propertyPath: "widgetData", + }, + } as IProviderConfiguration, + }, + }, + kpi5: { + id: "kpi5", + componentType: KpiComponent.lateLoadKey, + properties: { + widgetData: { + label: \`Another label which might be a pretty long one\`, + units: \`Which comes from somewhere\`, + backgroundColor: "skyblue", + }, + }, + providers: { + [WellKnownProviders.DataSource]: { + providerId: RatingsCountKpiDataSource.providerId, + } as IProviderConfiguration, + [WellKnownProviders.Adapter]: { + providerId: NOVA_KPI_DATASOURCE_ADAPTER, + properties: { + componentId: "kpi5", + propertyPath: "widgetData", + }, + } as IProviderConfiguration, + }, + }, + kpi6: { + id: "kpi6", + componentType: KpiComponent.lateLoadKey, + properties: { + widgetData: { + label: \`Random\`, + units: \`Data\`, + }, + }, + providers: { + [WellKnownProviders.DataSource]: { + providerId: MockKpiDataSource.providerId, + } as IProviderConfiguration, + [WellKnownProviders.Adapter]: { + providerId: NOVA_KPI_DATASOURCE_ADAPTER, + properties: { + componentId: "kpi6", + propertyPath: "widgetData", + }, + } as IProviderConfiguration, + }, + }, + }, + }, + }, +]; +`, + "widget-types/kpi/kpi-sync-broker-docs.component.ts": `// © 2022 SolarWinds Worldwide, LLC. All rights reserved. +// +// Permission is hereby granted, free of charge, to any person obtaining a copy +// of this software and associated documentation files (the "Software"), to +// deal in the Software without restriction, including without limitation the +// rights to use, copy, modify, merge, publish, distribute, sublicense, and/or +// sell copies of the Software, and to permit persons to whom the Software is +// furnished to do so, subject to the following conditions: +// +// The above copyright notice and this permission notice shall be included in +// all copies or substantial portions of the Software. +// +// THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR +// IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, +// FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE +// AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER +// LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, +// OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN +// THE SOFTWARE. + +import { Component } from "@angular/core"; + +@Component({ + selector: "kpi-sync-broker-docs", + templateUrl: "./kpi-sync-broker-docs.component.html", + standalone: false, +}) +export class KpiSyncBrokerDocsComponent { + public kpiScaleSyncBroker = \` +"tiles": { + "providers": { + kpiScaleSyncBroker: { + providerId: NOVA_KPI_SCALE_SYNC_BROKER, + properties: { + scaleSyncConfig: [ + { id: "value" }, + { id: "label" }, + { id: "units" }, + ], + }, + }, + }, +}, +\`; + + public defineScaleBrokerOnDashboardSetup = \` +// To add the sync broker globally to all the kpi tiles you may start with setting up the broker config +// Here you define which values to keep in sync +const brokerConfig = { + providerId: NOVA_KPI_SCALE_SYNC_BROKER, + properties: { + scaleSyncConfig: [ + { id: "value" }, + { id: "label" }, + { id: "units" }, + ], + }, + }; + +// And here is how you set the sync broker for every KPI widget in the dashboard. +// Later, you will be able to override this setting for each separate KPI widget in the configuration (just like it is shown in the third +// width of the example with the 'kpiWidgetId3') +this.widgetTypesService.setNode( + widgetTemplate, + "widget", + "tiles.providers.kpiScaleSyncBroker", + brokerConfig +); +\`; +} +`, + "widget-types/kpi/kpi-sync-broker-for-all-tiles/kpi-sync-broker-for-all-tiles-example.component.ts": `// © 2022 SolarWinds Worldwide, LLC. All rights reserved. +// +// Permission is hereby granted, free of charge, to any person obtaining a copy +// of this software and associated documentation files (the "Software"), to +// deal in the Software without restriction, including without limitation the +// rights to use, copy, modify, merge, publish, distribute, sublicense, and/or +// sell copies of the Software, and to permit persons to whom the Software is +// furnished to do so, subject to the following conditions: +// +// The above copyright notice and this permission notice shall be included in +// all copies or substantial portions of the Software. +// +// THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR +// IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, +// FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE +// AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER +// LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, +// OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN +// THE SOFTWARE. + +import { HttpClient, HttpErrorResponse } from "@angular/common/http"; +import { + ChangeDetectorRef, + Component, + Injectable, + OnDestroy, + OnInit, +} from "@angular/core"; +import { GridsterConfig, GridsterItem } from "angular-gridster2"; +import keyBy from "lodash/keyBy"; +import { BehaviorSubject, of } from "rxjs"; +import { delay, finalize, take } from "rxjs/operators"; + +import { DataSourceService, IFilteringOutputs } from "@nova-ui/bits"; +import { + DATA_SOURCE, + IDashboard, + IKpiData, + IProviderConfiguration, + IWidget, + KpiComponent, + NOVA_KPI_DATASOURCE_ADAPTER, + NOVA_KPI_SCALE_SYNC_BROKER, + PizzagnaLayer, + ProviderRegistryService, + WellKnownPathKey, + WellKnownProviders, + WidgetTypesService, +} from "@nova-ui/dashboards"; + +/** + * A simple KPI data source to retrieve the average rating of Harry Potter and the Sorcerer's Stone (book) via googleapis + */ +@Injectable() +export class AverageRatingKpiDataSource + extends DataSourceService + implements OnDestroy +{ + public static providerId = "AverageRatingKpiDataSource"; + public busy = new BehaviorSubject(false); + + constructor(private http: HttpClient) { + super(); + } + + public async getFilteredData(): Promise { + this.busy.next(true); + return new Promise((resolve) => { + // *** Make a resource request to an external API (if needed) + this.http + .get("https://www.googleapis.com/books/v1/volumes/5MQFrgEACAAJ") + .pipe(finalize(() => this.busy.next(false))) + .subscribe({ + next: (data: any) => { + resolve({ + result: { + value: data.volumeInfo.averageRating, + }, + }); + }, + error: (error: HttpErrorResponse) => { + resolve({ + result: null, + error: { + type: error.status, + }, + }); + }, + }); + }); + } + + public ngOnDestroy(): void { + this.outputsSubject.complete(); + } +} + +/** + * A simple KPI data source to retrieve the ratings count of Harry Potter and the Sorcerer's Stone (book) via googleapis + */ +@Injectable() +export class RatingsCountKpiDataSource + extends DataSourceService + implements OnDestroy +{ + public static providerId = "RatingsCountKpiDataSource"; + + // Use this subject to communicate the data source's busy state + public busy = new BehaviorSubject(false); + + constructor(private http: HttpClient) { + super(); + } + + public async getFilteredData(): Promise { + this.busy.next(true); + return new Promise((resolve) => { + this.http + .get("https://www.googleapis.com/books/v1/volumes/5MQFrgEACAAJ") + .pipe( + delay(2000), + finalize(() => this.busy.next(false)) + ) + .subscribe({ + next: (data: any) => { + resolve({ + result: { + value: data.volumeInfo.ratingsCount, + }, + }); + }, + error: (error: HttpErrorResponse) => { + resolve({ + result: null, + error: { + type: error.status, + }, + }); + }, + }); + }); + } + + public ngOnDestroy(): void { + this.outputsSubject.complete(); + } +} +/** + * A simple KPI data source to retrieve the ratings count of Harry Potter and the Sorcerer's Stone (book) via googleapis + */ +@Injectable() +export class MockKpiDataSource + extends DataSourceService + implements OnDestroy +{ + public static providerId = "MockKpiDataSource"; + + // Use this subject to communicate the data source's busy state + public busy = new BehaviorSubject(false); + public value: number = 3381342; + + constructor() { + super(); + } + + public async getFilteredData(): Promise { + this.busy.next(true); + return new Promise((resolve) => { + of(this.value) + .pipe( + delay(5000), + take(1), + finalize(() => this.busy.next(false)) + ) + .subscribe({ + next: (data: any) => { + resolve({ + result: { + value: data, + }, + }); + }, + }); + }); + } + + public ngOnDestroy(): void { + this.outputsSubject.complete(); + } +} + +/** + * A component that instantiates the dashboard + */ +@Component({ + selector: "kpi-sync-broker-for-all-tiles-example", + templateUrl: "./kpi-sync-broker-for-all-tiles-example.component.html", + styleUrls: ["./kpi-sync-broker-for-all-tiles-example.component.less"], + standalone: false, +}) +export class KpiSyncBrokerForAllTilesExampleComponent implements OnInit { + public dashboard: IDashboard | undefined; + public gridsterConfig: GridsterConfig = {}; + public editMode: boolean = false; + + constructor( + private widgetTypesService: WidgetTypesService, + private providerRegistry: ProviderRegistryService, + private changeDetectorRef: ChangeDetectorRef + ) {} + + public ngOnInit(): void { + this.setupDashboard(); + + this.initializeDashboard(); + } + + /** Used for restoring widgets state */ + public reInitializeDashboard(): void { + // destroys the components and their providers so the dashboard can re init data + this.dashboard = undefined; + this.changeDetectorRef.detectChanges(); + + this.initializeDashboard(); + } + + private setupDashboard() { + // To add the sync broker globally to all the kpi tiles you may start with setting up the broker config + // Here you define which values to keep in sync + const brokerConfig = { + providerId: NOVA_KPI_SCALE_SYNC_BROKER, + properties: { + scaleSyncConfig: [ + { id: "value" }, + { id: "label" }, + { id: "units" }, + ], + }, + }; + const widgetTemplate = this.widgetTypesService.getWidgetType("kpi", 1); + + this.widgetTypesService.setNode( + widgetTemplate, + "configurator", + WellKnownPathKey.DataSourceProviders, + [ + AverageRatingKpiDataSource.providerId, + RatingsCountKpiDataSource.providerId, + MockKpiDataSource.providerId, + ] + ); + + // And here is how you set the sync broker for every KPI widget in the dashboard. + // Later, you will be able to override this setting for each separate KPI widget in the configuration (just like it is shown in the third + // width of the example with the 'kpiWidgetId3') + this.widgetTypesService.setNode( + widgetTemplate, + "widget", + "tiles.providers.kpiScaleSyncBroker", + brokerConfig + ); + + this.providerRegistry.setProviders({ + [AverageRatingKpiDataSource.providerId]: { + provide: DATA_SOURCE, + useClass: AverageRatingKpiDataSource, + deps: [HttpClient], + }, + [RatingsCountKpiDataSource.providerId]: { + provide: DATA_SOURCE, + useClass: RatingsCountKpiDataSource, + deps: [HttpClient], + }, + [MockKpiDataSource.providerId]: { + provide: DATA_SOURCE, + useClass: MockKpiDataSource, + deps: [], + }, + }); + } + + private initializeDashboard(): void { + const widgetsWithStructure = widgetsConfig.map((w) => + this.widgetTypesService.mergeWithWidgetType(w) + ); + const widgetsIndex = keyBy(widgetsWithStructure, (w: IWidget) => w.id); + + const positions: Record = { + kpiWidgetId: { + cols: 3, + rows: 6, + y: 0, + x: 0, + }, + kpiWidgetId2: { + cols: 3, + rows: 6, + y: 0, + x: 3, + }, + kpiWidgetId3: { + cols: 3, + rows: 6, + y: 0, + x: 6, + }, + }; + + this.dashboard = { + positions, + widgets: widgetsIndex, + }; + } +} + +const widgetsConfig: IWidget[] = [ + { + id: "kpiWidgetId", + type: "kpi", + pizzagna: { + [PizzagnaLayer.Configuration]: { + header: { + properties: { + title: "Sync Broker Applied for ALL Widgets", + subtitle: "Values are being synced", + }, + }, + tiles: { + properties: { + nodes: ["kpi1", "kpi2", "kpi3"], + }, + }, + kpi1: { + id: "kpi1", + componentType: KpiComponent.lateLoadKey, + properties: { + widgetData: { + units: \`out of 5 Stars\`, + label: \`Average Rating\`, + backgroundColor: "lightpink", + }, + }, + providers: { + [WellKnownProviders.DataSource]: { + providerId: AverageRatingKpiDataSource.providerId, + } as IProviderConfiguration, + [WellKnownProviders.Adapter]: { + providerId: NOVA_KPI_DATASOURCE_ADAPTER, + properties: { + componentId: "kpi1", + propertyPath: "widgetData", + }, + } as IProviderConfiguration, + }, + }, + kpi2: { + id: "kpi2", + componentType: KpiComponent.lateLoadKey, + properties: { + widgetData: { + label: \`Another label which might be a pretty long one\`, + units: \`Which comes from somewhere\`, + backgroundColor: "skyblue", + }, + }, + providers: { + [WellKnownProviders.DataSource]: { + providerId: RatingsCountKpiDataSource.providerId, + } as IProviderConfiguration, + [WellKnownProviders.Adapter]: { + providerId: NOVA_KPI_DATASOURCE_ADAPTER, + properties: { + componentId: "kpi2", + propertyPath: "widgetData", + }, + } as IProviderConfiguration, + }, + }, + kpi3: { + id: "kpi3", + componentType: KpiComponent.lateLoadKey, + properties: { + widgetData: { + label: \`Random\`, + units: \`Data\`, + }, + }, + providers: { + [WellKnownProviders.DataSource]: { + providerId: MockKpiDataSource.providerId, + } as IProviderConfiguration, + [WellKnownProviders.Adapter]: { + providerId: NOVA_KPI_DATASOURCE_ADAPTER, + properties: { + componentId: "kpi3", + propertyPath: "widgetData", + }, + } as IProviderConfiguration, + }, + }, + }, + }, + }, + { + id: "kpiWidgetId2", + type: "kpi", + pizzagna: { + [PizzagnaLayer.Configuration]: { + header: { + properties: { + title: "Sync Broker Applied for ALL Widgets", + subtitle: + "Now the values of label, units, and value are being synced", + }, + }, + tiles: { + properties: { + nodes: ["kpi1", "kpi2", "kpi3"], + }, + }, + kpi1: { + id: "kpi1", + componentType: KpiComponent.lateLoadKey, + properties: { + widgetData: { + units: \`out of 5 Stars\`, + label: \`Average Rating\`, + backgroundColor: "lightpink", + }, + }, + providers: { + [WellKnownProviders.DataSource]: { + providerId: AverageRatingKpiDataSource.providerId, + } as IProviderConfiguration, + [WellKnownProviders.Adapter]: { + providerId: NOVA_KPI_DATASOURCE_ADAPTER, + properties: { + componentId: "kpi1", + propertyPath: "widgetData", + }, + } as IProviderConfiguration, + }, + }, + kpi2: { + id: "kpi2", + componentType: KpiComponent.lateLoadKey, + properties: { + widgetData: { + label: \`Another label which might be a pretty long one\`, + units: \`Which comes from somewhere\`, + backgroundColor: "skyblue", + }, + }, + providers: { + [WellKnownProviders.DataSource]: { + providerId: RatingsCountKpiDataSource.providerId, + } as IProviderConfiguration, + [WellKnownProviders.Adapter]: { + providerId: NOVA_KPI_DATASOURCE_ADAPTER, + properties: { + componentId: "kpi2", + propertyPath: "widgetData", + }, + } as IProviderConfiguration, + }, + }, + kpi3: { + id: "kpi3", + componentType: KpiComponent.lateLoadKey, + properties: { + widgetData: { + label: \`Random\`, + units: \`Data\`, + }, + }, + providers: { + [WellKnownProviders.DataSource]: { + providerId: MockKpiDataSource.providerId, + } as IProviderConfiguration, + [WellKnownProviders.Adapter]: { + providerId: NOVA_KPI_DATASOURCE_ADAPTER, + properties: { + componentId: "kpi3", + propertyPath: "widgetData", + }, + } as IProviderConfiguration, + }, + }, + }, + }, + }, + { + id: "kpiWidgetId3", + type: "kpi", + pizzagna: { + [PizzagnaLayer.Configuration]: { + header: { + properties: { + title: "Here We Sync Only Labels and Units", + subtitle: + "Now only the label, and units are being synced", + }, + }, + tiles: { + properties: { + nodes: ["kpi1", "kpi2", "kpi3"], + }, + providers: { + // This is where and how you can override the globally set broker config + kpiScaleSyncBroker: { + providerId: NOVA_KPI_SCALE_SYNC_BROKER, + properties: { + scaleSyncConfig: [ + { id: "label" }, + { id: "units" }, + ], + }, + }, + }, + }, + kpi1: { + id: "kpi1", + componentType: KpiComponent.lateLoadKey, + properties: { + widgetData: { + units: \`out of 5 Stars\`, + label: \`Average Rating\`, + backgroundColor: "lightpink", + }, + }, + providers: { + [WellKnownProviders.DataSource]: { + providerId: AverageRatingKpiDataSource.providerId, + } as IProviderConfiguration, + [WellKnownProviders.Adapter]: { + providerId: NOVA_KPI_DATASOURCE_ADAPTER, + properties: { + componentId: "kpi1", + propertyPath: "widgetData", + }, + } as IProviderConfiguration, + }, + }, + kpi2: { + id: "kpi2", + componentType: KpiComponent.lateLoadKey, + properties: { + widgetData: { + label: \`Another label which might be a pretty long one\`, + units: \`Which comes from somewhere\`, + backgroundColor: "skyblue", + }, + }, + providers: { + [WellKnownProviders.DataSource]: { + providerId: RatingsCountKpiDataSource.providerId, + } as IProviderConfiguration, + [WellKnownProviders.Adapter]: { + providerId: NOVA_KPI_DATASOURCE_ADAPTER, + properties: { + componentId: "kpi2", + propertyPath: "widgetData", + }, + } as IProviderConfiguration, + }, + }, + kpi3: { + id: "kpi3", + componentType: KpiComponent.lateLoadKey, + properties: { + widgetData: { + label: \`Random\`, + units: \`Data\`, + }, + }, + providers: { + [WellKnownProviders.DataSource]: { + providerId: MockKpiDataSource.providerId, + } as IProviderConfiguration, + [WellKnownProviders.Adapter]: { + providerId: NOVA_KPI_DATASOURCE_ADAPTER, + properties: { + componentId: "kpi3", + propertyPath: "widgetData", + }, + } as IProviderConfiguration, + }, + }, + }, + }, + }, +]; +`, + "widget-types/kpi/kpi-widget/kpi-widget-example.component.ts": `// © 2022 SolarWinds Worldwide, LLC. All rights reserved. +// +// Permission is hereby granted, free of charge, to any person obtaining a copy +// of this software and associated documentation files (the "Software"), to +// deal in the Software without restriction, including without limitation the +// rights to use, copy, modify, merge, publish, distribute, sublicense, and/or +// sell copies of the Software, and to permit persons to whom the Software is +// furnished to do so, subject to the following conditions: +// +// The above copyright notice and this permission notice shall be included in +// all copies or substantial portions of the Software. +// +// THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR +// IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, +// FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE +// AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER +// LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, +// OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN +// THE SOFTWARE. + +import { HttpClient, HttpErrorResponse } from "@angular/common/http"; +import { Component, Injectable, OnDestroy, OnInit } from "@angular/core"; +import { GridsterConfig, GridsterItem } from "angular-gridster2"; +import { BehaviorSubject } from "rxjs"; +import { finalize } from "rxjs/operators"; + +import { DataSourceService, IFilteringOutputs } from "@nova-ui/bits"; +import { + DATA_SOURCE, + DEFAULT_PIZZAGNA_ROOT, + IDashboard, + IKpiData, + IProviderConfiguration, + IRefresherProperties, + IWidget, + IWidgets, + KpiComponent, + NOVA_KPI_DATASOURCE_ADAPTER, + PizzagnaLayer, + ProviderRegistryService, + WellKnownPathKey, + WellKnownProviders, + WidgetTypesService, +} from "@nova-ui/dashboards"; + +/** + * A simple KPI data source to retrieve the average rating of Harry Potter and the Sorcerer's Stone (book) via googleapis + */ +@Injectable() +export class AverageRatingKpiDataSource + extends DataSourceService + implements OnDestroy +{ + // This is the ID we'll use to identify the provider + public static providerId = "AverageRatingKpiDataSource"; + + // Use this subject to communicate the data source's busy state + public busy = new BehaviorSubject(false); + + constructor(private http: HttpClient) { + super(); + } + + // In this example, getFilteredData is invoked every 10 minutes (Take a look at the refresher + // provider definition in the widget configuration below to see how the interval is set) + public async getFilteredData(): Promise { + this.busy.next(true); + return new Promise((resolve) => { + // *** Make a resource request to an external API (if needed) + this.http + .get("https://www.googleapis.com/books/v1/volumes/5MQFrgEACAAJ") + .pipe(finalize(() => this.busy.next(false))) + .subscribe({ + next: (data: any) => { + resolve({ + result: { + value: data.volumeInfo.averageRating, + }, + }); + }, + error: (error: HttpErrorResponse) => { + resolve({ + result: null, + error: { + type: error.status, + }, + }); + }, + }); + }); + } + + public ngOnDestroy(): void { + this.outputsSubject.complete(); + } +} + +/** + * A component that instantiates the dashboard + */ +@Component({ + selector: "kpi-widget-example", + templateUrl: "./kpi-widget-example.component.html", + styleUrls: ["./kpi-widget-example.component.less"], + standalone: false, +}) +export class KpiWidgetExampleComponent implements OnInit { + // This variable will hold all the data needed to define the layout and behavior of the widgets. + // Pass this to the dashboard component's dashboard input in the template. + public dashboard: IDashboard; + + // Angular gridster requires a configuration object even if it's empty. + // Pass this to the dashboard component's gridsterConfig input in the template. + public gridsterConfig: GridsterConfig = {}; + + // Boolean passed as an input to the dashboard. When true, widgets can be moved, resized, removed, or edited + public editMode: boolean = false; + + constructor( + // WidgetTypesService provides the widget's necessary structure information + private widgetTypesService: WidgetTypesService, + + // In general, the ProviderRegistryService is used for making entities available for injection into dynamically loaded components. + private providerRegistry: ProviderRegistryService + ) {} + + public ngOnInit(): void { + // Grabbing the widget's default template which will be needed as a parameter for setNode + const widgetTemplate = this.widgetTypesService.getWidgetType("kpi", 1); + // Registering our data sources as dropdown options in the widget editor/configurator + // Note: This could also be done in the parent module's constructor so that + // multiple dashboards could have access to the same widget template modification. + this.widgetTypesService.setNode( + // This is the template we grabbed above with getWidgetType + widgetTemplate, + // We are setting the editor/configurator part of the widget template + "configurator", + // This indicates which node you are changing and we want to change + // the data source providers available for selection in the editor. + WellKnownPathKey.DataSourceProviders, + // We are setting the data sources available for selection in the editor + [AverageRatingKpiDataSource.providerId] + ); + + // Registering the data source for injection into the KPI tile. + // Note: Each tile of a KPI widget is assigned its own instance of the data source + this.providerRegistry.setProviders({ + [AverageRatingKpiDataSource.providerId]: { + provide: DATA_SOURCE, + useClass: AverageRatingKpiDataSource, + // Any dependencies that need to be injected into the provider must be listed here + deps: [HttpClient], + }, + }); + + this.initializeDashboard(); + } + + public initializeDashboard(): void { + // We're using a static configuration object for this example, but this is where + // the widget's configuration could potentially be populated from a database + const kpiWidget = widgetConfig; + const widgetIndex: IWidgets = { + // Complete the KPI widget with information coming from its type definition + [kpiWidget.id]: + this.widgetTypesService.mergeWithWidgetType(kpiWidget), + }; + + // Setting the widget dimensions and position (this is for gridster) + const positions: Record = { + [kpiWidget.id]: { + cols: 4, + rows: 6, + y: 0, + x: 0, + }, + }; + + // Finally, assigning the variables we created above to the dashboard + this.dashboard = { + positions, + widgets: widgetIndex, + }; + } +} + +const widgetConfig: IWidget = { + id: "kpiWidgetId", + type: "kpi", + pizzagna: { + [PizzagnaLayer.Configuration]: { + [DEFAULT_PIZZAGNA_ROOT]: { + providers: { + [WellKnownProviders.Refresher]: { + properties: { + // Configuring the refresher interval so that our data source is invoked every ten minutes + interval: 60 * 10, + enabled: true, + } as IRefresherProperties, + } as Partial, + }, + }, + header: { + properties: { + title: "Harry Potter and the Sorcerer's Stone", + subtitle: "By J. K. Rowling", + }, + }, + tiles: { + properties: { + nodes: ["kpi1"], + }, + }, + kpi1: { + id: "kpi1", + componentType: KpiComponent.lateLoadKey, + properties: { + widgetData: { + units: \`out of 5 Stars\`, + label: \`Average Rating\`, + }, + }, + providers: { + [WellKnownProviders.DataSource]: { + // Setting the data source providerId for the tile with id "kpi1" + providerId: AverageRatingKpiDataSource.providerId, + } as IProviderConfiguration, + [WellKnownProviders.Adapter]: { + providerId: NOVA_KPI_DATASOURCE_ADAPTER, + properties: { + componentId: "kpi1", + propertyPath: "widgetData", + }, + } as IProviderConfiguration, + }, + }, + }, + }, +}; +`, + "widget-types/kpi/kpi-widget-background-color/kpi-widget-background-color-example.component.ts": `// © 2022 SolarWinds Worldwide, LLC. All rights reserved. +// +// Permission is hereby granted, free of charge, to any person obtaining a copy +// of this software and associated documentation files (the "Software"), to +// deal in the Software without restriction, including without limitation the +// rights to use, copy, modify, merge, publish, distribute, sublicense, and/or +// sell copies of the Software, and to permit persons to whom the Software is +// furnished to do so, subject to the following conditions: +// +// The above copyright notice and this permission notice shall be included in +// all copies or substantial portions of the Software. +// +// THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR +// IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, +// FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE +// AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER +// LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, +// OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN +// THE SOFTWARE. + +import { HttpClient, HttpErrorResponse } from "@angular/common/http"; +import { + ChangeDetectorRef, + Component, + Injectable, + OnDestroy, + OnInit, +} from "@angular/core"; +import { GridsterConfig, GridsterItem } from "angular-gridster2"; +import { BehaviorSubject } from "rxjs"; +import { finalize } from "rxjs/operators"; + +import { DataSourceService, IFilteringOutputs } from "@nova-ui/bits"; +import { + DATA_SOURCE, + DEFAULT_KPI_BACKGROUND_COLORS, + IDashboard, + IKpiColorRules, + IKpiData, + IProviderConfiguration, + IWidget, + IWidgets, + KpiComponent, + NOVA_KPI_COLOR_PRIORITIZER, + NOVA_KPI_DATASOURCE_ADAPTER, + PizzagnaLayer, + ProviderRegistryService, + WellKnownPathKey, + WellKnownProviders, + WidgetTypesService, +} from "@nova-ui/dashboards"; + +/** + * A simple KPI data source to retrieve the average rating of Harry Potter and the Sorcerer's Stone (book) via googleapis + */ +@Injectable() +export class AverageRatingKpiDataSource + extends DataSourceService + implements OnDestroy +{ + public static providerId = "AverageRatingKpiDataSource"; + public busy = new BehaviorSubject(false); + + constructor(private http: HttpClient) { + super(); + } + + public async getFilteredData(): Promise { + this.busy.next(true); + return new Promise((resolve) => { + // *** Make a resource request to an external API (if needed) + this.http + .get("https://www.googleapis.com/books/v1/volumes/5MQFrgEACAAJ") + .pipe(finalize(() => this.busy.next(false))) + .subscribe({ + next: (data: any) => { + resolve({ + result: { + value: data.volumeInfo.averageRating, + // setting the color on the dataSource "Sea Green", + // uncomment to get the background color update from the "Data" layer + // backgroundColor: "var(--nui-color-chart-three)", + }, + }); + }, + error: (error: HttpErrorResponse) => { + resolve({ + result: null, + error: { + type: error.status, + }, + }); + }, + }); + }); + } + + public ngOnDestroy(): void { + this.outputsSubject.complete(); + } +} + +/** + * A component that instantiates the dashboard + */ +@Component({ + selector: "kpi-widget-background-color-example", + templateUrl: "./kpi-widget-background-color-example.component.html", + styleUrls: ["./kpi-widget-background-color-example.component.less"], + standalone: false, +}) +export class KpiWidgetBackgroundColorExampleComponent implements OnInit { + public dashboard: IDashboard | undefined; + public gridsterConfig: GridsterConfig = {}; + public editMode: boolean = false; + + constructor( + private widgetTypesService: WidgetTypesService, + private providerRegistry: ProviderRegistryService, + private changeDetectorRef: ChangeDetectorRef + ) {} + + public ngOnInit(): void { + this.setupDashboard(); + + // KPI tile default color setup + this.setupDefaultColorStructure(); + + // Sets the custom pallette to the 'Description' section + this.setupCustomPalletteDescription(); + + // Sets the custom pallette to the 'Background color rules' section + this.setupCustomPalletteRules(); + + this.initializeDashboard(); + } + + /** Used for restoring widgets state */ + public reInitializeDashboard(): void { + // destroys the components and their providers so the dashboard can re init data + this.dashboard = undefined; + this.changeDetectorRef.detectChanges(); + + this.initializeDashboard(); + } + + private setupCustomPalletteDescription() { + const kpiWidgetTemplate = this.widgetTypesService.getWidgetType( + "kpi", + 1 + ); + this.widgetTypesService.setNode( + kpiWidgetTemplate, + "configurator", + WellKnownPathKey.TileDescriptionBackgroundColors, + [ + { color: "var(--nui-color-chart-one)", label: "Blue" }, + { + color: "var(--nui-color-chart-one-light)", + label: "Blue Light", + }, + { + color: "var(--nui-color-chart-one-dark)", + label: "Blue Dark", + }, + ] + ); + } + + private setupCustomPalletteRules() { + const kpiWidgetTemplate = this.widgetTypesService.getWidgetType( + "kpi", + 1 + ); + this.widgetTypesService.setNode( + kpiWidgetTemplate, + "configurator", + WellKnownPathKey.TileBackgroundColorRulesBackgroundColors, + [ + { color: "red", label: "Native Red" }, + ...DEFAULT_KPI_BACKGROUND_COLORS, + ] + ); + } + + private setupDefaultColorStructure() { + const widgetTemplate = this.widgetTypesService.getWidgetType("kpi", 1); + this.widgetTypesService.setNode( + widgetTemplate, + "widget", + "tiles.properties.template.properties.widgetData.backgroundColor", + "red" + ); + } + + private setupDashboard() { + const widgetTemplate = this.widgetTypesService.getWidgetType("kpi", 1); + this.widgetTypesService.setNode( + widgetTemplate, + "configurator", + WellKnownPathKey.DataSourceProviders, + [AverageRatingKpiDataSource.providerId] + ); + + this.providerRegistry.setProviders({ + [AverageRatingKpiDataSource.providerId]: { + provide: DATA_SOURCE, + useClass: AverageRatingKpiDataSource, + deps: [HttpClient], + }, + }); + } + + private initializeDashboard(): void { + const kpiWidget = widgetConfig; + const widgetIndex: IWidgets = { + [kpiWidget.id]: + this.widgetTypesService.mergeWithWidgetType(kpiWidget), + }; + + const positions: Record = { + [kpiWidget.id]: { + cols: 4, + rows: 6, + y: 0, + x: 0, + }, + }; + + this.dashboard = { + positions, + widgets: widgetIndex, + }; + } +} + +const widgetConfig: IWidget = { + id: "kpiWidgetId", + type: "kpi", + pizzagna: { + [PizzagnaLayer.Configuration]: { + header: { + properties: { + title: "Harry Potter and the Sorcerer's Stone", + subtitle: "By J. K. Rowling", + }, + }, + tiles: { + properties: { + nodes: ["kpi1"], + }, + }, + kpi1: { + id: "kpi1", + componentType: KpiComponent.lateLoadKey, + properties: { + widgetData: { + units: \`out of 5 Stars\`, + label: \`Average Rating\`, + // Configuration color "Blue" + backgroundColor: "var(--nui-color-chart-one)", + }, + }, + providers: { + [WellKnownProviders.DataSource]: { + providerId: AverageRatingKpiDataSource.providerId, + } as IProviderConfiguration, + [WellKnownProviders.Adapter]: { + providerId: NOVA_KPI_DATASOURCE_ADAPTER, + properties: { + componentId: "kpi1", + propertyPath: "widgetData", + }, + } as IProviderConfiguration, + [WellKnownProviders.KpiColorPrioritizer]: { + providerId: NOVA_KPI_COLOR_PRIORITIZER, + properties: { + // Color Prioritizer Rules + // settings rules - if the value is more than "2" display "Violet" color + rules: [ + { + comparisonType: ">", + value: 2, + color: "var(--nui-color-chart-four)", + }, + ] as IKpiColorRules[], + }, + } as IProviderConfiguration, + }, + }, + }, + }, +}; +`, + "widget-types/kpi/kpi-widget-background-color-docs.component.ts": `// © 2022 SolarWinds Worldwide, LLC. All rights reserved. +// +// Permission is hereby granted, free of charge, to any person obtaining a copy +// of this software and associated documentation files (the "Software"), to +// deal in the Software without restriction, including without limitation the +// rights to use, copy, modify, merge, publish, distribute, sublicense, and/or +// sell copies of the Software, and to permit persons to whom the Software is +// furnished to do so, subject to the following conditions: +// +// The above copyright notice and this permission notice shall be included in +// all copies or substantial portions of the Software. +// +// THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR +// IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, +// FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE +// AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER +// LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, +// OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN +// THE SOFTWARE. + +import { Component } from "@angular/core"; + +@Component({ + selector: "nui-kpi-background-color-docs", + templateUrl: "./kpi-widget-background-color-docs.component.html", + standalone: false, +}) +export class KpiWidgetBackgroundColorDocsComponent { + public comparatorsRegistryCode = \` + this.comparatorsRegistry.registerComparators({ + "!=": { + comparatorFn: (actual: any, reference: any) => actual != reference, + label: "Not equal", + }, + }); + \`; +} +`, + "widget-types/kpi/kpi-widget-interactive/kpi-widget-interactive-example.component.ts": `// © 2022 SolarWinds Worldwide, LLC. All rights reserved. +// +// Permission is hereby granted, free of charge, to any person obtaining a copy +// of this software and associated documentation files (the "Software"), to +// deal in the Software without restriction, including without limitation the +// rights to use, copy, modify, merge, publish, distribute, sublicense, and/or +// sell copies of the Software, and to permit persons to whom the Software is +// furnished to do so, subject to the following conditions: +// +// The above copyright notice and this permission notice shall be included in +// all copies or substantial portions of the Software. +// +// THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR +// IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, +// FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE +// AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER +// LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, +// OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN +// THE SOFTWARE. + +import { HttpClient, HttpErrorResponse } from "@angular/common/http"; +import { Component, Injectable, OnDestroy, OnInit } from "@angular/core"; +import { GridsterConfig, GridsterItem } from "angular-gridster2"; +import { BehaviorSubject } from "rxjs"; +import { finalize } from "rxjs/operators"; + +import { DataSourceService, IFilteringOutputs } from "@nova-ui/bits"; +import { + DATA_SOURCE, + IDashboard, + IKpiData, + IProviderConfiguration, + IWidget, + IWidgets, + KpiComponent, + NOVA_KPI_DATASOURCE_ADAPTER, + NOVA_URL_INTERACTION_HANDLER, + PizzagnaLayer, + ProviderRegistryService, + WellKnownPathKey, + WellKnownProviders, + WidgetTypesService, +} from "@nova-ui/dashboards"; + +/** + * A simple KPI data source to retrieve the average rating of Harry Potter and the Sorcerer's Stone (book) via googleapis + */ +@Injectable() +export class BookRatingDataSource + extends DataSourceService + implements OnDestroy +{ + // This is the ID we'll use to identify the provider + public static providerId = "BookRatingDataSource"; + + // Use this subject to communicate the data source's busy state + public busy = new BehaviorSubject(false); + + constructor(private http: HttpClient) { + super(); + } + + // In this example, getFilteredData is invoked every 10 minutes (Take a look at the refresher + // provider definition in the widget configuration below to see how the interval is set) + public async getFilteredData(): Promise { + this.busy.next(true); + return new Promise((resolve) => { + // *** Make a resource request to an external API (if needed) + this.http + .get("https://www.googleapis.com/books/v1/volumes/zpvysRGsBlwC") + .pipe(finalize(() => this.busy.next(false))) + .subscribe({ + next: (data: any) => { + resolve({ + result: { + value: data.volumeInfo.averageRating, + link: data.volumeInfo.infoLink, + }, + }); + }, + error: (error: HttpErrorResponse) => { + resolve({ + result: null, + error: { + type: error.status, + }, + }); + }, + }); + }); + } + + public ngOnDestroy(): void { + this.outputsSubject.complete(); + } +} + +/** + * A component that instantiates the dashboard + */ +@Component({ + selector: "kpi-widget-interactive-example", + templateUrl: "./kpi-widget-interactive-example.component.html", + styleUrls: ["./kpi-widget-interactive-example.component.less"], + standalone: false, +}) +export class KpiWidgetInteractiveExampleComponent implements OnInit { + // This variable will hold all the data needed to define the layout and behavior of the widgets. + // Pass this to the dashboard component's dashboard input in the template. + public dashboard: IDashboard; + + // Angular gridster requires a configuration object even if it's empty. + // Pass this to the dashboard component's gridsterConfig input in the template. + public gridsterConfig: GridsterConfig = {}; + + // Boolean passed as an input to the dashboard. When true, widgets can be moved, resized, removed, or edited + public editMode: boolean = false; + + constructor( + // WidgetTypesService provides the widget's necessary structure information + private widgetTypesService: WidgetTypesService, + + // In general, the ProviderRegistryService is used for making entities available for injection into dynamically loaded components. + private providerRegistry: ProviderRegistryService + ) {} + + public ngOnInit(): void { + // Grabbing the widget's default template which will be needed as a parameter for setNode + const widgetTemplate = this.widgetTypesService.getWidgetType("kpi", 1); + // Registering our data sources as dropdown options in the widget editor/configurator + // Note: This could also be done in the parent module's constructor so that + // multiple dashboards could have access to the same widget template modification. + this.widgetTypesService.setNode( + // This is the template we grabbed above with getWidgetType + widgetTemplate, + // We are setting the editor/configurator part of the widget template + "configurator", + // This indicates which node you are changing and we want to change + // the data source providers available for selection in the editor. + WellKnownPathKey.DataSourceProviders, + // We are setting the data sources available for selection in the editor + [BookRatingDataSource.providerId] + ); + + // Registering the data source for injection into the KPI tile. + // Note: Each tile of a KPI widget is assigned its own instance of the data source + this.providerRegistry.setProviders({ + [BookRatingDataSource.providerId]: { + provide: DATA_SOURCE, + useClass: BookRatingDataSource, + // Any dependencies that need to be injected into the provider must be listed here + deps: [HttpClient], + }, + }); + + this.initializeDashboard(); + } + + public initializeDashboard(): void { + // We're using a static configuration object for this example, but this is where + // the widget's configuration could potentially be populated from a database + const kpiWidget = widgetConfig; + const widgetIndex: IWidgets = { + // Complete the KPI widget with information coming from its type definition + [kpiWidget.id]: + this.widgetTypesService.mergeWithWidgetType(kpiWidget), + }; + + // Setting the widget dimensions and position (this is for gridster) + const positions: Record = { + [kpiWidget.id]: { + cols: 4, + rows: 6, + y: 0, + x: 0, + }, + }; + + // Finally, assigning the variables we created above to the dashboard + this.dashboard = { + positions, + widgets: widgetIndex, + }; + } +} + +const widgetConfig: IWidget = { + id: "kpiWidgetId", + type: "kpi", + pizzagna: { + [PizzagnaLayer.Configuration]: { + header: { + properties: { + title: "Harry Potter and the Order of the Phoenix", + subtitle: "By: J. K. Rowling", + }, + }, + tiles: { + providers: { + interaction: { + // Configuring the UrlInteractionHandler for interactions on the tiles + providerId: NOVA_URL_INTERACTION_HANDLER, + properties: { + // the 'url' property tells the handler what link to use when interaction occurs on the series + url: "\${data.link}", + }, + }, + }, + properties: { + nodes: ["kpi1"], + }, + }, + kpi1: { + id: "kpi1", + componentType: KpiComponent.lateLoadKey, + properties: { + widgetData: { + units: \`out of 5 stars\`, + label: \`Average Rating\`, + value: 0, + // the link property that is passed to the UrlInteractionHandler when the title is clicked + // this will be updated in BookRatingDataSource's 'getFilteredData' call. + link: "http://www.google.com", + }, + }, + providers: { + [WellKnownProviders.DataSource]: { + // Setting the data source providerId for the tile with id "kpi1" + providerId: BookRatingDataSource.providerId, + } as IProviderConfiguration, + [WellKnownProviders.Adapter]: { + providerId: NOVA_KPI_DATASOURCE_ADAPTER, + properties: { + componentId: "kpi1", + propertyPath: "widgetData", + }, + } as IProviderConfiguration, + }, + }, + }, + }, +}; +`, + "widget-types/proportional/models.ts": `export interface IMockBeerReview { + id: string; + name: string; + data: number[]; + icon: string; + link?: string; + value: string; + color?: string; +} +`, + "widget-types/proportional/proportional-docs.component.ts": `// © 2022 SolarWinds Worldwide, LLC. All rights reserved. +// +// Permission is hereby granted, free of charge, to any person obtaining a copy +// of this software and associated documentation files (the "Software"), to +// deal in the Software without restriction, including without limitation the +// rights to use, copy, modify, merge, publish, distribute, sublicense, and/or +// sell copies of the Software, and to permit persons to whom the Software is +// furnished to do so, subject to the following conditions: +// +// The above copyright notice and this permission notice shall be included in +// all copies or substantial portions of the Software. +// +// THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR +// IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, +// FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE +// AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER +// LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, +// OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN +// THE SOFTWARE. + +import { Component, OnInit } from "@angular/core"; + +import { mapContentFile } from "../../../../demo-files-factory"; + +@Component({ + selector: "nui-proportional-docs", + templateUrl: "./proportional-docs.component.html", + standalone: false, +}) +export class ProportionalDocsComponent implements OnInit { + public proportionalWidgetFileText = ""; + public proportionalConfiguratorFileText = ""; + + public async ngOnInit(): Promise { + this.proportionalWidgetFileText = await import( + "./../../../../../../src/lib/widget-types/proportional/proportional-widget" + ).then(mapContentFile); + this.proportionalConfiguratorFileText = await import( + "./../../../../../../src/lib/widget-types/proportional/proportional-configurator" + ).then(mapContentFile); + } +} +`, + "widget-types/proportional/proportional-docs.module.ts": `// © 2022 SolarWinds Worldwide, LLC. All rights reserved. +// +// Permission is hereby granted, free of charge, to any person obtaining a copy +// of this software and associated documentation files (the "Software"), to +// deal in the Software without restriction, including without limitation the +// rights to use, copy, modify, merge, publish, distribute, sublicense, and/or +// sell copies of the Software, and to permit persons to whom the Software is +// furnished to do so, subject to the following conditions: +// +// The above copyright notice and this permission notice shall be included in +// all copies or substantial portions of the Software. +// +// THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR +// IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, +// FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE +// AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER +// LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, +// OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN +// THE SOFTWARE. + +import { NgModule } from "@angular/core"; +import { RouterModule, Routes } from "@angular/router"; + +import { + NuiButtonModule, + NuiDocsModule, + NuiMessageModule, + NuiSwitchModule, + DEMO_PATH_TOKEN, +} from "@nova-ui/bits"; +import { NuiDashboardsModule } from "@nova-ui/dashboards"; + +import { ProportionalDocsComponent } from "./proportional-docs.component"; +import { ProportionalDonutContentDocsComponent } from "./proportional-donut-content-docs.component"; +import { ProportionalWidgetDonutContentFormattersExampleComponent } from "./proportional-donut-content-formatters/proportional-donut-content-formatters-example.component"; +import { ProportionalWidgetExampleComponent } from "./proportional-widget/proportional-widget-example.component"; +import { ProportionalWidgetInteractiveExampleComponent } from "./proportional-widget-interactive/proportional-widget-interactive-example.component"; +import { getDemoFiles } from "../../../../demo-files-factory"; + +const routes: Routes = [ + { + path: "", + component: ProportionalDocsComponent, + data: { + srlc: { + hideIndicator: true, + }, + showThemeSwitcher: true, + }, + }, + { + path: "example", + component: ProportionalWidgetExampleComponent, + data: { + srlc: { + hideIndicator: true, + }, + }, + }, + { + path: "donut-content-formatters", + component: ProportionalDonutContentDocsComponent, + data: { + srlc: { + hideIndicator: true, + }, + }, + }, + { + path: "donut-content-formatters-example", + component: ProportionalWidgetDonutContentFormattersExampleComponent, + data: { + srlc: { + hideIndicator: true, + }, + }, + }, + { + path: "proportional-widget-interactive-example", + component: ProportionalWidgetInteractiveExampleComponent, + data: { + srlc: { + hideIndicator: true, + }, + }, + }, +]; + +@NgModule({ + imports: [ + RouterModule.forChild(routes), + NuiButtonModule, + NuiDocsModule, + NuiDashboardsModule, + NuiMessageModule, + NuiSwitchModule, + ], + declarations: [ + ProportionalDocsComponent, + ProportionalWidgetExampleComponent, + ProportionalWidgetInteractiveExampleComponent, + ProportionalWidgetDonutContentFormattersExampleComponent, + ProportionalDonutContentDocsComponent, + ], + providers: [ + { + provide: DEMO_PATH_TOKEN, + useValue: getDemoFiles("proportional"), + }, + ], +}) +export default class ProportionalDocsModule {} +`, + "widget-types/proportional/proportional-donut-content-docs.component.ts": `// © 2022 SolarWinds Worldwide, LLC. All rights reserved. +// +// Permission is hereby granted, free of charge, to any person obtaining a copy +// of this software and associated documentation files (the "Software"), to +// deal in the Software without restriction, including without limitation the +// rights to use, copy, modify, merge, publish, distribute, sublicense, and/or +// sell copies of the Software, and to permit persons to whom the Software is +// furnished to do so, subject to the following conditions: +// +// The above copyright notice and this permission notice shall be included in +// all copies or substantial portions of the Software. +// +// THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR +// IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, +// FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE +// AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER +// LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, +// OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN +// THE SOFTWARE. + +import { Component } from "@angular/core"; + +@Component({ + selector: "nui-proportional-donut-content-docs", + templateUrl: "./proportional-donut-content-docs.component.html", + standalone: false, +}) +export class ProportionalDonutContentDocsComponent { + public dataSourceDataFieldsConfig = \` +public dataFieldsConfig: IProportionalDataFieldsConfig = { + dataFields$: new BehaviorSubject(this.dataFields), + chartSeriesDataFields$: new BehaviorSubject(this.chartSeriesDataFields), +}; + \`; + + public widgetConfigSlice = \` +"properties": { + "configuration": { + "chartOptions": { + donutContentConfig: { + formatter: { + componentType: SiUnitsFormatterComponent.lateLoadKey, + }, + aggregator: { + aggregatorType: sumAggregator.aggregatorType, + }, + }, + } + } +} + + \`; +} +`, + "widget-types/proportional/proportional-donut-content-formatters/proportional-donut-content-formatters-example.component.ts": `// © 2022 SolarWinds Worldwide, LLC. All rights reserved. +// +// Permission is hereby granted, free of charge, to any person obtaining a copy +// of this software and associated documentation files (the "Software"), to +// deal in the Software without restriction, including without limitation the +// rights to use, copy, modify, merge, publish, distribute, sublicense, and/or +// sell copies of the Software, and to permit persons to whom the Software is +// furnished to do so, subject to the following conditions: +// +// The above copyright notice and this permission notice shall be included in +// all copies or substantial portions of the Software. +// +// THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR +// IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, +// FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE +// AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER +// LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, +// OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN +// THE SOFTWARE. + +import { + ChangeDetectorRef, + Component, + Injectable, + OnDestroy, + OnInit, +} from "@angular/core"; +import { GridsterConfig, GridsterItem } from "angular-gridster2"; +import { BehaviorSubject } from "rxjs"; + +import { + DataSourceService, + IDataField, + IDataSource, + IFilteringOutputs, +} from "@nova-ui/bits"; +import { IAccessors, IChartAssistSeries } from "@nova-ui/charts"; +import { + DATA_SOURCE, + DEFAULT_LEGEND_FORMATTERS, + DEFAULT_PIZZAGNA_ROOT, + DEFAULT_PROPORTIONAL_CONTENT_AGGREGATORS, + DEFAULT_PROPORTIONAL_CONTENT_FORMATTERS, + DONUT_CONTENT_CONFIGURATION_SLICE, + IDashboard, + IDonutContentConfig, + IProportionalDataFieldsConfig, + IProportionalWidgetChartOptions, + IProportionalWidgetConfig, + IProviderConfiguration, + IWidget, + IWidgets, + LegendPlacement, + PizzagnaLayer, + ProportionalContentAggregatorsRegistryService, + ProportionalDonutContentFormattersRegistryService, + ProportionalLegendFormattersRegistryService, + ProportionalWidgetChartTypes, + ProviderRegistryService, + SiUnitsFormatterComponent, + sumAggregator, + WellKnownPathKey, + WellKnownProviders, + WidgetTypesService, +} from "@nova-ui/dashboards"; + +import { IMockBeerReview } from "../models"; + +/** + * A simple proportional data source to retrieve beer review counts by city + */ +@Injectable() +export class BeerReviewCountsByCityMockDataSource + extends DataSourceService> + implements IDataSource>, OnDestroy +{ + public static providerId = "BeerReviewCountsByCityMockDataSource"; + public busy = new BehaviorSubject(false); + + protected dataFields: IDataField[] = [ + { + id: "Brno", + label: "Brno", + // @ts-ignore + dataType: null, + }, + { + id: "kyiv", + label: "Kyiv", + // @ts-ignore + dataType: null, + }, + { + id: "austin", + label: "Austin", + // @ts-ignore + dataType: null, + }, + { + id: "lisbon", + label: "Lisbon", + // @ts-ignore + dataType: null, + }, + { + id: "sydney", + label: "Sydney", + // @ts-ignore + dataType: null, + }, + { + id: "nur-sultan", + label: "Nur-Sultan", + // @ts-ignore + dataType: null, + }, + ]; + protected chartSeriesDataFields: IDataField[] = [ + // default field in the chart series that is used for the aggregation + { + id: "data[0]", + label: "data", + // @ts-ignore + dataType: null, + }, + // any custom field in the chart series that is used for the aggregation + { + id: "customDonutContent", + label: "Custom Donut Content", + // @ts-ignore + dataType: null, + }, + ]; + + /** + * DataSource needs to implement the "IDataFieldsConfig" for this scenario. + * + * It's necessary to provide the "chartSeriesDataFields", + * that's why proportional widget dataSource has it's own interface for that - IProportionalDataFieldsConfig. + * + * dataFields$ - stands for possible series fields + * chartSeriesDataFields$ - stands for the fields IN the series + * + * see declaration of "dataFields" and "chartSeriesDataFields" for the example. + */ + public dataFieldsConfig: IProportionalDataFieldsConfig = { + dataFields$: new BehaviorSubject(this.dataFields), + chartSeriesDataFields$: new BehaviorSubject( + this.chartSeriesDataFields + ), + }; + + public async getFilteredData(): Promise { + this.busy.next(true); + return new Promise((resolve) => { + setTimeout(() => { + this.outputsSubject.next({ + result: getMockBeerReviewCountsByCity(), + }); + this.busy.next(false); + }, 300); + }); + } + + public ngOnDestroy(): void { + this.outputsSubject.complete(); + } +} + +/** + * A component that instantiates the dashboard + */ +@Component({ + selector: "proportional-widget-donut-content-formatters-example", + templateUrl: "./proportional-donut-content-formatters-example.component.html", + styleUrls: [ + "./proportional-donut-content-formatters-example.component.less", + ], + standalone: false, +}) +export class ProportionalWidgetDonutContentFormattersExampleComponent + implements OnInit +{ + public dashboard: IDashboard | undefined; + + // Angular gridster requires a configuration object even if it's empty. + // Pass this to the dashboard component's gridsterConfig input in the template. + public gridsterConfig: GridsterConfig = {}; + + // Boolean passed as an input to the dashboard. When true, widgets can be moved, resized, removed, or edited + public editMode: boolean = false; + + constructor( + // WidgetTypesService provides the widget's necessary structure information + private widgetTypesService: WidgetTypesService, + // In general, the ProviderRegistryService is used for making entities available for injection into dynamically loaded components. + private providerRegistry: ProviderRegistryService, + // registry for adding the formatter for donut content + contentFormattersRegistry: ProportionalDonutContentFormattersRegistryService, + // registry for adding the formatter for proportional legend + legendFormattersRegistry: ProportionalLegendFormattersRegistryService, + // registry for adding the aggregators for donut content + aggregatorRegistry: ProportionalContentAggregatorsRegistryService, + private changeDetectorRef: ChangeDetectorRef + ) { + // on the dashboard startup, it's necessary to add possible content formatters, legend formatters and content aggregators to the registry. + // using registry is a way for setting the available formatters. + legendFormattersRegistry.addItems(DEFAULT_LEGEND_FORMATTERS); + contentFormattersRegistry.addItems( + DEFAULT_PROPORTIONAL_CONTENT_FORMATTERS + ); + aggregatorRegistry.addItems(DEFAULT_PROPORTIONAL_CONTENT_AGGREGATORS); + } + + public ngOnInit(): void { + // Grabbing the widget's default template which will be needed as a parameter for setNode + const widgetTemplate = this.widgetTypesService.getWidgetType( + "proportional", + 1 + ); + + // Registering our data sources as dropdown options in the widget editor/configurator + // Note: This could also be done in the parent module's constructor so that + // multiple dashboards could have access to the same widget template modification. + this.widgetTypesService.setNode( + // This is the template we grabbed above with getWidgetType + widgetTemplate, + // We are setting the editor/configurator part of the widget template + "configurator", + // This indicates which node you are changing and we want to change + // the data source providers available for selection in the editor. + WellKnownPathKey.DataSourceProviders, + // We are setting the data sources available for selection in the editor + [BeerReviewCountsByCityMockDataSource.providerId] + ); + + // Setup of the configurator is done here + this.setupConfigurator(); + + // Registering the data source for injection into the Proportional widget. + this.providerRegistry.setProviders({ + [BeerReviewCountsByCityMockDataSource.providerId]: { + provide: DATA_SOURCE, + useClass: BeerReviewCountsByCityMockDataSource, + deps: [], + }, + }); + + this.initializeDashboard(); + } + + /** Used for restoring widgets state */ + public reInitializeDashboard(): void { + // destroys the components and their providers so the dashboard can re init data + this.dashboard = undefined; + this.changeDetectorRef.detectChanges(); + + this.initializeDashboard(); + } + + private initializeDashboard(): void { + // We're using a static configuration object for this example, but this is where + // the widget's configuration could potentially be populated from a database + const widgetIndex: IWidgets = { + // Complete the proportional widget with information coming from its type definition + [widgetConfig.id]: + this.widgetTypesService.mergeWithWidgetType(widgetConfig), + }; + + // Setting the widget dimensions and position (this is for gridster) + const positions: Record = { + [widgetConfig.id]: { + cols: 6, + rows: 6, + y: 0, + x: 0, + }, + }; + + // Finally, assigning the variables we created above to the dashboard + this.dashboard = { + positions, + widgets: widgetIndex, + }; + } + + /** + * Sets up the configurator sections for proportional donut + */ + private setupConfigurator() { + const widgetTemplate = this.widgetTypesService.getWidgetType( + "proportional", + 1 + ); + + // remove old "presentation", "chartOptionsEditor" and "donutContentConfiguration" sections from the configurator + delete widgetTemplate.configurator?.structure?.presentation; + delete widgetTemplate.configurator?.structure?.chartOptionsEditor; + delete widgetTemplate.configurator?.structure + ?.donutContentConfiguration; + + // add new "presentation" section + this.widgetTypesService.setNode( + widgetTemplate, + "configurator", + "presentation", + DONUT_CONTENT_CONFIGURATION_SLICE.presentation + ); + // add new "chartOptionsEditor" section + this.widgetTypesService.setNode( + widgetTemplate, + "configurator", + "chartOptionsEditor", + DONUT_CONTENT_CONFIGURATION_SLICE.chartOptionsEditor + ); + // add new "donutContentConfiguration" section + this.widgetTypesService.setNode( + widgetTemplate, + "configurator", + "donutContentConfiguration", + DONUT_CONTENT_CONFIGURATION_SLICE.donutContentConfiguration + ); + } +} + +const widgetConfig: IWidget = { + id: "proportionalWidgetId", + type: "proportional", + pizzagna: { + [PizzagnaLayer.Configuration]: { + [DEFAULT_PIZZAGNA_ROOT]: { + providers: {}, + }, + header: { + properties: { + title: "Beer Review Tally by City", + subtitle: "These People Love Beer", + }, + }, + chart: { + providers: { + [WellKnownProviders.DataSource]: { + // Setting the data source providerId for the chart + providerId: + BeerReviewCountsByCityMockDataSource.providerId, + } as IProviderConfiguration, + }, + properties: { + configuration: { + chartOptions: { + type: ProportionalWidgetChartTypes.DonutChart, + legendPlacement: LegendPlacement.Right, + // old configuration looks like this + // contentFormatter: { + // componentType: DonutContentSumFormatterComponent.lateLoadKey, + // }, + + // NEW configuration looks like this + donutContentConfig: { + formatter: { + componentType: + SiUnitsFormatterComponent.lateLoadKey, + }, + aggregator: { + aggregatorType: + sumAggregator.aggregatorType, + properties: { + // example of a default metric to be used for the percentage calculation + // activeMetricId: "austin", + }, + }, + } as IDonutContentConfig, + } as IProportionalWidgetChartOptions, + } as IProportionalWidgetConfig, + }, + }, + }, + }, +}; + +export function getMockBeerReviewCountsByCity(): IMockBeerReview[] { + return [ + { + id: "Brno", + name: "Brno", + data: [Math.round(Math.random() * 1000000)], + icon: "status_down", + link: "https://en.wikipedia.org/wiki/Brno", + value: "Brno", + customDonutContent: "Custom Brno", + }, + { + id: "kyiv", + name: "Kyiv", + data: [Math.round(Math.random() * 1000000)], + icon: "status_critical", + link: "https://en.wikipedia.org/wiki/Kyiv", + value: "Kyiv", + customDonutContent: "Custom Kyiv", + }, + { + id: "austin", + name: "Austin", + data: [Math.round(Math.random() * 1000000)], + icon: "status_warning", + link: "https://en.wikipedia.org/wiki/Austin", + value: "Austin", + customDonutContent: "Custom Austin", + }, + { + id: "lisbon", + name: "Lisbon", + data: [Math.round(Math.random() * 1000000)], + icon: "status_unknown", + link: "https://en.wikipedia.org/wiki/Lisbon", + value: "Lisbon", + customDonutContent: "Custom Lisbon", + }, + { + id: "sydney", + name: "Sydney", + data: [Math.round(Math.random() * 1000000)], + icon: "status_up", + link: "https://en.wikipedia.org/wiki/Sydney", + value: "Sydney", + customDonutContent: "Custom Sydney", + }, + { + id: "nur-sultan", + name: "Nur-Sultan", + data: [Math.round(Math.random() * 1000000)], + icon: "status_unmanaged", + link: "https://en.wikipedia.org/wiki/Nur-Sultan", + value: "Nur-Sultan", + customDonutContent: "Custom Nur-Sultan", + }, + ].sort((a, b) => a.data[0] - b.data[0]); +} +`, + "widget-types/proportional/proportional-widget/proportional-widget-example.component.ts": `// © 2022 SolarWinds Worldwide, LLC. All rights reserved. +// +// Permission is hereby granted, free of charge, to any person obtaining a copy +// of this software and associated documentation files (the "Software"), to +// deal in the Software without restriction, including without limitation the +// rights to use, copy, modify, merge, publish, distribute, sublicense, and/or +// sell copies of the Software, and to permit persons to whom the Software is +// furnished to do so, subject to the following conditions: +// +// The above copyright notice and this permission notice shall be included in +// all copies or substantial portions of the Software. +// +// THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR +// IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, +// FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE +// AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER +// LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, +// OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN +// THE SOFTWARE. + +import { + ChangeDetectorRef, + Component, + Injectable, + OnDestroy, + OnInit, +} from "@angular/core"; +import { GridsterConfig, GridsterItem } from "angular-gridster2"; +import { BehaviorSubject } from "rxjs"; + +import { + DataSourceService, + IDataSource, + IFilteringOutputs, +} from "@nova-ui/bits"; +import { + DATA_SOURCE, + DEFAULT_PIZZAGNA_ROOT, + IDashboard, + IProportionalWidgetChartOptions, + IProportionalWidgetConfig, + IProportionalWidgetData, + IProviderConfiguration, + IRefresherProperties, + IWidget, + IWidgets, + LegendPlacement, + PizzagnaLayer, + ProportionalWidgetChartTypes, + ProviderRegistryService, + WellKnownPathKey, + WellKnownProviders, + WidgetTypesService, +} from "@nova-ui/dashboards"; + +import { IMockBeerReview } from "../models"; + +/** + * A simple proportional data source to retrieve beer review counts by city + */ +@Injectable() +export class BeerReviewCountsByCityMockDataSource + extends DataSourceService + implements IDataSource, OnDestroy +{ + // This is the ID we'll use to identify the provider + public static providerId = "BeerReviewCountsByCityMockDataSource"; + public busy = new BehaviorSubject(false); + + public async getFilteredData(): Promise { + this.busy.next(true); + return new Promise((resolve) => { + setTimeout(() => { + this.outputsSubject.next({ + result: getMockBeerReviewCountsByCity(), + }); + this.busy.next(false); + }, 300); + }); + } + + public ngOnDestroy(): void { + this.outputsSubject.complete(); + } +} + +/** + * A component that instantiates the dashboard + */ +@Component({ + selector: "proportional-widget-example", + templateUrl: "./proportional-widget-example.component.html", + styleUrls: ["./proportional-widget-example.component.less"], + standalone: false, +}) +export class ProportionalWidgetExampleComponent implements OnInit { + // This variable will hold all the data needed to define the layout and behavior of the widgets. + // Pass this to the dashboard component's dashboard input in the template. + public dashboard: IDashboard | undefined; + + // Angular gridster requires a configuration object even if it's empty. + // Pass this to the dashboard component's gridsterConfig input in the template. + public gridsterConfig: GridsterConfig = {}; + + // Boolean passed as an input to the dashboard. When true, widgets can be moved, resized, removed, or edited + public editMode: boolean = false; + + constructor( + // WidgetTypesService provides the widget's necessary structure information + private widgetTypesService: WidgetTypesService, + // In general, the ProviderRegistryService is used for making entities available for injection into dynamically loaded components. + private providerRegistry: ProviderRegistryService, + private changeDetectorRef: ChangeDetectorRef + ) {} + + public ngOnInit(): void { + // Grabbing the widget's default template which will be needed as a parameter for setNode + const widgetTemplate = this.widgetTypesService.getWidgetType( + "proportional", + 1 + ); + + // Registering our data sources as dropdown options in the widget editor/configurator + // Note: This could also be done in the parent module's constructor so that + // multiple dashboards could have access to the same widget template modification. + this.widgetTypesService.setNode( + // This is the template we grabbed above with getWidgetType + widgetTemplate, + // We are setting the editor/configurator part of the widget template + "configurator", + // This indicates which node you are changing and we want to change + // the data source providers available for selection in the editor. + WellKnownPathKey.DataSourceProviders, + // We are setting the data sources available for selection in the editor + [BeerReviewCountsByCityMockDataSource.providerId] + ); + + // Registering the data source for injection into the Proportional widget. + this.providerRegistry.setProviders({ + [BeerReviewCountsByCityMockDataSource.providerId]: { + provide: DATA_SOURCE, + useClass: BeerReviewCountsByCityMockDataSource, + deps: [], + }, + }); + + this.initializeDashboard(); + } + + /** Used for restoring widgets state */ + public reInitializeDashboard(): void { + // destroys the components and their providers so the dashboard can re init data + this.dashboard = undefined; + this.changeDetectorRef.detectChanges(); + + this.initializeDashboard(); + } + + public initializeDashboard(): void { + // We're using a static configuration object for this example, but this is where + // the widget's configuration could potentially be populated from a database + const widgetIndex: IWidgets = { + // Complete the proportional widget with information coming from its type definition + [widgetConfig.id]: + this.widgetTypesService.mergeWithWidgetType(widgetConfig), + }; + + // Setting the widget dimensions and position (this is for gridster) + const positions: Record = { + [widgetConfig.id]: { + cols: 5, + rows: 6, + y: 0, + x: 0, + }, + }; + + // Finally, assigning the variables we created above to the dashboard + this.dashboard = { + positions, + widgets: widgetIndex, + }; + } +} + +const widgetConfig: IWidget = { + id: "proportionalWidgetId", + type: "proportional", + pizzagna: { + [PizzagnaLayer.Configuration]: { + [DEFAULT_PIZZAGNA_ROOT]: { + providers: { + [WellKnownProviders.Refresher]: { + properties: { + // Configuring the refresher interval so that our data source is invoked every ten minutes + interval: 60 * 10, + enabled: true, + } as IRefresherProperties, + } as Partial, + }, + }, + header: { + properties: { + title: "Beer Review Tally by City", + subtitle: "These People Love Beer", + }, + }, + chart: { + providers: { + [WellKnownProviders.DataSource]: { + // Setting the data source providerId for the chart + providerId: + BeerReviewCountsByCityMockDataSource.providerId, + } as IProviderConfiguration, + }, + properties: { + configuration: { + chartOptions: { + type: ProportionalWidgetChartTypes.DonutChart, + legendPlacement: LegendPlacement.Right, + } as IProportionalWidgetChartOptions, + // You can optionally define custom colors for the chart by setting the 'chartColors' configuration property + // "chartColors": [ + // "var(--nui-color-chart-five)", + // "var(--nui-color-chart-six)", + // "var(--nui-color-chart-seven)", + // "var(--nui-color-chart-eight)", + // "var(--nui-color-chart-nine)", + // "var(--nui-color-chart-ten)", + // ], + // or use-mapped structure + chartColors: { + Brno: "var(--nui-color-chart-five)", + kyiv: "var(--nui-color-chart-six)", + austin: "var(--nui-color-chart-seven)", + lisbon: "var(--nui-color-chart-eight)", + sydney: "var(--nui-color-chart-nine)", + "nur-sultan": "var(--nui-color-chart-ten)", + }, + prioritizeWidgetColors: false, + } as IProportionalWidgetConfig, + }, + }, + }, + }, +}; + +export function getMockBeerReviewCountsByCity(): IMockBeerReview[] { + return [ + { + id: "Brno", + name: "Brno", + data: [Math.round(Math.random() * 100000)], + icon: "status_down", + link: "https://en.wikipedia.org/wiki/Brno", + value: "Brno", + color: "var(--nui-color-chart-one)", + }, + { + id: "kyiv", + name: "Kyiv", + data: [Math.round(Math.random() * 100000)], + icon: "status_critical", + link: "https://en.wikipedia.org/wiki/Kyiv", + value: "Kyiv", + color: "var(--nui-color-chart-two)", + }, + { + id: "austin", + name: "Austin", + data: [Math.round(Math.random() * 100000)], + icon: "status_warning", + link: "https://en.wikipedia.org/wiki/Austin", + value: "Austin", + color: "var(--nui-color-chart-three)", + }, + { + id: "lisbon", + name: "Lisbon", + data: [Math.round(Math.random() * 100000)], + icon: "status_unknown", + link: "https://en.wikipedia.org/wiki/Lisbon", + value: "Lisbon", + color: "var(--nui-color-chart-four)", + }, + { + id: "sydney", + name: "Sydney", + data: [Math.round(Math.random() * 100000)], + icon: "status_up", + link: "https://en.wikipedia.org/wiki/Sydney", + value: "Sydney", + color: "var(--nui-color-chart-five)", + }, + { + id: "nur-sultan", + name: "Nur-Sultan", + data: [Math.round(Math.random() * 100000)], + icon: "status_unmanaged", + link: "https://en.wikipedia.org/wiki/Nur-Sultan", + value: "Nur-Sultan", + color: "var(--nui-color-chart-six)", + }, + ].sort((a, b) => a.data[0] - b.data[0]); +} +`, + "widget-types/proportional/proportional-widget-interactive/proportional-widget-interactive-example.component.ts": `// © 2022 SolarWinds Worldwide, LLC. All rights reserved. +// +// Permission is hereby granted, free of charge, to any person obtaining a copy +// of this software and associated documentation files (the "Software"), to +// deal in the Software without restriction, including without limitation the +// rights to use, copy, modify, merge, publish, distribute, sublicense, and/or +// sell copies of the Software, and to permit persons to whom the Software is +// furnished to do so, subject to the following conditions: +// +// The above copyright notice and this permission notice shall be included in +// all copies or substantial portions of the Software. +// +// THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR +// IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, +// FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE +// AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER +// LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, +// OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN +// THE SOFTWARE. + +import { + ChangeDetectorRef, + Component, + Injectable, + OnDestroy, + OnInit, +} from "@angular/core"; +import { GridsterConfig, GridsterItem } from "angular-gridster2"; +import keyBy from "lodash/keyBy"; +import { BehaviorSubject } from "rxjs"; + +import { + DataSourceService, + IDataSource, + IFilteringOutputs, +} from "@nova-ui/bits"; +import { + DATA_SOURCE, + DEFAULT_PIZZAGNA_ROOT, + IDashboard, + IProportionalWidgetChartOptions, + IProportionalWidgetConfig, + IProportionalWidgetData, + IProviderConfiguration, + IWidget, + LegendPlacement, + NOVA_URL_INTERACTION_HANDLER, + PizzagnaLayer, + ProportionalWidgetChartTypes, + ProviderRegistryService, + WellKnownPathKey, + WellKnownProviders, + WidgetTypesService, +} from "@nova-ui/dashboards"; + +import { IMockBeerReview } from "../models"; + +/** + * A simple proportional data source to retrieve beer review counts by city + */ +@Injectable() +export class ReviewCountsByCityMockDataSource + extends DataSourceService + implements IDataSource, OnDestroy +{ + // This is the ID we'll use to identify the provider + public static providerId = "ReviewCountsByCityMockDataSource"; + public busy = new BehaviorSubject(false); + + public async getFilteredData(): Promise { + this.busy.next(true); + return new Promise((resolve) => { + setTimeout(() => { + this.outputsSubject.next({ + result: getMockBeerReviewCountsByCity(), + }); + this.busy.next(false); + }, 300); + }); + } + + public ngOnDestroy(): void { + this.outputsSubject.complete(); + } +} + +/** + * A component that instantiates the dashboard + */ +@Component({ + selector: "proportional-widget-interactive-example", + templateUrl: "./proportional-widget-interactive-example.component.html", + styleUrls: ["./proportional-widget-interactive-example.component.less"], + standalone: false, +}) +export class ProportionalWidgetInteractiveExampleComponent implements OnInit { + // This variable will hold all the data needed to define the layout and behavior of the widgets. + // Pass this to the dashboard component's dashboard input in the template. + public dashboard: IDashboard | undefined; + + // Angular gridster requires a configuration object even if it's empty. + // Pass this to the dashboard component's gridsterConfig input in the template. + public gridsterConfig: GridsterConfig = {}; + + // Boolean passed as an input to the dashboard. When true, widgets can be moved, resized, removed, or edited + public editMode: boolean = false; + + constructor( + // WidgetTypesService provides the widget's necessary structure information + private widgetTypesService: WidgetTypesService, + // In general, the ProviderRegistryService is used for making entities available for injection into dynamically loaded components. + private providerRegistry: ProviderRegistryService, + private changeDetectorRef: ChangeDetectorRef + ) {} + + public ngOnInit(): void { + // Grabbing the widget's default template which will be needed as a parameter for setNode + const widgetTemplate = this.widgetTypesService.getWidgetType( + "proportional", + 1 + ); + + // Registering our data sources as dropdown options in the widget editor/configurator + // Note: This could also be done in the parent module's constructor so that + // multiple dashboards could have access to the same widget template modification. + this.widgetTypesService.setNode( + // This is the template we grabbed above with getWidgetType + widgetTemplate, + // We are setting the editor/configurator part of the widget template + "configurator", + // This indicates which node you are changing and we want to change + // the data source providers available for selection in the editor. + WellKnownPathKey.DataSourceProviders, + // We are setting the data sources available for selection in the editor + [ReviewCountsByCityMockDataSource.providerId] + ); + + // Registering the data source for injection into the Proportional widget. + this.providerRegistry.setProviders({ + [ReviewCountsByCityMockDataSource.providerId]: { + provide: DATA_SOURCE, + useClass: ReviewCountsByCityMockDataSource, + deps: [], + }, + }); + + this.initializeDashboard(); + } + + /** Used for restoring widgets state */ + public reInitializeDashboard(): void { + // destroys the components and their providers so the dashboard can re init data + this.dashboard = undefined; + this.changeDetectorRef.detectChanges(); + + this.initializeDashboard(); + } + + public initializeDashboard(): void { + // We're using a static configuration object for this example, but this is where + // the widget's configuration could potentially be populated from a database + const widgetsWithStructure = widgetConfigs.map((w) => + this.widgetTypesService.mergeWithWidgetType(w) + ); + const widgetsIndex = keyBy(widgetsWithStructure, (w: IWidget) => w.id); + + // Setting the widget dimensions and position (this is for gridster) + const positions: Record = { + [widgetConfigs[0].id]: { + cols: 6, + rows: 6, + y: 0, + x: 0, + }, + [widgetConfigs[1].id]: { + cols: 6, + rows: 6, + y: 0, + x: 6, + }, + }; + + // Finally, assigning the variables we created above to the dashboard + this.dashboard = { + positions, + widgets: widgetsIndex, + }; + } +} + +const widgetConfigs: IWidget[] = [ + { + id: "widget1", + type: "proportional", + pizzagna: { + [PizzagnaLayer.Configuration]: { + [DEFAULT_PIZZAGNA_ROOT]: { + providers: { + // Configuring the UrlInteractionHandler to handle interactions + [WellKnownProviders.InteractionHandler]: { + providerId: NOVA_URL_INTERACTION_HANDLER, + properties: { + // the 'url' property tells the handler what link to use when interaction occurs on the series + // if the series does not have a link we are passing one to the handler + url: "\${data.link || 'https://en.wikipedia.org/wiki/'+data.id}", + // by default the link is opened in the current window, set 'newWindow' to true to open in a new tab instead + // newWindow: true, + }, + }, + }, + }, + header: { + properties: { + title: "Proportional Widget", + subtitle: "With interaction handler", + }, + }, + chart: { + providers: { + [WellKnownProviders.DataSource]: { + // Setting the data source providerId for the chart + providerId: + ReviewCountsByCityMockDataSource.providerId, + } as IProviderConfiguration, + }, + properties: { + configuration: { + // Setting the interactive to true + interactive: true, + chartOptions: { + type: ProportionalWidgetChartTypes.VerticalBarChart, + legendPlacement: LegendPlacement.Bottom, + } as IProportionalWidgetChartOptions, + prioritizeWidgetColors: false, + } as IProportionalWidgetConfig, + }, + }, + }, + }, + }, + { + id: "widget2", + type: "proportional", + pizzagna: { + [PizzagnaLayer.Configuration]: { + header: { + properties: { + title: "Proportional Widget", + subtitle: "Without interaction handler", + }, + }, + chart: { + providers: { + [WellKnownProviders.DataSource]: { + // Setting the data source providerId for the chart + providerId: + ReviewCountsByCityMockDataSource.providerId, + } as IProviderConfiguration, + }, + properties: { + configuration: { + // interactive set to false so series without links are not styled like a link + interactive: false, + chartOptions: { + type: ProportionalWidgetChartTypes.HorizontalBarChart, + legendPlacement: LegendPlacement.Bottom, + } as IProportionalWidgetChartOptions, + prioritizeWidgetColors: false, + } as IProportionalWidgetConfig, + }, + }, + }, + }, + }, +]; + +export function getMockBeerReviewCountsByCity(): IMockBeerReview[] { + return [ + { + id: "Brno", + name: "Brno", + data: [Math.round(Math.random() * 100000)], + icon: "status_down", + link: "https://en.wikipedia.org/wiki/Brno", + value: "Brno", + color: "var(--nui-color-chart-one)", + }, + { + id: "kyiv", + name: "Kyiv", + data: [Math.round(Math.random() * 100000)], + icon: "status_critical", + link: "https://en.wikipedia.org/wiki/Kyiv", + value: "Kyiv", + color: "var(--nui-color-chart-two)", + }, + { + id: "austin", + name: "Austin", + data: [Math.round(Math.random() * 100000)], + icon: "status_warning", + value: "Austin", + color: "var(--nui-color-chart-three)", + }, + { + id: "lisbon", + name: "Lisbon", + data: [Math.round(Math.random() * 100000)], + icon: "status_unknown", + link: "https://en.wikipedia.org/wiki/Lisbon", + value: "Lisbon", + color: "var(--nui-color-chart-four)", + }, + { + id: "sydney", + name: "Sydney", + data: [Math.round(Math.random() * 100000)], + icon: "status_up", + value: "Sydney", + color: "var(--nui-color-chart-five)", + }, + { + id: "nur-sultan", + name: "Nur-Sultan", + data: [Math.round(Math.random() * 100000)], + icon: "status_unmanaged", + value: "Nur-Sultan", + color: "var(--nui-color-chart-six)", + }, + ].sort((a, b) => a.data[0] - b.data[0]); +} +`, + "widget-types/risk-score/risk-score-docs.component.ts": `// © 2023 SolarWinds Worldwide, LLC. All rights reserved. +// +// Permission is hereby granted, free of charge, to any person obtaining a copy +// of this software and associated documentation files (the "Software"), to +// deal in the Software without restriction, including without limitation the +// rights to use, copy, modify, merge, publish, distribute, sublicense, and/or +// sell copies of the Software, and to permit persons to whom the Software is +// furnished to do so, subject to the following conditions: +// +// The above copyright notice and this permission notice shall be included in +// all copies or substantial portions of the Software. +// +// THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR +// IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, +// FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE +// AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER +// LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, +// OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN +// THE SOFTWARE. + +import { Component, OnInit } from "@angular/core"; + +import { mapContentFile } from "../../../../demo-files-factory"; + +@Component({ + selector: "nui-risk-score-docs", + templateUrl: "./risk-score-docs.component.html", + standalone: false, +}) +export class RiskScoreDocsComponent implements OnInit { + public riskScoreWidgetFileText = ""; + public riskScoreConfiguratorFileText = ""; + + public async ngOnInit(): Promise { + this.riskScoreWidgetFileText = await import( + "./../../../../../../src/lib/widget-types/risk-score/risk-score-widget" + ).then(mapContentFile); + this.riskScoreConfiguratorFileText = await import( + "./../../../../../../src/lib/widget-types/risk-score/risk-score-configurator" + ).then(mapContentFile); + } +} +`, + "widget-types/risk-score/risk-score-docs.module.ts": `// © 2023 SolarWinds Worldwide, LLC. All rights reserved. +// +// Permission is hereby granted, free of charge, to any person obtaining a copy +// of this software and associated documentation files (the "Software"), to +// deal in the Software without restriction, including without limitation the +// rights to use, copy, modify, merge, publish, distribute, sublicense, and/or +// sell copies of the Software, and to permit persons to whom the Software is +// furnished to do so, subject to the following conditions: +// +// The above copyright notice and this permission notice shall be included in +// all copies or substantial portions of the Software. +// +// THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR +// IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, +// FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE +// AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER +// LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, +// OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN +// THE SOFTWARE. + +import { NgModule } from "@angular/core"; +import { RouterModule, Routes } from "@angular/router"; + +import { + DEMO_PATH_TOKEN, + NuiButtonModule, + NuiDocsModule, + NuiMessageModule, + NuiSwitchModule, +} from "@nova-ui/bits"; +import { NuiDashboardsModule } from "@nova-ui/dashboards"; + +import { RiskScoreDocsComponent } from "./risk-score-docs.component"; +import { RiskScoreWidgetExampleComponent } from "./risk-score-widget-example/risk-score-widget-example.component"; +import { getDemoFiles } from "../../../../demo-files-factory"; + +const routes: Routes = [ + { + path: "", + component: RiskScoreDocsComponent, + data: { + srlc: { + hideIndicator: true, + }, + showThemeSwitcher: true, + }, + }, + { + path: "example", + component: RiskScoreWidgetExampleComponent, + data: { + srlc: { + hideIndicator: true, + }, + }, + }, +]; + +@NgModule({ + imports: [ + RouterModule.forChild(routes), + NuiButtonModule, + NuiDocsModule, + NuiMessageModule, + NuiDashboardsModule, + NuiSwitchModule, + ], + declarations: [RiskScoreDocsComponent, RiskScoreWidgetExampleComponent], + providers: [ + { + provide: DEMO_PATH_TOKEN, + useValue: getDemoFiles("risk-score"), + }, + ], +}) +export default class RiskScoreDocsModule {} +`, + "widget-types/risk-score/risk-score-widget-example/risk-score-widget-example.component.ts": `// © 2023 SolarWinds Worldwide, LLC. All rights reserved. +// +// Permission is hereby granted, free of charge, to any person obtaining a copy +// of this software and associated documentation files (the "Software"), to +// deal in the Software without restriction, including without limitation the +// rights to use, copy, modify, merge, publish, distribute, sublicense, and/or +// sell copies of the Software, and to permit persons to whom the Software is +// furnished to do so, subject to the following conditions: +// +// The above copyright notice and this permission notice shall be included in +// all copies or substantial portions of the Software. +// +// THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR +// IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, +// FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE +// AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER +// LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, +// OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN +// THE SOFTWARE. + +import { HttpClient, HttpErrorResponse } from "@angular/common/http"; +import { Component, Injectable, OnDestroy, OnInit } from "@angular/core"; +import { GridsterConfig, GridsterItem } from "angular-gridster2"; +import { BehaviorSubject } from "rxjs"; +import { finalize } from "rxjs/operators"; + +import { DataSourceService, IFilteringOutputs } from "@nova-ui/bits"; +import { + DATA_SOURCE, + DEFAULT_PIZZAGNA_ROOT, + IDashboard, + IRiskScoreData, + IProviderConfiguration, + IRefresherProperties, + IWidget, + IWidgets, + RiskScoreTileComponent, + NOVA_KPI_DATASOURCE_ADAPTER, + PizzagnaLayer, + ProviderRegistryService, + WellKnownPathKey, + WellKnownProviders, + WidgetTypesService, +} from "@nova-ui/dashboards"; + +/** + * A simple KPI data source to retrieve the average rating of Harry Potter and the Sorcerer's Stone (book) via googleapis + */ +@Injectable() +export class AverageRatingRiskScoreDataSource + extends DataSourceService + implements OnDestroy +{ + // This is the ID we'll use to identify the provider + public static providerId = "AverageRatingRiskScoreDataSource"; + + // Use this subject to communicate the data source's busy state + public busy = new BehaviorSubject(false); + + constructor(private http: HttpClient) { + super(); + } + + // In this example, getFilteredData is invoked every 10 minutes (Take a look at the refresher + // provider definition in the widget configuration below to see how the interval is set) + public async getFilteredData(): Promise { + this.busy.next(true); + return new Promise((resolve) => { + // *** Make a resource request to an external API (if needed) + this.http + .get("https://www.googleapis.com/books/v1/volumes/5MQFrgEACAAJ") + .pipe(finalize(() => this.busy.next(false))) + .subscribe({ + next: (data: any) => { + resolve({ + result: { + value: data.volumeInfo.averageRating, + }, + }); + }, + error: (error: HttpErrorResponse) => { + resolve({ + result: null, + error: { + type: error.status, + }, + }); + }, + }); + }); + } + + public ngOnDestroy(): void { + this.outputsSubject.complete(); + } +} + +/** + * A component that instantiates the dashboard + */ +@Component({ + selector: "risk-score-widget-example", + templateUrl: "./risk-score-widget-example.component.html", + styleUrls: ["./risk-score-widget-example.component.less"], + standalone: false, +}) +export class RiskScoreWidgetExampleComponent implements OnInit { + // This variable will hold all the data needed to define the layout and behavior of the widgets. + // Pass this to the dashboard component's dashboard input in the template. + public dashboard: IDashboard; + + // Angular gridster requires a configuration object even if it's empty. + // Pass this to the dashboard component's gridsterConfig input in the template. + public gridsterConfig: GridsterConfig = {}; + + // Boolean passed as an input to the dashboard. When true, widgets can be moved, resized, removed, or edited + public editMode: boolean = false; + + constructor( + // WidgetTypesService provides the widget's necessary structure information + private widgetTypesService: WidgetTypesService, + + // In general, the ProviderRegistryService is used for making entities available for injection into dynamically loaded components. + private providerRegistry: ProviderRegistryService + ) {} + + public ngOnInit(): void { + // Grabbing the widget's default template which will be needed as a parameter for setNode + const widgetTemplate = this.widgetTypesService.getWidgetType( + "risk-score", + 1 + ); + // Registering our data sources as dropdown options in the widget editor/configurator + // Note: This could also be done in the parent module's constructor so that + // multiple dashboards could have access to the same widget template modification. + this.widgetTypesService.setNode( + // This is the template we grabbed above with getWidgetType + widgetTemplate, + // We are setting the editor/configurator part of the widget template + "configurator", + // This indicates which node you are changing and we want to change + // the data source providers available for selection in the editor. + WellKnownPathKey.DataSourceProviders, + // We are setting the data sources available for selection in the editor + [AverageRatingRiskScoreDataSource.providerId] + ); + + // Registering the data source for injection into the KPI tile. + // Note: Each tile of a KPI widget is assigned its own instance of the data source + this.providerRegistry.setProviders({ + [AverageRatingRiskScoreDataSource.providerId]: { + provide: DATA_SOURCE, + useClass: AverageRatingRiskScoreDataSource, + // Any dependencies that need to be injected into the provider must be listed here + deps: [HttpClient], + }, + }); + + this.initializeDashboard(); + } + + public initializeDashboard(): void { + // We're using a static configuration object for this example, but this is where + // the widget's configuration could potentially be populated from a database + const riskScoreWidget = widgetConfig; + const widgetIndex: IWidgets = { + // Complete the KPI widget with information coming from its type definition + [riskScoreWidget.id]: + this.widgetTypesService.mergeWithWidgetType(riskScoreWidget), + }; + + // Setting the widget dimensions and position (this is for gridster) + const positions: Record = { + [riskScoreWidget.id]: { + cols: 4, + rows: 6, + y: 0, + x: 0, + }, + }; + + // Finally, assigning the variables we created above to the dashboard + this.dashboard = { + positions, + widgets: widgetIndex, + }; + } +} + +const widgetConfig: IWidget = { + id: "riskScoreWidgetId", + type: "risk-score", + pizzagna: { + [PizzagnaLayer.Configuration]: { + [DEFAULT_PIZZAGNA_ROOT]: { + providers: { + [WellKnownProviders.Refresher]: { + properties: { + // Configuring the refresher interval so that our data source is invoked every ten minutes + interval: 60 * 10, + enabled: true, + } as IRefresherProperties, + } as Partial, + }, + }, + header: { + properties: { + title: "Harry Potter and the Sorcerer's Stone", + subtitle: "By J. K. Rowling", + }, + }, + tiles: { + properties: { + nodes: ["riskScore1"], + }, + }, + riskScore1: { + id: "riskScore1", + componentType: RiskScoreTileComponent.lateLoadKey, + properties: { + widgetData: { + minValue: 0, + maxValue: 5, + useStaticLabel: false, + staticLabel: undefined, + label: \`Average Rating\`, + description: \`Harry Potter and the Sorcerer's Stone By J. K. Rowling Average Rating Risk Score\`, + }, + }, + providers: { + [WellKnownProviders.DataSource]: { + // Setting the data source providerId for the tile with id "kpi1" + providerId: AverageRatingRiskScoreDataSource.providerId, + } as IProviderConfiguration, + [WellKnownProviders.Adapter]: { + providerId: NOVA_KPI_DATASOURCE_ADAPTER, + properties: { + componentId: "riskScore1", + propertyPath: "widgetData", + }, + } as IProviderConfiguration, + }, + }, + }, + }, +}; +`, + "widget-types/table/table-docs.component.ts": `// © 2022 SolarWinds Worldwide, LLC. All rights reserved. +// +// Permission is hereby granted, free of charge, to any person obtaining a copy +// of this software and associated documentation files (the "Software"), to +// deal in the Software without restriction, including without limitation the +// rights to use, copy, modify, merge, publish, distribute, sublicense, and/or +// sell copies of the Software, and to permit persons to whom the Software is +// furnished to do so, subject to the following conditions: +// +// The above copyright notice and this permission notice shall be included in +// all copies or substantial portions of the Software. +// +// THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR +// IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, +// FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE +// AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER +// LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, +// OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN +// THE SOFTWARE. + +import { Component, OnInit } from "@angular/core"; + +import { mapContentFile } from "../../../../demo-files-factory"; + +@Component({ + selector: "nui-table-docs", + templateUrl: "./table-docs.component.html", + standalone: false, +}) +export class TableDocsComponent implements OnInit { + public widgetFileText = ""; + public configuratorFileText = ""; + + public async ngOnInit(): Promise { + this.widgetFileText = await import( + "./../../../../../../src/lib/widget-types/table/table-widget" + ).then(mapContentFile); + this.configuratorFileText = await import( + "./../../../../../../src/lib/widget-types/table/table-configurator" + ).then(mapContentFile); + } +} +`, + "widget-types/table/table-docs.module.ts": `// © 2022 SolarWinds Worldwide, LLC. All rights reserved. +// +// Permission is hereby granted, free of charge, to any person obtaining a copy +// of this software and associated documentation files (the "Software"), to +// deal in the Software without restriction, including without limitation the +// rights to use, copy, modify, merge, publish, distribute, sublicense, and/or +// sell copies of the Software, and to permit persons to whom the Software is +// furnished to do so, subject to the following conditions: +// +// The above copyright notice and this permission notice shall be included in +// all copies or substantial portions of the Software. +// +// THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR +// IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, +// FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE +// AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER +// LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, +// OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN +// THE SOFTWARE. + +import { NgModule } from "@angular/core"; +import { RouterModule, Routes } from "@angular/router"; + +import { DEMO_PATH_TOKEN } from "@nova-ui/bits"; +import { + NuiButtonModule, + NuiDocsModule, + NuiMessageModule, + NuiSwitchModule, +} from "@nova-ui/bits"; +import { + NuiDashboardsModule, + TableFormatterRegistryService, +} from "@nova-ui/dashboards"; + +import { TableDocsComponent } from "./table-docs.component"; +import { TablePaginatorDocsComponent } from "./table-paginator-docs.component"; +import { TableSelectableDocsComponent } from "./table-selectable-docs.component"; +import { TableWidgetExampleComponent } from "./table-widget/table-widget-example.component"; +import { TableWidgetInteractiveExampleComponent } from "./table-widget-interactive/table-widget-interactive-example.component"; +import { TableWidgetPaginatorExampleComponent } from "./table-widget-paginator/table-widget-paginator-example.component"; +import { TableWidgetSearchExampleComponent } from "./table-widget-search/table-widget-search-example.component"; +import { TableSearchDocsComponent } from "./table-widget-search-docs.component"; +import { TableWidgetSelectableMultiExampleComponent } from "./table-widget-selectable/table-widget-selectable-multi/table-widget-selectable-multi.example.component"; +import { TableWidgetSelectableRadioExampleComponent } from "./table-widget-selectable/table-widget-selectable-radio/table-widget-selectable-radio.example.component"; +import { TableWidgetSelectableSingleExampleComponent } from "./table-widget-selectable/table-widget-selectable-single/table-widget-selectable-single.example.component"; +import { TableWidgetSelectableExampleComponent } from "./table-widget-selectable/table-widget-selectable.example.component"; +import { DEFAULT_TABLE_FORMATTERS } from "../../../../../../src/lib/widget-types/table/default-table-formatters"; +import { getDemoFiles } from "../../../../demo-files-factory"; + +const routes: Routes = [ + { + path: "", + component: TableDocsComponent, + data: { + srlc: { + hideIndicator: true, + }, + showThemeSwitcher: true, + }, + }, + { + path: "example", + component: TableWidgetExampleComponent, + data: { + srlc: { + hideIndicator: true, + }, + }, + }, + { + path: "table-search", + component: TableSearchDocsComponent, + data: { + srlc: { + hideIndicator: true, + }, + showThemeSwitcher: true, + }, + }, + { + path: "table-paginator", + component: TablePaginatorDocsComponent, + data: { + srlc: { + hideIndicator: true, + }, + showThemeSwitcher: true, + }, + }, + { + path: "table-select", + component: TableSelectableDocsComponent, + data: { + srlc: { + hideIndicator: true, + }, + showThemeSwitcher: true, + }, + }, +]; + +@NgModule({ + imports: [ + RouterModule.forChild(routes), + NuiButtonModule, + NuiDocsModule, + NuiMessageModule, + NuiSwitchModule, + NuiDashboardsModule, + ], + declarations: [ + TableDocsComponent, + TableSearchDocsComponent, + TablePaginatorDocsComponent, + TableWidgetPaginatorExampleComponent, + TableSelectableDocsComponent, + TableWidgetInteractiveExampleComponent, + TableWidgetExampleComponent, + TableWidgetSearchExampleComponent, + TableWidgetSelectableExampleComponent, + TableWidgetSelectableMultiExampleComponent, + TableWidgetSelectableSingleExampleComponent, + TableWidgetSelectableRadioExampleComponent, + ], + providers: [ + { + provide: DEMO_PATH_TOKEN, + useValue: getDemoFiles("table"), + }, + ], +}) +export default class TableDocsModule { + constructor(tableFormattersRegistryService: TableFormatterRegistryService) { + tableFormattersRegistryService.addItems(DEFAULT_TABLE_FORMATTERS); + } +} +`, + "widget-types/table/table-paginator-docs.component.ts": `// © 2022 SolarWinds Worldwide, LLC. All rights reserved. +// +// Permission is hereby granted, free of charge, to any person obtaining a copy +// of this software and associated documentation files (the "Software"), to +// deal in the Software without restriction, including without limitation the +// rights to use, copy, modify, merge, publish, distribute, sublicense, and/or +// sell copies of the Software, and to permit persons to whom the Software is +// furnished to do so, subject to the following conditions: +// +// The above copyright notice and this permission notice shall be included in +// all copies or substantial portions of the Software. +// +// THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR +// IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, +// FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE +// AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER +// LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, +// OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN +// THE SOFTWARE. + +import { Component } from "@angular/core"; + +@Component({ + selector: "nui-table-paginator-docs", + templateUrl: "./table-paginator-docs.component.html", + standalone: false, +}) +export class TablePaginatorDocsComponent { + public tableConfigurationText = \` + "table": { + ... + properties: { + configuration: { + // define paginator configuration here + scrollType: ScrollType.paginator, + paginatorConfiguration: { + pageSize: 10, // Value have to be one of pageSizeSet values + pageSizeSet: [10, 20, 30], + }, + // If not specified, default is set to + // pageSize: 10, + // pageSizeSet: [10, 20, 50], + hasVirtualScroll: false, // Has to be speciefied because of backward compatibility + } as ITableWidgetConfig, + }, + }, + \`; +} +`, + "widget-types/table/table-selectable-docs.component.ts": `// © 2022 SolarWinds Worldwide, LLC. All rights reserved. +// +// Permission is hereby granted, free of charge, to any person obtaining a copy +// of this software and associated documentation files (the "Software"), to +// deal in the Software without restriction, including without limitation the +// rights to use, copy, modify, merge, publish, distribute, sublicense, and/or +// sell copies of the Software, and to permit persons to whom the Software is +// furnished to do so, subject to the following conditions: +// +// The above copyright notice and this permission notice shall be included in +// all copies or substantial portions of the Software. +// +// THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR +// IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, +// FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE +// AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER +// LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, +// OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN +// THE SOFTWARE. + +import { Component } from "@angular/core"; + +@Component({ + selector: "nui-table-selectable-docs", + templateUrl: "./table-selectable-docs.component.html", + standalone: false, +}) +export class TableSelectableDocsComponent { + public tableConfigurationText = \` + "table": { + ... + properties: { + // enabling selection here + selectionConfiguration: { + // whether the selection is enabled or disabled + enabled: true, + // can be Multi | Radio | Single + selectionMode: TableSelectionMode.Multi, + // property that uniquely identifies row in a table + trackByProperty: "id", + // whether clicking on row should select it + clickableRow: true, + }, + }, + }, + \`; + + public eventSubscriptionText = \` +... +constructor(Inject(PIZZAGNA_EVENT_BUS) eventBus: EventBus) { + eventBus + .getStream(SELECTION) + // don't forget to unsubscribe! + .subscribe((selection: ISelection) => ...) +} +... + \`; +} +`, + "widget-types/table/table-widget/table-widget-example.component.ts": `// © 2022 SolarWinds Worldwide, LLC. All rights reserved. +// +// Permission is hereby granted, free of charge, to any person obtaining a copy +// of this software and associated documentation files (the "Software"), to +// deal in the Software without restriction, including without limitation the +// rights to use, copy, modify, merge, publish, distribute, sublicense, and/or +// sell copies of the Software, and to permit persons to whom the Software is +// furnished to do so, subject to the following conditions: +// +// The above copyright notice and this permission notice shall be included in +// all copies or substantial portions of the Software. +// +// THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR +// IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, +// FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE +// AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER +// LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, +// OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN +// THE SOFTWARE. + +import { ChangeDetectorRef, Component, OnInit } from "@angular/core"; +import { GridsterConfig, GridsterItem } from "angular-gridster2"; +import orderBy from "lodash/orderBy"; +import { BehaviorSubject, firstValueFrom, from } from "rxjs"; +import { map, tap } from "rxjs/operators"; + +import { + DataSourceService, + IDataField, + INovaFilteringOutputs, + INovaFilters, + nameof, +} from "@nova-ui/bits"; +import { + DATA_SOURCE, + IDashboard, + ITableWidgetColumnConfig, + IWidget, + IWidgets, + ProviderRegistryService, + WellKnownPathKey, + WellKnownProviders, + WidgetTypesService, +} from "@nova-ui/dashboards"; + +export const BREW_API_URL = "https://api.punkapi.com/v2/beers"; + +export interface IBrewInfo { + id: string; + name: string; + tagline: string; + first_brewed: string; + description: string; + brewers_tips: string; +} + +export interface IBrewDatasourceResponse { + brewInfo: IBrewInfo[]; + total: number; +} + +export class BeerDataSource extends DataSourceService { + public static providerId = "BeerDataSource"; + + private cache: IBrewInfo[] = []; + + public busy = new BehaviorSubject(false); + + public dataFields: Array = [ + { + id: nameof("id"), + label: "No", + dataType: "number", + sortable: true, + }, + // To indicate that a column should not be sortable, set the optional IDataField 'sortable' property to false + { + id: nameof("name"), + label: "Name", + dataType: "string", + sortable: true, + }, + { + id: nameof("tagline"), + label: "Tagline", + dataType: "string", + sortable: true, + }, + { + id: nameof("first_brewed"), + label: "First Brewed", + dataType: "string", + sortable: true, + }, + { + id: nameof("description"), + label: "Description", + dataType: "string", + sortable: false, + }, + { + id: nameof("brewers_tips"), + label: "Brewer's Tips", + dataType: "string", + sortable: false, + }, + ]; + + public async getFilteredData( + filters: INovaFilters + ): Promise { + const start = filters.virtualScroll?.value?.start ?? 0; + const end = filters.virtualScroll?.value?.end ?? 0; + + // Resetting cache on first page request + if (start === 0) { + this.cache = []; + } + + // extract sorter settings to send to the backend + // filters.sorterValue.sortBy; filters.sorterValue.direction + return firstValueFrom( + from(this.fetch(start, end)).pipe( + tap((response: IBrewDatasourceResponse | undefined) => { + if (!response) { + return; + } + this.cache = this.sortData( + this.cache.concat(response.brewInfo), + filters + ); + this.dataSubject.next(this.cache); + }), + map(() => ({ + repeat: { itemsSource: this.cache }, + dataFields: this.dataFields, + })) + ) + ); + } + + public async fetch( + start: number, + end: number + ): Promise { + const delta: number = end - start; + const currentPage: number = end / delta || 0; + const response: object | Array = await ( + await fetch( + \`\${BREW_API_URL}/?page=\${currentPage}&per_page=\${delta}\` + ) + ).json(); + + // Note: In case request fails we should not proceed with mapping + if (!Array.isArray(response)) { + return undefined; + } + + return { + brewInfo: response.map((result: IBrewInfo) => ({ + id: result.id, + name: result.name, + tagline: result.tagline, + first_brewed: result.first_brewed, + description: result.description, + brewers_tips: result.brewers_tips, + })), + total: response.length, + }; + } + + private sortData(data: IBrewInfo[], filters: INovaFilters): IBrewInfo[] { + return orderBy( + data, + filters.sorter?.value?.sortBy, + filters.sorter?.value?.direction as "desc" | "asc" + ); + } +} + +/** + * A component that instantiates the dashboard + */ +@Component({ + selector: "table-widget-example", + templateUrl: "./table-widget-example.component.html", + styleUrls: ["./table-widget-example.component.less"], + standalone: false, +}) +export class TableWidgetExampleComponent implements OnInit { + // This variable will hold all the data needed to define the layout and behavior of the widgets. + // Pass this to the dashboard component's dashboard input in the template. + public dashboard: IDashboard | undefined; + + // Angular gridster requires a configuration object even if it's empty. + // Pass this to the dashboard component's gridsterConfig input in the template. + public gridsterConfig: GridsterConfig = {}; + + // Boolean passed as an input to the dashboard. When true, widgets can be moved, resized, removed, or edited + public editMode: boolean = false; + + constructor( + // WidgetTypesService provides the widget's necessary structure information + private widgetTypesService: WidgetTypesService, + // In general, the ProviderRegistryService is used for making entities available for injection into dynamically loaded components. + private providerRegistry: ProviderRegistryService, + private changeDetectorRef: ChangeDetectorRef + ) {} + + public ngOnInit(): void { + // Grabbing the widget's default template which will be needed as a parameter for setNode + const widgetTemplate = this.widgetTypesService.getWidgetType( + "table", + 1 + ); + + // Registering our data sources as dropdown options in the widget editor/configurator + // Note: This could also be done in the parent module's constructor so that + // multiple dashboards could have access to the same widget template modification. + this.widgetTypesService.setNode( + // This is the template we grabbed above with getWidgetType + widgetTemplate, + // We are setting the editor/configurator part of the widget template + "configurator", + // This indicates which node you are changing and we want to change + // the data source providers available for selection in the editor. + WellKnownPathKey.DataSourceProviders, + // We are setting the data sources available for selection in the editor + [BeerDataSource.providerId] + ); + + // Registering the data source for injection into the widget. + this.providerRegistry.setProviders({ + [BeerDataSource.providerId]: { + provide: DATA_SOURCE, + useClass: BeerDataSource, + // Any dependencies that need to be injected into the provider must be listed here + deps: [], + }, + }); + + this.initializeDashboard(); + } + + /** Used for restoring widgets state */ + public reInitializeDashboard(): void { + // destroys the components and their providers so the dashboard can re init data + this.dashboard = undefined; + this.changeDetectorRef.detectChanges(); + + this.initializeDashboard(); + } + + public initializeDashboard(): void { + // We're using a static configuration object for this example, but this is where + // the widget's configuration could potentially be populated from a database + const tableWidget = widgetConfig; + const widgetIndex: IWidgets = { + // Enhance the widget with information coming from it's type definition + [tableWidget.id]: + this.widgetTypesService.mergeWithWidgetType(tableWidget), + }; + + // Setting the widget dimensions and position (this is for gridster) + const positions: Record = { + [tableWidget.id]: { + cols: 12, + rows: 6, + y: 0, + x: 0, + }, + }; + + // Finally, assigning the variables we created above to the dashboard + this.dashboard = { + positions, + widgets: widgetIndex, + }; + } +} + +const TABLE_COLUMNS: ITableWidgetColumnConfig[] = [ + { + id: "column1", + label: $localize\`Beer Name\`, + isActive: true, + width: 185, + formatter: { + componentType: "RawFormatterComponent", + properties: { + dataFieldIds: { + value: "name", + }, + }, + }, + }, + { + id: "column2", + label: $localize\`Tagline\`, + isActive: true, + width: 250, + formatter: { + componentType: "RawFormatterComponent", + properties: { + dataFieldIds: { + value: "tagline", + }, + }, + }, + }, + { + id: "column3", + label: $localize\`First Brewed\`, + isActive: true, + width: 100, + formatter: { + componentType: "RawFormatterComponent", + properties: { + dataFieldIds: { + value: "first_brewed", + }, + }, + }, + }, + { + id: "column4", + label: $localize\`Description\`, + isActive: true, + formatter: { + componentType: "RawFormatterComponent", + properties: { + dataFieldIds: { + value: "description", + }, + }, + }, + }, +]; + +export const widgetConfig: IWidget = { + id: "tableWidgetId", + type: "table", + pizzagna: { + configuration: { + header: { + properties: { + title: "Stupendous Suds", + subtitle: "Try These Brilliant Brews", + }, + }, + table: { + providers: { + [WellKnownProviders.DataSource]: { + providerId: BeerDataSource.providerId, + }, + }, + properties: { + configuration: { + columns: TABLE_COLUMNS, + sortable: true, + sorterConfiguration: { + descendantSorting: false, + sortBy: "", + }, + hasVirtualScroll: true, + }, + }, + }, + }, + }, +}; +`, + "widget-types/table/table-widget-interactive/table-widget-interactive-example.component.ts": `// © 2022 SolarWinds Worldwide, LLC. All rights reserved. +// +// Permission is hereby granted, free of charge, to any person obtaining a copy +// of this software and associated documentation files (the "Software"), to +// deal in the Software without restriction, including without limitation the +// rights to use, copy, modify, merge, publish, distribute, sublicense, and/or +// sell copies of the Software, and to permit persons to whom the Software is +// furnished to do so, subject to the following conditions: +// +// The above copyright notice and this permission notice shall be included in +// all copies or substantial portions of the Software. +// +// THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR +// IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, +// FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE +// AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER +// LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, +// OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN +// THE SOFTWARE. + +import { ChangeDetectorRef, Component, OnInit } from "@angular/core"; +import { GridsterConfig, GridsterItem } from "angular-gridster2"; +import orderBy from "lodash/orderBy"; +import { BehaviorSubject, firstValueFrom, from } from "rxjs"; +import { map, tap } from "rxjs/operators"; + +import { + DataSourceService, + IDataField, + INovaFilteringOutputs, + INovaFilters, + nameof, +} from "@nova-ui/bits"; +import { + DATA_SOURCE, + DEFAULT_PIZZAGNA_ROOT, + IDashboard, + ITableWidgetColumnConfig, + IWidget, + IWidgets, + NOVA_URL_INTERACTION_HANDLER, + ProviderRegistryService, + WellKnownPathKey, + WellKnownProviders, + WidgetTypesService, +} from "@nova-ui/dashboards"; + +export const BREW_API_URL = "https://api.punkapi.com/v2/beers"; + +export interface IBrewInfo { + id: string; + name: string; + tagline: string; + first_brewed: string; + description: string; + brewers_tips: string; +} + +export interface IBrewDatasourceResponse { + brewInfo: IBrewInfo[]; + total: number; +} + +export class MockBeerDataSource extends DataSourceService { + public static providerId = "MockBeerDataSource"; + + private cache: IBrewInfo[] = []; + + public busy = new BehaviorSubject(false); + + public dataFields: Array = [ + { + id: nameof("id"), + label: "No", + dataType: "number", + sortable: true, + }, + // To indicate that a column should not be sortable, set the optional IDataField 'sortable' property to false + { + id: nameof("name"), + label: "Name", + dataType: "string", + sortable: true, + }, + { + id: nameof("tagline"), + label: "Tagline", + dataType: "string", + sortable: true, + }, + { + id: nameof("first_brewed"), + label: "First Brewed", + dataType: "string", + sortable: true, + }, + { + id: nameof("description"), + label: "Description", + dataType: "string", + sortable: false, + }, + { + id: nameof("brewers_tips"), + label: "Brewer's Tips", + dataType: "string", + sortable: false, + }, + ]; + + public async getFilteredData( + filters: INovaFilters + ): Promise { + const start = filters.virtualScroll?.value?.start ?? 0; + const end = filters.virtualScroll?.value?.end ?? 0; + + // Resetting cache on first page request + if (start === 0) { + this.cache = []; + } + + // extract sorter settings to send to the backend + // filters.sorterValue.sortBy; filters.sorterValue.direction + return firstValueFrom( + from(this.fetch(start, end)).pipe( + tap((response) => { + if (!response) { + return; + } + this.cache = this.sortData( + this.cache.concat(response.brewInfo), + filters + ); + this.dataSubject.next(this.cache); + }), + map(() => ({ + repeat: { itemsSource: this.cache }, + dataFields: this.dataFields, + })) + ) + ); + } + + public async fetch( + start: number, + end: number + ): Promise { + const delta: number = end - start; + const currentPage: number = end / delta || 0; + const response: object | Array = await ( + await fetch( + \`\${BREW_API_URL}/?page=\${currentPage}&per_page=\${delta}\` + ) + ).json(); + console.log( + "📘 table-widget-interactive-example.component: 85# -> response:", + response + ); + + // Note: In case request fails we should not proceed with mapping + if (!Array.isArray(response)) { + return undefined; + } + + return { + brewInfo: response.map((result: IBrewInfo) => ({ + id: result.id, + name: result.name, + tagline: result.tagline, + first_brewed: result.first_brewed, + description: result.description, + brewers_tips: result.brewers_tips, + })), + total: response.length, + }; + } + + private sortData(data: IBrewInfo[], filters: INovaFilters): IBrewInfo[] { + return orderBy( + data, + filters.sorter?.value?.sortBy, + filters.sorter?.value?.direction as "desc" | "asc" + ); + } +} + +/** + * A component that instantiates the dashboard + */ +@Component({ + selector: "table-widget-interactive-example", + templateUrl: "./table-widget-interactive-example.component.html", + styleUrls: ["./table-widget-interactive-example.component.less"], + standalone: false, +}) +export class TableWidgetInteractiveExampleComponent implements OnInit { + // This variable will hold all the data needed to define the layout and behavior of the widgets. + // Pass this to the dashboard component's dashboard input in the template. + public dashboard: IDashboard | undefined; + + // Angular gridster requires a configuration object even if it's empty. + // Pass this to the dashboard component's gridsterConfig input in the template. + public gridsterConfig: GridsterConfig = {}; + + // Boolean passed as an input to the dashboard. When true, widgets can be moved, resized, removed, or edited + public editMode: boolean = false; + + constructor( + // WidgetTypesService provides the widget's necessary structure information + private widgetTypesService: WidgetTypesService, + // In general, the ProviderRegistryService is used for making entities available for injection into dynamically loaded components. + private providerRegistry: ProviderRegistryService, + private changeDetectorRef: ChangeDetectorRef + ) {} + + public ngOnInit(): void { + // Grabbing the widget's default template which will be needed as a parameter for setNode + const widgetTemplate = this.widgetTypesService.getWidgetType( + "table", + 1 + ); + + // Registering our data sources as dropdown options in the widget editor/configurator + // Note: This could also be done in the parent module's constructor so that + // multiple dashboards could have access to the same widget template modification. + this.widgetTypesService.setNode( + // This is the template we grabbed above with getWidgetType + widgetTemplate, + // We are setting the editor/configurator part of the widget template + "configurator", + // This indicates which node you are changing and we want to change + // the data source providers available for selection in the editor. + WellKnownPathKey.DataSourceProviders, + // We are setting the data sources available for selection in the editor + [MockBeerDataSource.providerId] + ); + + // Registering the data source for injection into the widget. + this.providerRegistry.setProviders({ + [MockBeerDataSource.providerId]: { + provide: DATA_SOURCE, + useClass: MockBeerDataSource, + // Any dependencies that need to be injected into the provider must be listed here + deps: [], + }, + }); + + this.initializeDashboard(); + } + + /** Used for restoring widgets state */ + public reInitializeDashboard(): void { + // destroys the components and their providers so the dashboard can re init data + this.dashboard = undefined; + this.changeDetectorRef.detectChanges(); + + this.initializeDashboard(); + } + + public initializeDashboard(): void { + // We're using a static configuration object for this example, but this is where + // the widget's configuration could potentially be populated from a database + const tableWidget = widgetConfig; + const widgetIndex: IWidgets = { + // Enhance the widget with information coming from it's type definition + [tableWidget.id]: + this.widgetTypesService.mergeWithWidgetType(tableWidget), + }; + + // Setting the widget dimensions and position (this is for gridster) + const positions: Record = { + [tableWidget.id]: { + cols: 12, + rows: 6, + y: 0, + x: 0, + }, + }; + + // Finally, assigning the variables we created above to the dashboard + this.dashboard = { + positions, + widgets: widgetIndex, + }; + } +} + +const TABLE_COLUMNS: ITableWidgetColumnConfig[] = [ + { + id: "column1", + label: $localize\`Beer Name\`, + isActive: true, + width: 185, + formatter: { + componentType: "RawFormatterComponent", + properties: { + dataFieldIds: { + value: "name", + }, + }, + }, + }, + { + id: "column2", + label: $localize\`Tagline\`, + isActive: true, + width: 250, + formatter: { + componentType: "RawFormatterComponent", + properties: { + dataFieldIds: { + value: "tagline", + }, + }, + }, + }, + { + id: "column3", + label: $localize\`First Brewed\`, + isActive: true, + width: 100, + formatter: { + componentType: "RawFormatterComponent", + properties: { + dataFieldIds: { + value: "first_brewed", + }, + }, + }, + }, + { + id: "column4", + label: $localize\`Description\`, + isActive: true, + formatter: { + componentType: "RawFormatterComponent", + properties: { + dataFieldIds: { + value: "description", + }, + }, + }, + }, +]; + +export const widgetConfig: IWidget = { + id: "tableWidgetId", + type: "table", + pizzagna: { + configuration: { + [DEFAULT_PIZZAGNA_ROOT]: { + providers: { + [WellKnownProviders.InteractionHandler]: { + // Configuring the UrlInteractionHandler to handle interactions + providerId: NOVA_URL_INTERACTION_HANDLER, + properties: { + // the 'url' property tells the handler what link to use when interaction occurs on the series + url: "\${'https://untappd.com/search?q='+data.name}", + // by default the link is opened in the current window, set 'newWindow' to true to open in a new tab instead + newWindow: true, + }, + }, + }, + }, + header: { + properties: { + title: "Stupendous Suds", + subtitle: "Try These Brilliant Brews", + }, + }, + table: { + providers: { + [WellKnownProviders.DataSource]: { + providerId: MockBeerDataSource.providerId, + }, + }, + properties: { + configuration: { + // set interactions to true on the table + interactive: true, + columns: TABLE_COLUMNS, + sortable: true, + sorterConfiguration: { + descendantSorting: false, + sortBy: "", + }, + hasVirtualScroll: true, + }, + }, + }, + }, + }, +}; +`, + "widget-types/table/table-widget-paginator/table-widget-paginator-example.component.ts": `// © 2022 SolarWinds Worldwide, LLC. All rights reserved. +// +// Permission is hereby granted, free of charge, to any person obtaining a copy +// of this software and associated documentation files (the "Software"), to +// deal in the Software without restriction, including without limitation the +// rights to use, copy, modify, merge, publish, distribute, sublicense, and/or +// sell copies of the Software, and to permit persons to whom the Software is +// furnished to do so, subject to the following conditions: +// +// The above copyright notice and this permission notice shall be included in +// all copies or substantial portions of the Software. +// +// THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR +// IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, +// FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE +// AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER +// LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, +// OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN +// THE SOFTWARE. + +import { HttpClient } from "@angular/common/http"; +import { ChangeDetectorRef, Component, OnInit } from "@angular/core"; +import { GridsterConfig, GridsterItem } from "angular-gridster2"; + +import { LoggerService } from "@nova-ui/bits"; +import { + DATA_SOURCE, + DEFAULT_PIZZAGNA_ROOT, + IDashboard, + IProviderConfiguration, + ITableWidgetConfig, + IWidget, + IWidgets, + NOVA_URL_INTERACTION_HANDLER, + PizzagnaLayer, + ProviderRegistryService, + RawFormatterComponent, + ScrollType, + WellKnownPathKey, + WellKnownProviders, + WidgetTypesService, +} from "@nova-ui/dashboards"; + +import { AcmeTableMockDataSource } from "../../../../prototypes/data/table/acme-table-mock-data-source.service"; + +/** + * A component that instantiates the dashboard + */ +@Component({ + selector: "table-widget-paginator-example", + templateUrl: "./table-widget-paginator-example.component.html", + styleUrls: ["./table-widget-paginator-example.component.less"], + standalone: false, +}) +export class TableWidgetPaginatorExampleComponent implements OnInit { + public dashboard: IDashboard | undefined; + public gridsterConfig: GridsterConfig = {}; + public editMode: boolean = false; + + constructor( + private widgetTypesService: WidgetTypesService, + private providerRegistry: ProviderRegistryService, + private changeDetectorRef: ChangeDetectorRef + ) {} + + public ngOnInit(): void { + const widgetTemplate = this.widgetTypesService.getWidgetType( + "table", + 1 + ); + this.widgetTypesService.setNode( + widgetTemplate, + "configurator", + WellKnownPathKey.DataSourceProviders, + [AcmeTableMockDataSource.providerId] + ); + + this.providerRegistry.setProviders({ + [AcmeTableMockDataSource.providerId]: { + provide: DATA_SOURCE, + useClass: AcmeTableMockDataSource, + deps: [LoggerService, HttpClient], + }, + }); + + this.initializeDashboard(); + } + + /** Used for restoring widgets state */ + public reInitializeDashboard(): void { + // destroys the components and their providers so the dashboard can re init data + this.dashboard = undefined; + this.changeDetectorRef.detectChanges(); + + this.initializeDashboard(); + } + + public initializeDashboard(): void { + const tableWithPaginator = tableWidgetWithPaginator; + const tableWithVirtualScroll = tableWidgetWithVirtualScroll; + + const widgetIndex: IWidgets = { + [tableWithPaginator.id]: + this.widgetTypesService.mergeWithWidgetType(tableWithPaginator), + [tableWithVirtualScroll.id]: + this.widgetTypesService.mergeWithWidgetType( + tableWithVirtualScroll + ), + }; + + const positions: Record = { + [tableWithPaginator.id]: { + cols: 6, + rows: 6, + y: 0, + x: 0, + }, + [tableWithVirtualScroll.id]: { + cols: 6, + rows: 6, + y: 0, + x: 0, + }, + }; + + this.dashboard = { + positions, + widgets: widgetIndex, + }; + } +} + +export const tableWidgetWithPaginator: IWidget = { + id: "widget1", + type: "table", + pizzagna: { + [PizzagnaLayer.Configuration]: { + [DEFAULT_PIZZAGNA_ROOT]: { + providers: { + [WellKnownProviders.InteractionHandler]: { + providerId: NOVA_URL_INTERACTION_HANDLER, + }, + }, + }, + header: { + properties: { + title: "Table Widget with paginator!", + subtitle: "Basic table widget", + collapsible: true, + }, + }, + table: { + providers: { + [WellKnownProviders.DataSource]: { + providerId: AcmeTableMockDataSource.providerId, + } as IProviderConfiguration, + }, + properties: { + configuration: { + interactive: true, + columns: [ + { + id: "column1", + label: "No.", + isActive: true, + formatter: { + componentType: + RawFormatterComponent.lateLoadKey, + properties: { + dataFieldIds: { + value: "position", + }, + }, + }, + }, + { + id: "column2", + label: "Name", + isActive: true, + formatter: { + componentType: + RawFormatterComponent.lateLoadKey, + properties: { + dataFieldIds: { + value: "name", + }, + }, + }, + }, + { + id: "column3", + label: "Status", + isActive: true, + formatter: { + componentType: + RawFormatterComponent.lateLoadKey, + properties: { + dataFieldIds: { + value: "status", + }, + }, + }, + }, + ], + sorterConfiguration: { + descendantSorting: false, + sortBy: "column1", + }, + scrollType: ScrollType.paginator, + paginatorConfiguration: { + pageSize: 5, + pageSizeSet: [5, 10, 20, 30], + }, + hasVirtualScroll: false, + searchConfiguration: { + enabled: true, + }, + } as ITableWidgetConfig, + }, + }, + }, + }, +}; + +export const tableWidgetWithVirtualScroll: IWidget = { + id: "widget2", + type: "table", + pizzagna: { + [PizzagnaLayer.Configuration]: { + [DEFAULT_PIZZAGNA_ROOT]: { + providers: { + [WellKnownProviders.InteractionHandler]: { + providerId: NOVA_URL_INTERACTION_HANDLER, + }, + }, + }, + header: { + properties: { + title: "Table Widget with virtual scroll!", + subtitle: "Basic table widget", + collapsible: true, + }, + }, + table: { + providers: { + [WellKnownProviders.DataSource]: { + providerId: AcmeTableMockDataSource.providerId, + } as IProviderConfiguration, + }, + properties: { + configuration: { + interactive: true, + columns: [ + { + id: "column1", + label: "No.", + isActive: true, + formatter: { + componentType: + RawFormatterComponent.lateLoadKey, + properties: { + dataFieldIds: { + value: "position", + }, + }, + }, + }, + { + id: "column2", + label: "Name", + isActive: true, + formatter: { + componentType: + RawFormatterComponent.lateLoadKey, + properties: { + dataFieldIds: { + value: "name", + }, + }, + }, + }, + { + id: "column3", + label: "Status", + isActive: true, + formatter: { + componentType: + RawFormatterComponent.lateLoadKey, + properties: { + dataFieldIds: { + value: "status", + }, + }, + }, + }, + ], + sorterConfiguration: { + descendantSorting: false, + sortBy: "column1", + }, + hasVirtualScroll: true, + searchConfiguration: { + enabled: true, + }, + } as ITableWidgetConfig, + }, + }, + }, + }, +}; +`, + "widget-types/table/table-widget-search/table-widget-search-example.component.ts": `// © 2022 SolarWinds Worldwide, LLC. All rights reserved. +// +// Permission is hereby granted, free of charge, to any person obtaining a copy +// of this software and associated documentation files (the "Software"), to +// deal in the Software without restriction, including without limitation the +// rights to use, copy, modify, merge, publish, distribute, sublicense, and/or +// sell copies of the Software, and to permit persons to whom the Software is +// furnished to do so, subject to the following conditions: +// +// The above copyright notice and this permission notice shall be included in +// all copies or substantial portions of the Software. +// +// THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR +// IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, +// FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE +// AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER +// LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, +// OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN +// THE SOFTWARE. + +import { HttpClient } from "@angular/common/http"; +import { + ChangeDetectorRef, + Component, + Injectable, + OnInit, +} from "@angular/core"; +import { GridsterConfig, GridsterItem } from "angular-gridster2"; +import isEqual from "lodash/isEqual"; +import isNil from "lodash/isNil"; +import { BehaviorSubject, firstValueFrom, Observable, of, Subject } from "rxjs"; +import { + catchError, + delay, + finalize, + map, + // eslint-disable-next-line import/no-deprecated + switchMap, + tap, +} from "rxjs/operators"; + +import { + DataSourceFeatures, + DataSourceService, + IDataField, + IDataSource, + IDataSourceFeatures, + IDataSourceFeaturesConfiguration, + IDataSourceOutput, + IFilter, + IFilters, + INovaFilteringOutputs, + INovaFilters, + LoggerService, +} from "@nova-ui/bits"; +import { + DATA_SOURCE, + IDashboard, + ITableWidgetConfig, + IWidget, + IWidgets, + ProviderRegistryService, + WellKnownPathKey, + WellKnownProviders, + WidgetTypesService, +} from "@nova-ui/dashboards"; + +import { GBOOKS_API_URL } from "../../../../prototypes/data/table/constants"; + +interface IGBooksApiResponse { + kind: string; + totalItems: number; + items: IGBooksItemModel[]; + [key: string]: any; +} + +interface IGBooksItemModel { + id: string; + volumeInfo: { + title: string; + subtitle: string; + authors: string[]; + [key: string]: any; + }; + accessInfo: { [key: string]: any }; + saleInfo: { [key: string]: any }; +} + +interface IGBooksData { + books: IGBooksVolume[]; + totalItems: number; +} + +interface IGBooksVolume { + title: string; + authors: string; +} + +type searchableColumnType = "title" | "authors"; + +@Injectable() +export class AcmeTableGBooksDataSource + extends DataSourceService + implements IDataSource +{ + public static providerId = "AcmeTableGBooksDataSource"; + public static mockError = false; + + public searchableColumn: searchableColumnType = "title"; + + public page: number = 1; + public busy = new BehaviorSubject(false); + public features: IDataSourceFeaturesConfiguration; + + private cache = Array.from({ length: 0 }); + private previousFilters: INovaFilters; + // DataSource Features declared + private supportedFeatures: IDataSourceFeatures = { + search: { enabled: true }, + pagination: { enabled: true }, + }; + private columnToQueryParamMap: { [k in searchableColumnType]: string } = { + title: "intitle", + authors: "inauthor", + }; + + private applyFilters$ = new Subject(); + + public dataFields: Array = [ + { + id: "title", + label: $localize\`Title\`, + dataType: "string", + sortable: false, + }, + { + id: "authors", + label: $localize\`Authors\`, + dataType: "string", + sortable: false, + }, + ]; + + constructor(private logger: LoggerService, private http: HttpClient) { + super(); + // Using Nova DataSourceFeatures implementation for the features + this.features = new DataSourceFeatures(this.supportedFeatures); + + this.applyFilters$ + // eslint-disable-next-line import/no-deprecated + .pipe(switchMap((filters) => this.getData(filters))) + .subscribe(async (res) => { + this.outputsSubject.next(await this.getFilteredData(res)); + }); + } + + public async getFilteredData( + booksData: IGBooksData + ): Promise> { + return firstValueFrom( + of(booksData).pipe( + tap((response) => { + this.cache = this.cache.concat(response.books); + }), + map((response) => ({ + result: { + repeat: { itemsSource: this.cache }, + paginator: { total: response.totalItems }, + dataFields: this.dataFields, + }, + })) + ) + ); + } + + private getData(filters: INovaFilters): Observable { + if ( + this.isNewSearchTerm(filters.search) && + filters.virtualScroll?.value.start === 0 + ) { + this.cache = []; + } + + return this.http + .get(this.getComposedUrl(filters)) + .pipe( + tap(() => this.busy.next(true)), + delay(300), // mock + map((response) => ({ + books: + response.items?.map((volume) => ({ + title: volume.volumeInfo.title, + authors: + volume.volumeInfo.authors?.join(", ") || "", + })) || [], + totalItems: response.totalItems, + })), + catchError((e) => { + this.logger.error(e); + return of({ + books: [], + totalItems: 0, + }); + }), + finalize(() => { + this.busy.next(false); + this.previousFilters = filters; + }) + ); + } + + private getComposedUrl(filters: INovaFilters) { + const initialUrl = \`\${GBOOKS_API_URL}?q=\`; + const maxResults = \`maxResults=\${ + (filters.virtualScroll?.value.end || 0) - + (filters.virtualScroll?.value.start || 0) + }\`; + + const virtualScrollPart = filters.virtualScroll + ? \`startIndex=\${filters.virtualScroll.value.start}\` + : ""; + + const searchQueryParam = + this.columnToQueryParamMap[this.searchableColumn]; + const searchPart = filters.search + ? \`\${searchQueryParam}:\${filters.search.value}\` + : "_"; // google books api requires some criteria to do the search + + return \`\${initialUrl}\${searchPart}&\${maxResults}&\${virtualScrollPart}&filter=full\`; + } + + private isNewSearchTerm(search: IFilter | undefined) { + return ( + !isNil(search?.value) && + !isEqual(search?.value, this.previousFilters?.search?.value) + ); + } + + // redefine parent method + public async applyFilters(): Promise { + this.applyFilters$.next(this.getFilters()); + } +} + +/** + * A component that instantiates the dashboard + */ +@Component({ + selector: "table-widget-search-example", + templateUrl: "./table-widget-search-example.component.html", + styleUrls: ["./table-widget-search-example.component.less"], + standalone: false, +}) +export class TableWidgetSearchExampleComponent implements OnInit { + public dashboard: IDashboard | undefined; + public gridsterConfig: GridsterConfig = {}; + public editMode: boolean = false; + + constructor( + private widgetTypesService: WidgetTypesService, + private providerRegistry: ProviderRegistryService, + private changeDetectorRef: ChangeDetectorRef + ) {} + + public ngOnInit(): void { + const widgetTemplate = this.widgetTypesService.getWidgetType( + "table", + 1 + ); + this.widgetTypesService.setNode( + widgetTemplate, + "configurator", + WellKnownPathKey.DataSourceProviders, + [AcmeTableGBooksDataSource.providerId] + ); + + this.providerRegistry.setProviders({ + [AcmeTableGBooksDataSource.providerId]: { + provide: DATA_SOURCE, + useClass: AcmeTableGBooksDataSource, + deps: [LoggerService, HttpClient], + }, + }); + + this.initializeDashboard(); + } + + /** Used for restoring widgets state */ + public reInitializeDashboard(): void { + // destroys the components and their providers so the dashboard can re init data + this.dashboard = undefined; + this.changeDetectorRef.detectChanges(); + + this.initializeDashboard(); + } + + public initializeDashboard(): void { + const tableWidget = widgetConfig; + const widgetIndex: IWidgets = { + [tableWidget.id]: + this.widgetTypesService.mergeWithWidgetType(tableWidget), + }; + + const positions: Record = { + [tableWidget.id]: { + cols: 12, + rows: 6, + y: 0, + x: 0, + }, + }; + + this.dashboard = { + positions, + widgets: widgetIndex, + }; + } +} + +export const widgetConfig: IWidget = { + id: "tableWidgetId", + type: "table", + pizzagna: { + configuration: { + header: { + properties: { + title: "Google Books", + }, + }, + table: { + providers: { + [WellKnownProviders.DataSource]: { + providerId: AcmeTableGBooksDataSource.providerId, + }, + }, + properties: { + configuration: { + columns: [ + { + id: "column1", + label: $localize\`Title\`, + isActive: true, + formatter: { + componentType: "RawFormatterComponent", + properties: { + dataFieldIds: { + value: "title", + }, + }, + }, + }, + { + id: "column2", + label: $localize\`Author\`, + isActive: true, + formatter: { + componentType: "RawFormatterComponent", + properties: { + dataFieldIds: { + value: "authors", + }, + }, + }, + }, + ], + sortable: false, + // define search configuration here + searchConfiguration: { + enabled: true, + // following properties below can be configured as well + // searchTerm: "search criteria here", + // searchDebounce: 300, + }, + hasVirtualScroll: true, + } as ITableWidgetConfig, + }, + }, + }, + }, +}; +`, + "widget-types/table/table-widget-search-docs.component.ts": `// © 2022 SolarWinds Worldwide, LLC. All rights reserved. +// +// Permission is hereby granted, free of charge, to any person obtaining a copy +// of this software and associated documentation files (the "Software"), to +// deal in the Software without restriction, including without limitation the +// rights to use, copy, modify, merge, publish, distribute, sublicense, and/or +// sell copies of the Software, and to permit persons to whom the Software is +// furnished to do so, subject to the following conditions: +// +// The above copyright notice and this permission notice shall be included in +// all copies or substantial portions of the Software. +// +// THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR +// IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, +// FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE +// AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER +// LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, +// OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN +// THE SOFTWARE. + +import { Component } from "@angular/core"; + +@Component({ + selector: "nui-table-search-docs", + templateUrl: "./table-widget-search-docs.component.html", + standalone: false, +}) +export class TableSearchDocsComponent { + public featuredDeclaredText = \` + private supportedFeatures: IDataSourceFeatures = { + search: { enabled: true }, + pagination: { enabled: true }, + };\`; + public featuresUsedText = \` + this.features = new DataSourceFeatures(this.supportedFeatures); + \`; + public tableConfigurationText = \` + "table": { + ... + properties: { + configuration: { + // define search configuration here + searchConfiguration: { + enabled: true, + // following optional properties below can be configured as well + // searchTerm: "search criteria here", + // searchDebounce: 300, + }, + } as ITableWidgetConfig, + }, + }, + \`; +} +`, + "widget-types/table/table-widget-selectable/table-widget-selectable-multi/table-widget-selectable-multi.example.component.ts": `// © 2022 SolarWinds Worldwide, LLC. All rights reserved. +// +// Permission is hereby granted, free of charge, to any person obtaining a copy +// of this software and associated documentation files (the "Software"), to +// deal in the Software without restriction, including without limitation the +// rights to use, copy, modify, merge, publish, distribute, sublicense, and/or +// sell copies of the Software, and to permit persons to whom the Software is +// furnished to do so, subject to the following conditions: +// +// The above copyright notice and this permission notice shall be included in +// all copies or substantial portions of the Software. +// +// THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR +// IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, +// FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE +// AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER +// LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, +// OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN +// THE SOFTWARE. + +import { Component } from "@angular/core"; + +import { TableSelectionMode } from "@nova-ui/bits"; +import { TableWidgetSelectionConfig } from "@nova-ui/dashboards"; + +/** + * A component that instantiates the dashboard + */ +@Component({ + selector: "table-widget-selectable-multi-example", + templateUrl: "./table-widget-selectable-multi.example.component.html", + styleUrls: ["./table-widget-selectable-multi.example.component.less"], + standalone: false, +}) +export class TableWidgetSelectableMultiExampleComponent { + public selectionConfiguration: TableWidgetSelectionConfig = { + enabled: true, + selectionMode: TableSelectionMode.Multi, + trackByProperty: "id", + clickableRow: true, + }; +} +`, + "widget-types/table/table-widget-selectable/table-widget-selectable-radio/table-widget-selectable-radio.example.component.ts": `// © 2022 SolarWinds Worldwide, LLC. All rights reserved. +// +// Permission is hereby granted, free of charge, to any person obtaining a copy +// of this software and associated documentation files (the "Software"), to +// deal in the Software without restriction, including without limitation the +// rights to use, copy, modify, merge, publish, distribute, sublicense, and/or +// sell copies of the Software, and to permit persons to whom the Software is +// furnished to do so, subject to the following conditions: +// +// The above copyright notice and this permission notice shall be included in +// all copies or substantial portions of the Software. +// +// THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR +// IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, +// FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE +// AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER +// LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, +// OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN +// THE SOFTWARE. + +import { Component } from "@angular/core"; + +import { TableSelectionMode } from "@nova-ui/bits"; +import { TableWidgetSelectionConfig } from "@nova-ui/dashboards"; + +/** + * A component that instantiates the dashboard + */ +@Component({ + selector: "table-widget-selectable-radio-example", + templateUrl: "./table-widget-selectable-radio.example.component.html", + styleUrls: ["./table-widget-selectable-radio.example.component.less"], + standalone: false, +}) +export class TableWidgetSelectableRadioExampleComponent { + public selectionConfiguration: TableWidgetSelectionConfig = { + enabled: true, + selectionMode: TableSelectionMode.Radio, + trackByProperty: "id", + clickableRow: true, + }; +} +`, + "widget-types/table/table-widget-selectable/table-widget-selectable-single/table-widget-selectable-single.example.component.ts": `// © 2022 SolarWinds Worldwide, LLC. All rights reserved. +// +// Permission is hereby granted, free of charge, to any person obtaining a copy +// of this software and associated documentation files (the "Software"), to +// deal in the Software without restriction, including without limitation the +// rights to use, copy, modify, merge, publish, distribute, sublicense, and/or +// sell copies of the Software, and to permit persons to whom the Software is +// furnished to do so, subject to the following conditions: +// +// The above copyright notice and this permission notice shall be included in +// all copies or substantial portions of the Software. +// +// THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR +// IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, +// FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE +// AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER +// LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, +// OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN +// THE SOFTWARE. + +import { Component } from "@angular/core"; + +import { TableSelectionMode } from "@nova-ui/bits"; +import { TableWidgetSelectionConfig } from "@nova-ui/dashboards"; + +/** + * A component that instantiates the dashboard + */ +@Component({ + selector: "table-widget-selectable-single-example", + templateUrl: "./table-widget-selectable-single.example.component.html", + styleUrls: ["./table-widget-selectable-single.example.component.less"], + standalone: false, +}) +export class TableWidgetSelectableSingleExampleComponent { + public selectionConfiguration: TableWidgetSelectionConfig = { + enabled: true, + selectionMode: TableSelectionMode.Single, + trackByProperty: "id", + }; +} +`, + "widget-types/table/table-widget-selectable/table-widget-selectable.example.component.ts": `// © 2022 SolarWinds Worldwide, LLC. All rights reserved. +// +// Permission is hereby granted, free of charge, to any person obtaining a copy +// of this software and associated documentation files (the "Software"), to +// deal in the Software without restriction, including without limitation the +// rights to use, copy, modify, merge, publish, distribute, sublicense, and/or +// sell copies of the Software, and to permit persons to whom the Software is +// furnished to do so, subject to the following conditions: +// +// The above copyright notice and this permission notice shall be included in +// all copies or substantial portions of the Software. +// +// THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR +// IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, +// FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE +// AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER +// LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, +// OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN +// THE SOFTWARE. + +import { HttpClient } from "@angular/common/http"; +import { ChangeDetectorRef, Component, Input, OnInit } from "@angular/core"; +import { GridsterConfig, GridsterItem } from "angular-gridster2"; + +import { LoggerService, TableSelectionMode } from "@nova-ui/bits"; +import { + DATA_SOURCE, + DEFAULT_PIZZAGNA_ROOT, + IDashboard, + IProviderConfiguration, + ITableWidgetConfig, + IWidget, + IWidgets, + NOVA_URL_INTERACTION_HANDLER, + PizzagnaLayer, + ProviderRegistryService, + RawFormatterComponent, + TableWidgetSelectionConfig, + WellKnownPathKey, + WellKnownProviders, + WidgetTypesService, +} from "@nova-ui/dashboards"; + +import { AcmeTableMockDataSource } from "../../../../prototypes/data/table/acme-table-mock-data-source.service"; + +/** + * A component that instantiates the dashboard + */ +@Component({ + selector: "table-widget-selectable-example", + templateUrl: "./table-widget-selectable.example.component.html", + styleUrls: ["./table-widget-selectable.example.component.less"], + standalone: false, +}) +export class TableWidgetSelectableExampleComponent implements OnInit { + public dashboard: IDashboard | undefined; + public gridsterConfig: GridsterConfig = {}; + public editMode: boolean = false; + + @Input() public selectionConfiguration: TableWidgetSelectionConfig = { + enabled: false, + selectionMode: TableSelectionMode.None, + }; + + constructor( + private widgetTypesService: WidgetTypesService, + private providerRegistry: ProviderRegistryService, + private changeDetectorRef: ChangeDetectorRef + ) {} + + public ngOnInit(): void { + const widgetTemplate = this.widgetTypesService.getWidgetType( + "table", + 1 + ); + this.widgetTypesService.setNode( + widgetTemplate, + "configurator", + WellKnownPathKey.DataSourceProviders, + [AcmeTableMockDataSource.providerId] + ); + + this.providerRegistry.setProviders({ + [AcmeTableMockDataSource.providerId]: { + provide: DATA_SOURCE, + useClass: AcmeTableMockDataSource, + deps: [LoggerService, HttpClient], + }, + }); + + this.initializeDashboard(); + } + + /** Used for restoring widgets state */ + public reInitializeDashboard(): void { + // destroys the components and their providers so the dashboard can re init data + this.dashboard = undefined; + this.changeDetectorRef.detectChanges(); + + this.initializeDashboard(); + } + + public initializeDashboard(): void { + const tableWidget = this.widgetConfig; + const widgetIndex: IWidgets = { + [tableWidget.id]: + this.widgetTypesService.mergeWithWidgetType(tableWidget), + }; + + const positions: Record = { + [tableWidget.id]: { + cols: 12, + rows: 6, + y: 0, + x: 0, + }, + }; + + this.dashboard = { + positions, + widgets: widgetIndex, + }; + } + + private get widgetConfig(): IWidget { + return { + id: "widget1", + type: "table", + pizzagna: { + [PizzagnaLayer.Configuration]: { + [DEFAULT_PIZZAGNA_ROOT]: { + providers: { + [WellKnownProviders.InteractionHandler]: { + providerId: NOVA_URL_INTERACTION_HANDLER, + }, + }, + }, + header: { + properties: { + title: "Table Widget with Selection!", + subtitle: "Basic table widget", + collapsible: true, + }, + }, + table: { + providers: { + [WellKnownProviders.DataSource]: { + providerId: AcmeTableMockDataSource.providerId, + } as IProviderConfiguration, + }, + properties: { + configuration: { + // enabling selection here + selectionConfiguration: + this.selectionConfiguration, + columns: [ + { + id: "column1", + label: "No.", + isActive: true, + formatter: { + componentType: + RawFormatterComponent.lateLoadKey, + properties: { + dataFieldIds: { + value: "position", + }, + }, + }, + }, + { + id: "column2", + label: "Name", + isActive: true, + formatter: { + componentType: + RawFormatterComponent.lateLoadKey, + properties: { + dataFieldIds: { + value: "name", + }, + }, + }, + }, + { + id: "column3", + label: "Status", + isActive: true, + formatter: { + componentType: + RawFormatterComponent.lateLoadKey, + properties: { + dataFieldIds: { + value: "status", + }, + }, + }, + }, + ], + } as ITableWidgetConfig, + }, + }, + }, + }, + }; + } +} +`, + "widget-types/timeseries/timeseries-docs.component.ts": `// © 2022 SolarWinds Worldwide, LLC. All rights reserved. +// +// Permission is hereby granted, free of charge, to any person obtaining a copy +// of this software and associated documentation files (the "Software"), to +// deal in the Software without restriction, including without limitation the +// rights to use, copy, modify, merge, publish, distribute, sublicense, and/or +// sell copies of the Software, and to permit persons to whom the Software is +// furnished to do so, subject to the following conditions: +// +// The above copyright notice and this permission notice shall be included in +// all copies or substantial portions of the Software. +// +// THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR +// IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, +// FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE +// AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER +// LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, +// OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN +// THE SOFTWARE. + +import { Component, OnInit } from "@angular/core"; + +import { mapContentFile } from "../../../../demo-files-factory"; + +@Component({ + selector: "nui-timeseries-docs", + templateUrl: "./timeseries-docs.component.html", + standalone: false, +}) +export class TimeseriesDocsComponent implements OnInit { + public timeseriesWidgetFileText = ""; + public timeseriesConfiguratorFileText = ""; + + async ngOnInit(): Promise { + this.timeseriesWidgetFileText = await import( + "./../../../../../../src/lib/widget-types/timeseries/timeseries-widget" + ).then(mapContentFile); + this.timeseriesConfiguratorFileText = await import( + "./../../../../../../src/lib/widget-types/timeseries/timeseries-configurator" + ).then(mapContentFile); + } +} +`, + "widget-types/timeseries/timeseries-docs.module.ts": `// © 2022 SolarWinds Worldwide, LLC. All rights reserved. +// +// Permission is hereby granted, free of charge, to any person obtaining a copy +// of this software and associated documentation files (the "Software"), to +// deal in the Software without restriction, including without limitation the +// rights to use, copy, modify, merge, publish, distribute, sublicense, and/or +// sell copies of the Software, and to permit persons to whom the Software is +// furnished to do so, subject to the following conditions: +// +// The above copyright notice and this permission notice shall be included in +// all copies or substantial portions of the Software. +// +// THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR +// IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, +// FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE +// AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER +// LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, +// OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN +// THE SOFTWARE. + +import { NgModule } from "@angular/core"; +import { RouterModule, Routes } from "@angular/router"; + +import { DEMO_PATH_TOKEN } from "@nova-ui/bits"; +import { + NuiButtonModule, + NuiDocsModule, + NuiMessageModule, + NuiSwitchModule, +} from "@nova-ui/bits"; +import { NuiDashboardsModule } from "@nova-ui/dashboards"; + +import { TimeseriesDocsComponent } from "./timeseries-docs.component"; +import { TimeseriesWidgetExampleComponent } from "./timeseries-widget-example/timeseries-widget-example.component"; +import { TimeseriesWidgetInteractiveExampleComponent } from "./timeseries-widget-interactive-example/timeseries-widget-interactive-example.component"; +import { TimeseriesWidgetStatusBarExampleComponent } from "./timeseries-widget-status-bar-example/timeseries-widget-status-bar-example.component"; +import { getDemoFiles } from "../../../../demo-files-factory"; + +const routes: Routes = [ + { + path: "", + component: TimeseriesDocsComponent, + data: { + srlc: { + hideIndicator: true, + }, + showThemeSwitcher: true, + }, + }, + { + path: "example", + component: TimeseriesWidgetExampleComponent, + data: { + srlc: { + hideIndicator: true, + }, + }, + }, +]; + +@NgModule({ + imports: [ + RouterModule.forChild(routes), + NuiButtonModule, + NuiDocsModule, + NuiMessageModule, + NuiSwitchModule, + NuiDashboardsModule, + ], + declarations: [ + TimeseriesDocsComponent, + TimeseriesWidgetExampleComponent, + TimeseriesWidgetInteractiveExampleComponent, + TimeseriesWidgetStatusBarExampleComponent, + ], + providers: [ + { + provide: DEMO_PATH_TOKEN, + useValue: getDemoFiles("timeseries"), + }, + ], +}) +export default class TimeseriesDocsModule {} +`, + "widget-types/timeseries/timeseries-widget-example/timeseries-widget-example.component.ts": `// © 2022 SolarWinds Worldwide, LLC. All rights reserved. +// +// Permission is hereby granted, free of charge, to any person obtaining a copy +// of this software and associated documentation files (the "Software"), to +// deal in the Software without restriction, including without limitation the +// rights to use, copy, modify, merge, publish, distribute, sublicense, and/or +// sell copies of the Software, and to permit persons to whom the Software is +// furnished to do so, subject to the following conditions: +// +// The above copyright notice and this permission notice shall be included in +// all copies or substantial portions of the Software. +// +// THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR +// IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, +// FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE +// AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER +// LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, +// OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN +// THE SOFTWARE. + +import { + ChangeDetectorRef, + Component, + Injectable, + OnInit, +} from "@angular/core"; +import { GridsterConfig, GridsterItem } from "angular-gridster2"; +import cloneDeep from "lodash/cloneDeep"; +import keyBy from "lodash/keyBy"; +import moment, { Moment } from "moment/moment"; +import { BehaviorSubject } from "rxjs"; + +import { + DataSourceService, + IDataSource, + INovaFilters, + ITimeframe, +} from "@nova-ui/bits"; +import { + DATA_SOURCE, + DEFAULT_PIZZAGNA_ROOT, + IDashboard, + IDataSourceOutput, + IProviderConfiguration, + ISerializableTimeframe, + ITimeseriesItemConfiguration, + ITimeseriesOutput, + ITimeseriesScaleConfig, + ITimeseriesWidgetConfig, + ITimeseriesWidgetData, + ITimeseriesWidgetSeriesData, + IWidget, + LegendPlacement, + PizzagnaLayer, + ProviderRegistryService, + TimeseriesChartPreset, + TimeseriesScaleType, + WellKnownPathKey, + WellKnownProviders, + WidgetTypesService, +} from "@nova-ui/dashboards"; + +/** + * A simple Timeseries data source implementation + */ +@Injectable() +export class BeerVsReadingMockDataSource + extends DataSourceService + implements IDataSource +{ + public static providerId = "BeerVsReadingMockDataSource"; + + public busy = new BehaviorSubject(false); + + public async getFilteredData( + filters: INovaFilters + ): Promise> { + // In this example we're using some static mock data located at the bottom of this file. In a real-world + // scenario, the data for the chart would likely be retrieved via an asynchronous backend call. + let filteredData = getData(); + + this.busy.next(true); + + // Filtering using the filter registered by the TimeFrameBar + const timeframeFilter = filters.timeframe?.value as ITimeframe; + if (timeframeFilter) { + filteredData = filteredData.map((item: ITimeseriesWidgetData) => ({ + id: item.id, + name: item.name, + description: item.description, + data: item.data.filter( + (seriesData: ITimeseriesWidgetSeriesData) => + filterDates( + seriesData.x, + timeframeFilter.startDatetime, + timeframeFilter.endDatetime + ) + ), + })); + } + + this.busy.next(false); + + return { result: { series: filteredData } }; + } +} + +function filterDates(dateToCheck: Date, startDate: Moment, endDate: Moment) { + const mom = moment(dateToCheck); + return ( + mom.isBetween(startDate, endDate) || + mom.isSame(startDate) || + mom.isSame(endDate) + ); +} + +/** + * A component that instantiates the dashboard + */ +@Component({ + selector: "timeseries-widget-example", + templateUrl: "./timeseries-widget-example.component.html", + styleUrls: ["./timeseries-widget-example.component.less"], + standalone: false, +}) +export class TimeseriesWidgetExampleComponent implements OnInit { + // This variable will hold all the data needed to define the layout and behavior of the widgets. + // Pass this to the dashboard component's dashboard input in the template. + public dashboard: IDashboard | undefined; + + // Angular gridster requires a configuration object even if it's empty. + // Pass this to the dashboard component's gridsterConfig input in the template. + public gridsterConfig: GridsterConfig = {}; + + // Boolean passed as an input to the dashboard. When true, widgets can be moved, resized, removed, or edited + public editMode: boolean = false; + + constructor( + // WidgetTypesService provides the widget's necessary structure information + private widgetTypesService: WidgetTypesService, + + // In general, the ProviderRegistryService is used for making entities available for injection into dynamically loaded components. + private providerRegistry: ProviderRegistryService, + + // Angular's ChangeDetectorRef + private changeDetectorRef: ChangeDetectorRef + ) {} + + public ngOnInit(): void { + // Grabbing the widget's default template which will be needed as a parameter for setNode + const widgetTemplate = this.widgetTypesService.getWidgetType( + "timeseries", + 1 + ); + // Registering our data sources as dropdown options in the widget editor/configurator + // Note: This could also be done in the parent module's constructor so that + // multiple dashboards could have access to the same widget template modification. + this.widgetTypesService.setNode( + // This is the template we grabbed above with getWidgetType + widgetTemplate, + // We are setting the editor/configurator part of the widget template + "configurator", + // This indicates which node you are changing and we want to change + // the data source providers available for selection in the editor. + WellKnownPathKey.DataSourceProviders, + // We are setting the data sources available for selection in the editor + [BeerVsReadingMockDataSource.providerId] + ); + + // Registering the data source for injection into the widget. + this.providerRegistry.setProviders({ + [BeerVsReadingMockDataSource.providerId]: { + provide: DATA_SOURCE, + useClass: BeerVsReadingMockDataSource, + deps: [], + }, + }); + + this.initializeDashboard(); + } + + public initializeDashboard(): void { + // We're using a static configuration object for this example, but this is where + // the widget's configuration could potentially be populated from a database + const widgetsWithStructure = widgetConfigs.map((w) => + this.widgetTypesService.mergeWithWidgetType(w) + ); + const widgetsIndex = keyBy(widgetsWithStructure, (w: IWidget) => w.id); + + // Finally, assigning the variables we created above to the dashboard + this.dashboard = { + positions: cloneDeep(positions), + widgets: widgetsIndex, + }; + } + + /** Used for restoring widgets state */ + public reInitializeDashboard(): void { + // destroys the components and their providers so the dashboard can re init data + this.dashboard = undefined; + this.changeDetectorRef.detectChanges(); + + this.initializeDashboard(); + } +} + +const widgetConfigs: IWidget[] = [ + { + id: "lineWidgetId", + type: "timeseries", + pizzagna: { + [PizzagnaLayer.Configuration]: { + [DEFAULT_PIZZAGNA_ROOT]: { + providers: { + [WellKnownProviders.DataSource]: { + // Setting the initially selected data source providerId + providerId: BeerVsReadingMockDataSource.providerId, + } as IProviderConfiguration, + }, + }, + header: { + properties: { + title: "Line Chart", + subtitle: "Survey of 1000 Solarians", + }, + }, + chart: { + providers: { + [WellKnownProviders.Adapter]: { + properties: { + // Setting the series and corresponding labels to initially display on the chart + series: [ + { + id: "series-1", + label: "Beer Tasting", + selectedSeriesId: "series-1", + }, + { + id: "series-2", + label: "Reading", + selectedSeriesId: "series-2", + }, + ] as ITimeseriesItemConfiguration[], + }, + } as Partial, + }, + properties: { + // Setting the general chart configuration + configuration: { + legendPlacement: LegendPlacement.Right, + enableZoom: true, + leftAxisLabel: "Solarians (%)", + // You can optionally define custom colors for the chart by setting the 'chartColors' configuration property + // "chartColors": [ + // "var(--nui-color-chart-eight)", + // "var(--nui-color-chart-nine)", + // "var(--nui-color-chart-ten)", + // ], + } as ITimeseriesWidgetConfig, + }, + }, + timeframeSelection: { + properties: { + // Setting the initial timeframe selected in the timeframe bar + timeframe: { + selectedPresetId: "last7Days", + } as ISerializableTimeframe, + minDate: moment().subtract(60, "days").format(), + maxDate: moment().format(), + }, + }, + }, + }, + }, + { + id: "stackedAreaWidgetId", + type: "timeseries", + pizzagna: { + [PizzagnaLayer.Configuration]: { + [DEFAULT_PIZZAGNA_ROOT]: { + providers: { + [WellKnownProviders.DataSource]: { + // Setting the initially selected data source providerId + providerId: BeerVsReadingMockDataSource.providerId, + } as IProviderConfiguration, + }, + }, + header: { + properties: { + title: "Stacked Area Chart", + subtitle: "Survey of 1000 Solarians", + }, + }, + chart: { + providers: { + [WellKnownProviders.Adapter]: { + properties: { + // Setting the series and corresponding labels to initially display on the chart + series: [ + { + id: "series-1", + label: "Beer Tasting", + selectedSeriesId: "series-1", + }, + { + id: "series-2", + label: "Reading", + selectedSeriesId: "series-2", + }, + ] as ITimeseriesItemConfiguration[], + }, + } as Partial, + }, + properties: { + // Setting the general chart configuration + configuration: { + legendPlacement: LegendPlacement.Right, + enableZoom: true, + // Setting the preset to stacked area + preset: TimeseriesChartPreset.StackedArea, + leftAxisLabel: "Solarians (%)", + // You can optionally define custom colors for the chart by setting the 'chartColors' configuration property + // "chartColors": [ + // "var(--nui-color-chart-eight)", + // "var(--nui-color-chart-nine)", + // "var(--nui-color-chart-ten)", + // ], + } as ITimeseriesWidgetConfig, + }, + }, + timeframeSelection: { + properties: { + // Setting the initial timeframe selected in the timeframe bar + timeframe: { + selectedPresetId: "last7Days", + } as ISerializableTimeframe, + minDate: moment().subtract(60, "days").format(), + maxDate: moment().format(), + }, + }, + }, + }, + }, + { + id: "stackedPercentageAreaWidgetId", + type: "timeseries", + pizzagna: { + [PizzagnaLayer.Configuration]: { + [DEFAULT_PIZZAGNA_ROOT]: { + providers: { + [WellKnownProviders.DataSource]: { + // Setting the initially selected data source providerId + providerId: BeerVsReadingMockDataSource.providerId, + } as IProviderConfiguration, + }, + }, + header: { + properties: { + title: "Stacked Percentage Area Chart", + subtitle: "Survey of 1000 Solarians", + }, + }, + chart: { + providers: { + [WellKnownProviders.Adapter]: { + properties: { + // Setting the series and corresponding labels to initially display on the chart + series: [ + { + id: "series-1", + label: "Beer Tasting", + selectedSeriesId: "series-1", + }, + { + id: "series-2", + label: "Reading", + selectedSeriesId: "series-2", + }, + ] as ITimeseriesItemConfiguration[], + }, + } as Partial, + }, + properties: { + // Setting the general chart configuration + configuration: { + legendPlacement: LegendPlacement.Right, + enableZoom: true, + // Setting the preset to stacked percentage area + preset: TimeseriesChartPreset.StackedPercentageArea, + leftAxisLabel: "Solarians (%)", + // You can optionally define custom colors for the chart by setting the 'chartColors' configuration property + // "chartColors": [ + // "var(--nui-color-chart-eight)", + // "var(--nui-color-chart-nine)", + // "var(--nui-color-chart-ten)", + // ], + } as ITimeseriesWidgetConfig, + }, + }, + timeframeSelection: { + properties: { + // Setting the initial timeframe selected in the timeframe bar + timeframe: { + selectedPresetId: "last7Days", + } as ISerializableTimeframe, + minDate: moment().subtract(60, "days").format(), + maxDate: moment().format(), + }, + }, + }, + }, + }, + { + id: "stackedBarWidgetId", + type: "timeseries", + pizzagna: { + [PizzagnaLayer.Configuration]: { + [DEFAULT_PIZZAGNA_ROOT]: { + providers: { + [WellKnownProviders.DataSource]: { + // Setting the initially selected data source providerId + providerId: BeerVsReadingMockDataSource.providerId, + } as IProviderConfiguration, + }, + }, + header: { + properties: { + title: "Stacked Bar Chart", + subtitle: "Survey of 1000 Solarians", + }, + }, + chart: { + providers: { + [WellKnownProviders.Adapter]: { + properties: { + // Setting the series and corresponding labels to initially display on the chart + series: [ + { + id: "series-1", + label: "Beer Tasting", + selectedSeriesId: "series-1", + }, + { + id: "series-2", + label: "Reading", + selectedSeriesId: "series-2", + }, + ] as ITimeseriesItemConfiguration[], + }, + } as Partial, + }, + properties: { + configuration: { + legendPlacement: LegendPlacement.Right, + enableZoom: true, + leftAxisLabel: "Solarians (%)", + // Setting the preset to stacked bar + preset: TimeseriesChartPreset.StackedBar, + scales: { + x: { + type: TimeseriesScaleType.TimeInterval, + properties: { + interval: 24 * 60 * 60, + }, + } as ITimeseriesScaleConfig, + }, + } as ITimeseriesWidgetConfig, + }, + }, + timeframeSelection: { + properties: { + // Setting the initial timeframe selected in the timeframe bar + timeframe: { + selectedPresetId: "last7Days", + } as ISerializableTimeframe, + minDate: moment().subtract(60, "days").format(), + maxDate: moment().format(), + }, + }, + }, + }, + }, +]; + +// using startOf("day") so that each band for the bar chart corresponds to a calendar day +const now = moment().startOf("day"); + +export const getData = (): ITimeseriesWidgetData[] => [ + { + id: "series-1", + name: "Beer Tasting", + description: "Havin' some suds", + data: [ + { x: now.clone().subtract(20, "day").toDate(), y: 30 }, + { x: now.clone().subtract(19, "day").toDate(), y: 35 }, + { x: now.clone().subtract(18, "day").toDate(), y: 33 }, + { x: now.clone().subtract(17, "day").toDate(), y: 40 }, + { x: now.clone().subtract(16, "day").toDate(), y: 35 }, + { x: now.clone().subtract(15, "day").toDate(), y: 30 }, + { x: now.clone().subtract(14, "day").toDate(), y: 35 }, + { x: now.clone().subtract(13, "day").toDate(), y: 15 }, + { x: now.clone().subtract(12, "day").toDate(), y: 30 }, + { x: now.clone().subtract(11, "day").toDate(), y: 45 }, + { x: now.clone().subtract(10, "day").toDate(), y: 60 }, + { x: now.clone().subtract(9, "day").toDate(), y: 54 }, + { x: now.clone().subtract(8, "day").toDate(), y: 42 }, + { x: now.clone().subtract(7, "day").toDate(), y: 44 }, + { x: now.clone().subtract(6, "day").toDate(), y: 54 }, + { x: now.clone().subtract(5, "day").toDate(), y: 43 }, + { x: now.clone().subtract(4, "day").toDate(), y: 76 }, + { x: now.clone().subtract(3, "day").toDate(), y: 54 }, + { x: now.clone().subtract(2, "day").toDate(), y: 42 }, + { x: now.clone().subtract(1, "day").toDate(), y: 34 }, + ], + }, + { + id: "series-2", + name: "Reading", + description: "Hittin' the books", + data: [ + { x: now.clone().subtract(20, "day").toDate(), y: 60 }, + { x: now.clone().subtract(19, "day").toDate(), y: 64 }, + { x: now.clone().subtract(18, "day").toDate(), y: 70 }, + { x: now.clone().subtract(17, "day").toDate(), y: 55 }, + { x: now.clone().subtract(16, "day").toDate(), y: 55 }, + { x: now.clone().subtract(15, "day").toDate(), y: 45 }, + { x: now.clone().subtract(14, "day").toDate(), y: 60 }, + { x: now.clone().subtract(13, "day").toDate(), y: 65 }, + { x: now.clone().subtract(12, "day").toDate(), y: 63 }, + { x: now.clone().subtract(11, "day").toDate(), y: 60 }, + { x: now.clone().subtract(10, "day").toDate(), y: 61 }, + { x: now.clone().subtract(9, "day").toDate(), y: 65 }, + { x: now.clone().subtract(8, "day").toDate(), y: 63 }, + { x: now.clone().subtract(7, "day").toDate(), y: 58 }, + { x: now.clone().subtract(6, "day").toDate(), y: 64 }, + { x: now.clone().subtract(5, "day").toDate(), y: 63 }, + { x: now.clone().subtract(4, "day").toDate(), y: 60 }, + { x: now.clone().subtract(3, "day").toDate(), y: 62 }, + { x: now.clone().subtract(2, "day").toDate(), y: 61 }, + { x: now.clone().subtract(1, "day").toDate(), y: 62 }, + ], + }, +]; + +// Setting the widget dimensions and position (this is for gridster) +const positions: Record = { + [widgetConfigs[0].id]: { + cols: 6, + rows: 6, + y: 0, + x: 0, + }, + [widgetConfigs[1].id]: { + cols: 6, + rows: 6, + y: 0, + x: 6, + }, + [widgetConfigs[3].id]: { + cols: 6, + rows: 6, + y: 6, + x: 0, + }, + [widgetConfigs[2].id]: { + cols: 6, + rows: 6, + y: 6, + x: 6, + }, +}; +`, + "widget-types/timeseries/timeseries-widget-interactive-example/timeseries-widget-interactive-example.component.ts": `// © 2022 SolarWinds Worldwide, LLC. All rights reserved. +// +// Permission is hereby granted, free of charge, to any person obtaining a copy +// of this software and associated documentation files (the "Software"), to +// deal in the Software without restriction, including without limitation the +// rights to use, copy, modify, merge, publish, distribute, sublicense, and/or +// sell copies of the Software, and to permit persons to whom the Software is +// furnished to do so, subject to the following conditions: +// +// The above copyright notice and this permission notice shall be included in +// all copies or substantial portions of the Software. +// +// THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR +// IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, +// FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE +// AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER +// LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, +// OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN +// THE SOFTWARE. + +import { + ChangeDetectorRef, + Component, + Injectable, + OnInit, +} from "@angular/core"; +import { GridsterConfig, GridsterItem } from "angular-gridster2"; +import cloneDeep from "lodash/cloneDeep"; +import keyBy from "lodash/keyBy"; +import moment, { Moment } from "moment/moment"; +import { BehaviorSubject } from "rxjs"; + +import { + DataSourceService, + IDataSource, + INovaFilters, + ITimeframe, +} from "@nova-ui/bits"; +import { + DATA_SOURCE, + DEFAULT_PIZZAGNA_ROOT, + IDashboard, + IDataSourceOutput, + IProviderConfiguration, + ISerializableTimeframe, + ITimeseriesItemConfiguration, + ITimeseriesOutput, + ITimeseriesScaleConfig, + ITimeseriesWidgetConfig, + ITimeseriesWidgetData, + ITimeseriesWidgetSeriesData, + IWidget, + NOVA_URL_INTERACTION_HANDLER, + LegendPlacement, + PizzagnaLayer, + ProviderRegistryService, + TimeseriesChartPreset, + TimeseriesScaleType, + WellKnownPathKey, + WellKnownProviders, + WidgetTypesService, +} from "@nova-ui/dashboards"; + +/** + * A simple Timeseries data source implementation + */ +@Injectable() +export class TimeseriesMockDataSource + extends DataSourceService + implements IDataSource +{ + public static providerId = "TimeseriesMockDataSource"; + + public busy = new BehaviorSubject(false); + + public async getFilteredData( + filters: INovaFilters + ): Promise> { + // In this example we're using some static mock data located at the bottom of this file. In a real-world + // scenario, the data for the chart would likely be retrieved via an asynchronous backend call. + let filteredData = getData(); + + this.busy.next(true); + + // Filtering using the filter registered by the TimeFrameBar + const timeframeFilter = filters.timeframe?.value as ITimeframe; + if (timeframeFilter) { + filteredData = filteredData.map((item: ITimeseriesWidgetData) => ({ + id: item.id, + name: item.name, + description: item.description, + // the filtered data should return the provided links if they are set. + link: item?.link, + secondaryLink: item?.secondaryLink, + data: item.data.filter( + (seriesData: ITimeseriesWidgetSeriesData) => + filterDates( + seriesData.x, + timeframeFilter.startDatetime, + timeframeFilter.endDatetime + ) + ), + })); + } + + this.busy.next(false); + + return { result: { series: filteredData } }; + } +} + +function filterDates(dateToCheck: Date, startDate: Moment, endDate: Moment) { + const mom = moment(dateToCheck); + return ( + mom.isBetween(startDate, endDate) || + mom.isSame(startDate) || + mom.isSame(endDate) + ); +} + +/** + * A component that instantiates the dashboard + */ +@Component({ + selector: "timeseries-widget-interactive-example", + templateUrl: "./timeseries-widget-interactive-example.component.html", + styleUrls: ["./timeseries-widget-interactive-example.component.less"], + standalone: false, +}) +export class TimeseriesWidgetInteractiveExampleComponent implements OnInit { + // This variable will hold all the data needed to define the layout and behavior of the widgets. + // Pass this to the dashboard component's dashboard input in the template. + public dashboard: IDashboard | undefined; + + // Angular gridster requires a configuration object even if it's empty. + // Pass this to the dashboard component's gridsterConfig input in the template. + public gridsterConfig: GridsterConfig = {}; + + // Boolean passed as an input to the dashboard. When true, widgets can be moved, resized, removed, or edited + public editMode: boolean = false; + + constructor( + // WidgetTypesService provides the widget's necessary structure information + private widgetTypesService: WidgetTypesService, + + // In general, the ProviderRegistryService is used for making entities available for injection into dynamically loaded components. + private providerRegistry: ProviderRegistryService, + + // Angular's ChangeDetectorRef + private changeDetectorRef: ChangeDetectorRef + ) {} + + public ngOnInit(): void { + // Grabbing the widget's default template which will be needed as a parameter for setNode + const widgetTemplate = this.widgetTypesService.getWidgetType( + "timeseries", + 1 + ); + // Registering our data sources as dropdown options in the widget editor/configurator + // Note: This could also be done in the parent module's constructor so that + // multiple dashboards could have access to the same widget template modification. + this.widgetTypesService.setNode( + // This is the template we grabbed above with getWidgetType + widgetTemplate, + // We are setting the editor/configurator part of the widget template + "configurator", + // This indicates which node you are changing and we want to change + // the data source providers available for selection in the editor. + WellKnownPathKey.DataSourceProviders, + // We are setting the data sources available for selection in the editor + [TimeseriesMockDataSource.providerId] + ); + + // Registering the data source for injection into the widget. + this.providerRegistry.setProviders({ + [TimeseriesMockDataSource.providerId]: { + provide: DATA_SOURCE, + useClass: TimeseriesMockDataSource, + deps: [], + }, + }); + + this.initializeDashboard(); + } + + public initializeDashboard(): void { + // We're using a static configuration object for this example, but this is where + // the widget's configuration could potentially be populated from a database + const widgetsWithStructure = widgetConfigs.map((w) => + this.widgetTypesService.mergeWithWidgetType(w) + ); + const widgetsIndex = keyBy(widgetsWithStructure, (w: IWidget) => w.id); + + // Finally, assigning the variables we created above to the dashboard + this.dashboard = { + positions: cloneDeep(positions), + widgets: widgetsIndex, + }; + } + + /** Used for restoring widgets state */ + public reInitializeDashboard(): void { + // destroys the components and their providers so the dashboard can re init data + this.dashboard = undefined; + this.changeDetectorRef.detectChanges(); + + this.initializeDashboard(); + } +} + +const widgetConfigs: IWidget[] = [ + { + id: "lineWidgetId", + type: "timeseries", + pizzagna: { + [PizzagnaLayer.Configuration]: { + [DEFAULT_PIZZAGNA_ROOT]: { + providers: { + [WellKnownProviders.DataSource]: { + // Setting the initially selected data source providerId + providerId: TimeseriesMockDataSource.providerId, + } as IProviderConfiguration, + [WellKnownProviders.InteractionHandler]: { + // Setting the UrlInteractionHandler as an interactionHandler + providerId: NOVA_URL_INTERACTION_HANDLER, + properties: { + // the 'url' property tells the handler what link to use when interaction occurs on the series + url: "\${data.link || 'https://en.wikipedia.org/wiki/'+data.legendDescriptionPrimary}", + // by default the link is opened in the current window, set 'newWindow' to true to open in a new tab instead + // newWindow: true, + }, + }, + }, + }, + header: { + properties: { + title: "Line Chart", + subtitle: "Basic Timeseries with Interaction", + }, + }, + chart: { + providers: { + [WellKnownProviders.Adapter]: { + properties: { + // Setting the series and corresponding labels to initially display on the chart + series: [ + { + id: "series-1", + label: "Nur-Sultan", + selectedSeriesId: "series-1", + }, + { + id: "series-2", + label: "Brno", + selectedSeriesId: "series-2", + }, + { + id: "series-3", + label: "Lisbon", + selectedSeriesId: "series-3", + }, + { + id: "series-4", + label: "Austin", + selectedSeriesId: "series-4", + }, + ] as ITimeseriesItemConfiguration[], + }, + } as Partial, + }, + properties: { + // Setting the general chart configuration + configuration: { + // setting interaction to 'series' will make all series in the chart interactable + interaction: "series", + legendPlacement: LegendPlacement.Right, + enableZoom: true, + } as ITimeseriesWidgetConfig, + }, + }, + timeframeSelection: { + properties: { + timeframe: { + selectedPresetId: "last7Days", + } as ISerializableTimeframe, + minDate: moment().subtract(60, "days").format(), + maxDate: moment().format(), + }, + }, + }, + }, + }, + { + id: "stackedBarWidgetId", + type: "timeseries", + pizzagna: { + [PizzagnaLayer.Configuration]: { + [DEFAULT_PIZZAGNA_ROOT]: { + providers: { + [WellKnownProviders.DataSource]: { + // Setting the initially selected data source providerId + providerId: TimeseriesMockDataSource.providerId, + } as IProviderConfiguration, + }, + }, + header: { + properties: { + title: "Stacked Bar Chart", + subtitle: + "Basic Timeseries without Interaction Handler", + }, + }, + chart: { + providers: { + [WellKnownProviders.Adapter]: { + properties: { + // Setting the series and corresponding labels to initially display on the chart + series: [ + { + id: "series-1", + label: "Nur-Sultan", + selectedSeriesId: "series-1", + }, + { + id: "series-2", + label: "Brno", + selectedSeriesId: "series-2", + }, + { + id: "series-3", + label: "Lisbon", + selectedSeriesId: "series-3", + }, + { + id: "series-4", + label: "Austin", + selectedSeriesId: "series-4", + }, + ] as ITimeseriesItemConfiguration[], + }, + } as Partial, + }, + properties: { + // Setting the general chart configuration + configuration: { + legendPlacement: LegendPlacement.Right, + enableZoom: true, + // Setting the preset to stacked bar + preset: TimeseriesChartPreset.StackedBar, + scales: { + x: { + type: TimeseriesScaleType.TimeInterval, + properties: { + interval: 24 * 60 * 60, + }, + } as ITimeseriesScaleConfig, + }, + } as ITimeseriesWidgetConfig, + }, + }, + timeframeSelection: { + properties: { + // Setting the initial timeframe selected in the timeframe bar + timeframe: { + selectedPresetId: "last7Days", + } as ISerializableTimeframe, + minDate: moment().subtract(60, "days").format(), + maxDate: moment().format(), + }, + }, + }, + }, + }, +]; + +// using startOf("day") so that each band for the bar chart corresponds to a calendar day +const startOfToday = moment().startOf("day").toDate(); + +export const getData = (): ITimeseriesWidgetData[] => [ + { + id: "series-1", + name: "Nur-Sultan", + description: "'link' only", + link: "https://en.wikipedia.org/wiki/Nur-Sultan", + data: [ + { x: moment(startOfToday).subtract(59, "day").toDate(), y: 35 }, + { x: moment(startOfToday).subtract(58, "day").toDate(), y: 33 }, + { x: moment(startOfToday).subtract(57, "day").toDate(), y: 40 }, + { x: moment(startOfToday).subtract(56, "day").toDate(), y: 35 }, + { x: moment(startOfToday).subtract(55, "day").toDate(), y: 30 }, + { x: moment(startOfToday).subtract(54, "day").toDate(), y: 35 }, + { x: moment(startOfToday).subtract(53, "day").toDate(), y: 15 }, + { x: moment(startOfToday).subtract(52, "day").toDate(), y: 30 }, + { x: moment(startOfToday).subtract(51, "day").toDate(), y: 35 }, + { x: moment(startOfToday).subtract(50, "day").toDate(), y: 34 }, + { x: moment(startOfToday).subtract(49, "day").toDate(), y: 35 }, + { x: moment(startOfToday).subtract(48, "day").toDate(), y: 33 }, + { x: moment(startOfToday).subtract(47, "day").toDate(), y: 40 }, + { x: moment(startOfToday).subtract(46, "day").toDate(), y: 35 }, + { x: moment(startOfToday).subtract(45, "day").toDate(), y: 30 }, + { x: moment(startOfToday).subtract(44, "day").toDate(), y: 35 }, + { x: moment(startOfToday).subtract(43, "day").toDate(), y: 15 }, + { x: moment(startOfToday).subtract(42, "day").toDate(), y: 30 }, + { x: moment(startOfToday).subtract(41, "day").toDate(), y: 35 }, + { x: moment(startOfToday).subtract(40, "day").toDate(), y: 34 }, + { x: moment(startOfToday).subtract(39, "day").toDate(), y: 35 }, + { x: moment(startOfToday).subtract(38, "day").toDate(), y: 33 }, + { x: moment(startOfToday).subtract(37, "day").toDate(), y: 40 }, + { x: moment(startOfToday).subtract(36, "day").toDate(), y: 35 }, + { x: moment(startOfToday).subtract(35, "day").toDate(), y: 30 }, + { x: moment(startOfToday).subtract(34, "day").toDate(), y: 35 }, + { x: moment(startOfToday).subtract(33, "day").toDate(), y: 15 }, + { x: moment(startOfToday).subtract(32, "day").toDate(), y: 30 }, + { x: moment(startOfToday).subtract(31, "day").toDate(), y: 35 }, + { x: moment(startOfToday).subtract(30, "day").toDate(), y: 34 }, + { x: moment(startOfToday).subtract(29, "day").toDate(), y: 35 }, + { x: moment(startOfToday).subtract(28, "day").toDate(), y: 33 }, + { x: moment(startOfToday).subtract(27, "day").toDate(), y: 40 }, + { x: moment(startOfToday).subtract(26, "day").toDate(), y: 35 }, + { x: moment(startOfToday).subtract(25, "day").toDate(), y: 30 }, + { x: moment(startOfToday).subtract(24, "day").toDate(), y: 35 }, + { x: moment(startOfToday).subtract(23, "day").toDate(), y: 15 }, + { x: moment(startOfToday).subtract(22, "day").toDate(), y: 30 }, + { x: moment(startOfToday).subtract(21, "day").toDate(), y: 35 }, + { x: moment(startOfToday).subtract(20, "day").toDate(), y: 34 }, + { x: moment(startOfToday).subtract(19, "day").toDate(), y: 35 }, + { x: moment(startOfToday).subtract(18, "day").toDate(), y: 33 }, + { x: moment(startOfToday).subtract(17, "day").toDate(), y: 40 }, + { x: moment(startOfToday).subtract(16, "day").toDate(), y: 35 }, + { x: moment(startOfToday).subtract(15, "day").toDate(), y: 30 }, + { x: moment(startOfToday).subtract(14, "day").toDate(), y: 35 }, + { x: moment(startOfToday).subtract(13, "day").toDate(), y: 15 }, + { x: moment(startOfToday).subtract(12, "day").toDate(), y: 30 }, + { x: moment(startOfToday).subtract(11, "day").toDate(), y: 35 }, + { x: moment(startOfToday).subtract(10, "day").toDate(), y: 34 }, + { x: moment(startOfToday).subtract(9, "day").toDate(), y: 33 }, + { x: moment(startOfToday).subtract(8, "day").toDate(), y: 35 }, + { x: moment(startOfToday).subtract(7, "day").toDate(), y: 36 }, + { x: moment(startOfToday).subtract(6, "day").toDate(), y: 34 }, + { x: moment(startOfToday).subtract(5, "day").toDate(), y: 33 }, + { x: moment(startOfToday).subtract(4, "day").toDate(), y: 30 }, + { x: moment(startOfToday).subtract(3, "day").toDate(), y: 32 }, + { x: moment(startOfToday).subtract(2, "day").toDate(), y: 31 }, + { x: moment(startOfToday).subtract(1, "day").toDate(), y: 34 }, + { x: moment(startOfToday).toDate(), y: 25 }, + ], + }, + { + id: "series-2", + name: "Brno", + description: "'link' and 'secondaryLink'", + link: "https://en.wikipedia.org/wiki/Brno", + secondaryLink: "https://en.wikipedia.org/wiki/Europe", + data: [ + { x: moment(startOfToday).subtract(59, "day").toDate(), y: 35 }, + { x: moment(startOfToday).subtract(58, "day").toDate(), y: 33 }, + { x: moment(startOfToday).subtract(57, "day").toDate(), y: 40 }, + { x: moment(startOfToday).subtract(56, "day").toDate(), y: 35 }, + { x: moment(startOfToday).subtract(55, "day").toDate(), y: 30 }, + { x: moment(startOfToday).subtract(54, "day").toDate(), y: 35 }, + { x: moment(startOfToday).subtract(53, "day").toDate(), y: 15 }, + { x: moment(startOfToday).subtract(52, "day").toDate(), y: 30 }, + { x: moment(startOfToday).subtract(51, "day").toDate(), y: 35 }, + { x: moment(startOfToday).subtract(50, "day").toDate(), y: 34 }, + { x: moment(startOfToday).subtract(49, "day").toDate(), y: 35 }, + { x: moment(startOfToday).subtract(48, "day").toDate(), y: 33 }, + { x: moment(startOfToday).subtract(47, "day").toDate(), y: 40 }, + { x: moment(startOfToday).subtract(46, "day").toDate(), y: 35 }, + { x: moment(startOfToday).subtract(45, "day").toDate(), y: 30 }, + { x: moment(startOfToday).subtract(44, "day").toDate(), y: 35 }, + { x: moment(startOfToday).subtract(43, "day").toDate(), y: 15 }, + { x: moment(startOfToday).subtract(42, "day").toDate(), y: 30 }, + { x: moment(startOfToday).subtract(41, "day").toDate(), y: 35 }, + { x: moment(startOfToday).subtract(40, "day").toDate(), y: 34 }, + { x: moment(startOfToday).subtract(39, "day").toDate(), y: 35 }, + { x: moment(startOfToday).subtract(38, "day").toDate(), y: 33 }, + { x: moment(startOfToday).subtract(37, "day").toDate(), y: 40 }, + { x: moment(startOfToday).subtract(36, "day").toDate(), y: 35 }, + { x: moment(startOfToday).subtract(35, "day").toDate(), y: 30 }, + { x: moment(startOfToday).subtract(34, "day").toDate(), y: 35 }, + { x: moment(startOfToday).subtract(33, "day").toDate(), y: 15 }, + { x: moment(startOfToday).subtract(32, "day").toDate(), y: 30 }, + { x: moment(startOfToday).subtract(31, "day").toDate(), y: 35 }, + { x: moment(startOfToday).subtract(30, "day").toDate(), y: 34 }, + { x: moment(startOfToday).subtract(29, "day").toDate(), y: 35 }, + { x: moment(startOfToday).subtract(28, "day").toDate(), y: 33 }, + { x: moment(startOfToday).subtract(27, "day").toDate(), y: 40 }, + { x: moment(startOfToday).subtract(26, "day").toDate(), y: 35 }, + { x: moment(startOfToday).subtract(25, "day").toDate(), y: 30 }, + { x: moment(startOfToday).subtract(24, "day").toDate(), y: 35 }, + { x: moment(startOfToday).subtract(23, "day").toDate(), y: 15 }, + { x: moment(startOfToday).subtract(22, "day").toDate(), y: 30 }, + { x: moment(startOfToday).subtract(21, "day").toDate(), y: 35 }, + { x: moment(startOfToday).subtract(20, "day").toDate(), y: 34 }, + { x: moment(startOfToday).subtract(19, "day").toDate(), y: 64 }, + { x: moment(startOfToday).subtract(18, "day").toDate(), y: 70 }, + { x: moment(startOfToday).subtract(17, "day").toDate(), y: 55 }, + { x: moment(startOfToday).subtract(16, "day").toDate(), y: 55 }, + { x: moment(startOfToday).subtract(15, "day").toDate(), y: 45 }, + { x: moment(startOfToday).subtract(14, "day").toDate(), y: 10 }, + { x: moment(startOfToday).subtract(13, "day").toDate(), y: 65 }, + { x: moment(startOfToday).subtract(12, "day").toDate(), y: 35 }, + { x: moment(startOfToday).subtract(11, "day").toDate(), y: 60 }, + { x: moment(startOfToday).subtract(10, "day").toDate(), y: 61 }, + { x: moment(startOfToday).subtract(9, "day").toDate(), y: 65 }, + { x: moment(startOfToday).subtract(8, "day").toDate(), y: 63 }, + { x: moment(startOfToday).subtract(7, "day").toDate(), y: 58 }, + { x: moment(startOfToday).subtract(6, "day").toDate(), y: 64 }, + { x: moment(startOfToday).subtract(5, "day").toDate(), y: 63 }, + { x: moment(startOfToday).subtract(4, "day").toDate(), y: 60 }, + { x: moment(startOfToday).subtract(3, "day").toDate(), y: 62 }, + { x: moment(startOfToday).subtract(2, "day").toDate(), y: 61 }, + { x: moment(startOfToday).subtract(1, "day").toDate(), y: 62 }, + { x: moment(startOfToday).toDate(), y: 55 }, + ], + }, + { + id: "series-3", + name: "Lisbon", + description: "'secondaryLink' only", + secondaryLink: "https://en.wikipedia.org/wiki/Lisbon", + data: [ + { x: moment(startOfToday).subtract(59, "day").toDate(), y: 35 }, + { x: moment(startOfToday).subtract(58, "day").toDate(), y: 33 }, + { x: moment(startOfToday).subtract(57, "day").toDate(), y: 40 }, + { x: moment(startOfToday).subtract(56, "day").toDate(), y: 35 }, + { x: moment(startOfToday).subtract(55, "day").toDate(), y: 30 }, + { x: moment(startOfToday).subtract(54, "day").toDate(), y: 35 }, + { x: moment(startOfToday).subtract(53, "day").toDate(), y: 15 }, + { x: moment(startOfToday).subtract(52, "day").toDate(), y: 30 }, + { x: moment(startOfToday).subtract(51, "day").toDate(), y: 35 }, + { x: moment(startOfToday).subtract(50, "day").toDate(), y: 34 }, + { x: moment(startOfToday).subtract(49, "day").toDate(), y: 35 }, + { x: moment(startOfToday).subtract(48, "day").toDate(), y: 33 }, + { x: moment(startOfToday).subtract(47, "day").toDate(), y: 40 }, + { x: moment(startOfToday).subtract(46, "day").toDate(), y: 35 }, + { x: moment(startOfToday).subtract(45, "day").toDate(), y: 30 }, + { x: moment(startOfToday).subtract(44, "day").toDate(), y: 35 }, + { x: moment(startOfToday).subtract(43, "day").toDate(), y: 15 }, + { x: moment(startOfToday).subtract(42, "day").toDate(), y: 30 }, + { x: moment(startOfToday).subtract(41, "day").toDate(), y: 35 }, + { x: moment(startOfToday).subtract(40, "day").toDate(), y: 34 }, + { x: moment(startOfToday).subtract(39, "day").toDate(), y: 35 }, + { x: moment(startOfToday).subtract(38, "day").toDate(), y: 33 }, + { x: moment(startOfToday).subtract(37, "day").toDate(), y: 40 }, + { x: moment(startOfToday).subtract(36, "day").toDate(), y: 35 }, + { x: moment(startOfToday).subtract(35, "day").toDate(), y: 30 }, + { x: moment(startOfToday).subtract(34, "day").toDate(), y: 35 }, + { x: moment(startOfToday).subtract(33, "day").toDate(), y: 15 }, + { x: moment(startOfToday).subtract(32, "day").toDate(), y: 30 }, + { x: moment(startOfToday).subtract(31, "day").toDate(), y: 35 }, + { x: moment(startOfToday).subtract(30, "day").toDate(), y: 34 }, + { x: moment(startOfToday).subtract(29, "day").toDate(), y: 35 }, + { x: moment(startOfToday).subtract(28, "day").toDate(), y: 33 }, + { x: moment(startOfToday).subtract(27, "day").toDate(), y: 40 }, + { x: moment(startOfToday).subtract(26, "day").toDate(), y: 35 }, + { x: moment(startOfToday).subtract(25, "day").toDate(), y: 30 }, + { x: moment(startOfToday).subtract(24, "day").toDate(), y: 35 }, + { x: moment(startOfToday).subtract(23, "day").toDate(), y: 15 }, + { x: moment(startOfToday).subtract(22, "day").toDate(), y: 30 }, + { x: moment(startOfToday).subtract(21, "day").toDate(), y: 35 }, + { x: moment(startOfToday).subtract(20, "day").toDate(), y: 34 }, + { x: moment(startOfToday).subtract(19, "day").toDate(), y: 80 }, + { x: moment(startOfToday).subtract(18, "day").toDate(), y: 70 }, + { x: moment(startOfToday).subtract(17, "day").toDate(), y: 95 }, + { x: moment(startOfToday).subtract(16, "day").toDate(), y: 90 }, + { x: moment(startOfToday).subtract(15, "day").toDate(), y: 85 }, + { x: moment(startOfToday).subtract(14, "day").toDate(), y: 70 }, + { x: moment(startOfToday).subtract(13, "day").toDate(), y: 75 }, + { x: moment(startOfToday).subtract(12, "day").toDate(), y: 69 }, + { x: moment(startOfToday).subtract(11, "day").toDate(), y: 75 }, + { x: moment(startOfToday).subtract(10, "day").toDate(), y: 81 }, + { x: moment(startOfToday).subtract(9, "day").toDate(), y: 93 }, + { x: moment(startOfToday).subtract(8, "day").toDate(), y: 83 }, + { x: moment(startOfToday).subtract(7, "day").toDate(), y: 70 }, + { x: moment(startOfToday).subtract(6, "day").toDate(), y: 74 }, + { x: moment(startOfToday).subtract(5, "day").toDate(), y: 73 }, + { x: moment(startOfToday).subtract(4, "day").toDate(), y: 68 }, + { x: moment(startOfToday).subtract(3, "day").toDate(), y: 72 }, + { x: moment(startOfToday).subtract(2, "day").toDate(), y: 61 }, + { x: moment(startOfToday).subtract(1, "day").toDate(), y: 69 }, + { x: moment(startOfToday).toDate(), y: 60 }, + ], + }, + { + id: "series-4", + name: "Austin", + description: "No links", + data: [ + { x: moment(startOfToday).subtract(59, "day").toDate(), y: 25 }, + { x: moment(startOfToday).subtract(58, "day").toDate(), y: 43 }, + { x: moment(startOfToday).subtract(57, "day").toDate(), y: 40 }, + { x: moment(startOfToday).subtract(56, "day").toDate(), y: 65 }, + { x: moment(startOfToday).subtract(55, "day").toDate(), y: 30 }, + { x: moment(startOfToday).subtract(54, "day").toDate(), y: 25 }, + { x: moment(startOfToday).subtract(53, "day").toDate(), y: 45 }, + { x: moment(startOfToday).subtract(52, "day").toDate(), y: 30 }, + { x: moment(startOfToday).subtract(51, "day").toDate(), y: 85 }, + { x: moment(startOfToday).subtract(50, "day").toDate(), y: 74 }, + { x: moment(startOfToday).subtract(49, "day").toDate(), y: 55 }, + { x: moment(startOfToday).subtract(48, "day").toDate(), y: 23 }, + { x: moment(startOfToday).subtract(47, "day").toDate(), y: 40 }, + { x: moment(startOfToday).subtract(46, "day").toDate(), y: 15 }, + { x: moment(startOfToday).subtract(45, "day").toDate(), y: 20 }, + { x: moment(startOfToday).subtract(44, "day").toDate(), y: 65 }, + { x: moment(startOfToday).subtract(43, "day").toDate(), y: 25 }, + { x: moment(startOfToday).subtract(42, "day").toDate(), y: 40 }, + { x: moment(startOfToday).subtract(41, "day").toDate(), y: 25 }, + { x: moment(startOfToday).subtract(40, "day").toDate(), y: 54 }, + { x: moment(startOfToday).subtract(39, "day").toDate(), y: 65 }, + { x: moment(startOfToday).subtract(38, "day").toDate(), y: 33 }, + { x: moment(startOfToday).subtract(37, "day").toDate(), y: 50 }, + { x: moment(startOfToday).subtract(36, "day").toDate(), y: 45 }, + { x: moment(startOfToday).subtract(35, "day").toDate(), y: 20 }, + { x: moment(startOfToday).subtract(34, "day").toDate(), y: 25 }, + { x: moment(startOfToday).subtract(33, "day").toDate(), y: 35 }, + { x: moment(startOfToday).subtract(32, "day").toDate(), y: 20 }, + { x: moment(startOfToday).subtract(31, "day").toDate(), y: 35 }, + { x: moment(startOfToday).subtract(30, "day").toDate(), y: 14 }, + { x: moment(startOfToday).subtract(29, "day").toDate(), y: 55 }, + { x: moment(startOfToday).subtract(28, "day").toDate(), y: 23 }, + { x: moment(startOfToday).subtract(27, "day").toDate(), y: 10 }, + { x: moment(startOfToday).subtract(26, "day").toDate(), y: 5 }, + { x: moment(startOfToday).subtract(25, "day").toDate(), y: 20 }, + { x: moment(startOfToday).subtract(24, "day").toDate(), y: 35 }, + { x: moment(startOfToday).subtract(23, "day").toDate(), y: 15 }, + { x: moment(startOfToday).subtract(22, "day").toDate(), y: 30 }, + { x: moment(startOfToday).subtract(21, "day").toDate(), y: 35 }, + { x: moment(startOfToday).subtract(20, "day").toDate(), y: 34 }, + { x: moment(startOfToday).subtract(19, "day").toDate(), y: 50 }, + { x: moment(startOfToday).subtract(18, "day").toDate(), y: 60 }, + { x: moment(startOfToday).subtract(17, "day").toDate(), y: 95 }, + { x: moment(startOfToday).subtract(16, "day").toDate(), y: 80 }, + { x: moment(startOfToday).subtract(15, "day").toDate(), y: 65 }, + { x: moment(startOfToday).subtract(14, "day").toDate(), y: 80 }, + { x: moment(startOfToday).subtract(13, "day").toDate(), y: 85 }, + { x: moment(startOfToday).subtract(12, "day").toDate(), y: 69 }, + { x: moment(startOfToday).subtract(11, "day").toDate(), y: 65 }, + { x: moment(startOfToday).subtract(10, "day").toDate(), y: 71 }, + { x: moment(startOfToday).subtract(9, "day").toDate(), y: 73 }, + { x: moment(startOfToday).subtract(8, "day").toDate(), y: 43 }, + { x: moment(startOfToday).subtract(7, "day").toDate(), y: 70 }, + { x: moment(startOfToday).subtract(6, "day").toDate(), y: 84 }, + { x: moment(startOfToday).subtract(5, "day").toDate(), y: 73 }, + { x: moment(startOfToday).subtract(4, "day").toDate(), y: 38 }, + { x: moment(startOfToday).subtract(3, "day").toDate(), y: 72 }, + { x: moment(startOfToday).subtract(2, "day").toDate(), y: 81 }, + { x: moment(startOfToday).subtract(1, "day").toDate(), y: 59 }, + { x: moment(startOfToday).toDate(), y: 60 }, + ], + }, +]; +// Setting the widget dimensions and position (this is for gridster) +const positions: Record = { + [widgetConfigs[0].id]: { + cols: 6, + rows: 6, + y: 0, + x: 0, + }, + [widgetConfigs[1].id]: { + cols: 6, + rows: 6, + y: 0, + x: 6, + }, +}; +`, + "widget-types/timeseries/timeseries-widget-status-bar-example/timeseries-widget-status-bar-example.component.ts": `// © 2022 SolarWinds Worldwide, LLC. All rights reserved. +// +// Permission is hereby granted, free of charge, to any person obtaining a copy +// of this software and associated documentation files (the "Software"), to +// deal in the Software without restriction, including without limitation the +// rights to use, copy, modify, merge, publish, distribute, sublicense, and/or +// sell copies of the Software, and to permit persons to whom the Software is +// furnished to do so, subject to the following conditions: +// +// The above copyright notice and this permission notice shall be included in +// all copies or substantial portions of the Software. +// +// THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR +// IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, +// FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE +// AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER +// LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, +// OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN +// THE SOFTWARE. + +import { + ChangeDetectorRef, + Component, + Injectable, + OnInit, +} from "@angular/core"; +import { GridsterConfig, GridsterItem } from "angular-gridster2"; +import keyBy from "lodash/keyBy"; +import moment, { Moment } from "moment/moment"; +import { BehaviorSubject } from "rxjs"; + +import { + DataSourceService, + IDataSource, + IDataSourceOutput, + INovaFilters, + ITimeframe, +} from "@nova-ui/bits"; +import { CHART_PALETTE_CS_S_EXTENDED } from "@nova-ui/charts"; +import { + applyStatusEndpoints, + DATA_SOURCE, + DEFAULT_PIZZAGNA_ROOT, + IDashboard, + IProviderConfiguration, + ISerializableTimeframe, + ITimeseriesItemConfiguration, + ITimeseriesOutput, + ITimeseriesScaleConfig, + ITimeseriesWidgetConfig, + ITimeseriesWidgetData, + ITimeseriesWidgetSeriesData, + ITimeseriesWidgetStatusData, + IWidget, + LegendPlacement, + PizzagnaLayer, + ProviderRegistryService, + TimeseriesChartPreset, + TimeseriesScaleType, + WellKnownPathKey, + WellKnownProviders, + WidgetTypesService, +} from "@nova-ui/dashboards"; + +/** + * A simple Timeseries data source implementation with continuous (non-interval-based) output + */ +@Injectable() +export class TimeseriesStatusContinuousDataSource + extends DataSourceService + implements IDataSource> +{ + public static providerId = "TimeseriesStatusContinuousDataSource"; + + public busy = new BehaviorSubject(false); + + public async getFilteredData( + filters: INovaFilters + ): Promise< + IDataSourceOutput> + > { + // In this example we're using some static mock data located at the bottom of this file. In a real-world + // scenario, the data for the chart would likely be retrieved via an asynchronous backend call. + const data = getContinuousData(); + let filteredData = data; + + this.busy.next(true); + + // Filtering using the filter registered by the TimeFrameBar + const timeframeFilter = filters.timeframe?.value as ITimeframe; + if (timeframeFilter) { + filteredData = filteredData.map((item: ITimeseriesWidgetData) => ({ + id: item.id, + name: item.name, + description: item.description, + data: item.data.filter( + (seriesData: ITimeseriesWidgetSeriesData) => + filterDates( + seriesData.x, + timeframeFilter.startDatetime, + timeframeFilter.endDatetime + ) + ), + })); + + // apply endpoints on the filtered status data so that when the status chart is zoomed (filtered), + // each status visualizations is ensured to have valid start and end values + filteredData = applyStatusEndpoints( + timeframeFilter, + filteredData, + data + ); + } + + this.busy.next(false); + return { result: { series: filteredData } }; + } +} + +/** + * A simple Timeseries data source implementation with interval-based output + */ +@Injectable() +export class TimeseriesStatusIntervalDataSource + extends DataSourceService + implements IDataSource> +{ + public static providerId = "TimeseriesStatusIntervalDataSource"; + + public busy = new BehaviorSubject(false); + + public async getFilteredData( + filters: INovaFilters + ): Promise< + IDataSourceOutput> + > { + // In this example we're using some static mock data located at the bottom of this file. In a real-world + // scenario, the data for the chart would likely be retrieved via an asynchronous backend call. + const data = getIntervalData(); + let filteredData = data; + + this.busy.next(true); + + // Filtering using the filter registered by the TimeFrameBar + const timeframeFilter = filters.timeframe?.value as ITimeframe; + if (timeframeFilter) { + filteredData = filteredData.map((item: ITimeseriesWidgetData) => ({ + id: item.id, + name: item.name, + description: item.description, + data: item.data.filter( + (seriesData: ITimeseriesWidgetSeriesData) => + filterDates( + seriesData.x, + timeframeFilter.startDatetime, + timeframeFilter.endDatetime + ) + ), + })); + + // Note: There's no need to apply filter endpoints to the status data in this case since we know it's visualized in regular intervals + } + + this.busy.next(false); + return { result: { series: filteredData } }; + } +} + +function filterDates(dateToCheck: Date, startDate: Moment, endDate: Moment) { + const mom = moment(dateToCheck); + return ( + mom.isBetween(startDate, endDate) || + mom.isSame(startDate) || + mom.isSame(endDate) + ); +} + +/** + * A component that instantiates the dashboard + */ +@Component({ + selector: "timeseries-widget-status-bar-example", + templateUrl: "./timeseries-widget-status-bar-example.component.html", + styleUrls: ["./timeseries-widget-status-bar-example.component.less"], + standalone: false, +}) +export class TimeseriesWidgetStatusBarExampleComponent implements OnInit { + // This variable will hold all the data needed to define the layout and behavior of the widgets. + // Pass this to the dashboard component's dashboard input in the template. + public dashboard: IDashboard | undefined; + + // Angular gridster requires a configuration object even if it's empty. + // Pass this to the dashboard component's gridsterConfig input in the template. + public gridsterConfig: GridsterConfig = {}; + + // Boolean passed as an input to the dashboard. When true, widgets can be moved, resized, removed, or edited + public editMode: boolean = false; + + constructor( + // WidgetTypesService provides the widget's necessary structure information + private widgetTypesService: WidgetTypesService, + + // In general, the ProviderRegistryService is used for making entities available for injection into dynamically loaded components. + private providerRegistry: ProviderRegistryService, + private changeDetectorRef: ChangeDetectorRef + ) {} + + public ngOnInit(): void { + // Grabbing the widget's default template which will be needed as a parameter for setNode + const widgetTemplate = this.widgetTypesService.getWidgetType( + "timeseries", + 1 + ); + // Registering our data sources as dropdown options in the widget editor/configurator + // Note: This could also be done in the parent module's constructor so that + // multiple dashboards could have access to the same widget template modification. + this.widgetTypesService.setNode( + // This is the template we grabbed above with getWidgetType + widgetTemplate, + // We are setting the editor/configurator part of the widget template + "configurator", + // This indicates which node you are changing and we want to change + // the data source providers available for selection in the editor. + WellKnownPathKey.DataSourceProviders, + // We are setting the data sources available for selection in the editor + [ + TimeseriesStatusContinuousDataSource.providerId, + TimeseriesStatusIntervalDataSource.providerId, + ] + ); + + // Registering the data source for injection into the widget. + this.providerRegistry.setProviders({ + [TimeseriesStatusContinuousDataSource.providerId]: { + provide: DATA_SOURCE, + useClass: TimeseriesStatusContinuousDataSource, + deps: [], + }, + [TimeseriesStatusIntervalDataSource.providerId]: { + provide: DATA_SOURCE, + useClass: TimeseriesStatusIntervalDataSource, + deps: [], + }, + }); + + this.initializeDashboard(); + } + + /** Used for restoring widgets state */ + public reInitializeDashboard(): void { + // destroys the components and their providers so the dashboard can re init data + this.dashboard = undefined; + this.changeDetectorRef.detectChanges(); + + this.initializeDashboard(); + } + + public initializeDashboard(): void { + // We're using a static configuration object for this example, but this is where + // the widget's configuration could potentially be populated from a database + const widgetsWithStructure = widgetConfigs.map((w) => + this.widgetTypesService.mergeWithWidgetType(w) + ); + const widgetsIndex = keyBy(widgetsWithStructure, (w: IWidget) => w.id); + + // Finally, assigning the variables we created above to the dashboard + this.dashboard = { + positions, + widgets: widgetsIndex, + }; + } +} + +const widgetConfigs: IWidget[] = [ + { + id: "statusChartWidgetId", + type: "timeseries", + pizzagna: { + [PizzagnaLayer.Configuration]: { + [DEFAULT_PIZZAGNA_ROOT]: { + providers: { + [WellKnownProviders.DataSource]: { + // Setting the initially selected data source providerId + providerId: + TimeseriesStatusContinuousDataSource.providerId, + } as IProviderConfiguration, + }, + }, + header: { + properties: { + title: "Status Bar Chart with Continuous (Non-Interval) Scale", + subtitle: "Basic Timeseries Widget", + }, + }, + chart: { + providers: { + [WellKnownProviders.Adapter]: { + properties: { + // Setting the series and corresponding labels to initially display on the chart + series: [ + { + id: "series-1", + label: "Node Status", + selectedSeriesId: "series-1", + }, + { + id: "series-2", + label: "Node Status", + selectedSeriesId: "series-2", + }, + ] as ITimeseriesItemConfiguration[], + }, + } as Partial, + }, + properties: { + configuration: { + legendPlacement: LegendPlacement.Right, + enableZoom: true, + // Setting the preset to status bar + preset: TimeseriesChartPreset.StatusBar, + } as ITimeseriesWidgetConfig, + }, + }, + timeframeSelection: { + properties: { + // Setting the initial timeframe selected in the timeframe bar + timeframe: { + selectedPresetId: "last7Days", + } as ISerializableTimeframe, + maxDate: moment().format(), + }, + }, + }, + }, + }, + { + id: "statusIntervalChartWidgetId", + type: "timeseries", + pizzagna: { + [PizzagnaLayer.Configuration]: { + [DEFAULT_PIZZAGNA_ROOT]: { + providers: { + [WellKnownProviders.DataSource]: { + // Setting the initially selected data source providerId + providerId: + TimeseriesStatusIntervalDataSource.providerId, + } as IProviderConfiguration, + }, + }, + header: { + properties: { + title: "Status Bar Chart with Interval Scale", + subtitle: "Basic Timeseries Widget", + }, + }, + chart: { + providers: { + [WellKnownProviders.Adapter]: { + properties: { + // Setting the series and corresponding labels to initially display on the chart + series: [ + { + id: "series-1", + label: "Node Status", + selectedSeriesId: "series-1", + }, + { + id: "series-2", + label: "Node Status", + selectedSeriesId: "series-2", + }, + ] as ITimeseriesItemConfiguration[], + }, + } as Partial, + }, + properties: { + configuration: { + legendPlacement: LegendPlacement.Right, + enableZoom: true, + // Setting the preset to status bar + preset: TimeseriesChartPreset.StatusBar, + scales: { + x: { + type: TimeseriesScaleType.TimeInterval, + properties: { + // one-day interval in seconds + interval: 24 * 60 * 60, + }, + } as ITimeseriesScaleConfig, + }, + } as ITimeseriesWidgetConfig, + }, + }, + timeframeSelection: { + properties: { + // Setting the initial timeframe selected in the timeframe bar + timeframe: { + selectedPresetId: "last7Days", + } as ISerializableTimeframe, + maxDate: moment().format(), + }, + }, + }, + }, + }, +]; + +export const startOfToday = (): Moment => moment().startOf("day"); + +export const getContinuousData = + (): ITimeseriesWidgetData[] => { + const series: ITimeseriesWidgetData[] = [ + { + id: "series-1", + name: "Node Status", + description: "lastchance.demo.lab", + data: [ + // the 'x' value is set to the time and 'y' to the status at that given time + { + x: startOfToday().subtract(20, "day").toDate(), + y: Status.Warning, + }, + { + x: startOfToday().subtract(19, "day").toDate(), + y: Status.Down, + }, + { + x: startOfToday().subtract(17, "day").toDate(), + y: Status.Critical, + }, + { + x: startOfToday().subtract(16, "day").toDate(), + y: Status.Warning, + }, + { + x: startOfToday().subtract(15, "day").toDate(), + y: Status.Down, + }, + { + x: startOfToday().subtract(14, "day").toDate(), + y: Status.Critical, + }, + { + x: startOfToday().subtract(12, "day").toDate(), + y: Status.Unknown, + }, + { + x: startOfToday().subtract(10, "day").toDate(), + y: Status.Up, + }, + { + x: startOfToday().subtract(9, "day").toDate(), + y: Status.Critical, + }, + { + x: startOfToday().subtract(6, "day").toDate(), + y: Status.Up, + }, + { + x: startOfToday().subtract(3, "day").toDate(), + y: Status.Warning, + }, + { + x: startOfToday().subtract(2, "day").toDate(), + y: Status.Critical, + }, + { + x: startOfToday().subtract(1, "day").toDate(), + y: Status.Up, + }, + // This data point will be ignored and is only here to provide an endpoint for the previous status. + { x: moment().toDate(), y: Status.Up }, + ], + }, + { + id: "series-2", + name: "Node Status", + description: "newhope.demo.lab", + data: [ + { + x: startOfToday().subtract(19, "day").toDate(), + y: Status.Critical, + }, + { + x: startOfToday().subtract(18, "day").toDate(), + y: Status.Unknown, + }, + { + x: startOfToday().subtract(17, "day").toDate(), + y: Status.Warning, + }, + { + x: startOfToday().subtract(15, "day").toDate(), + y: Status.Down, + }, + { + x: startOfToday().subtract(8, "day").toDate(), + y: Status.Critical, + }, + { + x: startOfToday().subtract(7, "day").toDate(), + y: Status.Down, + }, + { + x: startOfToday().subtract(6, "day").toDate(), + y: Status.Up, + }, + { + x: startOfToday().subtract(5, "day").toDate(), + y: Status.Critical, + }, + { + x: startOfToday().subtract(4, "day").toDate(), + y: Status.Up, + }, + { + x: startOfToday().subtract(3, "day").toDate(), + y: Status.Warning, + }, + { + x: startOfToday().subtract(2, "day").toDate(), + y: Status.Up, + }, + { + x: startOfToday().subtract(1, "day").toDate(), + y: Status.Down, + }, + // This data point will be ignored and is only here to provide an endpoint for the previous status. + { x: moment().toDate(), y: Status.Down }, + ], + }, + ]; + + for (const s of series) { + // here are we setting the color and icon associated to the status for each data point + s.data = s.data.map((d: any, i: number) => ({ + ...d, + color: statusColors[d.y as Status], + // The thickness of the line is dependant on the status. If the status equals 'Up' then 'thick' is set to false. + thick: d.y !== Status.Up, + icon: "status_" + d.y, + })); + } + + return series; + }; + +// Note that the output of this function is spaced evenly at one-day intervals +export const getIntervalData = + (): ITimeseriesWidgetData[] => { + const series: ITimeseriesWidgetData[] = [ + { + id: "series-1", + name: "Node Status", + description: "lastchance.demo.lab", + data: [ + // the 'x' value is set to the time and 'y' to the status at that given time + { + x: startOfToday().subtract(20, "day").toDate(), + y: Status.Up, + }, + { + x: startOfToday().subtract(19, "day").toDate(), + y: Status.Critical, + }, + { + x: startOfToday().subtract(18, "day").toDate(), + y: Status.Warning, + }, + { + x: startOfToday().subtract(17, "day").toDate(), + y: Status.Down, + }, + { + x: startOfToday().subtract(16, "day").toDate(), + y: Status.Critical, + }, + { + x: startOfToday().subtract(15, "day").toDate(), + y: Status.Down, + }, + { + x: startOfToday().subtract(14, "day").toDate(), + y: Status.Up, + }, + { + x: startOfToday().subtract(13, "day").toDate(), + y: Status.Warning, + }, + { + x: startOfToday().subtract(12, "day").toDate(), + y: Status.Up, + }, + { + x: startOfToday().subtract(11, "day").toDate(), + y: Status.Critical, + }, + { + x: startOfToday().subtract(10, "day").toDate(), + y: Status.Up, + }, + { + x: startOfToday().subtract(9, "day").toDate(), + y: Status.Down, + }, + { + x: startOfToday().subtract(8, "day").toDate(), + y: Status.Critical, + }, + { + x: startOfToday().subtract(7, "day").toDate(), + y: Status.Warning, + }, + { + x: startOfToday().subtract(6, "day").toDate(), + y: Status.Down, + }, + { + x: startOfToday().subtract(5, "day").toDate(), + y: Status.Critical, + }, + { + x: startOfToday().subtract(4, "day").toDate(), + y: Status.Down, + }, + { + x: startOfToday().subtract(3, "day").toDate(), + y: Status.Up, + }, + { + x: startOfToday().subtract(2, "day").toDate(), + y: Status.Warning, + }, + { + x: startOfToday().subtract(1, "day").toDate(), + y: Status.Critical, + }, + { x: startOfToday().toDate(), y: Status.Up }, + ], + }, + { + id: "series-2", + name: "Node Status", + description: "newhope.demo.lab", + data: [ + { + x: startOfToday().subtract(20, "day").toDate(), + y: Status.Up, + }, + { + x: startOfToday().subtract(19, "day").toDate(), + y: Status.Up, + }, + { + x: startOfToday().subtract(18, "day").toDate(), + y: Status.Down, + }, + { + x: startOfToday().subtract(17, "day").toDate(), + y: Status.Critical, + }, + { + x: startOfToday().subtract(16, "day").toDate(), + y: Status.Down, + }, + { + x: startOfToday().subtract(15, "day").toDate(), + y: Status.Up, + }, + { + x: startOfToday().subtract(14, "day").toDate(), + y: Status.Critical, + }, + { + x: startOfToday().subtract(13, "day").toDate(), + y: Status.Up, + }, + { + x: startOfToday().subtract(12, "day").toDate(), + y: Status.Critical, + }, + { + x: startOfToday().subtract(11, "day").toDate(), + y: Status.Warning, + }, + { + x: startOfToday().subtract(10, "day").toDate(), + y: Status.Up, + }, + { + x: startOfToday().subtract(9, "day").toDate(), + y: Status.Down, + }, + { + x: startOfToday().subtract(8, "day").toDate(), + y: Status.Up, + }, + { + x: startOfToday().subtract(7, "day").toDate(), + y: Status.Down, + }, + { + x: startOfToday().subtract(6, "day").toDate(), + y: Status.Critical, + }, + { + x: startOfToday().subtract(5, "day").toDate(), + y: Status.Down, + }, + { + x: startOfToday().subtract(4, "day").toDate(), + y: Status.Up, + }, + { + x: startOfToday().subtract(3, "day").toDate(), + y: Status.Critical, + }, + { + x: startOfToday().subtract(2, "day").toDate(), + y: Status.Up, + }, + { + x: startOfToday().subtract(1, "day").toDate(), + y: Status.Warning, + }, + { x: startOfToday().toDate(), y: Status.Critical }, + ], + }, + ]; + + for (const s of series) { + // here are we setting the color and icon associated to the status for each data point + s.data = s.data.map((d: any, i: number) => ({ + ...d, + color: statusColors[d.y as Status], + // The thickness of the line is dependant on the status. If the status equals 'Up' then 'thick' is set to false. + thick: d.y !== Status.Up, + icon: "status_" + d.y, + })); + } + + return series; + }; + +// An enumeration of statuses +enum Status { + Unknown = "unknown", + Up = "up", + Warning = "warning", + Down = "down", + Critical = "critical", +} + +// This is the map used for setting the color of each status bar +const statusColors: Record = { + [Status.Unknown]: CHART_PALETTE_CS_S_EXTENDED[6], + [Status.Up]: CHART_PALETTE_CS_S_EXTENDED[8], + [Status.Warning]: CHART_PALETTE_CS_S_EXTENDED[4], + [Status.Down]: CHART_PALETTE_CS_S_EXTENDED[0], + [Status.Critical]: CHART_PALETTE_CS_S_EXTENDED[2], +}; + +// Setting the widget dimensions and position (this is for gridster) +const positions: Record = { + [widgetConfigs[0].id]: { + cols: 12, + rows: 4, + y: 0, + x: 0, + }, + [widgetConfigs[1].id]: { + cols: 12, + rows: 4, + y: 4, + x: 0, + }, +}; +`, + "widget-types/view-components/kpi-tile-view-basic/kpi-tile-view-basic-example.component.ts": `// © 2022 SolarWinds Worldwide, LLC. All rights reserved. +// +// Permission is hereby granted, free of charge, to any person obtaining a copy +// of this software and associated documentation files (the "Software"), to +// deal in the Software without restriction, including without limitation the +// rights to use, copy, modify, merge, publish, distribute, sublicense, and/or +// sell copies of the Software, and to permit persons to whom the Software is +// furnished to do so, subject to the following conditions: +// +// The above copyright notice and this permission notice shall be included in +// all copies or substantial portions of the Software. +// +// THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR +// IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, +// FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE +// AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER +// LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, +// OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN +// THE SOFTWARE. + +import { Component } from "@angular/core"; +import { FormControl } from "@angular/forms"; + +type KpiTileState = "normal" | "loading" | "empty"; + +/** + * KPI Tile View - Playground example. + * Switch between all visual states (normal / loading / empty) and toggle + * interactivity to explore every variant the standalone tile supports. + */ +@Component({ + selector: "kpi-tile-view-basic-example", + templateUrl: "./kpi-tile-view-basic-example.component.html", + standalone: false, +}) +export class KpiTileViewBasicExampleComponent { + public readonly stateControl = new FormControl("normal", { + nonNullable: true, + }); + public interactive = false; + public lastClicked = ""; + + public readonly stateOptions: KpiTileState[] = ["normal", "loading", "empty"]; + + public onTileClick(label: string): void { + this.lastClicked = label; + } +} +`, + "widget-types/view-components/kpi-tile-view-interactive/kpi-tile-view-interactive-example.component.ts": `// © 2022 SolarWinds Worldwide, LLC. All rights reserved. +// +// Permission is hereby granted, free of charge, to any person obtaining a copy +// of this software and associated documentation files (the "Software"), to +// deal in the Software without restriction, including without limitation the +// rights to use, copy, modify, merge, publish, distribute, sublicense, and/or +// sell copies of the Software, and to permit persons to whom the Software is +// furnished to do so, subject to the following conditions: +// +// The above copyright notice and this permission notice shall be included in +// all copies or substantial portions of the Software. +// +// THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR +// IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, +// FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE +// AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER +// LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, +// OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN +// THE SOFTWARE. + +import { Component, TemplateRef, ViewChild } from "@angular/core"; + +/** + * Interactive KPI Tile View example with custom value formatting + * and click event handling. + */ +@Component({ + selector: "kpi-tile-view-interactive-example", + templateUrl: "./kpi-tile-view-interactive-example.component.html", + standalone: false, +}) +export class KpiTileViewInteractiveExampleComponent { + public currentValue = 1_247; + public lastClickedTile = ""; + + @ViewChild("customValueTpl", { static: true }) + public customValueTpl: TemplateRef; + + public onTileClick(): void { + this.lastClickedTile = "Active Sessions"; + } + + public onUptimeClick(): void { + this.lastClickedTile = "Uptime"; + } +} +`, + "widget-types/view-components/proportional-chart-view-playground/proportional-chart-view-playground-example.component.ts": `// © 2022 SolarWinds Worldwide, LLC. All rights reserved. +// +// Permission is hereby granted, free of charge, to any person obtaining a copy +// of this software and associated documentation files (the "Software"), to +// deal in the Software without restriction, including without limitation the +// rights to use, copy, modify, merge, publish, distribute, sublicense, and/or +// sell copies of the Software, and to permit persons to whom the Software is +// furnished to do so, subject to the following conditions: +// +// The above copyright notice and this permission notice shall be included in +// all copies or substantial portions of the Software. +// +// THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR +// IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, +// FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE +// AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER +// LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, +// OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN +// THE SOFTWARE. + +import { Component } from "@angular/core"; +import { FormControl } from "@angular/forms"; + +import { IProportionalDataItem } from "@nova-ui/dashboards"; + +type ProportionalChartType = "donut" | "pie" | "verticalBar" | "horizontalBar"; +type LegendPlacement = "right" | "bottom" | "none"; + +/** + * Proportional Chart View - Playground example. + * Lets you switch between all supported chart types and legend placements + * to see every visual variant the standalone view component provides. + */ +@Component({ + selector: "proportional-chart-view-playground-example", + templateUrl: "./proportional-chart-view-playground-example.component.html", + standalone: false, +}) +export class ProportionalChartViewPlaygroundExampleComponent { + public readonly chartTypeControl = new FormControl( + "donut", + { nonNullable: true } + ); + public readonly legendPlacementControl = new FormControl( + "right", + { nonNullable: true } + ); + + public readonly chartTypeOptions: ProportionalChartType[] = [ + "donut", + "pie", + "verticalBar", + "horizontalBar", + ]; + public readonly legendPlacementOptions: LegendPlacement[] = [ + "right", + "bottom", + "none", + ]; + + public colors: Record = { + down: "#dc3545", + up: "#2cc079", + warning: "#f3a002", + unknown: "#707070", + }; + + public chartData: Array = [ + { id: "up", name: "Up", value: 78 }, + { id: "down", name: "Down", value: 8 }, + { id: "warning", name: "Warning", value: 12 }, + { id: "unknown", name: "Unknown", value: 2 }, + ]; + + public totalOf(data: IProportionalDataItem[] | undefined): number { + return (data ?? []).reduce((sum, d) => sum + (d?.value ?? 0), 0); + } +} +`, + "widget-types/view-components/view-components-docs.component.ts": `// © 2022 SolarWinds Worldwide, LLC. All rights reserved. +// +// Permission is hereby granted, free of charge, to any person obtaining a copy +// of this software and associated documentation files (the "Software"), to +// deal in the Software without restriction, including without limitation the +// rights to use, copy, modify, merge, publish, distribute, sublicense, and/or +// sell copies of the Software, and to permit persons to whom the Software is +// furnished to do so, subject to the following conditions: +// +// The above copyright notice and this permission notice shall be included in +// all copies or substantial portions of the Software. +// +// THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR +// IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, +// FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE +// AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER +// LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, +// OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN +// THE SOFTWARE. + +import { Component } from "@angular/core"; + +@Component({ + selector: "nui-view-components-docs", + templateUrl: "./view-components-docs.component.html", + standalone: false, +}) +export class ViewComponentsDocsComponent { + public readonly installationSnippet = \`import { NuiDashboardViewsModule } from "@nova-ui/dashboards"; + +@NgModule({ + imports: [NuiDashboardViewsModule], +}) +export class MyFeatureModule {}\`; + + public readonly proportionalDataItemSnippet = \`interface IProportionalDataItem { + id: string; // Unique segment identifier + name: string; // Display name in legend + value: number; // Numeric value determining segment size + color?: string; // Optional CSS color (hex or token) + icon?: string; // Optional icon name for legend + link?: string; // Optional drill-down URL +}\`; +} +`, + "widget-types/view-components/view-components-docs.module.ts": `// © 2022 SolarWinds Worldwide, LLC. All rights reserved. +// +// Permission is hereby granted, free of charge, to any person obtaining a copy +// of this software and associated documentation files (the "Software"), to +// deal in the Software without restriction, including without limitation the +// rights to use, copy, modify, merge, publish, distribute, sublicense, and/or +// sell copies of the Software, and to permit persons to whom the Software is +// furnished to do so, subject to the following conditions: +// +// The above copyright notice and this permission notice shall be included in +// all copies or substantial portions of the Software. +// +// THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR +// IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, +// FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE +// AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER +// LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, +// OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN +// THE SOFTWARE. + +import { CommonModule } from "@angular/common"; +import { NgModule } from "@angular/core"; +import { ReactiveFormsModule } from "@angular/forms"; +import { RouterModule, Routes } from "@angular/router"; + +import { + NuiDocsModule, + NuiIconModule, + NuiMessageModule, + NuiFormFieldModule, + NuiSelectV2Module, + NuiSwitchModule, + DEMO_PATH_TOKEN, +} from "@nova-ui/bits"; +import { NuiDashboardViewsModule } from "@nova-ui/dashboards"; + +import { getDemoFiles } from "../../../../demo-files-factory"; +import { KpiTileViewBasicExampleComponent } from "./kpi-tile-view-basic/kpi-tile-view-basic-example.component"; +import { KpiTileViewInteractiveExampleComponent } from "./kpi-tile-view-interactive/kpi-tile-view-interactive-example.component"; +import { ProportionalChartViewPlaygroundExampleComponent } from "./proportional-chart-view-playground/proportional-chart-view-playground-example.component"; +import { ViewComponentsDocsComponent } from "./view-components-docs.component"; + +const routes: Routes = [ + { + path: "", + component: ViewComponentsDocsComponent, + data: { + srlc: { + hideIndicator: true, + }, + showThemeSwitcher: true, + }, + }, + { + path: "kpi-tile-view-basic", + component: KpiTileViewBasicExampleComponent, + data: { + srlc: { + hideIndicator: true, + }, + }, + }, + { + path: "proportional-chart-view-playground", + component: ProportionalChartViewPlaygroundExampleComponent, + data: { + srlc: { + hideIndicator: true, + }, + }, + }, +]; + +@NgModule({ + imports: [ + CommonModule, + ReactiveFormsModule, + RouterModule.forChild(routes), + NuiDocsModule, + NuiMessageModule, + NuiIconModule, + NuiFormFieldModule, + NuiSelectV2Module, + NuiSwitchModule, + NuiDashboardViewsModule, + ], + declarations: [ + ViewComponentsDocsComponent, + KpiTileViewBasicExampleComponent, + KpiTileViewInteractiveExampleComponent, + ProportionalChartViewPlaygroundExampleComponent, + ], + providers: [ + { + provide: DEMO_PATH_TOKEN, + useValue: getDemoFiles("view-components"), + }, + ], +}) +export default class ViewComponentsDocsModule {} +`, + "widget-types/widget-types.module.ts": `// © 2022 SolarWinds Worldwide, LLC. All rights reserved. +// +// Permission is hereby granted, free of charge, to any person obtaining a copy +// of this software and associated documentation files (the "Software"), to +// deal in the Software without restriction, including without limitation the +// rights to use, copy, modify, merge, publish, distribute, sublicense, and/or +// sell copies of the Software, and to permit persons to whom the Software is +// furnished to do so, subject to the following conditions: +// +// The above copyright notice and this permission notice shall be included in +// all copies or substantial portions of the Software. +// +// THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR +// IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, +// FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE +// AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER +// LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, +// OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN +// THE SOFTWARE. + +import { NgModule, Type } from "@angular/core"; +import { RouterModule, Routes } from "@angular/router"; + +import { NuiDocsModule } from "@nova-ui/bits"; +import { + ConfiguratorHeadingService, + NuiDashboardsModule, +} from "@nova-ui/dashboards"; + +export enum WidgetTypesRoute { + kpi = "kpi", + riskScore = "risk-score", + timeseries = "timeseries", + table = "table", + proportional = "proportional", + embedded = "embedded", + drilldown = "drilldown", + viewComponents = "view-components", +} + +const routes: Routes = [ + { + path: WidgetTypesRoute.kpi, + loadChildren: async () => + import("./kpi/kpi-docs.module") as object as Promise>, + data: { + srlc: { + hideIndicator: true, + }, + }, + }, + { + path: WidgetTypesRoute.riskScore, + loadChildren: async () => + import("./risk-score/risk-score-docs.module") as object as Promise< + Type + >, + data: { + srlc: { + hideIndicator: true, + }, + }, + }, + { + path: WidgetTypesRoute.timeseries, + loadChildren: async () => + import("./timeseries/timeseries-docs.module") as object as Promise< + Type + >, + data: { + srlc: { + hideIndicator: true, + }, + }, + }, + { + path: WidgetTypesRoute.table, + loadChildren: async () => + import("./table/table-docs.module") as object as Promise>, + data: { + srlc: { + hideIndicator: true, + }, + }, + }, + { + path: WidgetTypesRoute.proportional, + loadChildren: async () => + import( + "./proportional/proportional-docs.module" + ) as object as Promise>, + data: { + srlc: { + hideIndicator: true, + }, + }, + }, + { + path: WidgetTypesRoute.embedded, + loadChildren: async () => + import( + "./embedded-content/embedded-content-docs.module" + ) as object as Promise>, + data: { + srlc: { + hideIndicator: true, + }, + }, + }, + { + path: WidgetTypesRoute.drilldown, + loadChildren: async () => + import( + "./drilldown/drilldown-widget-docs.module" + ) as object as Promise>, + data: { + srlc: { + hideIndicator: true, + }, + }, + }, + { + path: WidgetTypesRoute.viewComponents, + loadChildren: async () => + import( + "./view-components/view-components-docs.module" + ) as object as Promise>, + data: { + srlc: { + hideIndicator: true, + }, + }, + }, +]; + +@NgModule({ + imports: [ + RouterModule.forChild(routes), + NuiDocsModule, + NuiDashboardsModule, + ], + providers: [ConfiguratorHeadingService], +}) +export default class WidgetTypesModule {} +`, +}; diff --git a/packages/dashboards/examples/src/components/docs/widget-types/view-components/kpi-tile-view-basic/kpi-tile-view-basic-example.component.html b/packages/dashboards/examples/src/components/docs/widget-types/view-components/kpi-tile-view-basic/kpi-tile-view-basic-example.component.html new file mode 100644 index 000000000..de75e89dc --- /dev/null +++ b/packages/dashboards/examples/src/components/docs/widget-types/view-components/kpi-tile-view-basic/kpi-tile-view-basic-example.component.html @@ -0,0 +1,63 @@ +
+ +
+ + + + {{ opt }} + + + + + + + +
+ +
+ + + + + +
+ +

+ Last clicked: {{ lastClicked }} +

+
diff --git a/packages/dashboards/examples/src/components/docs/widget-types/view-components/kpi-tile-view-basic/kpi-tile-view-basic-example.component.ts b/packages/dashboards/examples/src/components/docs/widget-types/view-components/kpi-tile-view-basic/kpi-tile-view-basic-example.component.ts new file mode 100644 index 000000000..b3e806945 --- /dev/null +++ b/packages/dashboards/examples/src/components/docs/widget-types/view-components/kpi-tile-view-basic/kpi-tile-view-basic-example.component.ts @@ -0,0 +1,48 @@ +// © 2022 SolarWinds Worldwide, LLC. All rights reserved. +// +// Permission is hereby granted, free of charge, to any person obtaining a copy +// of this software and associated documentation files (the "Software"), to +// deal in the Software without restriction, including without limitation the +// rights to use, copy, modify, merge, publish, distribute, sublicense, and/or +// sell copies of the Software, and to permit persons to whom the Software is +// furnished to do so, subject to the following conditions: +// +// The above copyright notice and this permission notice shall be included in +// all copies or substantial portions of the Software. +// +// THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR +// IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, +// FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE +// AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER +// LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, +// OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN +// THE SOFTWARE. + +import { Component } from "@angular/core"; +import { FormControl } from "@angular/forms"; + +type KpiTileState = "normal" | "loading" | "empty"; + +/** + * KPI Tile View - Playground example. + * Switch between all visual states (normal / loading / empty) and toggle + * interactivity to explore every variant the standalone tile supports. + */ +@Component({ + selector: "kpi-tile-view-basic-example", + templateUrl: "./kpi-tile-view-basic-example.component.html", + standalone: false, +}) +export class KpiTileViewBasicExampleComponent { + public readonly stateControl = new FormControl("normal", { + nonNullable: true, + }); + public interactive = false; + public lastClicked = ""; + + public readonly stateOptions: KpiTileState[] = ["normal", "loading", "empty"]; + + public onTileClick(label: string): void { + this.lastClicked = label; + } +} diff --git a/packages/dashboards/examples/src/components/docs/widget-types/view-components/kpi-tile-view-interactive/kpi-tile-view-interactive-example.component.html b/packages/dashboards/examples/src/components/docs/widget-types/view-components/kpi-tile-view-interactive/kpi-tile-view-interactive-example.component.html new file mode 100644 index 000000000..e76b03860 --- /dev/null +++ b/packages/dashboards/examples/src/components/docs/widget-types/view-components/kpi-tile-view-interactive/kpi-tile-view-interactive-example.component.html @@ -0,0 +1,35 @@ +
+ + + + +
+ +

+ Last clicked: {{ lastClickedTile }} +

+ + + + + {{ val }} + diff --git a/packages/dashboards/examples/src/components/docs/widget-types/view-components/kpi-tile-view-interactive/kpi-tile-view-interactive-example.component.ts b/packages/dashboards/examples/src/components/docs/widget-types/view-components/kpi-tile-view-interactive/kpi-tile-view-interactive-example.component.ts new file mode 100644 index 000000000..997c8b0a7 --- /dev/null +++ b/packages/dashboards/examples/src/components/docs/widget-types/view-components/kpi-tile-view-interactive/kpi-tile-view-interactive-example.component.ts @@ -0,0 +1,46 @@ +// © 2022 SolarWinds Worldwide, LLC. All rights reserved. +// +// Permission is hereby granted, free of charge, to any person obtaining a copy +// of this software and associated documentation files (the "Software"), to +// deal in the Software without restriction, including without limitation the +// rights to use, copy, modify, merge, publish, distribute, sublicense, and/or +// sell copies of the Software, and to permit persons to whom the Software is +// furnished to do so, subject to the following conditions: +// +// The above copyright notice and this permission notice shall be included in +// all copies or substantial portions of the Software. +// +// THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR +// IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, +// FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE +// AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER +// LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, +// OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN +// THE SOFTWARE. + +import { Component, TemplateRef, ViewChild } from "@angular/core"; + +/** + * Interactive KPI Tile View example with custom value formatting + * and click event handling. + */ +@Component({ + selector: "kpi-tile-view-interactive-example", + templateUrl: "./kpi-tile-view-interactive-example.component.html", + standalone: false, +}) +export class KpiTileViewInteractiveExampleComponent { + public currentValue = 1_247; + public lastClickedTile = ""; + + @ViewChild("customValueTpl", { static: true }) + public customValueTpl: TemplateRef; + + public onTileClick(): void { + this.lastClickedTile = "Active Sessions"; + } + + public onUptimeClick(): void { + this.lastClickedTile = "Uptime"; + } +} diff --git a/packages/dashboards/examples/src/components/docs/widget-types/view-components/proportional-chart-view-playground/proportional-chart-view-playground-example.component.html b/packages/dashboards/examples/src/components/docs/widget-types/view-components/proportional-chart-view-playground/proportional-chart-view-playground-example.component.html new file mode 100644 index 000000000..fbcf1d74f --- /dev/null +++ b/packages/dashboards/examples/src/components/docs/widget-types/view-components/proportional-chart-view-playground/proportional-chart-view-playground-example.component.html @@ -0,0 +1,42 @@ +
+ +
+ + + + {{ opt }} + + + + + + + + {{ opt }} + + + +
+ +
+ +
+
+ + + +
{{ totalOf(data) }}
+
diff --git a/packages/dashboards/examples/src/components/docs/widget-types/view-components/proportional-chart-view-playground/proportional-chart-view-playground-example.component.ts b/packages/dashboards/examples/src/components/docs/widget-types/view-components/proportional-chart-view-playground/proportional-chart-view-playground-example.component.ts new file mode 100644 index 000000000..776265803 --- /dev/null +++ b/packages/dashboards/examples/src/components/docs/widget-types/view-components/proportional-chart-view-playground/proportional-chart-view-playground-example.component.ts @@ -0,0 +1,78 @@ +// © 2022 SolarWinds Worldwide, LLC. All rights reserved. +// +// Permission is hereby granted, free of charge, to any person obtaining a copy +// of this software and associated documentation files (the "Software"), to +// deal in the Software without restriction, including without limitation the +// rights to use, copy, modify, merge, publish, distribute, sublicense, and/or +// sell copies of the Software, and to permit persons to whom the Software is +// furnished to do so, subject to the following conditions: +// +// The above copyright notice and this permission notice shall be included in +// all copies or substantial portions of the Software. +// +// THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR +// IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, +// FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE +// AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER +// LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, +// OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN +// THE SOFTWARE. + +import { Component } from "@angular/core"; +import { FormControl } from "@angular/forms"; + +import { IProportionalDataItem } from "@nova-ui/dashboards"; + +type ProportionalChartType = "donut" | "pie" | "verticalBar" | "horizontalBar"; +type LegendPlacement = "right" | "bottom" | "none"; + +/** + * Proportional Chart View - Playground example. + * Lets you switch between all supported chart types and legend placements + * to see every visual variant the standalone view component provides. + */ +@Component({ + selector: "proportional-chart-view-playground-example", + templateUrl: "./proportional-chart-view-playground-example.component.html", + standalone: false, +}) +export class ProportionalChartViewPlaygroundExampleComponent { + public readonly chartTypeControl = new FormControl( + "donut", + { nonNullable: true } + ); + public readonly legendPlacementControl = new FormControl( + "right", + { nonNullable: true } + ); + + public readonly chartTypeOptions: ProportionalChartType[] = [ + "donut", + "pie", + "verticalBar", + "horizontalBar", + ]; + public readonly legendPlacementOptions: LegendPlacement[] = [ + "right", + "bottom", + "none", + ]; + + public colors: Record = { + down: "#dc3545", + up: "#2cc079", + warning: "#f3a002", + unknown: "#707070", + }; + + public chartData: Array = [ + { id: "up", name: "Up", value: 78 }, + { id: "down", name: "Down", value: 8 }, + { id: "warning", name: "Warning", value: 12 }, + { id: "unknown", name: "Unknown", value: 2 }, + ]; + + public totalOf(data: IProportionalDataItem[] | undefined): number { + return (data ?? []).reduce((sum, d) => sum + (d?.value ?? 0), 0); + } +} diff --git a/packages/dashboards/examples/src/components/docs/widget-types/view-components/view-components-docs.component.html b/packages/dashboards/examples/src/components/docs/widget-types/view-components/view-components-docs.component.html new file mode 100644 index 000000000..42244e8b4 --- /dev/null +++ b/packages/dashboards/examples/src/components/docs/widget-types/view-components/view-components-docs.component.html @@ -0,0 +1,152 @@ +

Standalone View Components

+ +

+ Nova Dashboards provides standalone view components that encapsulate the + rendering logic of proportional charts and KPI tiles without + requiring the Pizzagna framework. These components can be used directly in + any Angular application by importing NuiDashboardViewsModule. +

+ + + These view components have zero dependency on + PIZZAGNA_EVENT_BUS, DATA_SOURCE, or + ComponentPortalService. They accept data via + @Input() properties and emit events via + @Output(). + + +

Installation

+ +

Import the module in your feature module:

+ + + +
+ +

KPI Tile View

+ +

+ The <nui-kpi-tile-view> component renders a single KPI + indicator tile with a label, value, units, and configurable background color. + It supports interactive click handling and custom value formatting via + ng-template. +

+ +

Playground — All States

+

+ Use the controls below to switch between the supported visual states + (normal, loading, empty) and toggle + interactive click handling on each tile. +

+ + + + +

Interactive with Custom Formatting

+ + + + +
+ +

Proportional Chart View

+ +

+ The <nui-proportional-chart-view> component renders a + proportional chart (donut, pie, vertical bar, or horizontal bar) with an + integrated legend. It supports custom donut center content and legend item + templates via ng-template. +

+ +

Playground — All Chart Types

+

+ Use the controls below to switch between every supported + chartType (donut, pie, + verticalBar, horizontalBar) and every + legendPlacement option. +

+ + + + +
+ +

API Reference

+ +

nui-kpi-tile-view Inputs

+ + + + + + + + + + + + + + + + + + + + + + +
InputTypeDefaultDescription
valuestring | number | nullnullThe KPI value to display
labelstring""KPI label text
unitsstring""Unit suffix (e.g., "%", "ms")
backgroundColorstring""Tile background color
textColorstring""Optional text color override; auto-computed if omitted
linkstring""Optional drill-down URL (renders as anchor)
interactivebooleanfalseEnable click interactions
loadingbooleanfalseShows busy/spinner state
emptybooleanfalseShows empty state placeholder
valueTemplateTemplateRefnullCustom value rendering template
syncValuesBrokerIKpiTileViewBroker[][]Brokers for multi-tile zoom synchronization
+ +

nui-kpi-tile-view Outputs

+ + + + + + + +
OutputTypeDescription
tileClickEventEmitter<void>Emitted when the tile is clicked (only when interactive)
+ +

nui-proportional-chart-view Inputs

+ + + + + + + + + + + + + + + + + + +
InputTypeDefaultDescription
dataIProportionalDataItem[][]Array of chart data items
chartType"donut" | "pie" | "verticalBar" | "horizontalBar""donut"Chart type variant
legendPlacement"right" | "bottom" | "none""right"Legend position relative to chart
colorsstring[] | Record<string, string>[]Color palette array or id-to-color map
interactivebooleanfalseEnable click interactions on legend items
donutContentTemplateTemplateRefnullCustom donut center content template
legendItemTemplateTemplateRefnullCustom legend item template
+ +

nui-proportional-chart-view Outputs

+ + + + + + + +
OutputTypeDescription
itemClickEventEmitter<IProportionalDataItem>Emitted when a chart segment or legend item is clicked
+ +

IProportionalDataItem Interface

+ diff --git a/packages/dashboards/examples/src/components/docs/widget-types/view-components/view-components-docs.component.ts b/packages/dashboards/examples/src/components/docs/widget-types/view-components/view-components-docs.component.ts new file mode 100644 index 000000000..47be4ba32 --- /dev/null +++ b/packages/dashboards/examples/src/components/docs/widget-types/view-components/view-components-docs.component.ts @@ -0,0 +1,44 @@ +// © 2022 SolarWinds Worldwide, LLC. All rights reserved. +// +// Permission is hereby granted, free of charge, to any person obtaining a copy +// of this software and associated documentation files (the "Software"), to +// deal in the Software without restriction, including without limitation the +// rights to use, copy, modify, merge, publish, distribute, sublicense, and/or +// sell copies of the Software, and to permit persons to whom the Software is +// furnished to do so, subject to the following conditions: +// +// The above copyright notice and this permission notice shall be included in +// all copies or substantial portions of the Software. +// +// THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR +// IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, +// FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE +// AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER +// LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, +// OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN +// THE SOFTWARE. + +import { Component } from "@angular/core"; + +@Component({ + selector: "nui-view-components-docs", + templateUrl: "./view-components-docs.component.html", + standalone: false, +}) +export class ViewComponentsDocsComponent { + public readonly installationSnippet = `import { NuiDashboardViewsModule } from "@nova-ui/dashboards"; + +@NgModule({ + imports: [NuiDashboardViewsModule], +}) +export class MyFeatureModule {}`; + + public readonly proportionalDataItemSnippet = `interface IProportionalDataItem { + id: string; // Unique segment identifier + name: string; // Display name in legend + value: number; // Numeric value determining segment size + color?: string; // Optional CSS color (hex or token) + icon?: string; // Optional icon name for legend + link?: string; // Optional drill-down URL +}`; +} diff --git a/packages/dashboards/examples/src/components/docs/widget-types/view-components/view-components-docs.module.ts b/packages/dashboards/examples/src/components/docs/widget-types/view-components/view-components-docs.module.ts new file mode 100644 index 000000000..12ded880b --- /dev/null +++ b/packages/dashboards/examples/src/components/docs/widget-types/view-components/view-components-docs.module.ts @@ -0,0 +1,100 @@ +// © 2022 SolarWinds Worldwide, LLC. All rights reserved. +// +// Permission is hereby granted, free of charge, to any person obtaining a copy +// of this software and associated documentation files (the "Software"), to +// deal in the Software without restriction, including without limitation the +// rights to use, copy, modify, merge, publish, distribute, sublicense, and/or +// sell copies of the Software, and to permit persons to whom the Software is +// furnished to do so, subject to the following conditions: +// +// The above copyright notice and this permission notice shall be included in +// all copies or substantial portions of the Software. +// +// THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR +// IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, +// FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE +// AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER +// LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, +// OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN +// THE SOFTWARE. + +import { CommonModule } from "@angular/common"; +import { NgModule } from "@angular/core"; +import { ReactiveFormsModule } from "@angular/forms"; +import { RouterModule, Routes } from "@angular/router"; + +import { + NuiDocsModule, + NuiIconModule, + NuiMessageModule, + NuiFormFieldModule, + NuiSelectV2Module, + NuiSwitchModule, + DEMO_PATH_TOKEN, +} from "@nova-ui/bits"; +import { NuiDashboardViewsModule } from "@nova-ui/dashboards"; + +import { getDemoFiles } from "../../../../demo-files-factory"; +import { KpiTileViewBasicExampleComponent } from "./kpi-tile-view-basic/kpi-tile-view-basic-example.component"; +import { KpiTileViewInteractiveExampleComponent } from "./kpi-tile-view-interactive/kpi-tile-view-interactive-example.component"; +import { ProportionalChartViewPlaygroundExampleComponent } from "./proportional-chart-view-playground/proportional-chart-view-playground-example.component"; +import { ViewComponentsDocsComponent } from "./view-components-docs.component"; + +const routes: Routes = [ + { + path: "", + component: ViewComponentsDocsComponent, + data: { + srlc: { + hideIndicator: true, + }, + showThemeSwitcher: true, + }, + }, + { + path: "kpi-tile-view-basic", + component: KpiTileViewBasicExampleComponent, + data: { + srlc: { + hideIndicator: true, + }, + }, + }, + { + path: "proportional-chart-view-playground", + component: ProportionalChartViewPlaygroundExampleComponent, + data: { + srlc: { + hideIndicator: true, + }, + }, + }, +]; + +@NgModule({ + imports: [ + CommonModule, + ReactiveFormsModule, + RouterModule.forChild(routes), + NuiDocsModule, + NuiMessageModule, + NuiIconModule, + NuiFormFieldModule, + NuiSelectV2Module, + NuiSwitchModule, + NuiDashboardViewsModule, + ], + declarations: [ + ViewComponentsDocsComponent, + KpiTileViewBasicExampleComponent, + KpiTileViewInteractiveExampleComponent, + ProportionalChartViewPlaygroundExampleComponent, + ], + providers: [ + { + provide: DEMO_PATH_TOKEN, + useValue: getDemoFiles("view-components"), + }, + ], +}) +export default class ViewComponentsDocsModule {} diff --git a/packages/dashboards/examples/src/components/docs/widget-types/widget-types.module.ts b/packages/dashboards/examples/src/components/docs/widget-types/widget-types.module.ts index 59829d391..953e3d068 100644 --- a/packages/dashboards/examples/src/components/docs/widget-types/widget-types.module.ts +++ b/packages/dashboards/examples/src/components/docs/widget-types/widget-types.module.ts @@ -35,6 +35,7 @@ export enum WidgetTypesRoute { proportional = "proportional", embedded = "embedded", drilldown = "drilldown", + viewComponents = "view-components", } const routes: Routes = [ @@ -118,6 +119,18 @@ const routes: Routes = [ }, }, }, + { + path: WidgetTypesRoute.viewComponents, + loadChildren: async () => + import( + "./view-components/view-components-docs.module" + ) as object as Promise>, + data: { + srlc: { + hideIndicator: true, + }, + }, + }, ]; @NgModule({ diff --git a/packages/dashboards/examples/src/demo-files-factory.ts b/packages/dashboards/examples/src/demo-files-factory.ts index 4df857e3e..06449aa5b 100644 --- a/packages/dashboards/examples/src/demo-files-factory.ts +++ b/packages/dashboards/examples/src/demo-files-factory.ts @@ -1,6 +1,6 @@ import { CodeSourceFiles } from "@nova-ui/bits"; -import { DEMO_PATHS } from "./components/docs/demo.files"; +import { DEMO_PATHS, DEMO_TS_SOURCES } from "./components/docs/demo.files"; export const getDemoFiles = ( filePrefix: string @@ -11,14 +11,20 @@ export const getDemoFiles = ( return { context: filePrefix, files: files.map((filePath) => ({ - content: async () => - import(`./components/docs/${filePath}`).then((e) => { + content: async () => { + // For .ts files, return the raw source captured at build time + // (dynamic import would return Angular's compiled JS instead). + if (filePath.endsWith(".ts")) { + return DEMO_TS_SOURCES[filePath] ?? ""; + } + return import(`./components/docs/${filePath}`).then((e) => { if (e.default) { return e.default; } // typescript files may have exports non default members return `${Object.values(e).join("\n")}`; - }), + }); + }, path: filePath, })), }; diff --git a/packages/dashboards/examples/src/google-books.interceptor.ts b/packages/dashboards/examples/src/google-books.interceptor.ts new file mode 100644 index 000000000..bb5f6e103 --- /dev/null +++ b/packages/dashboards/examples/src/google-books.interceptor.ts @@ -0,0 +1,132 @@ +// © 2022 SolarWinds Worldwide, LLC. All rights reserved. +// +// Permission is hereby granted, free of charge, to any person obtaining a copy +// of this software and associated documentation files (the "Software"), to +// deal in the Software without restriction, including without limitation the +// rights to use, copy, modify, merge, publish, distribute, sublicense, and/or +// sell copies of the Software, and to permit persons to whom the Software is +// furnished to do so, subject to the following conditions: +// +// The above copyright notice and this permission notice shall be included in +// all copies or substantial portions of the Software. +// +// THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR +// IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, +// FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE +// AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER +// LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, +// OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN +// THE SOFTWARE. + +import { Injectable } from "@angular/core"; +import { + HttpEvent, + HttpHandler, + HttpInterceptor, + HttpRequest, + HttpResponse, +} from "@angular/common/http"; +import { Observable, of } from "rxjs"; +import { delay } from "rxjs/operators"; + +/** + * Intercepts HTTP requests to the Google Books API and returns mock data. + * This avoids hitting quota limits on the external API during development. + */ +@Injectable() +export class GoogleBooksInterceptor implements HttpInterceptor { + private static readonly MOCK_BOOKS: Record = { + "5MQFrgEACAAJ": { + kind: "books#volume", + id: "5MQFrgEACAAJ", + volumeInfo: { + title: "Harry Potter and the Sorcerer's Stone", + authors: ["J. K. Rowling"], + averageRating: 4.5, + ratingsCount: 120, + pageCount: 309, + infoLink: + "https://books.google.com/books?id=5MQFrgEACAAJ", + }, + }, + "zpvysRGsBlwC": { + kind: "books#volume", + id: "zpvysRGsBlwC", + volumeInfo: { + title: "Harry Potter and the Chamber of Secrets", + authors: ["J. K. Rowling"], + averageRating: 4.3, + ratingsCount: 95, + pageCount: 341, + infoLink: + "https://books.google.com/books?id=zpvysRGsBlwC", + }, + }, + }; + + private static readonly MOCK_SEARCH_RESPONSE = { + kind: "books#volumes", + totalItems: 3, + items: [ + { + id: "5MQFrgEACAAJ", + volumeInfo: { + title: "Harry Potter and the Sorcerer's Stone", + authors: ["J. K. Rowling"], + averageRating: 4.5, + ratingsCount: 120, + pageCount: 309, + }, + }, + { + id: "zpvysRGsBlwC", + volumeInfo: { + title: "Harry Potter and the Chamber of Secrets", + authors: ["J. K. Rowling"], + averageRating: 4.3, + ratingsCount: 95, + pageCount: 341, + }, + }, + { + id: "mockBook3", + volumeInfo: { + title: "Harry Potter and the Prisoner of Azkaban", + authors: ["J. K. Rowling"], + averageRating: 4.6, + ratingsCount: 150, + pageCount: 435, + }, + }, + ], + }; + + intercept( + req: HttpRequest, + next: HttpHandler + ): Observable> { + if (!req.url.includes("googleapis.com/books")) { + return next.handle(req); + } + + // Match single volume request: /volumes/{id} + const volumeMatch = req.url.match(/\/volumes\/([^?/]+)$/); + if (volumeMatch) { + const id = volumeMatch[1]; + const mockData = + GoogleBooksInterceptor.MOCK_BOOKS[id] ?? + GoogleBooksInterceptor.MOCK_BOOKS["5MQFrgEACAAJ"]; + return of( + new HttpResponse({ status: 200, body: mockData }) + ).pipe(delay(300)); + } + + // Match search/list request: /volumes?q=... + return of( + new HttpResponse({ + status: 200, + body: GoogleBooksInterceptor.MOCK_SEARCH_RESPONSE, + }) + ).pipe(delay(300)); + } +} diff --git a/packages/dashboards/examples/src/module.ts b/packages/dashboards/examples/src/module.ts index 9dc4b501f..d7dd0738c 100644 --- a/packages/dashboards/examples/src/module.ts +++ b/packages/dashboards/examples/src/module.ts @@ -19,7 +19,7 @@ // THE SOFTWARE. import { CommonModule, DatePipe } from "@angular/common"; -import { HttpClientModule } from "@angular/common/http"; +import { HTTP_INTERCEPTORS, HttpClientModule } from "@angular/common/http"; import { NgModule } from "@angular/core"; import { BrowserModule } from "@angular/platform-browser"; import { RouterModule } from "@angular/router"; @@ -34,6 +34,7 @@ import { NuiChartsModule } from "@nova-ui/charts"; import { AppComponent } from "./components"; import { AppRoutingModule } from "./components/app/app-routing.module"; import { AnimationsModule } from "./environments/environment"; +import { GoogleBooksInterceptor } from "./google-books.interceptor"; @NgModule({ imports: [ @@ -52,6 +53,11 @@ import { AnimationsModule } from "./environments/environment"; providers: [ DatePipe, LocalFilteringDataSource, + { + provide: HTTP_INTERCEPTORS, + useClass: GoogleBooksInterceptor, + multi: true, + }, ], bootstrap: [AppComponent], }) diff --git a/packages/dashboards/scripts/compile-demo-paths.js b/packages/dashboards/scripts/compile-demo-paths.js index 619f723cb..b55c59340 100644 --- a/packages/dashboards/scripts/compile-demo-paths.js +++ b/packages/dashboards/scripts/compile-demo-paths.js @@ -11,7 +11,7 @@ const getFilesRecursively = (directory) => { if (fs.statSync(absolute).isDirectory()) { getFilesRecursively(absolute); } else { - files.push(`"${absolute}"`); + files.push(absolute); } } }; @@ -22,9 +22,22 @@ const trimmedFiles = files.map((filePath) => filePath.replace(/\\/g, "/").replace(dir, "") ); +// Embed raw .ts source so docs viewer shows the original TypeScript instead of +// the Ivy-compiled output produced by a dynamic ES import. +const escapeBackticks = (s) => s.replace(/\\/g, "\\\\").replace(/`/g, "\\`").replace(/\$\{/g, "\\${"); +const tsSources = files + .map((absolutePath, i) => { + if (!absolutePath.endsWith(".ts")) return null; + const relative = trimmedFiles[i]; + const content = fs.readFileSync(absolutePath, "utf8"); + return ` "${relative}": \`${escapeBackticks(content)}\``; + }) + .filter(Boolean) + .join(",\n"); + fs.writeFileSync( `${dir}/demo.files.ts`, - `// this file autogenerated, do not edit it manually please run the script\n// yarn run compile-demo-paths\nexport const DEMO_PATHS = [\n ${trimmedFiles.join( - ",\n " - )},\n];\n` + `// this file autogenerated, do not edit it manually please run the script\n// yarn run compile-demo-paths\nexport const DEMO_PATHS = [\n ${trimmedFiles + .map((f) => `"${f}"`) + .join(",\n ")},\n];\n\nexport const DEMO_TS_SOURCES: Record = {\n${tsSources},\n};\n` ); diff --git a/packages/dashboards/src/docs/development/summary.json b/packages/dashboards/src/docs/development/summary.json index 576820646..837d28100 100644 --- a/packages/dashboards/src/docs/development/summary.json +++ b/packages/dashboards/src/docs/development/summary.json @@ -156,6 +156,10 @@ { "title": "Drilldown", "file": "../pages/widget-types/drilldown.md" + }, + { + "title": "Standalone View Components", + "file": "../pages/widget-types/view-components.md" } ] } diff --git a/packages/dashboards/src/docs/pages/widget-types/view-components.md b/packages/dashboards/src/docs/pages/widget-types/view-components.md new file mode 100644 index 000000000..a11c95562 --- /dev/null +++ b/packages/dashboards/src/docs/pages/widget-types/view-components.md @@ -0,0 +1 @@ + diff --git a/packages/dashboards/src/docs/production/summary.json b/packages/dashboards/src/docs/production/summary.json index 576820646..837d28100 100644 --- a/packages/dashboards/src/docs/production/summary.json +++ b/packages/dashboards/src/docs/production/summary.json @@ -156,6 +156,10 @@ { "title": "Drilldown", "file": "../pages/widget-types/drilldown.md" + }, + { + "title": "Standalone View Components", + "file": "../pages/widget-types/view-components.md" } ] } diff --git a/packages/dashboards/src/lib/components/kpi-widget/kpi.component.html b/packages/dashboards/src/lib/components/kpi-widget/kpi.component.html index 9675da113..6e27fcbbf 100644 --- a/packages/dashboards/src/lib/components/kpi-widget/kpi.component.html +++ b/packages/dashboards/src/lib/components/kpi-widget/kpi.component.html @@ -1,77 +1,33 @@ -
- - - - - + - -
- -
+ + + + + -
- - -
-
-
-
-
- {{widgetData?.label}} -
- - -
- - - -
-
- -
- {{widgetData?.units}} -
-
-
-
- - -
- - {{widgetData?.value}} -
diff --git a/packages/dashboards/src/lib/components/public-api.ts b/packages/dashboards/src/lib/components/public-api.ts index 15fcd5936..99f521a87 100644 --- a/packages/dashboards/src/lib/components/public-api.ts +++ b/packages/dashboards/src/lib/components/public-api.ts @@ -35,3 +35,4 @@ export * from "./embedded-content/embedded-content.component"; export * from "./list-widget/public-api"; export * from "./widget-search/widget-search.component"; export * from "./widget-search/types"; +export * from "./views/public-api"; diff --git a/packages/dashboards/src/lib/components/views/kpi-tile-view/kpi-tile-view.component.html b/packages/dashboards/src/lib/components/views/kpi-tile-view/kpi-tile-view.component.html new file mode 100644 index 000000000..562ab225c --- /dev/null +++ b/packages/dashboards/src/lib/components/views/kpi-tile-view/kpi-tile-view.component.html @@ -0,0 +1,72 @@ +
+ + + + + + + +
+ +
+
+
+ + +
+
+
+
+
+ {{ label }} +
+ + +
+ + + +
+
+ + +
+ + {{ value }} + +
+
+ +
+ {{ units }} +
+
+
+
diff --git a/packages/dashboards/src/lib/components/views/kpi-tile-view/kpi-tile-view.component.less b/packages/dashboards/src/lib/components/views/kpi-tile-view/kpi-tile-view.component.less new file mode 100644 index 000000000..09261ba0c --- /dev/null +++ b/packages/dashboards/src/lib/components/views/kpi-tile-view/kpi-tile-view.component.less @@ -0,0 +1,108 @@ +@import (reference) "@nova-ui/bits/sdk/less/nui-framework-variables"; +@import (reference) "@nova-ui/bits/sdk/less/mixins"; + +.flex-center() { + display: flex; + align-items: center; + justify-content: center; +} + +.restrict-content-height(@height: 30%; @maxHeight: @nui-space-md * 3; @minHeight: @nui-space-sm) { + height: @height; + min-height: @minHeight; + max-height: @maxHeight; +} + +.nui-kpi-indicator { + position: relative; + display: block; + height: 100%; + width: 100%; + + &__background { + height: 100%; + width: 100%; + + .setCssVariable(background-color, nui-color-bg-secondary); + .setCssVariable(color, nui-color-text-default); + } + + &__text { + position: absolute; + width: 100%; + height: 100%; + top: 0; + pointer-events: none; + + * > span { + filter: invert(1) grayscale(1) contrast(100); + } + } + + &__value { + font-size: @nui-font-size-hero * 3; // 54 + font-weight: @nui-font-weight-semibold; + margin: auto 0; + min-width: 100%; + white-space: nowrap; + .flex-center(); + .restrict-content-height(100%, 200px, 5px); + + & > span.without-filter { + filter: unset; + } + } + + &__units { + font-size: @nui-font-size-big; + font-weight: @nui-font-weight-semibold; + min-width: 0; + margin-bottom: auto; + .flex-center(); + .restrict-content-height(100%); + + & > span { + .text-overflow(ellipsis); + } + } + + &__description { + font-size: @nui-font-size-big; + font-weight: @nui-font-weight-semibold; + min-width: 0; + margin-top: auto; + .flex-center(); + .restrict-content-height(100%); + + & > span { + .text-overflow(ellipsis); + } + } + + &__zoom-container { + display: grid; + grid-template-columns: 1fr; + width: 100%; + height: 100%; + } + &__zoom-container:has(.nui-kpi-indicator__zoom-container__row:nth-child(3)) { + grid-template-rows: 30% 40% 30%; + } + + &__zoom-container:not(:has(.nui-kpi-indicator__zoom-container__row:nth-child(3))) { + grid-template-rows: 30% 70%; + } + &--interactive { + cursor: pointer; + + &:hover { + filter: brightness(90%); + } + } +} + +.is-empty { + display: grid; + place-content: center; + height: 100%; +} diff --git a/packages/dashboards/src/lib/components/views/kpi-tile-view/kpi-tile-view.component.spec.ts b/packages/dashboards/src/lib/components/views/kpi-tile-view/kpi-tile-view.component.spec.ts new file mode 100644 index 000000000..e8e3c80b4 --- /dev/null +++ b/packages/dashboards/src/lib/components/views/kpi-tile-view/kpi-tile-view.component.spec.ts @@ -0,0 +1,254 @@ +// © 2022 SolarWinds Worldwide, LLC. All rights reserved. +// +// Permission is hereby granted, free of charge, to any person obtaining a copy +// of this software and associated documentation files (the "Software"), to +// deal in the Software without restriction, including without limitation the +// rights to use, copy, modify, merge, publish, distribute, sublicense, and/or +// sell copies of the Software, and to permit persons to whom the Software is +// furnished to do so, subject to the following conditions: +// +// The above copyright notice and this permission notice shall be included in +// all copies or substantial portions of the Software. +// +// THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR +// IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, +// FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE +// AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER +// LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, +// OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN +// THE SOFTWARE. + +import { Component, TemplateRef, ViewChild } from "@angular/core"; +import { ComponentFixture, TestBed } from "@angular/core/testing"; +import { BehaviorSubject } from "rxjs"; + +import { NuiDashboardViewsModule } from "../views.module"; +import { IKpiTileViewBroker, KpiTileViewComponent } from "./kpi-tile-view.component"; + +@Component({ + template: ` + + + + {{ val }} custom + + `, + standalone: false, +}) +class TestHostComponent { + value: string | number | null = null; + label = ""; + units = ""; + backgroundColor = ""; + textColor = ""; + link = ""; + interactive = false; + loading = false; + empty = false; + fontSize = ""; + syncValuesBroker: Array = []; + customValueTpl: TemplateRef | null = null; + clicked = false; + + @ViewChild("customValueTpl") customValueTplRef: TemplateRef; + + onTileClick(): void { + this.clicked = true; + } +} + +describe("KpiTileViewComponent", () => { + let fixture: ComponentFixture; + let host: TestHostComponent; + let component: KpiTileViewComponent; + + beforeEach(async () => { + await TestBed.configureTestingModule({ + imports: [NuiDashboardViewsModule], + declarations: [TestHostComponent], + }).compileComponents(); + + fixture = TestBed.createComponent(TestHostComponent); + host = fixture.componentInstance; + component = fixture.debugElement.children[0].componentInstance; + }); + + it("should create without Pizzagna providers", () => { + expect(component).toBeTruthy(); + }); + + it("should render value and label", () => { + host.value = 42; + host.label = "Nodes Up"; + fixture.detectChanges(); + + const el: HTMLElement = fixture.nativeElement; + expect(el.textContent).toContain("42"); + expect(el.textContent).toContain("Nodes Up"); + }); + + it("should render units", () => { + host.value = 99; + host.units = "%"; + fixture.detectChanges(); + + const el: HTMLElement = fixture.nativeElement; + expect(el.textContent).toContain("%"); + }); + + it("should apply background color", () => { + host.value = 10; + host.backgroundColor = "#2cc079"; + fixture.detectChanges(); + + const bg = fixture.nativeElement.querySelector(".nui-kpi-indicator__background"); + expect(bg.style.backgroundColor).toBeTruthy(); + }); + + it("should show empty state when empty is true", () => { + host.empty = true; + fixture.detectChanges(); + + expect(component.showEmpty).toBe(true); + }); + + it("should show empty state when value is null", () => { + host.value = null; + fixture.detectChanges(); + + expect(component.showEmpty).toBe(true); + }); + + it("should not show empty state when value is 0", () => { + host.value = 0; + fixture.detectChanges(); + + expect(component.showEmpty).toBe(false); + }); + + it("should not show empty state when value is boolean", () => { + host.value = false as any; + fixture.detectChanges(); + + expect(component.showEmpty).toBe(false); + }); + + it("should emit tileClick when interactive and clicked", () => { + host.value = 42; + host.interactive = true; + fixture.detectChanges(); + + component.onInteraction(); + expect(host.clicked).toBe(true); + }); + + it("should not emit tileClick when not interactive", () => { + host.value = 42; + host.interactive = false; + fixture.detectChanges(); + + component.onInteraction(); + expect(host.clicked).toBe(false); + }); + + it("should not be interactive when showEmpty is true", () => { + host.value = null; + host.interactive = true; + fixture.detectChanges(); + + expect(component.isInteractive).toBe(false); + }); + + it("should render link as anchor element", () => { + host.value = 42; + host.label = "Test"; + host.link = "https://example.com"; + fixture.detectChanges(); + + const anchor = fixture.nativeElement.querySelector("a.nui-kpi-indicator"); + expect(anchor).toBeTruthy(); + expect(anchor.href).toContain("example.com"); + }); + + it("should render div when no link", () => { + host.value = 42; + host.label = "Test"; + host.link = ""; + fixture.detectChanges(); + + const div = fixture.nativeElement.querySelector("div.nui-kpi-indicator"); + expect(div).toBeTruthy(); + }); + + it("should compute text color from background when no explicit textColor", () => { + host.value = 42; + host.backgroundColor = "#ff0000"; + host.textColor = ""; + fixture.detectChanges(); + + expect(component.computedTextColor).toBe("#ff0000"); + }); + + it("should use explicit textColor when provided", () => { + host.value = 42; + host.backgroundColor = "#ff0000"; + host.textColor = "#ffffff"; + fixture.detectChanges(); + + expect(component.computedTextColor).toBe("#ffffff"); + }); + + it("should use default color when no background or text color", () => { + host.value = 42; + fixture.detectChanges(); + + expect(component.computedTextColor).toBe("var(--nui-color-bg-secondary)"); + }); + + it("should render custom value template when provided", () => { + host.value = 42; + fixture.detectChanges(); + + // Set the custom template + host.customValueTpl = host.customValueTplRef; + fixture.detectChanges(); + + const el: HTMLElement = fixture.nativeElement; + expect(el.querySelector(".custom-value")).toBeTruthy(); + }); + + it("should return correct scale broker", () => { + const mockBroker: IKpiTileViewBroker = { + id: "value", + in$: new BehaviorSubject({ id: "value", targetID: "", targetValue: 0 }), + out$: new BehaviorSubject({ id: "value", targetID: "", targetValue: 0 }), + }; + host.syncValuesBroker = [mockBroker]; + fixture.detectChanges(); + + expect(component.getScaleBroker("value")).toBe(mockBroker); + expect(component.getScaleBroker("nonexistent")).toBeUndefined(); + }); + + it("should show loading state", () => { + host.loading = true; + fixture.detectChanges(); + + const busyEl = fixture.nativeElement.querySelector("[nui-busy]"); + expect(busyEl).toBeTruthy(); + }); +}); diff --git a/packages/dashboards/src/lib/components/views/kpi-tile-view/kpi-tile-view.component.ts b/packages/dashboards/src/lib/components/views/kpi-tile-view/kpi-tile-view.component.ts new file mode 100644 index 000000000..9122d4210 --- /dev/null +++ b/packages/dashboards/src/lib/components/views/kpi-tile-view/kpi-tile-view.component.ts @@ -0,0 +1,116 @@ +// © 2022 SolarWinds Worldwide, LLC. All rights reserved. +// +// Permission is hereby granted, free of charge, to any person obtaining a copy +// of this software and associated documentation files (the "Software"), to +// deal in the Software without restriction, including without limitation the +// rights to use, copy, modify, merge, publish, distribute, sublicense, and/or +// sell copies of the Software, and to permit persons to whom the Software is +// furnished to do so, subject to the following conditions: +// +// The above copyright notice and this permission notice shall be included in +// all copies or substantial portions of the Software. +// +// THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR +// IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, +// FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE +// AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER +// LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, +// OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN +// THE SOFTWARE. + +import { + ChangeDetectionStrategy, + ChangeDetectorRef, + Component, + EventEmitter, + Input, + OnChanges, + Output, + SimpleChanges, + TemplateRef, + ViewEncapsulation, +} from "@angular/core"; +import _isNil from "lodash/isNil"; +import { BehaviorSubject } from "rxjs"; + +import { IBrokerValue } from "../../providers/types"; + +export interface IKpiTileViewBroker { + id: string; + type?: "min" | "max"; + in$: BehaviorSubject; + out$: BehaviorSubject; +} + +@Component({ + selector: "nui-kpi-tile-view", + templateUrl: "./kpi-tile-view.component.html", + styleUrls: ["./kpi-tile-view.component.less"], + encapsulation: ViewEncapsulation.Emulated, + changeDetection: ChangeDetectionStrategy.OnPush, + standalone: false, +}) +export class KpiTileViewComponent implements OnChanges { + @Input() public value: string | number | null; + @Input() public label: string; + @Input() public units: string; + @Input() public backgroundColor: string; + @Input() public textColor: string; + @Input() public icon: string; + @Input() public link: string; + @Input() public interactive = false; + @Input() public loading = false; + @Input() public empty = false; + @Input() public fontSize: string; + @Input() public margin: number = 2; + @Input() public syncValuesBroker: Array; + @Input() public valueTemplate: TemplateRef; + + @Output() public tileClick = new EventEmitter(); + + public defaultColor = "var(--nui-color-bg-secondary)"; + + public get computedTextColor(): string { + return this.textColor || this.backgroundColor || this.defaultColor; + } + + public get isInteractive(): boolean { + return this.interactive && !this.showEmpty; + } + + public get showEmpty(): boolean { + if (this.empty) { + return true; + } + + if (typeof this.value === "boolean") { + return false; + } + + if (Array.isArray(this.value) && this.value.length === 0) { + return true; + } + + return _isNil(this.value) && this.value !== 0; + } + + constructor(private changeDetector: ChangeDetectorRef) {} + + public ngOnChanges(_changes: SimpleChanges): void { + this.changeDetector.markForCheck(); + } + + public onInteraction(): void { + if (!this.isInteractive) { + return; + } + this.tileClick.emit(); + } + + public getScaleBroker(id: string): IKpiTileViewBroker | undefined { + if (this.syncValuesBroker) { + return this.syncValuesBroker.find((b) => b.id === id); + } + return undefined; + } +} diff --git a/packages/dashboards/src/lib/components/views/proportional-chart-view/proportional-chart-view.component.html b/packages/dashboards/src/lib/components/views/proportional-chart-view/proportional-chart-view.component.html new file mode 100644 index 000000000..6d0df7de5 --- /dev/null +++ b/packages/dashboards/src/lib/components/views/proportional-chart-view/proportional-chart-view.component.html @@ -0,0 +1,95 @@ +
+
+ + +
+
+ + + +
+
+
+
+
+ + + +
+ +
+
+ + + + + +
+
+ {{ legendSeries.name }} +
+ + {{ legendSeries.name }} + +
+
+
+
+
+
diff --git a/packages/dashboards/src/lib/components/views/proportional-chart-view/proportional-chart-view.component.less b/packages/dashboards/src/lib/components/views/proportional-chart-view/proportional-chart-view.component.less new file mode 100644 index 000000000..e28653790 --- /dev/null +++ b/packages/dashboards/src/lib/components/views/proportional-chart-view/proportional-chart-view.component.less @@ -0,0 +1,80 @@ +@import (reference) "@nova-ui/bits/sdk/less/nui-framework-variables.less"; +@import (reference) "@nova-ui/bits/sdk/less/mixins.less"; + +.nui-proportional-chart-view { + &__chart-donut-content { + position: absolute; + } + + &__legend { + &--interactive { + cursor: pointer !important; + } + } +} + +.has-overlay { + overflow: auto; +} + +.nui-chart-layout { + grid-template: + "axis-label-left axis-label-right ." 0fr + "chart chart legend" minmax(60%, auto) + "axis-label-bottom axis-label-bottom ." 0fr + "legend-bottom legend-bottom ." minmax(auto, min-content) + ~"/" 1fr 1fr auto; + + .legend-bottom { + overflow: auto; + align-self: auto; + padding-left: 0; + } +} + +.prioritize-rows { + &__right, + &__bottom { + .nui-legend-series { + max-width: unset; + width: 100%; + } + } + + .legend { + justify-self: start; + } + + &__right { + grid-template: + "chart chart ." 60% + "legend legend ." 40%; + } + + &__bottom { + grid-template: + "chart chart ." 60% + "legend-bottom legend-bottom ." 40%; + } +} + +.empty-grid { + grid-template-columns: 1fr; + grid-template-rows: 1fr; +} + +.proportional-chart { + &__legend-series { + .link { + &.description-primary { + .setCssVariable(color, nui-color-text-link); + } + } + } +} + +.is-empty { + display: grid; + place-content: center; + height: 100%; +} diff --git a/packages/dashboards/src/lib/components/views/proportional-chart-view/proportional-chart-view.component.spec.ts b/packages/dashboards/src/lib/components/views/proportional-chart-view/proportional-chart-view.component.spec.ts new file mode 100644 index 000000000..0735de368 --- /dev/null +++ b/packages/dashboards/src/lib/components/views/proportional-chart-view/proportional-chart-view.component.spec.ts @@ -0,0 +1,235 @@ +// © 2022 SolarWinds Worldwide, LLC. All rights reserved. +// +// Permission is hereby granted, free of charge, to any person obtaining a copy +// of this software and associated documentation files (the "Software"), to +// deal in the Software without restriction, including without limitation the +// rights to use, copy, modify, merge, publish, distribute, sublicense, and/or +// sell copies of the Software, and to permit persons to whom the Software is +// furnished to do so, subject to the following conditions: +// +// The above copyright notice and this permission notice shall be included in +// all copies or substantial portions of the Software. +// +// THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR +// IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, +// FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE +// AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER +// LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, +// OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN +// THE SOFTWARE. + +import { Component, TemplateRef, ViewChild } from "@angular/core"; +import { ComponentFixture, TestBed } from "@angular/core/testing"; + +import { UnitConversionService } from "@nova-ui/bits"; +import { NuiChartsModule } from "@nova-ui/charts"; + +import { NuiDashboardViewsModule } from "../views.module"; +import { IProportionalDataItem } from "../types"; +import { ProportionalChartViewComponent } from "./proportional-chart-view.component"; + +@Component({ + template: ` + + + + Custom Content + + + + {{ item?.name }} + + `, + standalone: false, +}) +class TestHostComponent { + data: Array = []; + chartType: "donut" | "pie" | "verticalBar" | "horizontalBar" = "donut"; + legendPlacement: "right" | "bottom" | "none" = "right"; + colors: Array | Record = []; + interactive = false; + clickedItem: IProportionalDataItem | null = null; + + @ViewChild("donutTpl") donutTpl: TemplateRef; + @ViewChild("legendTpl") legendTpl: TemplateRef; + + onItemClick(item: IProportionalDataItem): void { + this.clickedItem = item; + } +} + +describe("ProportionalChartViewComponent", () => { + let fixture: ComponentFixture; + let host: TestHostComponent; + let component: ProportionalChartViewComponent; + + const mockData: Array = [ + { id: "segment-1", name: "Segment One", value: 30, color: "#ff0000" }, + { id: "segment-2", name: "Segment Two", value: 50, color: "#00ff00" }, + { id: "segment-3", name: "Segment Three", value: 20, color: "#0000ff" }, + ]; + + beforeEach(async () => { + await TestBed.configureTestingModule({ + imports: [NuiDashboardViewsModule, NuiChartsModule], + declarations: [TestHostComponent], + providers: [UnitConversionService], + }).compileComponents(); + + fixture = TestBed.createComponent(TestHostComponent); + host = fixture.componentInstance; + component = fixture.debugElement.children[0].componentInstance; + }); + + it("should create without Pizzagna providers", () => { + expect(component).toBeTruthy(); + }); + + it("should display empty state when data is empty", () => { + host.data = []; + fixture.detectChanges(); + expect(component.isEmpty).toBe(true); + }); + + it("should build a donut chart by default", () => { + host.data = mockData; + fixture.detectChanges(); + + expect(component.chartAssist).toBeTruthy(); + expect(component.isDonutChart()).toBe(true); + expect(component.donutContentPlugin).toBeTruthy(); + }); + + it("should build a pie chart when chartType is 'pie'", () => { + host.data = mockData; + host.chartType = "pie"; + fixture.detectChanges(); + + expect(component.chartAssist).toBeTruthy(); + expect(component.isDonutChart()).toBe(false); + expect(component.donutContentPlugin).toBeNull(); + }); + + it("should build a vertical bar chart", () => { + host.data = mockData; + host.chartType = "verticalBar"; + fixture.detectChanges(); + + expect(component.chartAssist).toBeTruthy(); + expect(component.donutContentPlugin).toBeNull(); + }); + + it("should build a horizontal bar chart", () => { + host.data = mockData; + host.chartType = "horizontalBar"; + fixture.detectChanges(); + + expect(component.chartAssist).toBeTruthy(); + expect(component.donutContentPlugin).toBeNull(); + }); + + it("should show legend when legendPlacement is 'right'", () => { + host.data = mockData; + host.legendPlacement = "right"; + fixture.detectChanges(); + + expect(component.hasLegend()).toBe(true); + expect(component.legendShouldBeAlignedRight()).toBe(true); + }); + + it("should show legend when legendPlacement is 'bottom'", () => { + host.data = mockData; + host.legendPlacement = "bottom"; + fixture.detectChanges(); + + expect(component.hasLegend()).toBe(true); + expect(component.legendShouldBeAlignedRight()).toBe(false); + }); + + it("should hide legend when legendPlacement is 'none'", () => { + host.data = mockData; + host.legendPlacement = "none"; + fixture.detectChanges(); + + expect(component.hasLegend()).toBe(false); + }); + + it("should not emit itemClick when not interactive", () => { + host.data = mockData; + host.interactive = false; + fixture.detectChanges(); + + component.onInteraction({ id: "segment-1" }); + expect(host.clickedItem).toBeNull(); + }); + + it("should emit itemClick when interactive", () => { + host.data = mockData; + host.interactive = true; + fixture.detectChanges(); + + component.onInteraction({ id: "segment-1" }); + expect(host.clickedItem).toEqual(mockData[0]); + }); + + it("should apply colors from data", () => { + host.data = mockData; + fixture.detectChanges(); + + // Chart should be built with data-driven colors + expect(component.chartAssist).toBeTruthy(); + }); + + it("should apply configuration colors when provided", () => { + host.data = [ + { id: "a", name: "A", value: 10 }, + { id: "b", name: "B", value: 20 }, + ]; + host.colors = ["#aaa", "#bbb"]; + fixture.detectChanges(); + + expect(component.chartAssist).toBeTruthy(); + }); + + it("should apply mapped colors when provided as record", () => { + host.data = [ + { id: "a", name: "A", value: 10 }, + { id: "b", name: "B", value: 20 }, + ]; + host.colors = { a: "#aaa", b: "#bbb" }; + fixture.detectChanges(); + + expect(component.chartAssist).toBeTruthy(); + }); + + it("should update chart when data changes", () => { + host.data = mockData; + fixture.detectChanges(); + + const chartAssist1 = component.chartAssist; + + host.data = [ + { id: "x", name: "X", value: 100, color: "#fff" }, + ]; + fixture.detectChanges(); + + // Chart assist may be rebuilt due to color changes + expect(component.chartAssist).toBeTruthy(); + }); + + it("should clean up on destroy", () => { + host.data = mockData; + fixture.detectChanges(); + + expect(() => fixture.destroy()).not.toThrow(); + }); +}); diff --git a/packages/dashboards/src/lib/components/views/proportional-chart-view/proportional-chart-view.component.ts b/packages/dashboards/src/lib/components/views/proportional-chart-view/proportional-chart-view.component.ts new file mode 100644 index 000000000..6290157e1 --- /dev/null +++ b/packages/dashboards/src/lib/components/views/proportional-chart-view/proportional-chart-view.component.ts @@ -0,0 +1,346 @@ +// © 2022 SolarWinds Worldwide, LLC. All rights reserved. +// +// Permission is hereby granted, free of charge, to any person obtaining a copy +// of this software and associated documentation files (the "Software"), to +// deal in the Software without restriction, including without limitation the +// rights to use, copy, modify, merge, publish, distribute, sublicense, and/or +// sell copies of the Software, and to permit persons to whom the Software is +// furnished to do so, subject to the following conditions: +// +// The above copyright notice and this permission notice shall be included in +// all copies or substantial portions of the Software. +// +// THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR +// IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, +// FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE +// AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER +// LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, +// OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN +// THE SOFTWARE. + +import { + AfterViewInit, + ChangeDetectorRef, + Component, + ElementRef, + EventEmitter, + Input, + KeyValueDiffer, + KeyValueDiffers, + NgZone, + OnChanges, + OnDestroy, + Output, + SimpleChanges, + TemplateRef, + ViewChild, + ViewEncapsulation, +} from "@angular/core"; +import some from "lodash/some"; +import { Subscription } from "rxjs"; + +import { UnitConversionService } from "@nova-ui/bits"; +import { + Chart, + ChartAssist, + ChartDonutContentPlugin, + ChartPalette, + defaultColorProvider, + IAccessors, + IChartAssistSeries, + IChartPalette, + IValueProvider, + MappedValueProvider, + Renderer, + Scales, + SELECT_DATA_POINT_EVENT, + SequentialColorProvider, + XYGridConfig, +} from "@nova-ui/charts"; + +import { DashboardUnitConversionPipe } from "../../../common/pipes/dashboard-unit-conversion-pipe"; +import { CategoryChartUtilService } from "../../../services/category-chart-util.service"; +import { ProportionalWidgetChartTypes } from "../../proportional-widget/types"; +import { + IProportionalDataItem, + ProportionalChartType, + ViewLegendPlacement, +} from "../types"; + +const CHART_TYPE_MAP: Record = { + donut: ProportionalWidgetChartTypes.DonutChart, + pie: ProportionalWidgetChartTypes.PieChart, + verticalBar: ProportionalWidgetChartTypes.VerticalBarChart, + horizontalBar: ProportionalWidgetChartTypes.HorizontalBarChart, +}; + +@Component({ + selector: "nui-proportional-chart-view", + templateUrl: "./proportional-chart-view.component.html", + styleUrls: ["./proportional-chart-view.component.less"], + encapsulation: ViewEncapsulation.Emulated, + standalone: false, +}) +export class ProportionalChartViewComponent implements AfterViewInit, OnChanges, OnDestroy { + private static NO_SWITCH_LAYOUT_INTERVAL_SIZE = 20; + private static MAX_ROW_LAYOUT_SIZE = 360; + private static TICK_LABEL_MAX_WIDTH = 75; + + @Input() public data: Array = []; + @Input() public chartType: ProportionalChartType = "donut"; + @Input() public legendPlacement: ViewLegendPlacement = "right"; + @Input() public colors: Array | Record = []; + @Input() public interactive = false; + @Input() public donutContentTemplate: TemplateRef; + @Input() public legendItemTemplate: TemplateRef; + + @Output() public itemClick = new EventEmitter(); + + public chartAssist: ChartAssist; + public accessors: IAccessors; + public donutContentPlugin: ChartDonutContentPlugin | null; + public prioritizedGridRows = { + right: false, + bottom: false, + }; + + @ViewChild("gridContainer", { static: true }) + private gridContainer: ElementRef; + + private differ: KeyValueDiffer; + private renderer: Renderer; + private scales: Scales; + private chartPalette: IChartPalette = new ChartPalette(defaultColorProvider()); + private resizeObserver: ResizeObserver; + private chartTypeSubscription$: Subscription; + private unitConversionPipe: DashboardUnitConversionPipe; + private chartSeries: Array> = []; + + constructor( + private changeDetector: ChangeDetectorRef, + private ngZone: NgZone, + private kvDiffers: KeyValueDiffers, + unitConversionService: UnitConversionService + ) { + this.differ = this.kvDiffers.find(this.prioritizedGridRows).create(); + this.unitConversionPipe = new DashboardUnitConversionPipe(unitConversionService); + } + + public get isEmpty(): boolean { + return !this.data || this.data.length === 0; + } + + public ngOnChanges(changes: SimpleChanges): void { + if (changes.data || changes.colors) { + this.updateChartColors(); + } + + if (changes.chartType) { + this.buildChart(CHART_TYPE_MAP[this.chartType]); + if (this.data?.length) { + this.updateChart(); + } + this.changeDetector.markForCheck(); + } else if (changes.data && this.chartAssist) { + this.updateChart(); + this.changeDetector.markForCheck(); + } + + if (changes.legendPlacement) { + this.changeDetector.markForCheck(); + } + } + + public ngAfterViewInit(): void { + this.handleGridFlowOnResize(); + } + + public ngOnDestroy(): void { + this.resizeObserver?.disconnect(); + this.chartTypeSubscription$?.unsubscribe(); + } + + public isDonutChart(): boolean { + return this.chartType === "donut"; + } + + public hasLegend(): boolean { + return this.legendPlacement !== "none"; + } + + public legendShouldBeAlignedRight(): boolean { + return this.legendPlacement === "right"; + } + + public onInteraction(legendSeries: any): void { + if (!this.interactive) { + return; + } + const item = this.data.find((d) => d.id === legendSeries?.id); + if (item) { + this.itemClick.emit(item); + } + } + + public computeLegendTileValue(legendSeries: unknown): string | undefined { + // @ts-ignore: Suppressing series null parameter value error + return this.accessors.data?.value?.(legendSeries, 0, null, null); + } + + private buildChart(chartType: ProportionalWidgetChartTypes): void { + this.donutContentPlugin = null; + const { grid, accessors, renderer, scales, preprocessor } = + CategoryChartUtilService.getChartAttributes(chartType, this.chartPalette?.standardColors); + + this.chartAssist = new ChartAssist( + new Chart(grid), + preprocessor, + this.chartPalette + ); + this.renderer = renderer; + this.accessors = accessors; + this.scales = scales; + + if (this.isDonutChart()) { + this.donutContentPlugin = new ChartDonutContentPlugin(); + this.chartAssist.chart.addPlugin(this.donutContentPlugin); + } + + if (chartType === ProportionalWidgetChartTypes.HorizontalBarChart) { + this.scales.x.formatters.tick = (value: string | number | undefined) => + this.unitConversionPipe.transform(value); + this.applyTickLabelMaxWidths(); + } else if (chartType === ProportionalWidgetChartTypes.VerticalBarChart) { + this.scales.y.formatters.tick = (value: string | number | undefined) => + this.unitConversionPipe.transform(value); + } + + this.chartTypeSubscription$?.unsubscribe(); + this.chartTypeSubscription$ = this.chartAssist.chart + .getEventBus() + .getStream(SELECT_DATA_POINT_EVENT) + .subscribe((event) => { + const item = this.data.find((d) => d.id === event.data.seriesId); + if (item) { + this.onInteraction({ id: item.id }); + } + }); + } + + private handleGridFlowOnResize(): void { + this.resizeObserver = new ResizeObserver(() => this.onResize()); + this.ngZone.runOutsideAngular(() => { + this.resizeObserver.observe(this.gridContainer.nativeElement); + }); + } + + private applyTickLabelMaxWidths(): void { + const gridConfigAxis = (this.chartAssist.chart.getGrid().config() as XYGridConfig).axis; + gridConfigAxis.left.tickLabel.maxWidth = ProportionalChartViewComponent.TICK_LABEL_MAX_WIDTH; + gridConfigAxis.right.tickLabel.maxWidth = ProportionalChartViewComponent.TICK_LABEL_MAX_WIDTH; + } + + private onResize(): void { + if (this.isContainerInNoSwitchLayoutInterval()) { + return; + } + + switch (this.legendPlacement) { + case "bottom": + this.prioritizedGridRows.bottom = this.containerHasRowLayoutWidth(); + break; + case "right": + this.prioritizedGridRows.right = this.containerHasRowLayoutWidth(); + break; + } + + if (this.differ.diff(this.prioritizedGridRows)) { + this.changeDetector.detectChanges(); + } + } + + private isContainerInNoSwitchLayoutInterval(): boolean { + const containerWidth = this.gridContainer.nativeElement.getBoundingClientRect().width; + return ( + containerWidth > + ProportionalChartViewComponent.MAX_ROW_LAYOUT_SIZE - + ProportionalChartViewComponent.NO_SWITCH_LAYOUT_INTERVAL_SIZE / 2 && + containerWidth < + ProportionalChartViewComponent.MAX_ROW_LAYOUT_SIZE + + ProportionalChartViewComponent.NO_SWITCH_LAYOUT_INTERVAL_SIZE / 2 + ); + } + + private containerHasRowLayoutWidth(): boolean { + const containerWidth = this.gridContainer.nativeElement.getBoundingClientRect().width; + return containerWidth < ProportionalChartViewComponent.MAX_ROW_LAYOUT_SIZE; + } + + private updateChart(): void { + this.chartSeries = this.mapDataToSeries(); + this.chartAssist.update( + CategoryChartUtilService.buildChartSeries( + this.chartSeries, + this.accessors, + this.renderer, + this.scales + ) + ); + } + + private updateChartColors(): void { + let colorProvider: IValueProvider; + + const dataColors = this.data?.map((v) => v.color); + const configColors = this.colors; + + if (some(dataColors)) { + colorProvider = this.getDataDrivenColorProvider(); + } else if (configColors && (Array.isArray(configColors) ? configColors.length > 0 : Object.keys(configColors).length > 0)) { + colorProvider = this.getConfigurationColorProvider(configColors); + } else { + colorProvider = defaultColorProvider(); + } + + this.chartPalette = new ChartPalette(colorProvider); + this.buildChart(CHART_TYPE_MAP[this.chartType]); + if (this.chartAssist) { + this.chartAssist.palette = this.chartPalette; + this.updateChart(); + } + } + + private getDataDrivenColorProvider(): IValueProvider { + const dataColors = this.data?.map((v) => v.color).filter((v): v is string => !!v); + + if (dataColors.length === this.data.length) { + const colorMap = this.data.reduce((acc: Record, next) => { + acc[next.id] = next.color!; + return acc; + }, {}); + return new MappedValueProvider(colorMap); + } + + return new SequentialColorProvider(dataColors); + } + + private getConfigurationColorProvider(colors: Array | Record): IValueProvider { + if (Array.isArray(colors)) { + return new SequentialColorProvider(colors); + } + return new MappedValueProvider(colors); + } + + private mapDataToSeries(): Array> { + return this.data.map((item) => ({ + id: item.id, + name: item.name, + data: [{ value: item.value }], + color: item.color, + link: item.link, + accessors: this.accessors, + renderer: this.renderer, + scales: this.scales, + })); + } +} diff --git a/packages/dashboards/src/lib/components/views/public-api.ts b/packages/dashboards/src/lib/components/views/public-api.ts new file mode 100644 index 000000000..d680f1b87 --- /dev/null +++ b/packages/dashboards/src/lib/components/views/public-api.ts @@ -0,0 +1,24 @@ +// © 2022 SolarWinds Worldwide, LLC. All rights reserved. +// +// Permission is hereby granted, free of charge, to any person obtaining a copy +// of this software and associated documentation files (the "Software"), to +// deal in the Software without restriction, including without limitation the +// rights to use, copy, modify, merge, publish, distribute, sublicense, and/or +// sell copies of the Software, and to permit persons to whom the Software is +// furnished to do so, subject to the following conditions: +// +// The above copyright notice and this permission notice shall be included in +// all copies or substantial portions of the Software. +// +// THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR +// IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, +// FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE +// AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER +// LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, +// OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN +// THE SOFTWARE. + +export * from "./types"; +export * from "./views.module"; +export * from "./proportional-chart-view/proportional-chart-view.component"; +export * from "./kpi-tile-view/kpi-tile-view.component"; diff --git a/packages/dashboards/src/lib/components/views/types.ts b/packages/dashboards/src/lib/components/views/types.ts new file mode 100644 index 000000000..25592439a --- /dev/null +++ b/packages/dashboards/src/lib/components/views/types.ts @@ -0,0 +1,41 @@ +// © 2022 SolarWinds Worldwide, LLC. All rights reserved. +// +// Permission is hereby granted, free of charge, to any person obtaining a copy +// of this software and associated documentation files (the "Software"), to +// deal in the Software without restriction, including without limitation the +// rights to use, copy, modify, merge, publish, distribute, sublicense, and/or +// sell copies of the Software, and to permit persons to whom the Software is +// furnished to do so, subject to the following conditions: +// +// The above copyright notice and this permission notice shall be included in +// all copies or substantial portions of the Software. +// +// THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR +// IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, +// FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE +// AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER +// LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, +// OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN +// THE SOFTWARE. + +/** A single data item in the proportional chart. */ +export interface IProportionalDataItem { + /** Unique identifier for the segment. */ + id: string; + /** Display name shown in legend. */ + name: string; + /** Numeric value determining segment size. */ + value: number; + /** Optional CSS color (hex or token). If not provided, palette is used. */ + color?: string; + /** Optional icon name displayed in legend. */ + icon?: string; + /** Optional link for drill-down interaction. */ + link?: string; +} + +/** Chart type variants the view supports. */ +export type ProportionalChartType = "donut" | "pie" | "verticalBar" | "horizontalBar"; + +/** Legend placement relative to the chart. */ +export type ViewLegendPlacement = "right" | "bottom" | "none"; diff --git a/packages/dashboards/src/lib/components/views/views.module.ts b/packages/dashboards/src/lib/components/views/views.module.ts new file mode 100644 index 000000000..83d650958 --- /dev/null +++ b/packages/dashboards/src/lib/components/views/views.module.ts @@ -0,0 +1,58 @@ +// © 2022 SolarWinds Worldwide, LLC. All rights reserved. +// +// Permission is hereby granted, free of charge, to any person obtaining a copy +// of this software and associated documentation files (the "Software"), to +// deal in the Software without restriction, including without limitation the +// rights to use, copy, modify, merge, publish, distribute, sublicense, and/or +// sell copies of the Software, and to permit persons to whom the Software is +// furnished to do so, subject to the following conditions: +// +// The above copyright notice and this permission notice shall be included in +// all copies or substantial portions of the Software. +// +// THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR +// IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, +// FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE +// AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER +// LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, +// OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN +// THE SOFTWARE. + +import { CommonModule } from "@angular/common"; +import { NgModule } from "@angular/core"; + +import { + NuiBusyModule, + NuiCommonModule, + NuiIconModule, +} from "@nova-ui/bits"; +import { NuiChartsModule } from "@nova-ui/charts"; + +import { NuiDashboardsCommonModule } from "../../common/common.module"; +import { KpiTileViewComponent } from "./kpi-tile-view/kpi-tile-view.component"; +import { ProportionalChartViewComponent } from "./proportional-chart-view/proportional-chart-view.component"; + +/** + * A Pizzagna-free module that exports standalone view components for + * proportional charts and KPI tiles. Consumers can import this module + * without needing PIZZAGNA_EVENT_BUS, DATA_SOURCE, or any Pizzagna providers. + */ +@NgModule({ + imports: [ + CommonModule, + NuiBusyModule, + NuiChartsModule, + NuiCommonModule, + NuiIconModule, + NuiDashboardsCommonModule, + ], + declarations: [ + ProportionalChartViewComponent, + KpiTileViewComponent, + ], + exports: [ + ProportionalChartViewComponent, + KpiTileViewComponent, + ], +}) +export class NuiDashboardViewsModule {} diff --git a/packages/dashboards/src/lib/dashboards.module.ts b/packages/dashboards/src/lib/dashboards.module.ts index ce54f7662..9522ce29e 100644 --- a/packages/dashboards/src/lib/dashboards.module.ts +++ b/packages/dashboards/src/lib/dashboards.module.ts @@ -88,6 +88,7 @@ import { NuiDashboardConfiguratorModule } from "./configurator/configurator.modu import { DATA_SOURCE_OUTPUT } from "./configurator/types"; import { GridsterItemWidgetIdDirective } from "./directives/gridster-item-widget-id/gridster-item-widget-id.directive"; import { WidgetEditorDirective } from "./directives/widget-editor/widget-editor.directive"; +import { NuiDashboardViewsModule } from "./components/views/views.module"; import { NuiPizzagnaModule } from "./pizzagna/pizzagna.module"; import { ComponentPortalService } from "./pizzagna/services/component-portal.service"; import { @@ -191,6 +192,7 @@ const entryComponents: IComponentWithLateLoadKey[] = [ @NgModule({ imports: [ NuiDashboardsCommonModule, + NuiDashboardViewsModule, GridsterModule, NuiBusyModule, NuiButtonModule,